diff --git a/.mailmap b/.mailmap index 389b94a0124e31..0a0be49bc4de6c 100644 --- a/.mailmap +++ b/.mailmap @@ -101,6 +101,7 @@ Andy Chiu Andy Chiu Andy Shevchenko Andy Shevchenko +Andy Yan Anilkumar Kolli Anirudh Ghayal Antoine Tenart @@ -286,6 +287,8 @@ Ed L. Cashin Elliot Berman Enric Balletbo i Serra Enric Balletbo i Serra +Eric Dumazet +Eric Dumazet Erik Kaneda Ethan Carter Edwards Ethan Edwards Eugen Hristev @@ -459,7 +462,8 @@ Johan Hovold Johan Hovold John Crispin John Fastabend -John Garry +John Garry +John Garry John Keeping John Moon John Paul Adrian Glaubitz @@ -484,6 +488,7 @@ Juha Yrjola Juha Yrjola Julien Thierry Justin Iurman +Imre Kaloz Ira Weiny Iskren Chernev Kalle Valo diff --git a/Documentation/ABI/testing/sysfs-class-spi-master b/Documentation/ABI/testing/sysfs-class-spi-master index 0a524fcd96d255..4e1e8a7ca83c6f 100644 --- a/Documentation/ABI/testing/sysfs-class-spi-master +++ b/Documentation/ABI/testing/sysfs-class-spi-master @@ -17,7 +17,8 @@ Description: (WO) Instantiate a new SPI device on bus B, where B with delete_device; platform and DT devices are not affected. - Example: + Example:: + # echo spidev 0 > /sys/class/spi_master/spi0/new_device # echo spidev 0 10000000 > /sys/class/spi_master/spi0/new_device # echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device @@ -30,5 +31,6 @@ Description: (WO) Remove a SPI device previously created via new_device. Takes a single parameter: the chip select number of the device to remove. - Example: + Example:: + # echo 0 > /sys/class/spi_master/spi0/delete_device diff --git a/Documentation/admin-guide/ext4.rst b/Documentation/admin-guide/ext4.rst index 742a48e6fc0c21..8b23b6ff3df18e 100644 --- a/Documentation/admin-guide/ext4.rst +++ b/Documentation/admin-guide/ext4.rst @@ -172,7 +172,8 @@ When mounting an ext4 filesystem, the following option are accepted: data=journal All data are committed into the journal prior to being written into the main file system. Enabling this mode will disable delayed allocation - and O_DIRECT support. + and O_DIRECT support. This feature is deprecated and support + will be removed in January 2028. data=ordered (*) All data are forced directly out to the main file system prior to its @@ -418,7 +419,8 @@ There are 3 different data modes: consistent state. This mode is the slowest except when data needs to be read from and written to disk at the same time where it outperforms all others modes. Enabling this mode will disable delayed allocation and O_DIRECT - support. + support. The data=journal feature is deprecated and support will be + removed in January 2028. /proc entries ============= diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index ac3248b9f2f3bb..99a1eb4b833ddb 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -174,6 +174,8 @@ stable kernels. +----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-A725 | #3456106 | ARM64_ERRATUM_3194386 | +----------------+-----------------+-----------------+-----------------------------+ +| ARM | Cortex-A725 | #3821522 | ARM64_ERRATUM_3821522 | ++----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-X1 | #1502854 | N/A | +----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-X1 | #3324344 | ARM64_ERRATUM_3194386 | diff --git a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml index f0551d66591629..0cce357cb01de9 100644 --- a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml @@ -12,19 +12,19 @@ maintainers: properties: compatible: oneOf: - - const: rockchip,saradc - - const: rockchip,rk3066-tsadc - - const: rockchip,rk3399-saradc - - const: rockchip,rk3528-saradc + - enum: + - rockchip,saradc + - rockchip,rk3066-tsadc + - rockchip,rk3399-saradc + - rockchip,rk3528-saradc + - rockchip,rk3562-saradc + - rockchip,rk3588-saradc + - rockchip,rv1106-saradc - items: - const: rockchip,rk3506-saradc - const: rockchip,rk3528-saradc - - const: rockchip,rk3562-saradc - - const: rockchip,rk3588-saradc - items: - - enum: - - rockchip,rk3576-saradc - - rockchip,rv1106-saradc + - const: rockchip,rk3576-saradc - const: rockchip,rk3588-saradc - items: - enum: diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index 1ba809888a88c4..e674811b22b204 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -71,6 +71,58 @@ properties: maximum: 7 default: 0 + cirrus,ldpm-group1-boost-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the integrated boost converter will enter the configured low power mode. + + cirrus,boost-low-power-mode: + description: + Configures the integrated boost converter reaction to a low power mode + request. + 0 = No reaction + 1 = Force boost DCM low power mode + 2 = Force boost static bypass + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2 + enum: + - 0 + - 1 + - 2 + + cirrus,ldpm-group1-amp-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the amplifier will enter the configured low power mode. + + cirrus,amplifier-low-power-mode: + description: + Configures the amplifier output stage reaction to a low power mode + request. + 0 = Stop output switching (noise gate mode) + 1 = Noise floor reduction mode + $ref: /schemas/types.yaml#/definitions/uint32 + default: 1 + enum: + - 0 + - 1 + + cirrus,ldpm-group2-vmon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the voltage monitor (VMON) circuit is put into a low power operational + state. + + cirrus,ldpm-group2-imon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the current monitor (IMON) circuit is put into a low power operational + state. + patternProperties: "^cirrus,gpio-ctrl[1-3]$": description: @@ -139,6 +191,30 @@ patternProperties: minimum: 0 maximum: 7 default: 0 + + "^cirrus,ldpm-group[1-2]-pcm-threshold-db$": + description: + Amplifier level dependent power management (LDPM) threshold. Input + referred threshold of the PCM audio signal in which the level is low + enough for the LDPM group to engage after entry delay. A value of dB + -2147483648 is assumed to mean true zero data with an LSB dither. + $ref: /schemas/types.yaml#/definitions/int32-array + minItems: 1 + maxItems: 1 + items: + enum: [-2147483648, -102, -96, -90, -84, -78, -72, -66] + + "^cirrus,ldpm-group[1-2]-delay-ms$": + description: + Amplifier level dependent power management (LDPM) state entry delay. + Time that the incoming audio signal must be below the PCM threshold + before the LDPM group engages. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 1 + items: + enum: [5, 10, 25, 50, 100, 250, 500, 1000] + required: - compatible - reg @@ -165,6 +241,16 @@ examples: CS35L45_ASP_TX_HIZ_DISABLED)>; cirrus,sync-lsw-txid = <0x1>; cirrus,sync-sw-txid = <0x1>; + cirrus,ldpm-group1-pcm-threshold-db = <(-66)>; + cirrus,ldpm-group1-delay-ms = <500>; + cirrus,ldpm-group1-boost-select; + cirrus,boost-low-power-mode = ; + cirrus,ldpm-group1-amp-select; + cirrus,amplifier-low-power-mode = ; + cirrus,ldpm-group2-pcm-threshold-db = <(-102)>; + cirrus,ldpm-group2-delay-ms = <1000>; + cirrus,ldpm-group2-vmon-select; + cirrus,ldpm-group2-imon-select; cirrus,gpio-ctrl1 { gpio-ctrl = <0x2>; }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml index 2588589ad62d39..316806fc7346ee 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml @@ -48,6 +48,11 @@ properties: $ref: /schemas/types.yaml#/definitions/phandle description: The phandle of audio codec + nvidia,aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: List of phandles pointing to auxiliary devices, + such as amplifiers or dsp, to be added to the sound card. + nvidia,spkr-en-gpios: maxItems: 1 description: The GPIO that enables the speakers diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml new file mode 100644 index 00000000000000..d2c317f831ced1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8994.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex with WM8994 CODEC + +maintainers: + - Svyatoslav Ryhel + +allOf: + - $ref: nvidia,tegra-audio-common.yaml# + +properties: + compatible: + items: + - pattern: '^[a-z0-9]+,tegra-audio-wm8994(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-wm8994 + + nvidia,audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: | + A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names for sources and + sinks are the pins (documented in the binding document), + and the jacks on the board. + minItems: 2 + items: + enum: + # Board Connectors + - Headphone Jack + - Int Spk + - Earpiece + - Line Out + - Mic Jack + - Int Mic + - Internal Mic 1 + - Internal Mic 2 + - FM Radio + + # CODEC Pins + - SPKOUTLP + - SPKOUTLN + - SPKOUTRP + - SPKOUTRN + - HPOUT1L + - HPOUT1R + - HPOUT2P + - HPOUT2N + - LINEOUT1P + - LINEOUT1N + - LINEOUT2P + - LINEOUT2N + - IN1LN + - IN1LP + - IN2LN + - IN2LP:VXRN + - IN1RN + - IN1RP + - IN2RN + - IN2RP:VXRP + - DMIC1DAT + - DMIC2DAT + - MICBIAS1 + - MICBIAS2 + +required: + - nvidia,i2s-controller + +unevaluatedProperties: false + +examples: + - | + #include + #include + + sound { + compatible = "lg,tegra-audio-wm8994-star", + "nvidia,tegra-audio-wm8994"; + nvidia,model = "LG Optimus 2X WM8994"; + + nvidia,audio-routing = + "Headphone Jack", "HPOUT1L", + "Headphone Jack", "HPOUT1R", + "Int Spk", "SPKOUTLN", + "Int Spk", "SPKOUTLP", + "Earpiece", "HPOUT2N", + "Earpiece", "HPOUT2P", + "IN2RN", "MICBIAS2", + "IN2RN", "Mic Jack", + "IN1LN", "MICBIAS1", + "IN1LN", "Internal Mic 1", + "IN1RN", "MICBIAS1", + "IN1RN", "Internal Mic 2"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&wm8994>; + + nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; + nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA20_CLK_PLL_A>, + <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA20_CLK_CDEV1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt deleted file mode 100644 index 0e9a1895d7fb06..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt +++ /dev/null @@ -1,88 +0,0 @@ -NVIDIA Tegra30 AHUB (Audio Hub) - -Required properties: -- compatible : For Tegra30, must contain "nvidia,tegra30-ahub". For Tegra114, - must contain "nvidia,tegra114-ahub". For Tegra124, must contain - "nvidia,tegra124-ahub". Otherwise, must contain "nvidia,-ahub", - plus at least one of the above, where is tegra132. -- reg : Should contain the register physical address and length for each of - the AHUB's register blocks. - - Tegra30 requires 2 entries, for the APBIF and AHUB/AUDIO register blocks. - - Tegra114 requires an additional entry, for the APBIF2 register block. -- interrupts : Should contain AHUB interrupt -- clocks : Must contain an entry for each entry in clock-names. - See ../clocks/clock-bindings.txt for details. -- clock-names : Must include the following entries: - - d_audio - - apbif -- resets : Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. -- reset-names : Must include the following entries: - Tegra30 and later: - - d_audio - - apbif - - i2s0 - - i2s1 - - i2s2 - - i2s3 - - i2s4 - - dam0 - - dam1 - - dam2 - - spdif - Tegra114 and later additionally require: - - amx - - adx - Tegra124 and later additionally require: - - amx1 - - adx1 - - afc0 - - afc1 - - afc2 - - afc3 - - afc4 - - afc5 -- ranges : The bus address mapping for the configlink register bus. - Can be empty since the mapping is 1:1. -- dmas : Must contain an entry for each entry in clock-names. - See ../dma/dma.txt for details. -- dma-names : Must include the following entries: - - rx0 .. rx - - tx0 .. tx - ... where n is: - Tegra30: 3 - Tegra114, Tegra124: 9 -- #address-cells : For the configlink bus. Should be <1>; -- #size-cells : For the configlink bus. Should be <1>. - -AHUB client modules need to specify the IDs of their CIFs (Client InterFaces). -For RX CIFs, the numbers indicate the register number within AHUB routing -register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, SPDIF RX 0..1). -For TX CIFs, the numbers indicate the bit position within the AHUB routing -registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF TX 0..1). - -Example: - -ahub@70080000 { - compatible = "nvidia,tegra30-ahub"; - reg = <0x70080000 0x200 0x70080200 0x100>; - interrupts = < 0 103 0x04 >; - nvidia,dma-request-selector = <&apbdma 1>; - clocks = <&tegra_car 106>, <&tegra_car 107>; - clock-names = "d_audio", "apbif"; - resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, - <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, - <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, - <&tegra_car 110>, <&tegra_car 10>; - reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", - "i2s3", "i2s4", "dam0", "dam1", "dam2", - "spdif"; - dmas = <&apbdma 1>, <&apbdma 1>; - <&apbdma 2>, <&apbdma 2>; - <&apbdma 3>, <&apbdma 3>; - <&apbdma 4>, <&apbdma 4>; - dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; - ranges; - #address-cells = <1>; - #size-cells = <1>; -}; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml new file mode 100644 index 00000000000000..26077c3ce19817 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml @@ -0,0 +1,289 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra30-ahub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra30 AHUB (Audio Hub) + +maintainers: + - Thierry Reding + - Jon Hunter + +description: + AHUB client modules need to specify the IDs of their CIFs (Client + InterFaces). For RX CIFs, the numbers indicate the register number within + AHUB routing register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, + SPDIF RX 0..1). For TX CIFs, the numbers indicate the bit position within + the AHUB routing registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF + TX 0..1). + +properties: + compatible: + oneOf: + - const: nvidia,tegra30-ahub + - const: nvidia,tegra114-ahub + - const: nvidia,tegra124-ahub + - items: + - const: nvidia,tegra132-ahub + - const: nvidia,tegra124-ahub + + reg: + minItems: 2 + maxItems: 3 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: digital audio clock + - description: module clock + + clock-names: + items: + - const: d_audio + - const: apbif + + resets: + minItems: 11 + maxItems: 21 + + reset-names: + minItems: 11 + maxItems: 21 + + ranges: true + + dmas: + minItems: 8 + maxItems: 20 + + dma-names: + minItems: 8 + maxItems: 20 + + "#address-cells": + enum: [1, 2] + + "#size-cells": + enum: [1, 2] + +patternProperties: + "^i2s@[0-9a-f]+$": + type: object + required: + - compatible + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - reset-names + - ranges + - dmas + - dma-names + - "#address-cells" + - "#size-cells" + +allOf: + - if: + properties: + compatible: + const: nvidia,tegra30-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + + resets: + maxItems: 11 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + + dmas: + maxItems: 8 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + + - if: + properties: + compatible: + const: nvidia,tegra114-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 13 + maxItems: 13 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: adx + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + + - if: + properties: + compatible: + contains: + const: nvidia,tegra124-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 21 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: amx1 + - const: adx + - const: adx1 + - const: afc0 + - const: afc1 + - const: afc2 + - const: afc3 + - const: afc4 + - const: afc5 + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + +additionalProperties: false + +examples: + - | + #include + #include + + ahub@70080000 { + compatible = "nvidia,tegra30-ahub"; + reg = <0x70080000 0x200>, + <0x70080200 0x100>; + interrupts = ; + clocks = <&tegra_car TEGRA30_CLK_D_AUDIO>, + <&tegra_car TEGRA30_CLK_APBIF>; + clock-names = "d_audio", "apbif"; + resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, + <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, + <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, + <&tegra_car 110>, <&tegra_car 10>; + reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", + "i2s3", "i2s4", "dam0", "dam1", "dam2", + "spdif"; + dmas = <&apbdma 1>, <&apbdma 1>, + <&apbdma 2>, <&apbdma 2>, + <&apbdma 3>, <&apbdma 3>, + <&apbdma 4>, <&apbdma 4>; + dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; + ranges; + #address-cells = <1>; + #size-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml index 33bc23b6176a64..b603bb6eb9b7eb 100644 --- a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - qcom,sdm660-wcd-digital-codec + - qcom,sdm670-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml index 9e5b30d9c6e62d..c1f9fc9eab645e 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml @@ -20,9 +20,24 @@ properties: minItems: 1 maxItems: 2 + memory-region: + minItems: 1 + items: + - description: + Control-path buffer (audio_heap_mem) for platforms where the DSP + runs in a stage-2 protected context (e.g. modem-DSP). + - description: + Data-path buffer pool from which per-stream DMA buffers are + allocated. + required: - compatible - - iommus + +oneOf: + - required: + - iommus + - required: + - memory-region additionalProperties: false diff --git a/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml b/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml index 8e6973fa229c9e..81532cf69dd0c4 100644 --- a/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml +++ b/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml @@ -17,6 +17,7 @@ properties: compatible: oneOf: - enum: + - qcom,shikra-soundwire - qcom,soundwire-v1.3.0 - qcom,soundwire-v1.5.0 - qcom,soundwire-v1.5.1 diff --git a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml index 9bfb8089f7ea2f..427d8883009cc9 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml @@ -11,12 +11,11 @@ maintainers: - Xianwei Zhao - Sunny Luo -allOf: - - $ref: spi-controller.yaml# - properties: compatible: - const: amlogic,a4-spisg + enum: + - amlogic,a4-spisg + - amlogic,a9-spisg reg: maxItems: 1 @@ -35,6 +34,16 @@ properties: resets: maxItems: 1 + amlogic,mo-idle-output: + description: | + Controls the MOSI output level when the controller is idle. + 0 - Drive MOSI low + 1 - Drive MOSI high + 2 - Follow the last transmitted bit + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + default: 0 + required: - compatible - reg @@ -42,6 +51,19 @@ required: - clocks - clock-names +allOf: + - $ref: spi-controller.yaml# + - if: + not: + properties: + compatible: + contains: + enum: + - amlogic,a9-spisg + then: + properties: + amlogic,mo-idle-output: false + unevaluatedProperties: false examples: diff --git a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml index 4e28e6e9d8e058..f0ba15402a4c14 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml @@ -16,10 +16,15 @@ description: | properties: compatible: - enum: - - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs - - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs - - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs + oneOf: + - items: + - enum: + - amlogic,t7-spicc + - const: amlogic,meson-g12a-spicc + - enum: + - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs + - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs + - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs interrupts: maxItems: 1 diff --git a/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml new file mode 100644 index 00000000000000..4669b521fa1fce --- /dev/null +++ b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/microchip,pic32mzda-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PIC32MZDA SPI Controller + +maintainers: + - Thomas Bogendoerfer + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: microchip,pic32mzda-spi + + reg: + maxItems: 1 + + interrupts: + items: + - description: Fault interrupt + - description: Receive interrupt + - description: Transmit interrupt + + interrupt-names: + items: + - const: fault + - const: rx + - const: tx + + clocks: + maxItems: 1 + + clock-names: + items: + - const: mck0 + + cs-gpios: + maxItems: 1 + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: spi-rx + - const: spi-tx + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - cs-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi@1f821000 { + compatible = "microchip,pic32mzda-spi"; + reg = <0x1f821000 0x200>; + interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, + <110 IRQ_TYPE_LEVEL_HIGH>, + <111 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "fault", "rx", "tx"; + clocks = <&PBCLK2>; + clock-names = "mck0"; + cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; + dmas = <&dma 134>, <&dma 135>; + dma-names = "spi-rx", "spi-tx"; + }; diff --git a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt b/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt deleted file mode 100644 index 79de379f4dc067..00000000000000 --- a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt +++ /dev/null @@ -1,34 +0,0 @@ -Microchip PIC32 SPI Master controller - -Required properties: -- compatible: Should be "microchip,pic32mzda-spi". -- reg: Address and length of register space for the device. -- interrupts: Should contain all three spi interrupts in sequence - of , , . -- interrupt-names: Should be "fault", "rx", "tx" in order. -- clocks: Phandle of the clock generating SPI clock on the bus. -- clock-names: Should be "mck0". -- cs-gpios: Specifies the gpio pins to be used for chipselects. - See: Documentation/devicetree/bindings/spi/spi-bus.txt - -Optional properties: -- dmas: Two or more DMA channel specifiers following the convention outlined - in Documentation/devicetree/bindings/dma/dma.txt -- dma-names: Names for the dma channels. There must be at least one channel - named "spi-tx" for transmit and named "spi-rx" for receive. - -Example: - -spi1: spi@1f821000 { - compatible = "microchip,pic32mzda-spi"; - reg = <0x1f821000 0x200>; - interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, - <110 IRQ_TYPE_LEVEL_HIGH>, - <111 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "fault", "rx", "tx"; - clocks = <&PBCLK2>; - clock-names = "mck0"; - cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; - dmas = <&dma 134>, <&dma 135>; - dma-names = "spi-rx", "spi-tx"; -}; diff --git a/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml new file mode 100644 index 00000000000000..014ddfe0f42139 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/realtek,rtd1625-nor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek RTD1625 SPI NOR Flash Controller + +maintainers: + - Jyan Chou + - Yu-Chun Lin + +properties: + compatible: + enum: + - realtek,rtd1625-nor + + reg: + maxItems: 3 + + reg-names: + items: + - const: ctrl + - const: dma + - const: dirmap + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +required: + - compatible + - reg + - reg-names + - clocks + - resets + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + spi@14b000 { + compatible = "realtek,rtd1625-nor"; + reg = <0x14b000 0x50>, <0x14bf00 0x30>, <0x88100000 0x2000000>; + reg-names = "ctrl", "dma", "dirmap"; + clocks = <&cc 18>; + resets = <&cc 26>; + pinctrl-0 = <&spi_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml index e0c7047ae8adb6..41e423a42654d0 100644 --- a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml @@ -54,6 +54,11 @@ properties: - renesas,msiof-r8a779h0 # R-Car V4M - const: renesas,rcar-gen4-msiof # generic R-Car Gen4 # compatible device + - items: + - enum: + - renesas,msiof-r8a78000 # R-Car X5H + - const: renesas,rcar-gen5-msiof # generic R-Car Gen5 + # compatible device - items: - const: renesas,sh-msiof # deprecated @@ -100,11 +105,11 @@ properties: dmas: minItems: 2 - maxItems: 4 + maxItems: 6 dma-names: minItems: 2 - maxItems: 4 + maxItems: 6 items: enum: [ tx, rx ] diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index e8d6d5d858b6bd..41ec69f5dcebe9 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -77,6 +77,7 @@ properties: - mscc,ocelot-spi - mscc,jaguar2-spi - sophgo,sg2042-spi + - syna,sl2610-spi - thead,th1520-spi - ultrarisc,dp1000-spi - const: snps,dw-apb-ssi diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index 880a9f62456678..afcf9c41c058fb 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -91,6 +91,14 @@ properties: The delay from the default sample time before the actual sample of the rxd input signal occurs. + This describes the board rather than the peripheral, namely the extra + delay this particular design needs, for example because of the flight time + of the clock and data signals between controller and peripheral. + + Timing parameters of the peripheral itself, such as its + clock-to-output-valid time, are the same on every board using that chip + and should be described with the chip. + spi-tx-bus-width: description: Bus width to the SPI bus used for write transfers. diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 5067500ded5cc7..4f0b90e7e13886 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -460,8 +460,6 @@ SPI devm_spi_alloc_target() devm_spi_optimize_message() devm_spi_register_controller() - devm_spi_register_host() - devm_spi_register_target() WATCHDOG devm_watchdog_register_device() diff --git a/Documentation/filesystems/netfs_library.rst b/Documentation/filesystems/netfs_library.rst index ddd799df6ce363..a9de281db8ee34 100644 --- a/Documentation/filesystems/netfs_library.rst +++ b/Documentation/filesystems/netfs_library.rst @@ -451,6 +451,32 @@ one. The inode should be marked ``NETFS_ICTX_SINGLE_NO_UPLOAD`` if this API is to be used. The writeback function requires the buffer to be of ITER_FOLIOQ type. +Clearing Stale Post-EOF Pagecache +--------------------------------- + +When a file is extended, data left in the pagecache past the old EOF by a write +through an mmap must not be exposed as file content. Netfslib clears this on +its own write paths, and exports a helper so a filesystem can do the same from +a resize path (truncate, setattr, fallocate and the like) that holds the +inode's ``i_rwsem`` exclusively across the whole resize:: + + void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to); + +This zeroes any such data within the ``[from, to)`` hole to be made, where +@from is the old EOF and @to is the new one, and the caller must have already +updated ``i_size`` to @to before calling it. Only the folio straddling @from +can hold data written past the EOF through an mmap, as pages wholly beyond the +EOF can't be faulted in, so the zeroing is limited to that folio. The folio is +zeroed rather than dropped so that a concurrent extending write can't lose +data. + +This overlaps with ``pagecache_isize_extended()`` but can't reuse it: that +helper is keyed on a sub-page block size and is a no-op when the block size is +``>= PAGE_SIZE`` (as on network filesystems), and it doesn't wait for +writeback. As both address the same problem, a change to one should probably +be reflected in the other to keep them in sync. + High-Level VM API ================== diff --git a/Documentation/filesystems/propagate_umount.txt b/Documentation/filesystems/propagate_umount.txt index 9a7eb96df300e9..de58ce817582fb 100644 --- a/Documentation/filesystems/propagate_umount.txt +++ b/Documentation/filesystems/propagate_umount.txt @@ -32,12 +32,12 @@ of set. They can be reparented to the place where the bottom of stack is attached to a mount that will survive. NOTE: doing that will violate a constraint on having no more than one mount with the same parent/mountpoint pair; however, the caller (umount_tree()) -will immediately remedy that - it may keep unmounted element attached -to parent, but only if the parent itself is unmounted. Since all -conflicts created by reparenting have common parent *not* in the -set and one side of the conflict (bottom of the stack of overmounts) -is in the set, it will be resolved. However, we rely upon umount_tree() -doing that pretty much immediately after the call of propagate_umount(). +will immediately remedy that - it detaches every element of the set +from its parent. Since all conflicts created by reparenting have +common parent *not* in the set and one side of the conflict (bottom +of the stack of overmounts) is in the set, it will be resolved. +However, we rely upon umount_tree() doing that pretty much immediately +after the call of propagate_umount(). Algorithm is based on two statements: 1) for any set S, there is a maximal non-shifting subset of S diff --git a/Documentation/filesystems/sharedsubtree.rst b/Documentation/filesystems/sharedsubtree.rst index 8b7dc915908377..8bae6d9a7e0479 100644 --- a/Documentation/filesystems/sharedsubtree.rst +++ b/Documentation/filesystems/sharedsubtree.rst @@ -564,8 +564,8 @@ f) Unmount semantics where 'A' is a mount mounted on mount 'B' at dentry 'b'. If mount 'B' is shared, then all most-recently-mounted mounts at dentry - 'b' on mounts that receive propagation from mount 'B' and does not have - sub-mounts within them are unmounted. + 'b' on mounts that receive propagation from mount 'B' are unmounted as + well, if every mount below them is also unmounted. Example: Let's say 'B1', 'B2', 'B3' are shared mounts that propagate to each other. @@ -584,10 +584,18 @@ f) Unmount semantics So all 'C1', 'C2' and 'C3' should be unmounted. - If any of 'C2' or 'C3' has some child mounts, then that mount is not - unmounted, but all other mounts are unmounted. However if 'C1' is told - to be unmounted and 'C1' has some sub-mounts, the umount operation is - failed entirely. + If any of 'C2' or 'C3' has a child mount that cannot be unmounted + then that mount is not unmounted. But all other mounts are unmounted. + A child mount that is itself unmounted by the same unmount + propagation does not keep its parent mounted. However if 'C1' is + supposed to be unmounted and 'C1' has some sub-mounts, the unmount + fails. + + A lazy umount (MNT_DETACH) takes a whole tree. Every mount of the + tree then propagates its unmount from its own parent as described + above, so the mounts that receive propagation lose the corresponding + trees as well. Documentation/filesystems/propagate_umount.txt has the + precise rules, including the ones for locked mounts. g) Clone Namespace diff --git a/Documentation/networking/strparser.rst b/Documentation/networking/strparser.rst index 372106b61e6512..d0804c88ad0a04 100644 --- a/Documentation/networking/strparser.rst +++ b/Documentation/networking/strparser.rst @@ -77,7 +77,7 @@ Functions strp_done is called to release any resources held by the stream parser instance. This must be called after the stream processor - has been stopped. + has been stopped. The socket lock must not be held. :: diff --git a/Documentation/spi/multiple-data-lanes.rst b/Documentation/spi/multiple-data-lanes.rst index 69cb532d052fb1..802aece35d56c5 100644 --- a/Documentation/spi/multiple-data-lanes.rst +++ b/Documentation/spi/multiple-data-lanes.rst @@ -127,7 +127,7 @@ field to indicate which mode they want to use for a given transfer. The possible values for this field have the following semantics: -- :c:macro:`SPI_MULTI_BUS_MODE_SINGLE`: Only use the first lane. Other lanes are +- :c:macro:`SPI_MULTI_LANE_MODE_SINGLE`: Only use the first lane. Other lanes are ignored. This means that it is operating just like a conventional SPI peripheral. This is the default, so it does not need to be explicitly set. @@ -149,7 +149,7 @@ The possible values for this field have the following semantics: ---------- ---------------- ---------- SDO 0 0-0-0-1-0-0-0-1 SDI 0 -- :c:macro:`SPI_MULTI_BUS_MODE_MIRROR`: Send a single data word over all of the +- :c:macro:`SPI_MULTI_LANE_MODE_MIRROR`: Send a single data word over all of the lanes at the same time. This only makes sense for writes and not for reads. @@ -160,7 +160,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .tx_buf = tx_buf, .len = 1, - .multi_lane_mode = SPI_MULTI_BUS_MODE_MIRROR, + .multi_lane_mode = SPI_MULTI_LANE_MODE_MIRROR, }; spi_sync_transfer(spi, &xfer, 1); @@ -172,7 +172,7 @@ The possible values for this field have the following semantics: SDO 0 0-0-0-1-0-0-0-1 SDI 0 SDO 1 0-0-0-1-0-0-0-1 SDI 1 -- :c:macro:`SPI_MULTI_BUS_MODE_STRIPE`: Send or receive two different data words +- :c:macro:`SPI_MULTI_LANE_MODE_STRIPE`: Send or receive two different data words at the same time, one on each lane. This means that the buffer needs to be sized to hold data for all lanes. Data is interleaved in the buffer, with the first word corresponding to lane 0, the second to lane 1, and so on. @@ -185,7 +185,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .rx_buf = rx_buf, .len = 2, - .multi_lane_mode = SPI_MULTI_BUS_MODE_STRIPE, + .multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE, }; spi_sync_transfer(spi, &xfer, 1); diff --git a/MAINTAINERS b/MAINTAINERS index 072ea3087a3a87..73270347245bd1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3020,7 +3020,7 @@ F: arch/arm/boot/dts/ti/omap/omap3-igep* ARM/INTEL IXP4XX ARM ARCHITECTURE M: Linus Walleij -M: Imre Kaloz +M: Imre Kaloz L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/arm/intel-ixp4xx.yaml @@ -4434,6 +4434,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/pci/hws/ +AWINIC AW88399 HDA SIDE CODEC DRIVER +M: Marco Giunta +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/hda/codecs/side-codecs/aw88399* + AWINIC AW99706 WLED BACKLIGHT DRIVER M: Junjie Cao S: Maintained @@ -5158,6 +5164,7 @@ L: netdev@vger.kernel.org L: openwrt-devel@lists.openwrt.org (subscribers-only) S: Supported F: Documentation/devicetree/bindings/net/dsa/brcm,b53.yaml +F: Documentation/networking/dsa/b53.rst F: drivers/net/dsa/b53/* F: drivers/net/dsa/bcm_sf2* F: include/linux/dsa/brcm.h @@ -9882,7 +9889,7 @@ L: linux-ext4@vger.kernel.org S: Maintained W: http://ext4.wiki.kernel.org Q: http://patchwork.ozlabs.org/project/linux-ext4/list/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git dev F: Documentation/filesystems/ext4/ F: fs/ext4/ F: include/trace/events/ext4.h @@ -17231,31 +17238,6 @@ F: include/trace/events/ksm.h F: mm/ksm.c F: mm/mm_slot.h -MEMORY MANAGEMENT - MEMORY POLICY AND MIGRATION -M: Andrew Morton -M: David Hildenbrand -R: Zi Yan -R: Matthew Brost -R: Joshua Hahn -R: Rakie Kim -R: Byungchul Park -R: Gregory Price -R: Ying Huang -R: Alistair Popple -L: linux-mm@kvack.org -S: Maintained -W: http://www.linux-mm.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm -F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy -F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave -F: include/linux/mempolicy.h -F: include/uapi/linux/mempolicy.h -F: include/linux/migrate.h -F: include/linux/migrate_mode.h -F: mm/mempolicy.c -F: mm/migrate.c -F: mm/migrate_device.c - MEMORY MANAGEMENT - MGLRU (MULTI-GEN LRU) M: Andrew Morton R: Kairui Song @@ -17278,6 +17260,25 @@ F: include/linux/mmzone.h F: mm/vmscan.c F: mm/workingset.c +MEMORY MANAGEMENT - MIGRATION +M: Andrew Morton +M: David Hildenbrand +R: Zi Yan +R: Matthew Brost +R: Joshua Hahn +R: Byungchul Park +R: Gregory Price +R: Ying Huang +R: Alistair Popple +L: linux-mm@kvack.org +S: Maintained +W: http://www.linux-mm.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: include/linux/migrate.h +F: include/linux/migrate_mode.h +F: mm/migrate.c +F: mm/migrate_device.c + MEMORY MANAGEMENT - MISC M: Andrew Morton M: David Hildenbrand @@ -17293,7 +17294,6 @@ W: http://www.linux-mm.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm F: Documentation/ABI/testing/sysfs-kernel-mm F: Documentation/ABI/testing/sysfs-kernel-mm-cma -F: Documentation/ABI/testing/sysfs-kernel-mm-memory-tiers F: Documentation/ABI/testing/sysfs-kernel-mm-numa F: Documentation/admin-guide/mm/ F: Documentation/mm/ @@ -17301,7 +17301,6 @@ F: drivers/char/mem.c F: include/linux/cma.h F: include/linux/dmapool.h F: include/linux/ioremap.h -F: include/linux/memory-tiers.h F: include/linux/page_idle.h F: mm/backing-dev.c F: mm/cma.c @@ -17313,7 +17312,6 @@ F: mm/early_ioremap.c F: mm/fadvise.c F: mm/ioremap.c F: mm/mapping_dirty_helpers.c -F: mm/memory-tiers.c F: mm/page_idle.c F: mm/pgalloc-track.h F: mm/process_vm_access.c @@ -17330,6 +17328,27 @@ F: mm/numa.c F: mm/numa_emulation.c F: mm/numa_memblks.c +MEMORY MANAGEMENT - NUMA PLACEMENT +M: Andrew Morton +M: David Hildenbrand +M: Gregory Price +R: Zi Yan +R: Joshua Hahn +R: Rakie Kim +R: Ying Huang +L: linux-mm@kvack.org +S: Maintained +W: http://www.linux-mm.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: Documentation/ABI/testing/sysfs-kernel-mm-memory-tiers +F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy +F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave +F: include/linux/memory-tiers.h +F: include/linux/mempolicy.h +F: include/uapi/linux/mempolicy.h +F: mm/memory-tiers.c +F: mm/mempolicy.c + MEMORY MANAGEMENT - OOM KILLER M: Michal Hocko R: David Rientjes @@ -18907,7 +18926,7 @@ F: net/core/drop_monitor.c NETWORKING DRIVERS M: Andrew Lunn M: "David S. Miller" -M: Eric Dumazet +M: Eric Dumazet M: Jakub Kicinski M: Paolo Abeni L: netdev@vger.kernel.org @@ -19024,7 +19043,7 @@ K: struct\s+phy_port|phy_port_ NETWORKING [GENERAL] M: "David S. Miller" -M: Eric Dumazet +M: Eric Dumazet M: Jakub Kicinski M: Paolo Abeni R: Simon Horman @@ -19250,7 +19269,7 @@ F: net/ipv6/seg6* F: tools/testing/selftests/net/srv6* NETWORKING [TCP] -M: Eric Dumazet +M: Eric Dumazet M: Neal Cardwell R: Kuniyuki Iwashima L: netdev@vger.kernel.org @@ -19283,7 +19302,7 @@ F: net/tls/ F: tools/testing/selftests/net/tls.c NETWORKING [SOCKETS] -M: Eric Dumazet +M: Eric Dumazet M: Kuniyuki Iwashima M: Paolo Abeni M: Willem de Bruijn @@ -20579,6 +20598,7 @@ ORACLE CLUSTER FILESYSTEM 2 (OCFS2) M: Mark Fasheh M: Joel Becker M: Joseph Qi +R: Heming Zhao L: ocfs2-devel@lists.linux.dev S: Supported W: http://ocfs2.wiki.kernel.org @@ -23032,6 +23052,12 @@ S: Maintained F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml F: drivers/spi/spi-realtek-rtl-snand.c +REALTEK SPI-NOR +M: Jyan Chou +M: Yu-Chun Lin +S: Supported +F: drivers/spi/spi-rtk-nor.c + REALTEK SYSTIMER DRIVER M: Hao-Wen Ting S: Maintained @@ -24497,6 +24523,11 @@ R: Marc Murphy S: Supported F: arch/arm/boot/dts/ti/omap/am335x-sancloud* +SB1250-DUART BROADCOM SOC SERIAL DRIVER +M: "Maciej W. Rozycki" +S: Maintained +F: drivers/tty/serial/sb1250-duart.c + SBOM M: Luis Augenstein M: Maximilian Huber @@ -25207,7 +25238,7 @@ R: David Rientjes R: Roman Gushchin L: linux-mm@kvack.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/slab.git F: Documentation/admin-guide/mm/slab.rst F: Documentation/mm/slab.rst F: include/linux/mempool.h diff --git a/Makefile b/Makefile index 6b8b812f23b928..0dc1a9f4e63ec4 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc5 +EXTRAVERSION = -rc7 NAME = Baby Opossum Posse # *DOCUMENTATION* diff --git a/arch/arc/Kconfig b/arch/arc/Kconfig index 2ed7186c81c573..80e61175bf526b 100644 --- a/arch/arc/Kconfig +++ b/arch/arc/Kconfig @@ -331,13 +331,9 @@ config ARC_FPU_SAVE_RESTORE for floating point exceptions and rounding modes. These are preserved across task context switch when enabled. -config ARC_CANT_LLSC - def_bool n - config ARC_HAS_LLSC bool "Insn: LLOCK/SCOND (efficient atomic ops)" default y - depends on !ARC_CANT_LLSC if ISA_ARCV2 diff --git a/arch/arc/include/asm/atomic64-arcv2.h b/arch/arc/include/asm/atomic64-arcv2.h index 73080a664369b4..1ca4a1047fb378 100644 --- a/arch/arc/include/asm/atomic64-arcv2.h +++ b/arch/arc/include/asm/atomic64-arcv2.h @@ -156,6 +156,12 @@ static inline u64 __arch_cmpxchg64_relaxed(volatile void *ptr, u64 old, u64 new) } #define arch_cmpxchg64_relaxed __arch_cmpxchg64_relaxed +static inline s64 arch_atomic64_cmpxchg_relaxed(atomic64_t *v, s64 old, s64 new) +{ + return arch_cmpxchg64_relaxed(&v->counter, old, new); +} +#define arch_atomic64_cmpxchg_relaxed arch_atomic64_cmpxchg_relaxed + static inline s64 arch_atomic64_xchg(atomic64_t *ptr, s64 new) { s64 prev; diff --git a/arch/arc/include/asm/cmpxchg.h b/arch/arc/include/asm/cmpxchg.h index 76f43db0890fcd..66b3743c94008f 100644 --- a/arch/arc/include/asm/cmpxchg.h +++ b/arch/arc/include/asm/cmpxchg.h @@ -46,7 +46,7 @@ __typeof__(*(ptr)) _n_ = (new); \ __typeof__(*(ptr)) _prev_; \ \ - switch(sizeof((_p_))) { \ + switch (sizeof(*(_p_))) { \ case 1: \ _prev_ = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *__force)_p_, (uintptr_t)_o_, (uintptr_t)_n_); \ break; \ @@ -102,7 +102,7 @@ __typeof__(ptr) _p_ = (ptr); \ __typeof__(*(ptr)) _val_ = (val); \ \ - switch(sizeof(*(_p_))) { \ + switch (sizeof(*(_p_))) { \ case 4: \ _val_ = __arch_xchg(_p_, _val_); \ break; \ diff --git a/arch/arc/kernel/setup.c b/arch/arc/kernel/setup.c index 7b6a9beba9db6d..8bebd0e6fc2f09 100644 --- a/arch/arc/kernel/setup.c +++ b/arch/arc/kernel/setup.c @@ -593,6 +593,7 @@ static int show_cpuinfo(struct seq_file *m, void *v) cpu_id); } else { freq = clk_get_rate(cpu_clk); + clk_put(cpu_clk); } if (freq) seq_printf(m, "CPU speed\t: %lu.%02lu Mhz\n", diff --git a/arch/arc/kernel/smp.c b/arch/arc/kernel/smp.c index 2d99dffed0ce5c..ee210a94ce459d 100644 --- a/arch/arc/kernel/smp.c +++ b/arch/arc/kernel/smp.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..cab741a695f523 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1040,9 +1040,17 @@ config ARM64_ERRATUM_1902691 If unsure, say Y. +config ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT + bool + # Targeting errata affecting AMEVCNTR01, the AMU constant counter. + # On affected CPUs the counter may not increment at the expected rate, + # making it unreliable. As a result, AMU users should treat it as + # unavailable rather than relying on misleading counter values. + config ARM64_ERRATUM_2457168 bool "Cortex-A510: 2457168: workaround for AMEVCNTR01 incrementing incorrectly" depends on ARM64_AMU_EXTN + select ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT default y help This option adds the workaround for ARM Cortex-A510 erratum 2457168. @@ -1073,6 +1081,28 @@ config ARM64_ERRATUM_2645198 If unsure, say Y. +config ARM64_ERRATUM_3821522 + bool "Cortex-A725: 3821522: workaround for possible CNT_CYCLES increment error due to WFE/WFI" + depends on ARM64_AMU_EXTN + select ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT + default y + help + This option adds the workaround for ARM Cortex-A725 erratum 3821522. + + On affected A725 cores, the CNT_CYCLES event may incur a significant + increment error when entering and subsequently exiting WFx. + As a result, the CNT_CYCLES may diverge from the system counter + frequency at which it is expected to increment. + This renders the AMU counter AMEVCNTR01, that implements CNT_CYCLES, + being unreliable and unsuitable for use. + + Since there is no hardware workaround, reads of the affected CNT_CYCLES + counter return 0 in the relevant paths. This causes users of the counter + to treat it as unavailable and is functionally equivalent to firmware + disabling the affected counter. + + If unsure, say Y. + config ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD bool diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index ae6e37870c5e53..bccf2d67302302 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1602,7 +1602,7 @@ }; dspi2_miso_mosi_pins: iic5-spi3-pins { - pinctrl-single,bits = <0x3 (0x2 << 9) (0x7 << 9)>; + pinctrl-single,bits = <0x0 (0x3 << 9) (0x7 << 9)>; }; i2c5_pins: iic6-i2c-pins { @@ -1622,7 +1622,7 @@ }; gpio1_31_28_pins: xspi1-data74-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 15)>; + pinctrl-single,bits = <0x0 (0x1 << 15) (0x7 << 15)>; }; fspi_data30_pins: xspi1-data30-pins { @@ -1630,7 +1630,7 @@ }; gpio1_27_24_pins: xspi1-data30-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 18)>; + pinctrl-single,bits = <0x0 (0x1 << 18) (0x7 << 18)>; }; fspi_dqs_sck_cs10_pins: xspi1-base-pins { @@ -1638,7 +1638,7 @@ }; gpio1_23_20_pins: xspi1-base-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 21)>; + pinctrl-single,bits = <0x0 (0x1 << 21) (0x7 << 21)>; }; esdhc0_cmd_data30_clk_vsel_pins: sdhc1-base-sdhc-vsel-pins { @@ -1687,12 +1687,19 @@ }; /* RCWSR14 */ + /* + * IIC1_PMUX is a single bit at RCWSR14[10], but bit-per-mux + * uses the mask for the field to clear before applying the + * value. + * So bits 11 and 12, which are reserved, are written as 0 + * on every mux switch, it is harmless. + */ i2c0_pins: iic1-i2c-pins { - pinctrl-single,bits = <0x8 0x0 (0x1 << 10)>; + pinctrl-single,bits = <0x8 0x0 (0x7 << 10)>; }; gpio0_3_2_pins: iic1-gpio-pins { - pinctrl-single,bits = <0x8 (0x1 << 10) (0x1 << 10)>; + pinctrl-single,bits = <0x8 (0x1 << 10) (0x7 << 10)>; }; }; diff --git a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts index 983d67a0c1de7a..7f78026e70ffe9 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts @@ -511,6 +511,7 @@ pinctrl-2 = <&pinctrl_usdhc2_200mhz>, <&pinctrl_usdhc2_gpio>; cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; vmmc-supply = <®_usdhc2_vmmc>; + vqmmc-supply = <&ldo5>; bus-width = <4>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi index ce7d958ca68798..e3a53b8930d94e 100644 --- a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi @@ -863,11 +863,11 @@ }; &lpass_vamacro { - pinctrl-0 = <&dmic01_default>, <&dmic23_default>; + pinctrl-0 = <&dmic01_default>; pinctrl-names = "default"; vdd-micb-supply = <&vreg_l1b_1p8>; - qcom,dmic-sample-rate = <4800000>; + qcom,dmic-sample-rate = <2400000>; }; &mdss { diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index 87560d8b254e67..1da277baacf78c 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -421,8 +421,9 @@ mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 - cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 - b.lt .Lskip_pmuv3p9_\@ + cmp x1, #ID_AA64DFR0_EL1_PMUVer_IMP_DEF + ccmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9, #8, ne + b.lt .Lskip_pmuv3p9_\@ // Skip if < PMUv3p9 or IMP_DEF orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index d2690c3ec52885..ea66339435bf0b 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -25,6 +25,9 @@ KASAN_SANITIZE_stacktrace.o := n KCOV_INSTRUMENT_entry-common.o := n KCOV_INSTRUMENT_idle.o := n +# Softirq stack switching can run outside interrupt context. +KCOV_INSTRUMENT_irq.o := n + # Object file lists. obj-y := debug-monitors.o entry.o irq.o fpsimd.o \ entry-common.o process.o ptrace.o \ diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index 8ec47d89b45bb3..e0c09402540cec 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -384,6 +384,20 @@ static const struct arm64_cpu_capabilities arm64_repeat_tlbi_list[] = { }; #endif +#ifdef CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT +static const struct midr_range workaround_amu_constcnt_list[] = { +#ifdef CONFIG_ARM64_ERRATUM_2457168 + /* Cortex-A510 r0p0-r1p1 */ + MIDR_RANGE(MIDR_CORTEX_A510, 0, 0, 1, 1), +#endif +#ifdef CONFIG_ARM64_ERRATUM_3821522 + /* Cortex-A725 r0p0 - r0p2 */ + MIDR_RANGE(MIDR_CORTEX_A725, 0, 0, 0, 2), +#endif + {} +}; +#endif /* CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT */ + #ifdef CONFIG_CAVIUM_ERRATUM_23154 static const struct midr_range cavium_erratum_23154_cpus[] = { MIDR_ALL_VERSIONS(MIDR_THUNDERX), @@ -919,14 +933,12 @@ const struct arm64_cpu_capabilities arm64_errata[] = { ERRATA_MIDR_REV_RANGE(MIDR_CORTEX_A510, 0, 0, 2) }, #endif -#ifdef CONFIG_ARM64_ERRATUM_2457168 +#ifdef CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT { - .desc = "ARM erratum 2457168", - .capability = ARM64_WORKAROUND_2457168, + .desc = "Broken AMU AMEVCNTR01 (const counter)", + .capability = ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT, .type = ARM64_CPUCAP_WEAK_LOCAL_CPU_FEATURE, - - /* Cortex-A510 r0p0-r1p1 */ - CAP_MIDR_RANGE(MIDR_CORTEX_A510, 0, 0, 1, 1) + CAP_MIDR_RANGE_LIST(workaround_amu_constcnt_list) }, #endif #ifdef CONFIG_ARM64_ERRATUM_2038923 diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa7b..3ae3ad1a1cdfac 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -2089,7 +2089,7 @@ static void cpu_amu_enable(struct arm64_cpu_capabilities const *cap) cpumask_set_cpu(smp_processor_id(), &amu_cpus); /* 0 reference values signal broken/disabled counters */ - if (!this_cpu_has_cap(ARM64_WORKAROUND_2457168)) + if (!this_cpu_has_cap(ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT)) update_freq_counters_refs(); } } diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 38e6fa204c17b5..6a06a4e352b1d5 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -433,6 +433,7 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) unsigned int vl, vq; struct user_fpsimd_state fpsimd; u16 user_vl, flags; + bool fpsimd_only; bool sm; if (user->sve_size < sizeof(*user->sve)) @@ -443,19 +444,33 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (err) return err; + fpsimd_only = (user->sve_size == sizeof(*user->sve)); sm = flags & SVE_SIG_FLAG_SM; + if (sm) { if (!system_supports_sme()) return -EINVAL; + /* + * Streaming SVE state is always preserved with an SVE payload. + * Only accept streaming state which has an SVE payload. + */ + if (fpsimd_only) + return -EINVAL; + vl = task_get_sme_vl(current); } else { /* - * A SME only system use SVE for streaming mode so can - * have a SVE formatted context with a zero VL and no - * payload data. + * Non-streaming SVE state may be preserved without an SVE + * payload, in which case all state is saved in the FPSIMD + * context. + * + * On SME-only systems, non-streaming (FPSIMD-only) state is + * always preserved without an SVE payload, and with VL==0. On + * such systems, only accept non-streaming state without an SVE + * payload. */ - if (!system_supports_sve() && !system_supports_sme()) + if (!system_supports_sve() && !fpsimd_only) return -EINVAL; vl = task_get_sve_vl(current); @@ -464,16 +479,7 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (user_vl != vl) return -EINVAL; - /* - * Non-streaming SVE state may be preserved without an SVE payload, in - * which case the SVE context only has a header with VL==0, and all - * state can be restored from the FPSIMD context. - * - * Streaming SVE state is always preserved with an SVE payload. For - * consistency and robustness, reject restoring streaming SVE state - * without an SVE payload. - */ - if (!sm && user->sve_size == sizeof(*user->sve)) + if (fpsimd_only) return restore_fpsimd_context(user); vq = sve_vq_from_vl(vl); diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index d28438f8b83f12..806373aac3dbfc 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -186,7 +186,6 @@ int arch_freq_get_on_cpu(int cpu) struct amu_cntr_sample *amu_sample; unsigned int start_cpu = cpu; unsigned long last_update; - unsigned int freq = 0; u64 scale; if (!amu_fie_cpu_supported(cpu) || !arch_scale_freq_ref(cpu)) @@ -245,9 +244,8 @@ int arch_freq_get_on_cpu(int cpu) * (see amu_scale_freq_tick for details) */ scale = arch_scale_freq_capacity(cpu); - freq = scale * arch_scale_freq_ref(cpu); - freq >>= SCHED_CAPACITY_SHIFT; - return freq; + + return (scale * arch_scale_freq_ref(cpu)) >> SCHED_CAPACITY_SHIFT; } static void amu_fie_setup(const struct cpumask *cpus) @@ -397,12 +395,13 @@ static void cpu_read_corecnt(void *val) static void cpu_read_constcnt(void *val) { /* - * Return 0 if the current CPU is affected by erratum 2457168. A value - * of 0 is also returned if the current CPU does not support AMUs or if - * the counter is disabled. A return value of 0 at counter read is - * properly handled as an error case by the users of the counter. + * Return 0 if the current CPU is affected by a HW erratum. + * A value of 0 is also returned if the current CPU does not + * support AMUs or if the counter is disabled. A return + * value of 0 at counter read is properly handled as an error + * case by the users of the counter. */ - *(u64 *)val = this_cpu_has_cap(ARM64_WORKAROUND_2457168) ? + *(u64 *)val = this_cpu_has_cap(ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT) ? 0UL : read_constcnt(); } @@ -463,8 +462,9 @@ static void amu_read_core_const_ctrs(void *val) /* * cpu_read_constcnt() incurs slight latency due to the - * ARM64_WORKAROUND_2457168 check. Read it first to minimize - * the sampling skew between the const and core counters. + * ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT check. + * Read it first to minimize the sampling skew between the const + * and core counters. */ cpu_read_constcnt(&ctrs->constcnt); cpu_read_corecnt(&ctrs->corecnt); diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile index 7dec05dd33b70e..f6619e1cb2ce76 100644 --- a/arch/arm64/kernel/vdso/Makefile +++ b/arch/arm64/kernel/vdso/Makefile @@ -41,7 +41,7 @@ CC_FLAGS_REMOVE_VDSO := $(CC_FLAGS_FTRACE) -Os $(CC_FLAGS_SCS) \ $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) \ -Wmissing-prototypes -Wmissing-declarations -CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables +CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables $(CONFIG_CC_OPT_INLINE_MEMSET) CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO) CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 3806ff0920fe75..f59d89d04ecaf6 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2386,9 +2386,6 @@ int __init populate_nv_trap_config(void) print_nv_trap_error(fgt, "FGT bit is reserved", ret); } - if (!cpus_have_final_cap(ARM64_HAS_FGT)) - continue; - prev = xa_store(&sr_forward_xa, enc, xa_mk_value(tc.val), GFP_KERNEL); diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h index c904647d2f7606..5c050f21066ab0 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h @@ -12,6 +12,15 @@ #include #include +/* + * HCR_EL2 bits EL2 takes from the host on each entry, per VM type. The rest + * are EL2's own and nothing the host sets there reaches the guest. + */ +#define PKVM_HCR_EL2_HOST_PVM (HCR_EL2_TWI | HCR_EL2_TWE | HCR_EL2_VSE) +#define PKVM_HCR_EL2_HOST_NPVM (PKVM_HCR_EL2_HOST_PVM | HCR_EL2_VI | HCR_EL2_VF | \ + HCR_EL2_TVM | HCR_EL2_TID2 | HCR_EL2_TID4 | \ + HCR_EL2_TID5 | HCR_EL2_TTLBOS) + /* * Holds the relevant data for maintaining the vcpu state completely at hyp. */ diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index 9a3b92e626adba..ac64a036b0a959 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -216,6 +216,7 @@ static void sync_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu) static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) { struct kvm_vcpu *host_vcpu = hyp_vcpu->host_vcpu; + u64 host_hcr_mask = PKVM_HCR_EL2_HOST_PVM; fpsimd_sve_flush(); flush_debug_state(hyp_vcpu); @@ -228,6 +229,7 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) if (!pkvm_hyp_vcpu_is_protected(hyp_vcpu)) { if (vcpu_get_flag(host_vcpu, PKVM_HOST_STATE_DIRTY)) flush_hyp_vcpu_state(hyp_vcpu); + host_hcr_mask = PKVM_HCR_EL2_HOST_NPVM; } else { hyp_vcpu->vcpu.arch.ctxt = host_vcpu->arch.ctxt; } @@ -241,9 +243,8 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) * trap-control bit, so it must flow to the hyp vCPU alongside TWI/TWE * for the vSError to be delivered. sync_hyp_vcpu() reflects it back. */ - hyp_vcpu->vcpu.arch.hcr_el2 &= ~(HCR_TWI | HCR_TWE | HCR_VSE); - hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) & - (HCR_TWI | HCR_TWE | HCR_VSE); + hyp_vcpu->vcpu.arch.hcr_el2 &= ~host_hcr_mask; + hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) & host_hcr_mask; hyp_vcpu->vcpu.arch.iflags = host_vcpu->arch.iflags; diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index bb3e0dc0676ec9..6290c4b62659ba 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -30,6 +30,7 @@ unsigned int kvm_host_sve_max_vl; */ static DEFINE_PER_CPU(struct pkvm_hyp_vcpu *, loaded_hyp_vcpu); +/* The PKVM_HCR_EL2_HOST_{PVM,NPVM} bits of this value come from the host on each entry. */ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) { vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS; @@ -47,18 +48,17 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) if (cpus_have_final_cap(ARM64_HAS_STAGE2_FWB)) vcpu->arch.hcr_el2 |= HCR_FWB; - if (cpus_have_final_cap(ARM64_HAS_EVT) && - !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) && - kvm_read_vm_id_reg(vcpu->kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0)) - vcpu->arch.hcr_el2 |= HCR_TID4; - else - vcpu->arch.hcr_el2 |= HCR_TID2; + /* + * Without AArch32 EL1, leave RW set and let the entry fail with an + * illegal exception return: the *32_EL2 registers EL2 would otherwise + * switch are UNDEFINED there. + */ + if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_EL1_32BIT) && + cpus_have_final_cap(ARM64_HAS_32BIT_EL1)) + vcpu->arch.hcr_el2 &= ~HCR_EL2_RW; if (vcpu_has_ptrauth(vcpu)) vcpu->arch.hcr_el2 |= (HCR_API | HCR_APK); - - if (kvm_has_mte(vcpu->kvm)) - vcpu->arch.hcr_el2 |= HCR_ATA; } static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu) @@ -76,6 +76,13 @@ static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu) */ val |= HCR_TACR | HCR_TIDCP | HCR_TID3 | HCR_TID1; + if (cpus_have_final_cap(ARM64_HAS_EVT) && + !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) && + kvm_read_vm_id_reg(kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0)) + val |= HCR_EL2_TID4; + else + val |= HCR_EL2_TID2; + if (!kvm_has_feat(kvm, ID_AA64PFR0_EL1, RAS, IMP)) { val |= HCR_TERR | HCR_TEA; val &= ~(HCR_FIEN); diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 2d44cd6a5aed90..bf6d6526639faf 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1468,32 +1468,11 @@ transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot, return PAGE_SIZE; } -static int get_vma_page_shift(struct vm_area_struct *vma, unsigned long hva) +static int get_vma_page_shift(struct vm_area_struct *vma) { - unsigned long pa; - - if (is_vm_hugetlb_page(vma) && !(vma->vm_flags & VM_PFNMAP)) + if (is_vm_hugetlb_page(vma)) return huge_page_shift(hstate_vma(vma)); - if (!(vma->vm_flags & VM_PFNMAP)) - return PAGE_SHIFT; - - VM_BUG_ON(is_vm_hugetlb_page(vma)); - - pa = (vma->vm_pgoff << PAGE_SHIFT) + (hva - vma->vm_start); - -#ifndef __PAGETABLE_PMD_FOLDED - if ((hva & (PUD_SIZE - 1)) == (pa & (PUD_SIZE - 1)) && - ALIGN_DOWN(hva, PUD_SIZE) >= vma->vm_start && - ALIGN(hva, PUD_SIZE) <= vma->vm_end) - return PUD_SHIFT; -#endif - - if ((hva & (PMD_SIZE - 1)) == (pa & (PMD_SIZE - 1)) && - ALIGN_DOWN(hva, PMD_SIZE) >= vma->vm_start && - ALIGN(hva, PMD_SIZE) <= vma->vm_end) - return PMD_SHIFT; - return PAGE_SHIFT; } @@ -1794,7 +1773,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd, vma_shift = PAGE_SHIFT; } else { s2vi->max_map_size = PUD_SIZE; - vma_shift = get_vma_page_shift(vma, s2fd->hva); + vma_shift = get_vma_page_shift(vma); } switch (vma_shift) { @@ -1952,16 +1931,6 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd, return -EFAULT; } } else { - /* - * If the page was identified as device early by looking at - * the VMA flags, vma_pagesize is already representing the - * largest quantity we can map. If instead it was mapped - * via __kvm_faultin_pfn(), vma_pagesize is set to PAGE_SIZE - * and must not be upgraded. - * - * In both cases, we don't let transparent_hugepage_adjust() - * change things at the last minute. - */ s2vi->map_non_cacheable = true; } @@ -2051,10 +2020,10 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd, /* * If we are not forced to use page mapping, check if we are - * backed by a THP and thus use block mapping if possible. + * backed by a huge stage-1 mapping and thus use block mapping if + * possible. */ - if (mapping_size == PAGE_SIZE && - !(s2vi->max_map_size == PAGE_SIZE || s2vi->map_non_cacheable)) { + if (mapping_size == PAGE_SIZE && s2vi->max_map_size != PAGE_SIZE) { if (perm_fault_granule > PAGE_SIZE) { mapping_size = perm_fault_granule; } else { @@ -2135,10 +2104,6 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd) return ret; } - /* - * Let's check if we will get back a huge page backed by hugetlbfs, or - * get block mapping for device MMIO region. - */ ret = kvm_s2_fault_pin_pfn(s2fd, &s2vi); if (ret != 1) return ret; diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c index 4012df6002ea66..a58575df36e94a 100644 --- a/arch/arm64/kvm/vgic/vgic-init.c +++ b/arch/arm64/kvm/vgic/vgic-init.c @@ -97,6 +97,9 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) /* * - Acquiring the vCPU mutex for every *online* vCPU to prevent * concurrent vCPU ioctls for vCPUs already visible to userspace. + * This also ensures KVM isn't in the middle of creating a vCPU, + * i.e. that there are no vCPUs that have been created but aren't + * yet fully online. */ ret = -EBUSY; if (kvm_trylock_all_vcpus(kvm)) @@ -105,18 +108,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) /* * - Taking the config_lock which protects VGIC data structures such * as the per-vCPU arrays of private IRQs (SGIs, PPIs). - */ - mutex_lock(&kvm->arch.config_lock); - - /* - * - Bailing on the entire thing if a vCPU is in the middle of creation, - * dropped the kvm->lock, but hasn't reached kvm_arch_vcpu_create(). * * The whole combination of this guarantees that no vCPU can get into * KVM with a VGIC configuration inconsistent with the VM's VGIC. */ - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) - goto out_unlock; + mutex_lock(&kvm->arch.config_lock); if (irqchip_in_kernel(kvm)) { ret = -EEXIST; diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 0904ae850c3595..c10a11373e106b 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -319,12 +319,16 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq, return ret; } -static int update_affinity(struct vgic_irq *irq, struct kvm_vcpu *vcpu) +static int update_affinity(struct vgic_irq *irq, + struct kvm_vcpu *from_vcpu, struct kvm_vcpu *vcpu) { struct its_vlpi_map map; int ret; guard(raw_spinlock_irqsave)(&irq->irq_lock); + if (from_vcpu && irq->target_vcpu != from_vcpu) + return 0; + irq->target_vcpu = vcpu; if (!irq->hw) @@ -362,7 +366,7 @@ static void update_affinity_ite(struct kvm *kvm, struct its_ite *ite) return; vcpu = collection_to_vcpu(kvm, ite->collection); - update_affinity(ite->irq, vcpu); + update_affinity(ite->irq, NULL, vcpu); } /* @@ -856,7 +860,7 @@ static int vgic_its_cmd_handle_movi(struct kvm *kvm, struct vgic_its *its, vgic_its_invalidate_cache(its); - return update_affinity(ite->irq, vcpu); + return update_affinity(ite->irq, NULL, vcpu); } static bool __is_visible_gfn_locked(struct vgic_its *its, gpa_t gpa) @@ -1383,7 +1387,7 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its, if (!irq) continue; - update_affinity(irq, vcpu2); + update_affinity(irq, vcpu1, vcpu2); vgic_put_irq(kvm, irq); } diff --git a/arch/arm64/kvm/vgic/vgic-v2.c b/arch/arm64/kvm/vgic/vgic-v2.c index 7182f63fc9382c..70cc53ba810ade 100644 --- a/arch/arm64/kvm/vgic/vgic-v2.c +++ b/arch/arm64/kvm/vgic/vgic-v2.c @@ -122,6 +122,10 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu) for (int lr = 0; lr < vgic_cpu->vgic_v2.used_lrs; lr++) vgic_v2_fold_lr(vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs = 0; + if (!irq) + return; + /* See the GICv3 equivalent for the EOIcount handling rationale */ list_for_each_entry_continue(irq, &vgic_cpu->ap_list_head, ap_list) { u32 lr; @@ -144,8 +148,6 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu) vgic_v2_fold_lr(vcpu, lr); eoicount--; } - - cpuif->used_lrs = 0; } void vgic_v2_deactivate(struct kvm_vcpu *vcpu, u32 val) diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c index 726e20a1da6e72..346bacb3198f22 100644 --- a/arch/arm64/kvm/vgic/vgic-v3.c +++ b/arch/arm64/kvm/vgic/vgic-v3.c @@ -155,6 +155,10 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu) for (int lr = 0; lr < cpuif->used_lrs; lr++) vgic_v3_fold_lr(vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs = 0; + if (!irq) + return; + /* * EOIMode=0: use EOIcount to emulate deactivation. We are * guaranteed to deactivate in reverse order of the activation, so @@ -188,8 +192,6 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu) vgic_v3_fold_lr(vcpu, lr); eoicount--; } - - cpuif->used_lrs = 0; } void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val) diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index b25303d9919fd2..5cf5a1ef86cdd9 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -853,6 +853,17 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) goto retry; } + /* + * Fix the last_lr_irq refcount which was obtained while + * populating the LRs. This can also result in the LPI being + * deleted. + */ + irq = *host_data_ptr(last_lr_irq); + if (irq) { + deleted_lpis |= vgic_put_irq_norelease(vcpu->kvm, irq); + *host_data_ptr(last_lr_irq) = NULL; + } + raw_spin_unlock(&vgic_cpu->ap_list_lock); if (unlikely(deleted_lpis)) @@ -866,9 +877,6 @@ static void vgic_fold_state(struct kvm_vcpu *vcpu) return; } - if (!*host_data_ptr(last_lr_irq)) - return; - if (kvm_vgic_global_state.type == VGIC_V2) vgic_v2_fold_lr_state(vcpu); else @@ -1021,11 +1029,14 @@ static void vgic_flush_lr_state(struct kvm_vcpu *vcpu) scoped_guard(raw_spinlock, &irq->irq_lock) { if (likely(vgic_target_oracle(irq) == vcpu)) { vgic_populate_lr(vcpu, irq, count++); - *host_data_ptr(last_lr_irq) = irq; + if (count == kvm_vgic_global_state.nr_lr) { + vgic_get_irq_ref(irq); + *host_data_ptr(last_lr_irq) = irq; + } } } - if (count == kvm_vgic_global_state.nr_lr) + if (*host_data_ptr(last_lr_irq)) break; } diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 79d90226fd5dc9..d4384131e10d8d 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -2295,7 +2295,7 @@ pte_t modify_prot_start_ptes(struct vm_area_struct *vma, unsigned long addr, * in cases where cpu is affected with errata #2645198. */ if (pte_accessible(vma->vm_mm, pte) && pte_user_exec(pte)) - __flush_tlb_range(vma, addr, nr * PAGE_SIZE, + __flush_tlb_range(vma, addr, addr + nr * PAGE_SIZE, PAGE_SIZE, 3, TLBF_NOWALKCACHE); } diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c5f55d6161fee0..c1279fabce4731 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -2418,10 +2418,11 @@ bool bpf_jit_supports_subprog_tailcalls(void) return true; } -static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, - int bargs_off, int retval_off, int run_ctx_off, - bool save_ret) +static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_node *node, int bargs_off, + int retval_off, int run_ctx_off, bool save_ret) { + void *skip; __le32 *branch; u64 enter_prog; u64 exit_prog; @@ -2431,6 +2432,10 @@ static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, enter_prog = (u64)bpf_trampoline_enter(p); exit_prog = (u64)bpf_trampoline_exit(p); + /* nop, patched to skip this prog when it is detached */ + skip = ctx->ro_image + ctx->idx; + emit(A64_NOP, ctx); + if (node->cookie == 0) { /* if cookie is zero, one instruction is enough to store it */ emit(A64_STR64I(A64_ZR, A64_SP, run_ctx_off + cookie_off), ctx); @@ -2483,11 +2488,13 @@ static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, emit(A64_ADD_I(1, A64_R(2), A64_SP, run_ctx_off), ctx); emit_call(exit_prog, ctx); + + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_image + ctx->idx); } -static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, - int bargs_off, int retval_off, int run_ctx_off, - __le32 **branches) +static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_nodes *tn, int bargs_off, + int retval_off, int run_ctx_off, __le32 **branches) { int i; @@ -2496,7 +2503,7 @@ static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, */ emit(A64_STR64I(A64_ZR, A64_SP, retval_off), ctx); for (i = 0; i < tn->nr_nodes; i++) { - invoke_bpf_prog(ctx, tn->nodes[i], bargs_off, retval_off, + invoke_bpf_prog(ctx, im, tn->nodes[i], bargs_off, retval_off, run_ctx_off, true); /* if (*(u64 *)(sp + retval_off) != 0) * goto do_fexit; @@ -2882,7 +2889,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, store_func_meta(ctx, meta, func_meta_off); cookie_bargs_off--; } - invoke_bpf_prog(ctx, fentry->nodes[i], bargs_off, + invoke_bpf_prog(ctx, im, fentry->nodes[i], bargs_off, retval_off, run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET); } @@ -2893,7 +2900,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, if (!branches) return -ENOMEM; - invoke_bpf_mod_ret(ctx, fmod_ret, bargs_off, retval_off, + invoke_bpf_mod_ret(ctx, im, fmod_ret, bargs_off, retval_off, run_ctx_off, branches); } @@ -2906,9 +2913,6 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, emit(A64_RET(A64_R(10)), ctx); /* store return value */ emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx); - /* reserve a nop for bpf_tramp_image_put */ - im->ip_after_call = ctx->ro_image + ctx->idx; - emit(A64_NOP, ctx); } /* update the branches saved in invoke_bpf_mod_ret with cbnz */ @@ -2930,12 +2934,11 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, store_func_meta(ctx, meta, func_meta_off); cookie_bargs_off--; } - invoke_bpf_prog(ctx, fexit->nodes[i], bargs_off, retval_off, + invoke_bpf_prog(ctx, im, fexit->nodes[i], bargs_off, retval_off, run_ctx_off, false); } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_image + ctx->idx; /* for the first pass, assume the worst case */ if (!ctx->image) ctx->idx += 4; @@ -2994,7 +2997,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, .image = NULL, .idx = 0, }; - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct arg_aux aaux; int ret; @@ -3281,6 +3284,10 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, * longer reachable, since bpf_tramp_image_put() function already * uses percpu_ref and task-based rcu to do the sync, no need to call * the sync version here, see bpf_tramp_image_put() for details. + * + * 3. when a detached prog is patched out of a trampoline, a CPU that + * still executes the old nop calls the prog before it went through + * a quiescent state, and the prog is freed after grace periods. */ ret = aarch64_insn_patch_text_nosync(ip, new_insn); out: diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index 2775ba3359cfe3..58b6c084f9f25c 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -105,7 +105,6 @@ WORKAROUND_1902691 WORKAROUND_2038923 WORKAROUND_2064142 WORKAROUND_2077057 -WORKAROUND_2457168 WORKAROUND_2645198 WORKAROUND_2658417 WORKAROUND_4193714 @@ -132,3 +131,4 @@ WORKAROUND_REPEAT_TLBI_SYNC WORKAROUND_SPECULATIVE_AT WORKAROUND_SPECULATIVE_SSBS WORKAROUND_SPECULATIVE_UNPRIV_LOAD +WORKAROUND_BROKEN_AMU_CONSTCNT diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 4da2789009383b..e78eb582c3005e 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1578,6 +1578,24 @@ void *bpf_arch_text_copy(void *dst, void *src, size_t len) return ret ? ERR_PTR(-EINVAL) : dst; } +int arch_bpf_trampoline_skip(void *nop, void *target) +{ + u32 old_insn = INSN_NOP; + u32 new_insn = larch_insn_gen_b((unsigned long)nop, (unsigned long)target); + int ret; + + if (memcmp(nop, &old_insn, LOONGARCH_INSN_SIZE)) + return -EFAULT; + + cpus_read_lock(); + mutex_lock(&text_mutex); + ret = larch_insn_text_copy(nop, &new_insn, LOONGARCH_INSN_SIZE); + mutex_unlock(&text_mutex); + cpus_read_unlock(); + + return ret; +} + int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, enum bpf_text_poke_type new_t, void *old_addr, void *new_addr) @@ -1696,13 +1714,18 @@ static void restore_stk_args(struct jit_ctx *ctx, int nr_stk_args, int args_off, } } -static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *n, - int args_off, int retval_off, int run_ctx_off, bool save_ret) +static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_node *n, int args_off, int retval_off, + int run_ctx_off, bool save_ret) { int ret; u32 *branch; struct bpf_prog *p = n->link->prog; int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); + void *skip = ctx->ro_image + ctx->idx; + + /* nop, patched to a b over this prog when it is detached */ + emit_insn(ctx, nop); if (n->cookie) emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, @@ -1755,13 +1778,17 @@ static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *n, /* arg3: &run_ctx */ emit_insn(ctx, addid, LOONGARCH_GPR_A2, LOONGARCH_GPR_FP, -run_ctx_off); ret = emit_call(ctx, (const u64)bpf_trampoline_exit(p)); + if (ret) + return ret; - return ret; + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_image + ctx->idx); + return 0; } -static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, - int args_off, int retval_off, int run_ctx_off, - int func_meta_off, bool save_ret, u64 func_meta, int cookie_off) +static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_nodes *tn, int args_off, int retval_off, + int run_ctx_off, int func_meta_off, bool save_ret, + u64 func_meta, int cookie_off) { int i, cur_cookie = (cookie_off - args_off) / 8; @@ -1774,7 +1801,8 @@ static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, -func_meta_off, meta); cur_cookie--; } - err = invoke_bpf_prog(ctx, tn->nodes[i], args_off, retval_off, run_ctx_off, save_ret); + err = invoke_bpf_prog(ctx, im, tn->nodes[i], args_off, retval_off, + run_ctx_off, save_ret); if (err) return err; } @@ -2017,7 +2045,7 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i } if (fentry->nr_nodes) { - ret = invoke_bpf(ctx, fentry, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(ctx, im, fentry, args_off, retval_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off); if (ret) return ret; @@ -2029,7 +2057,7 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i emit_insn(ctx, std, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_FP, -retval_off); for (i = 0; i < fmod_ret->nr_nodes; i++) { - ret = invoke_bpf_prog(ctx, fmod_ret->nodes[i], + ret = invoke_bpf_prog(ctx, im, fmod_ret->nodes[i], args_off, retval_off, run_ctx_off, true); if (ret) goto out; @@ -2051,10 +2079,6 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i goto out; emit_insn(ctx, std, LOONGARCH_GPR_A0, LOONGARCH_GPR_FP, -retval_off); emit_insn(ctx, std, regmap[BPF_REG_0], LOONGARCH_GPR_FP, -(retval_off - 8)); - im->ip_after_call = ctx->ro_image + ctx->idx; - /* Reserve space for the move_imm + jirl instruction */ - for (i = 0; i < LOONGARCH_LONG_JUMP_NINSNS; i++) - emit_insn(ctx, nop); } for (i = 0; ctx->image && i < fmod_ret->nr_nodes; i++) { @@ -2068,14 +2092,13 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, -func_meta_off, func_meta); if (fexit->nr_nodes) { - ret = invoke_bpf(ctx, fexit, args_off, retval_off, run_ctx_off, + ret = invoke_bpf(ctx, im, fexit, args_off, retval_off, run_ctx_off, func_meta_off, false, func_meta, cookie_off); if (ret) goto out; } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_image + ctx->idx; move_addr(ctx, LOONGARCH_GPR_A0, (const u64)im); ret = emit_call(ctx, (const u64)__bpf_tramp_exit); if (ret) @@ -2177,11 +2200,8 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { int ret; - struct jit_ctx ctx; - struct bpf_tramp_image im; - - ctx.image = NULL; - ctx.idx = 0; + struct jit_ctx ctx = {}; + struct bpf_tramp_image im = {}; ret = __arch_prepare_bpf_trampoline(&ctx, &im, m, tnodes, func_addr, flags); diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile index 9c9181bb40715b..0b84892d084b64 100644 --- a/arch/loongarch/vdso/Makefile +++ b/arch/loongarch/vdso/Makefile @@ -16,6 +16,7 @@ ccflags-vdso := \ $(filter -m64,$(KBUILD_CFLAGS)) \ $(filter -march=%,$(KBUILD_CFLAGS)) \ $(filter -m%-float,$(KBUILD_CFLAGS)) \ + $(CONFIG_CC_OPT_INLINE_MEMSET) \ $(CLANG_FLAGS) \ -D__VDSO__ diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index 368759f8170841..0d1a49529885a5 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -43,6 +43,7 @@ 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 +ccflags-y += $(CONFIG_CC_OPT_INLINE_MEMSET) ccflags-y += -ffreestanding -fasynchronous-unwind-tables ccflags-remove-y := $(CC_FLAGS_FTRACE) ldflags-y := -Wl,--hash-style=both -nostdlib -shared -z noexecstack $(CLANG_FLAGS) diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c index dbac3573b2c899..de05d721edc4ef 100644 --- a/arch/powerpc/kvm/book3s_hv.c +++ b/arch/powerpc/kvm/book3s_hv.c @@ -3058,7 +3058,6 @@ static int kvmppc_core_vcpu_create_hv(struct kvm_vcpu *vcpu) init_waitqueue_head(&vcpu->arch.cpu_run); - mutex_lock(&kvm->lock); vcore = NULL; err = -EINVAL; if (cpu_has_feature(CPU_FTR_ARCH_300)) { @@ -3091,7 +3090,6 @@ static int kvmppc_core_vcpu_create_hv(struct kvm_vcpu *vcpu) mutex_unlock(&kvm->arch.mmu_setup_lock); } } - mutex_unlock(&kvm->lock); if (!vcore) return err; diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c index 7b07b43575f111..7a612688e7a20b 100644 --- a/arch/powerpc/net/bpf_jit_comp.c +++ b/arch/powerpc/net/bpf_jit_comp.c @@ -602,14 +602,18 @@ int arch_protect_bpf_trampoline(void *image, unsigned int size) } static int invoke_bpf_prog(u32 *image, u32 *ro_image, struct codegen_context *ctx, - struct bpf_tramp_node *n, int regs_off, int retval_off, - int run_ctx_off, bool save_ret) + struct bpf_tramp_image *im, struct bpf_tramp_node *n, + int regs_off, int retval_off, int run_ctx_off, bool save_ret) { struct bpf_prog *p = n->link->prog; ppc_inst_t branch_insn; - u32 jmp_idx; + u32 jmp_idx, skip_idx; int ret = 0; + /* nop, patched to skip this prog when it is detached */ + skip_idx = ctx->idx; + EMIT(PPC_RAW_NOP()); + /* Save cookie */ if (IS_ENABLED(CONFIG_PPC64)) { PPC_LI64(_R3, n->cookie); @@ -679,13 +683,17 @@ static int invoke_bpf_prog(u32 *image, u32 *ro_image, struct codegen_context *ct EMIT(PPC_RAW_ADDI(_R5, _R1, run_ctx_off)); ret = bpf_jit_emit_func_call_rel(image, ro_image, ctx, (unsigned long)bpf_trampoline_exit(p)); + if (ret) + return ret; - return ret; + if (ro_image) /* image is NULL for dummy pass */ + bpf_tramp_image_add_skip(im, p, &ro_image[skip_idx], &ro_image[ctx->idx]); + return 0; } static int invoke_bpf_mod_ret(u32 *image, u32 *ro_image, struct codegen_context *ctx, - struct bpf_tramp_nodes *tn, int regs_off, int retval_off, - int run_ctx_off, u32 *branches) + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tn, + int regs_off, int retval_off, int run_ctx_off, u32 *branches) { int i; @@ -696,8 +704,8 @@ static int invoke_bpf_mod_ret(u32 *image, u32 *ro_image, struct codegen_context EMIT(PPC_RAW_LI(_R3, 0)); EMIT(PPC_RAW_STL(_R3, _R1, retval_off)); for (i = 0; i < tn->nr_nodes; i++) { - if (invoke_bpf_prog(image, ro_image, ctx, tn->nodes[i], regs_off, retval_off, - run_ctx_off, true)) + if (invoke_bpf_prog(image, ro_image, ctx, im, tn->nodes[i], regs_off, + retval_off, run_ctx_off, true)) return -EINVAL; /* @@ -1043,8 +1051,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im cookie_ctx_off--; } - if (invoke_bpf_prog(image, ro_image, ctx, fentry->nodes[i], regs_off, retval_off, - run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET)) + if (invoke_bpf_prog(image, ro_image, ctx, im, fentry->nodes[i], regs_off, + retval_off, run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET)) return -EINVAL; } @@ -1053,7 +1061,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im if (!branches) return -ENOMEM; - if (invoke_bpf_mod_ret(image, ro_image, ctx, fmod_ret, regs_off, retval_off, + if (invoke_bpf_mod_ret(image, ro_image, ctx, im, fmod_ret, regs_off, retval_off, run_ctx_off, branches)) { ret = -EINVAL; goto cleanup; @@ -1090,11 +1098,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im /* Restore updated tail_call_cnt */ if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) bpf_trampoline_restore_tail_call_cnt(image, ctx, bpf_frame_size, r4_off); - - /* Reserve space to patch branch instruction to skip fexit progs */ - if (ro_image) /* image is NULL for dummy pass */ - im->ip_after_call = &((u32 *)ro_image)[ctx->idx]; - EMIT(PPC_RAW_NOP()); } /* Update branches saved in invoke_bpf_mod_ret with address of do_fexit */ @@ -1123,16 +1126,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im cookie_ctx_off--; } - if (invoke_bpf_prog(image, ro_image, ctx, fexit->nodes[i], regs_off, retval_off, - run_ctx_off, false)) { + if (invoke_bpf_prog(image, ro_image, ctx, im, fexit->nodes[i], regs_off, + retval_off, run_ctx_off, false)) { ret = -EINVAL; goto cleanup; } } if (flags & BPF_TRAMP_F_CALL_ORIG) { - if (ro_image) /* image is NULL for dummy pass */ - im->ip_epilogue = &((u32 *)ro_image)[ctx->idx]; PPC_LI_ADDR(_R3, im); ret = bpf_jit_emit_func_call_rel(image, ro_image, ctx, (unsigned long)__bpf_tramp_exit); @@ -1192,7 +1193,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; int ret; ret = __arch_prepare_bpf_trampoline(&im, NULL, NULL, NULL, m, flags, tnodes, func_addr); @@ -1320,7 +1321,7 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, /* * If we are not poking at bpf prog entry, then we are simply patching in/out - * an unconditional branch instruction at im->ip_after_call + * an unconditional branch instruction in a trampoline image */ if (offset) { if (old_t == BPF_MOD_CALL || new_t == BPF_MOD_CALL) { diff --git a/arch/powerpc/perf/imc-pmu.c b/arch/powerpc/perf/imc-pmu.c index f401181d8669c0..a9f8464997a227 100644 --- a/arch/powerpc/perf/imc-pmu.c +++ b/arch/powerpc/perf/imc-pmu.c @@ -1275,6 +1275,8 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, struct perf_event_header *header, struct perf_event *event) { + u16 misc = 0; + /* Sanity checks for a valid record */ if (be64_to_cpu(READ_ONCE(mem->tb1)) > *prev_tb) *prev_tb = be64_to_cpu(READ_ONCE(mem->tb1)); @@ -1289,23 +1291,19 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, data->ip = be64_to_cpu(READ_ONCE(mem->ip)); data->period = event->hw.last_period; - header->type = PERF_RECORD_SAMPLE; - header->size = sizeof(*header) + event->header_size; - header->misc = 0; - if (cpu_has_feature(CPU_FTR_ARCH_31)) { switch (IMC_TRACE_RECORD_VAL_HVPR(be64_to_cpu(READ_ONCE(mem->val)))) { case 0:/* when MSR HV and PR not set in the trace-record */ - header->misc |= PERF_RECORD_MISC_GUEST_KERNEL; + misc |= PERF_RECORD_MISC_GUEST_KERNEL; break; case 1: /* MSR HV is 0 and PR is 1 */ - header->misc |= PERF_RECORD_MISC_GUEST_USER; + misc |= PERF_RECORD_MISC_GUEST_USER; break; case 2: /* MSR HV is 1 and PR is 0 */ - header->misc |= PERF_RECORD_MISC_KERNEL; + misc |= PERF_RECORD_MISC_KERNEL; break; case 3: /* MSR HV is 1 and PR is 1 */ - header->misc |= PERF_RECORD_MISC_USER; + misc |= PERF_RECORD_MISC_USER; break; default: pr_info("IMC: Unable to set the flag based on MSR bits\n"); @@ -1313,11 +1311,14 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, } } else { if (is_kernel_addr(data->ip)) - header->misc |= PERF_RECORD_MISC_KERNEL; + misc |= PERF_RECORD_MISC_KERNEL; else - header->misc |= PERF_RECORD_MISC_USER; + misc |= PERF_RECORD_MISC_USER; } - perf_event_header__init_id(header, data, event); + perf_event_header__init(header, data, + PERF_RECORD_SAMPLE, misc, + sizeof(*header) + event->header_size, + event); return 0; } diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index d6c2dbf8455ced..5874ca30f4f6d3 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -453,9 +453,10 @@ config SMP config NR_CPUS int "Maximum number of CPUs (2-512)" depends on SMP - range 2 512 if !RISCV_SBI_V01 - range 2 32 if RISCV_SBI_V01 && 32BIT + range 2 512 if !RISCV_SBI_V01 && 64BIT + range 2 128 if !RISCV_SBI_V01 && 32BIT range 2 64 if RISCV_SBI_V01 && 64BIT + range 2 32 if RISCV_SBI_V01 && 32BIT default "32" if 32BIT default "64" if RISCV_SBI_V01 && 64BIT default "256" if !RISCV_SBI_V01 && 64BIT diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 3c945d086c7d02..622ade116a2d4f 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -46,7 +46,7 @@ config ERRATA_MIPS_P8700_PAUSE_OPCODE config ERRATA_SIFIVE bool "SiFive errata" - depends on RISCV_ALTERNATIVE + depends on RISCV_ALTERNATIVE && 64BIT help All SiFive errata Kconfig depend on this Kconfig. Disabling this Kconfig will disable all SiFive errata. Please say "Y" @@ -96,6 +96,20 @@ config ERRATA_STARFIVE_JH7100 Say "Y" if you want to support the BeagleV Starlight and/or StarFive VisionFive V1 boards. +config ERRATA_SIFIVE_MAL_9092 + bool "Apply SiFive HLV/HLVX/HSV fence errata" + depends on ERRATA_SIFIVE && 64BIT + default y + help + This will apply the SiFive MAL-9092 errata to add fence.rw + instructions around all HLV/HLVX/HSV instructions. + + Note: HSTATUS.HU is cleared during KVM initialization. If future + changes enable HSTATUS.HU to allow hypervisor instructions in + U-mode, additional errata handling may be required. + + If you don't know what to do here, say "Y". + config ERRATA_THEAD bool "T-HEAD errata" depends on RISCV_ALTERNATIVE @@ -146,6 +160,7 @@ config ERRATA_THEAD_PMU config ERRATA_THEAD_GHOSTWRITE bool "Apply T-Head Ghostwrite errata" depends on ERRATA_THEAD && RISCV_ISA_XTHEADVECTOR + select RISCV_ALTERNATIVE_EARLY default y help The T-Head C9xx cores have a vulnerability in the xtheadvector diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 429e0758930687..cecbce0c498384 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -34,6 +34,7 @@ config ARCH_RENESAS config ARCH_SIFIVE bool "SiFive SoCs" select ERRATA_SIFIVE + depends on 64BIT help This enables support for SiFive SoC platform hardware. diff --git a/arch/riscv/errata/sifive/errata.c b/arch/riscv/errata/sifive/errata.c index df80c9614df1a0..1a5d223ebf35b8 100644 --- a/arch/riscv/errata/sifive/errata.c +++ b/arch/riscv/errata/sifive/errata.c @@ -51,6 +51,22 @@ static bool errata_cip_1200_check_func(unsigned long arch_id, unsigned long imp return true; } +static bool errata_mal_9092_check_func(unsigned long arch_id, unsigned long impid) +{ + /* + * Affected cores: + * Architecture ID: 0x8000000000000109 + * Implementation ID: 0x19251031, 0x19251217, 0x19260320 + */ + + if (arch_id != 0x8000000000000109) + return false; + if (impid != 0x19251031 && impid != 0x19251217 && impid != 0x19260320) + return false; + + return true; +} + static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { { .name = "cip-453", @@ -60,6 +76,10 @@ static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { .name = "cip-1200", .check_func = errata_cip_1200_check_func }, + { + .name = "mal-9092", + .check_func = errata_mal_9092_check_func + }, }; static u32 __init_or_module sifive_errata_probe(unsigned long archid, diff --git a/arch/riscv/include/asm/cpufeature.h b/arch/riscv/include/asm/cpufeature.h index 739fcc84bf7b28..5efa72823475fd 100644 --- a/arch/riscv/include/asm/cpufeature.h +++ b/arch/riscv/include/asm/cpufeature.h @@ -40,6 +40,8 @@ extern u32 thead_vlenb_of; void __init riscv_user_isa_enable(void); +void riscv_clear_hypervisor_csr(void); + #define _RISCV_ISA_EXT_DATA(_name, _id, _subset_exts, _subset_exts_size, _validate) { \ .name = #_name, \ .property = #_name, \ diff --git a/arch/riscv/include/asm/errata_list_vendors.h b/arch/riscv/include/asm/errata_list_vendors.h index ec7eba3734371a..c62a82f60468a3 100644 --- a/arch/riscv/include/asm/errata_list_vendors.h +++ b/arch/riscv/include/asm/errata_list_vendors.h @@ -11,7 +11,8 @@ #ifdef CONFIG_ERRATA_SIFIVE #define ERRATA_SIFIVE_CIP_453 0 #define ERRATA_SIFIVE_CIP_1200 1 -#define ERRATA_SIFIVE_NUMBER 2 +#define ERRATA_SIFIVE_MAL_9092 2 +#define ERRATA_SIFIVE_NUMBER 3 #endif #ifdef CONFIG_ERRATA_THEAD diff --git a/arch/riscv/include/asm/futex.h b/arch/riscv/include/asm/futex.h index 90c86b115e008a..6f3bc9bdb85e39 100644 --- a/arch/riscv/include/asm/futex.h +++ b/arch/riscv/include/asm/futex.h @@ -40,6 +40,7 @@ arch_futex_atomic_op_inuser(int op, int oparg, int *oval, u32 __user *uaddr) if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); switch (op) { case FUTEX_OP_SET: @@ -82,6 +83,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); __enable_user_access(); __asm__ __volatile__ ( diff --git a/arch/riscv/include/asm/insn-def.h b/arch/riscv/include/asm/insn-def.h index 7c6daf11675675..8fcc848c196e00 100644 --- a/arch/riscv/include/asm/insn-def.h +++ b/arch/riscv/include/asm/insn-def.h @@ -192,18 +192,28 @@ INSN_R(OPCODE_SYSTEM, FUNC3(0), FUNC7(49), \ __RD(0), RS1(gaddr), RS2(vmid)) +#define ALT_SIFIVE_MAL_9092_FENCE \ +ALTERNATIVE("nop", "fence rw, rw", SIFIVE_VENDOR_ID, \ + ERRATA_SIFIVE_MAL_9092, CONFIG_ERRATA_SIFIVE_MAL_9092) + #define HLVX_HU(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(50), \ - RD(dest), RS1(addr), __RS2(3)) + RD(dest), RS1(addr), __RS2(3)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #define HLV_W(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(52), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #ifdef CONFIG_64BIT #define HLV_D(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(54), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #else #define HLV_D(dest, addr) \ __ASM_STR(.error "hlv.d requires 64-bit support") diff --git a/arch/riscv/include/asm/scs.h b/arch/riscv/include/asm/scs.h index 023a412fe38d37..49e40eef4ed7b2 100644 --- a/arch/riscv/include/asm/scs.h +++ b/arch/riscv/include/asm/scs.h @@ -4,6 +4,7 @@ #ifdef __ASSEMBLER__ #include +#include #ifdef CONFIG_SHADOW_CALL_STACK @@ -22,9 +23,17 @@ REG_L gp, TASK_TI_SCS_SP(tp) .endm -/* Load task_scs_sp(current) to gp, but only if tp has changed. */ -.macro scs_load_current_if_task_changed prev - beq \prev, tp, _skip_scs +/* + * Load task_scs_sp(current) to gp, but only when the trap came from U-mode. + * The source privilege level is taken from the saved sstatus.SPP bit (written + * by hardware on trap entry), not by comparing tp. tp is a user-writable + * register, so gating the shadow call stack reload on it let a user task that + * set tp == ¤t skip the reload and run the kernel with an attacker + * controlled gp (the shadow call stack pointer). + */ +.macro scs_load_current_if_from_user status, tmp + andi \tmp, \status, SR_SPP + bnez \tmp, _skip_scs scs_load_current _skip_scs: .endm @@ -42,7 +51,7 @@ .endm .macro scs_load_current .endm -.macro scs_load_current_if_task_changed prev +.macro scs_load_current_if_from_user status, tmp .endm .macro scs_save_current .endm diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index fffe72a7720804..1f8dcae3af517e 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -137,8 +137,8 @@ static __always_inline void __vstate_csr_save(struct __riscv_v_ext_state *dest) "csrr %0, " __stringify(CSR_VSTART) "\n\t" "csrr %1, " __stringify(CSR_VTYPE) "\n\t" "csrr %2, " __stringify(CSR_VL) "\n\t" - : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl), - "=r" (dest->vcsr) : :); + : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl) + : :); if (has_xtheadvector()) { unsigned long status; @@ -230,7 +230,7 @@ static inline void __riscv_v_vstate_save(struct __riscv_v_ext_state *save_to, "add %1, %1, %0\n\t" "vse8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } riscv_v_disable(); } @@ -266,7 +266,7 @@ static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_ "add %1, %1, %0\n\t" "vle8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } __vstate_csr_restore(restore_from); riscv_v_disable(); diff --git a/arch/riscv/include/uapi/asm/vendor/mips.h b/arch/riscv/include/uapi/asm/vendor/mips.h index e65ab268b26551..a31c23afe74fb2 100644 --- a/arch/riscv/include/uapi/asm/vendor/mips.h +++ b/arch/riscv/include/uapi/asm/vendor/mips.h @@ -1,3 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL BIT(0) +#include + +#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL _BITULL(0) diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index 61d21f7148305c..7329792978cc0f 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -1233,6 +1233,18 @@ void __init riscv_user_isa_enable(void) pr_warn("Zicbop disabled as it is unavailable on some harts\n"); } +void riscv_clear_hypervisor_csr(void) +{ + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_H)) + return; + + /* + * Clear HSTATUS.HU to restrict hypervisor instructions to HS-mode. + * This prevents user-mode from executing HLV/HSV instructions. + */ + csr_clear(CSR_HSTATUS, HSTATUS_HU); +} + #ifdef CONFIG_RISCV_ALTERNATIVE /* * Alternative patch sites consider 48 bits when determining when to patch diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index d799c4e56f8049..61b7a77166971d 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -208,7 +208,7 @@ SYM_CODE_START(handle_exception) load_global_pointer /* Load the kernel shadow call stack pointer if coming from userspace */ - scs_load_current_if_task_changed s5 + scs_load_current_if_from_user s1, s5 #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE move a0, sp diff --git a/arch/riscv/kernel/patch.c b/arch/riscv/kernel/patch.c index 2239c28981bc39..2b5adf01c55027 100644 --- a/arch/riscv/kernel/patch.c +++ b/arch/riscv/kernel/patch.c @@ -48,7 +48,7 @@ static __always_inline void *patch_map(void *addr, const unsigned int fixmap) if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) return addr; phys = __pa_symbol(addr); - } else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) { + } else if (IS_ENABLED(CONFIG_EXECMEM)) { struct page *page = vmalloc_to_page(addr); BUG_ON(!page); diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index f336a183667eb8..a0055743d3ba50 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -230,6 +230,7 @@ static int riscv_vr_set(struct task_struct *target, return -ENODATA; /* Copy rest of the vstate except datap */ + memset(&ptrace_vstate, 0, sizeof(ptrace_vstate)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0, sizeof(struct __riscv_v_regset_state)); if (unlikely(ret)) @@ -299,6 +300,9 @@ static int riscv_cfi_get(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + memset(&user_cfi, 0, sizeof(user_cfi)); regs = task_pt_regs(target); @@ -337,8 +341,12 @@ static int riscv_cfi_set(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + regs = task_pt_regs(target); + memset(&user_cfi, 0, sizeof(user_cfi)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); if (ret) return ret; diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index a32344bb220dff..d79f99b8603342 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -366,6 +366,8 @@ void __init setup_arch(char **cmdline_p) if (!IS_ENABLED(CONFIG_RISCV_ISA_ZBB) || !riscv_isa_extension_available(NULL, ZBB)) static_branch_disable(&efficient_ffs_key); + + riscv_clear_hypervisor_csr(); } bool arch_cpu_is_hotpluggable(int cpu) diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c index f6ef57930b50a8..a9de2dae804c61 100644 --- a/arch/riscv/kernel/smpboot.c +++ b/arch/riscv/kernel/smpboot.c @@ -244,6 +244,8 @@ asmlinkage __visible void smp_callin(void) numa_add_cpu(curr_cpuid); + riscv_clear_hypervisor_csr(); + pr_debug("CPU%u: Booted secondary hartid %lu\n", curr_cpuid, cpuid_to_hartid_map(curr_cpuid)); diff --git a/arch/riscv/kernel/suspend.c b/arch/riscv/kernel/suspend.c index 3efbf7874f3ba2..db220966f78272 100644 --- a/arch/riscv/kernel/suspend.c +++ b/arch/riscv/kernel/suspend.c @@ -43,6 +43,8 @@ void suspend_save_csrs(struct suspend_context *context) void suspend_restore_csrs(struct suspend_context *context) { + riscv_clear_hypervisor_csr(); + csr_write(CSR_SCRATCH, 0); if (riscv_has_extension_unlikely(RISCV_ISA_EXT_XLINUXENVCFG)) csr_write(CSR_ENVCFG, context->envcfg); diff --git a/arch/riscv/kernel/vdso/Makefile b/arch/riscv/kernel/vdso/Makefile index 8dbf2532a57303..c023046a3fd75a 100644 --- a/arch/riscv/kernel/vdso/Makefile +++ b/arch/riscv/kernel/vdso/Makefile @@ -36,6 +36,7 @@ endif ccflags-y := -fno-stack-protector ccflags-y += -DDISABLE_BRANCH_PROFILING ccflags-y += -fno-builtin +ccflags-y += $(CONFIG_CC_OPT_INLINE_MEMSET) ccflags-y += $(KBUILD_BASE_ISA)$(CFI_MARCH) ccflags-y += $(CFI_FULL) asflags-y += $(KBUILD_BASE_ISA)$(CFI_MARCH) diff --git a/arch/riscv/kvm/aia_device.c b/arch/riscv/kvm/aia_device.c index efc7c0bcfba921..97833a04268a9c 100644 --- a/arch/riscv/kvm/aia_device.c +++ b/arch/riscv/kvm/aia_device.c @@ -237,7 +237,7 @@ static int aia_init(struct kvm *kvm) return -EBUSY; /* We might be in the middle of creating a VCPU? */ - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) + if (kvm_is_vcpu_creation_in_progress(kvm)) return -EBUSY; /* Number of sources should be less than or equals number of IDs */ diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S index a8911605c24801..5b3bbf0a30987a 100644 --- a/arch/riscv/lib/strnlen.S +++ b/arch/riscv/lib/strnlen.S @@ -87,9 +87,25 @@ strnlen_zbb: * Aligned boundary. Use the address of the last valid byte * (s + count - 1) to avoid loading a word past the count * boundary in the loop below. count == 0 is handled above. + * + * Saturate the boundary when s + count would wrap around (very + * large counts, e.g. SIZE_MAX passed by FORTIFY strcat/strlcat + * with a destination whose size is unknown at compile time). + * Without this, the wrapped boundary lands before s and the + * pre-loop guard below always exits, returning a truncated + * length. + * + * Clamping the increment to ~s (== SIZE_MAX - s) keeps the + * computation branchless and wrap-free: + * + * s + min(count - 1, ~s) == saturating_add(s, count - 1) + * + * Saturating makes the scan equivalent to strlen(). */ - add t4, a0, a1 - addi t4, t4, -1 + addi t4, a1, -1 /* count - 1 */ + not t1, a0 /* SIZE_MAX - s */ + minu t4, t4, t1 /* clamp, so s + t4 can never wrap */ + add t4, a0, t4 /* saturated s + count - 1 */ andi t4, t4, -SZREG /* Get the first word. */ diff --git a/arch/riscv/mm/extable.c b/arch/riscv/mm/extable.c index dd1530af3ef15b..e252eba55edece 100644 --- a/arch/riscv/mm/extable.c +++ b/arch/riscv/mm/extable.c @@ -68,8 +68,8 @@ ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex, addr = regs_get_gpr(regs, reg_addr * sizeof(unsigned long)); - offset = addr & 0x7UL; - addr &= ~0x7UL; + offset = addr & (sizeof(data) - 1); + addr &= ~(sizeof(data) - 1); data = *(unsigned long *)addr >> (offset * 8); diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c index 151031e97a243c..01fe66774f029f 100644 --- a/arch/riscv/net/bpf_jit_comp64.c +++ b/arch/riscv/net/bpf_jit_comp64.c @@ -822,6 +822,24 @@ static int gen_jump_or_nops(void *target, void *ip, u32 *insns, bool is_call) return emit_jump_and_link(is_call ? RV_REG_T0 : RV_REG_ZERO, rvoff, false, &ctx); } +int arch_bpf_trampoline_skip(void *nop, void *target) +{ + u32 old_insn = rv_nop(); + u32 new_insn = rv_jal(RV_REG_ZERO, (target - nop) >> 1); + int ret; + + if (memcmp(nop, &old_insn, sizeof(old_insn))) + return -EFAULT; + + cpus_read_lock(); + mutex_lock(&text_mutex); + ret = patch_text(nop, &new_insn, sizeof(new_insn)); + mutex_unlock(&text_mutex); + cpus_read_unlock(); + + return ret; +} + int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, enum bpf_text_poke_type new_t, void *old_addr, void *new_addr) @@ -904,12 +922,17 @@ static void emit_store_stack_imm64(u8 reg, int stack_off, u64 imm64, emit_sd(RV_REG_FP, stack_off, reg, ctx); } -static int invoke_bpf_prog(struct bpf_tramp_node *node, int args_off, int retval_off, - int run_ctx_off, bool save_ret, struct rv_jit_context *ctx) +static int invoke_bpf_prog(struct bpf_tramp_image *im, struct bpf_tramp_node *node, + int args_off, int retval_off, int run_ctx_off, bool save_ret, + struct rv_jit_context *ctx) { int ret, branch_off; struct bpf_prog *p = node->link->prog; int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); + void *skip = ctx->ro_insns + ctx->ninsns; + + /* nop, patched to a jal over this prog when it is detached */ + emit(rv_nop(), ctx); if (node->cookie) emit_store_stack_imm64(RV_REG_T1, -run_ctx_off + cookie_off, node->cookie, ctx); @@ -962,13 +985,17 @@ static int invoke_bpf_prog(struct bpf_tramp_node *node, int args_off, int retval /* arg3: &run_ctx */ emit_addi(RV_REG_A2, RV_REG_FP, -run_ctx_off, ctx); ret = emit_call((const u64)bpf_trampoline_exit(p), true, ctx); + if (ret) + return ret; - return ret; + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_insns + ctx->ninsns); + return 0; } -static int invoke_bpf(struct bpf_tramp_nodes *tn, int args_off, int retval_off, - int run_ctx_off, int func_meta_off, bool save_ret, u64 func_meta, - int cookie_off, struct rv_jit_context *ctx) +static int invoke_bpf(struct bpf_tramp_image *im, struct bpf_tramp_nodes *tn, + int args_off, int retval_off, int run_ctx_off, int func_meta_off, + bool save_ret, u64 func_meta, int cookie_off, + struct rv_jit_context *ctx) { int i, cur_cookie = (cookie_off - args_off) / 8; @@ -981,8 +1008,8 @@ static int invoke_bpf(struct bpf_tramp_nodes *tn, int args_off, int retval_off, emit_store_stack_imm64(RV_REG_T1, -func_meta_off, meta, ctx); cur_cookie--; } - err = invoke_bpf_prog(tn->nodes[i], args_off, retval_off, run_ctx_off, - save_ret, ctx); + err = invoke_bpf_prog(im, tn->nodes[i], args_off, retval_off, + run_ctx_off, save_ret, ctx); if (err) return err; } @@ -1170,7 +1197,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, } if (fentry->nr_nodes) { - ret = invoke_bpf(fentry, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(im, fentry, args_off, retval_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off, ctx); if (ret) return ret; @@ -1184,7 +1211,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* cleanup to avoid garbage return value confusion */ emit_sd(RV_REG_FP, -retval_off, RV_REG_ZERO, ctx); for (i = 0; i < fmod_ret->nr_nodes; i++) { - ret = invoke_bpf_prog(fmod_ret->nodes[i], args_off, retval_off, + ret = invoke_bpf_prog(im, fmod_ret->nodes[i], args_off, retval_off, run_ctx_off, true, ctx); if (ret) goto out; @@ -1211,10 +1238,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx); emit_sd(RV_REG_FP, -retval_off, RV_REG_A0, ctx); emit_sd(RV_REG_FP, -(retval_off - 8), regmap[BPF_REG_0], ctx); - im->ip_after_call = ctx->ro_insns + ctx->ninsns; - /* 2 nops reserved for auipc+jalr pair */ - emit(rv_nop(), ctx); - emit(rv_nop(), ctx); } /* update branches saved in invoke_bpf_mod_ret with bnez */ @@ -1230,14 +1253,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_store_stack_imm64(RV_REG_T1, -func_meta_off, func_meta, ctx); if (fexit->nr_nodes) { - ret = invoke_bpf(fexit, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(im, fexit, args_off, retval_off, run_ctx_off, func_meta_off, false, func_meta, cookie_off, ctx); if (ret) goto out; } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_insns + ctx->ninsns; emit_imm(RV_REG_A0, ctx->insns ? (const s64)im : RV_MAX_COUNT_IMM, ctx); ret = emit_call((const u64)__bpf_tramp_exit, true, ctx); if (ret) @@ -1299,7 +1321,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct rv_jit_context ctx; int ret; diff --git a/arch/s390/kernel/vdso/Makefile b/arch/s390/kernel/vdso/Makefile index 35c834b895ecb3..54bcf2984ca11a 100644 --- a/arch/s390/kernel/vdso/Makefile +++ b/arch/s390/kernel/vdso/Makefile @@ -29,6 +29,7 @@ KBUILD_CFLAGS_VDSO := $(filter-out -munaligned-symbols,$(KBUILD_CFLAGS_VDSO)) KBUILD_CFLAGS_VDSO := $(filter-out -fno-asynchronous-unwind-tables,$(KBUILD_CFLAGS_VDSO)) KBUILD_CFLAGS_VDSO += -fPIC -fno-common -fno-builtin -fasynchronous-unwind-tables KBUILD_CFLAGS_VDSO += -fno-stack-protector $(DISABLE_KSTACK_ERASE) +KBUILD_CFLAGS_VDSO += $(CONFIG_CC_OPT_INLINE_MEMSET) ldflags-y := -shared -soname=linux-vdso.so.1 \ --hash-style=both --build-id=sha1 \ $(call ld-option, --eh-frame-hdr) -T diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index efd77b9cd36c5e..e4912db67adc14 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -3579,12 +3579,11 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu) void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) { - mutex_lock(&vcpu->kvm->lock); preempt_disable(); vcpu->arch.sie_block->epoch = vcpu->kvm->arch.epoch; vcpu->arch.sie_block->epdx = vcpu->kvm->arch.epdx; preempt_enable(); - mutex_unlock(&vcpu->kvm->lock); + if (!kvm_is_ucontrol(vcpu->kvm)) { vcpu->arch.gmap = vcpu->kvm->arch.gmap; sca_add_vcpu(vcpu); @@ -3757,13 +3756,11 @@ static int kvm_s390_vcpu_setup(struct kvm_vcpu *vcpu) kvm_s390_vcpu_pci_setup(vcpu); - mutex_lock(&vcpu->kvm->lock); if (kvm_s390_pv_is_protected(vcpu->kvm)) { rc = kvm_s390_pv_create_cpu(vcpu, &uvrc, &uvrrc); if (rc) kvm_s390_vcpu_unsetup_cmma(vcpu); } - mutex_unlock(&vcpu->kvm->lock); return rc; } diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c index c4b47070bb590c..1b2566012b63af 100644 --- a/arch/s390/net/bpf_jit_comp.c +++ b/arch/s390/net/bpf_jit_comp.c @@ -2566,6 +2566,8 @@ struct bpf_tramp_jit { int r14_off; /* Offset of saved %r14, has to be at the * bottom */ int do_fexit; /* do_fexit: label */ + int skip[BPF_MAX_TRAMP_LINKS]; /* skip: labels after each prog */ + int nr_progs; }; static void load_imm64(struct bpf_jit *jit, int dst_reg, u64 val) @@ -2584,6 +2586,7 @@ static void emit_store_stack_imm64(struct bpf_jit *jit, int tmp_reg, int stack_o } static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, + struct bpf_tramp_image *im, const struct btf_func_model *m, struct bpf_tramp_node *node, bool save_ret) { @@ -2591,8 +2594,20 @@ static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, int cookie_off = tjit->run_ctx_off + offsetof(struct bpf_tramp_run_ctx, bpf_cookie); struct bpf_prog *p = node->link->prog; + void *skip = jit->prg_buf + jit->prg; + int idx = tjit->nr_progs++; int patch; + if (idx >= ARRAY_SIZE(tjit->skip)) + return -E2BIG; + + /* + * nop, patched to skip this prog when it is detached + */ + + /* brcl 0,skip */ + EMIT6_PCREL_RILC(0xc0040000, 0, tjit->skip[idx]); + /* * run_ctx.cookie = node->cookie; */ @@ -2652,10 +2667,15 @@ static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, /* brasl %r14,__bpf_prog_exit */ EMIT6_PCREL_RILB_PTR(0xc0050000, REG_14, bpf_trampoline_exit(p)); + /* skip: */ + tjit->skip[idx] = jit->prg; + bpf_tramp_image_add_skip(im, p, skip, jit->prg_buf + jit->prg); + return 0; } static int invoke_bpf(struct bpf_tramp_jit *tjit, + struct bpf_tramp_image *im, const struct btf_func_model *m, struct bpf_tramp_nodes *tn, bool save_ret, u64 func_meta, int cookie_off) @@ -2670,7 +2690,7 @@ static int invoke_bpf(struct bpf_tramp_jit *tjit, emit_store_stack_imm64(jit, REG_0, tjit->func_meta_off, meta); cur_cookie--; } - if (invoke_bpf_prog(tjit, m, tn->nodes[i], save_ret)) + if (invoke_bpf_prog(tjit, im, m, tn->nodes[i], save_ret)) return -EINVAL; } @@ -2712,6 +2732,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, u64 func_meta; int i, j; + /* The skip labels are taken from the previous pass. */ + tjit->nr_progs = 0; + if (im) + im->nr_skips = 0; + /* Support as many stack arguments as "mvc" instruction can handle. */ nr_reg_args = min_t(int, m->nr_args, MAX_NR_REG_ARGS); nr_stack_args = m->nr_args - nr_reg_args; @@ -2875,7 +2900,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_store_stack_imm64(jit, REG_0, tjit->retval_off, 0); } - if (invoke_bpf(tjit, m, fentry, flags & BPF_TRAMP_F_RET_FENTRY_RET, + if (invoke_bpf(tjit, im, m, fentry, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off)) return -EINVAL; @@ -2889,7 +2914,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, 0xf000 | tjit->retval_off); for (i = 0; i < fmod_ret->nr_nodes; i++) { - if (invoke_bpf_prog(tjit, m, fmod_ret->nodes[i], true)) + if (invoke_bpf_prog(tjit, im, m, fmod_ret->nodes[i], true)) return -EINVAL; /* @@ -2943,15 +2968,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* mvc tccnt_off(%r15),tail_call_cnt(4,%r15) */ _EMIT6(0xd203f000 | tjit->tccnt_off, 0xf000 | offsetof(struct prog_frame, tail_call_cnt)); - - im->ip_after_call = jit->prg_buf + jit->prg; - - /* - * The following nop will be patched by bpf_tramp_image_put(). - */ - - /* brcl 0,im->ip_epilogue */ - EMIT6_PCREL_RILC(0xc0040000, 0, (u64)im->ip_epilogue); } /* Set the "is_return" flag for fsession. */ @@ -2962,12 +2978,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* do_fexit: */ tjit->do_fexit = jit->prg; - if (invoke_bpf(tjit, m, fexit, false, func_meta, cookie_off)) + if (invoke_bpf(tjit, im, m, fexit, false, func_meta, cookie_off)) return -EINVAL; if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = jit->prg_buf + jit->prg; - /* * __bpf_tramp_exit(im); */ @@ -3016,7 +3030,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *orig_call) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct bpf_tramp_jit tjit; int ret; diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c index ddd127ee96785d..e1fb2a76fdff59 100644 --- a/arch/um/drivers/vector_transports.c +++ b/arch/um/drivers/vector_transports.c @@ -209,6 +209,7 @@ static int raw_verify_header( if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) return 1; + skb_set_network_header(skb, ETH_HLEN); virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); return 0; } diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index f522fff9231e8e..1903ed8bbe3b31 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -1293,6 +1293,7 @@ static void gcm_process_assoc(const struct aes_gcm_key *key, u8 ghash_acc[16], unsigned int len; const u8 *src = walk.addr; + assoclen -= orig_len_this_step; if (unlikely(pos)) { len = min(len_this_step, 16 - pos); memcpy(&buf[pos], src, len); @@ -1320,7 +1321,6 @@ static void gcm_process_assoc(const struct aes_gcm_key *key, u8 ghash_acc[16], kernel_fpu_end(); kernel_fpu_begin(); } - assoclen -= orig_len_this_step; } if (unlikely(pos)) aes_gcm_aad_update(key, ghash_acc, buf, pos, flags); diff --git a/arch/x86/entry/vdso/vdso64/Makefile b/arch/x86/entry/vdso/vdso64/Makefile index 7c0790065b5e76..c2353a06527965 100644 --- a/arch/x86/entry/vdso/vdso64/Makefile +++ b/arch/x86/entry/vdso/vdso64/Makefile @@ -14,7 +14,7 @@ vobjs-$(CONFIG_X86_SGX) += vsgx.o vobjs-$(CONFIG_FUTEX_ROBUST_UNLOCK) += vfutex.o # Compilation flags -flags-y := -DBUILD_VDSO64 -m64 -mcmodel=small +flags-y := -DBUILD_VDSO64 -m64 -mcmodel=small $(CONFIG_CC_OPT_INLINE_MEMSET) # The location of this include matters! include $(src)/../common/Makefile.include diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f9217..fe84cf4e97d35a 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -477,6 +477,8 @@ #define X86_FEATURE_NO_NESTED_DATA_BP (20*32+ 0) /* No Nested Data Breakpoints */ #define X86_FEATURE_WRMSR_XX_BASE_NS (20*32+ 1) /* WRMSR to {FS,GS,KERNEL_GS}_BASE is non-serializing */ #define X86_FEATURE_LFENCE_RDTSC (20*32+ 2) /* LFENCE always serializing / synchronizes RDTSC */ + +#define X86_FEATURE_TLBSYNC_SFW_NO (20*32+ 4) /* TLBI Erratum #1718 fixed in microcode */ #define X86_FEATURE_VERW_CLEAR (20*32+ 5) /* The memory form of VERW mitigates TSA */ #define X86_FEATURE_NULL_SEL_CLR_BASE (20*32+ 6) /* Null Selector Clears Base */ @@ -587,4 +589,5 @@ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ #define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ +#define X86_BUG_TLBI X86_BUG( 1*32+12) /* "tlbi" AMD TLBI Erratum #1718 */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/arch/x86/include/asm/tlb.h b/arch/x86/include/asm/tlb.h index 866ea78ba156ae..eba925c85f522b 100644 --- a/arch/x86/include/asm/tlb.h +++ b/arch/x86/include/asm/tlb.h @@ -110,6 +110,12 @@ static inline void __tlbsync(void) /* TLBSYNC: supported in binutils >= 0.36. */ asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); + + if (!static_cpu_has_bug(X86_BUG_TLBI)) + return; + + /* TLBSYNC: supported in binutils >= 0.36. */ + asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); } #else /* Some compilers (I'm looking at you clang!) simply can't do DCE */ diff --git a/arch/x86/include/uapi/asm/ptrace-abi.h b/arch/x86/include/uapi/asm/ptrace-abi.h index 5823584dea132b..3656955c6faad3 100644 --- a/arch/x86/include/uapi/asm/ptrace-abi.h +++ b/arch/x86/include/uapi/asm/ptrace-abi.h @@ -2,6 +2,7 @@ #ifndef _ASM_X86_PTRACE_ABI_H #define _ASM_X86_PTRACE_ABI_H +#if defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) #ifdef __i386__ #define EBX 0 @@ -25,7 +26,6 @@ #else /* __i386__ */ -#if defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) /* * C ABI says these regs are callee-preserved. They aren't saved on kernel entry * unless syscall needs a complete, fully filled "struct pt_regs". @@ -57,12 +57,12 @@ #define EFLAGS 144 #define RSP 152 #define SS 160 -#endif /* __ASSEMBLER__ */ /* top of stack page */ #define FRAME_SIZE 168 #endif /* !__i386__ */ +#endif /* defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) */ /* Arbitrarily choose the same ptrace numbers as used by the Sparc code. */ #define PTRACE_GETREGS 12 diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 54e14ed276b521..40463bad107846 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -1049,6 +1049,10 @@ static void init_amd_zen5(struct cpuinfo_x86 *c) msr_clear_bit(MSR_AMD64_CPUID_FN_7, 18); pr_emerg_once("RDSEED32 is broken. Disabling the corresponding CPUID bit.\n"); } + + if (cpu_has(c, X86_FEATURE_INVLPGB) && + !cpu_has(c, X86_FEATURE_TLBSYNC_SFW_NO)) + set_cpu_bug(c, X86_BUG_TLBI); } static void init_amd(struct cpuinfo_x86 *c) diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c index 776ae6fa7f2d67..4c078827f26a15 100644 --- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -50,9 +50,16 @@ static unsigned long get_align_mask(struct file *filp) * value before calling vm_unmapped_area() or ORed directly to the * address. */ -static unsigned long get_align_bits(void) +static unsigned long get_align_bits(struct file *filp) { - return va_align.bits & get_align_mask(NULL); + /* + * va_align.bits is smaller than the huge page size and will + * lead to misaligned huge pages. Ignore it for huge mappings. + */ + if (is_file_hugepages(filp)) + return 0; + + return va_align.bits & get_align_mask(filp); } static int __init control_va_addr_alignment(char *str) @@ -157,7 +164,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, } if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + info.align_offset += get_align_bits(filp); } return vm_unmapped_area(&info); @@ -222,7 +229,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0, if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + info.align_offset += get_align_bits(filp); } addr = vm_unmapped_area(&info); if (!(addr & ~PAGE_MASK)) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 8e62476e477b8b..a0d608e3fceb13 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -2512,6 +2512,12 @@ static void shadow_walk_init_using_root(struct kvm_shadow_walk_iterator *iterato iterator->addr = addr; iterator->shadow_addr = root; + + if (WARN_ON_ONCE(!VALID_PAGE(root)) || kvm_mmu_is_dummy_root(root)) { + iterator->level = 0; + return; + } + iterator->level = vcpu->arch.mmu->root_role.level; if (iterator->level >= PT64_ROOT_4LEVEL && diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 63eb2155a7749b..0c1ebb16cec60c 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -488,6 +488,20 @@ static void snp_guest_req_cleanup(struct kvm *kvm) sev->guest_resp_buf = NULL; } +static int sev_alloc_have_run_cpus(struct kvm_sev_info *sev) +{ + if (!zalloc_cpumask_var(&sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) + return -ENOMEM; + + return 0; +} + +static void sev_free_have_run_cpus(struct kvm_sev_info *sev) +{ + free_cpumask_var(sev->have_run_cpus); + memset(&sev->have_run_cpus, 0, sizeof(sev->have_run_cpus)); +} + static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, struct kvm_sev_init *data, unsigned long vm_type) @@ -545,10 +559,9 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, if (ret) goto e_free_asid; - if (!zalloc_cpumask_var(&sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(sev); + if (ret) goto e_free_asid; - } /* This needs to happen after SEV/SNP firmware initialization. */ if (snp_active) { @@ -566,7 +579,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, return 0; e_free: - free_cpumask_var(sev->have_run_cpus); + sev_free_have_run_cpus(sev); e_free_asid: argp->error = init_args.error; sev_asid_free(sev); @@ -1125,9 +1138,6 @@ static int sev_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp) if (!sev_es_guest(kvm)) return -ENOTTY; - if (kvm_is_vcpu_creation_in_progress(kvm)) - return -EBUSY; - ret = kvm_lock_all_vcpus(kvm); if (ret) return ret; @@ -2035,6 +2045,13 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) struct kvm_sev_info *mirror; unsigned long i; + /* + * Do cache maintenance on the source VM *before* clearing "SEV active", + * as memory reclaim flows won't trigger cache maintenance on the VM + * once it's no longer an SEV VM. + */ + sev_writeback_caches(src_kvm); + dst->active = true; dst->asid = src->asid; dst->handle = src->handle; @@ -2048,12 +2065,6 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->pages_locked = 0; src->es_active = false; - /* - * Do cache maintenance on the source VM as it is no longer an SEV VM, - * i.e. memory reclaim flows won't trigger cache maintenance on the VM. - */ - sev_writeback_caches(src_kvm); - list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); mutex_lock(&sev_mirror_lock); @@ -2121,10 +2132,6 @@ static int sev_check_source_vcpus(struct kvm *dst, struct kvm *src) struct kvm_vcpu *src_vcpu; unsigned long i; - if (kvm_is_vcpu_creation_in_progress(src) || - kvm_is_vcpu_creation_in_progress(dst)) - return -EBUSY; - if (!sev_es_guest(src)) return 0; @@ -2198,10 +2205,9 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) * does not, i.e. KVM could skip flushes if memory is reclaimed from * the old VM but not the new VM. */ - if (!zalloc_cpumask_var(&dst_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(dst_sev); + if (ret) goto out_source_vcpu; - } sev_migrate_from(kvm, source_kvm); kvm_vm_dead(source_kvm); @@ -2520,9 +2526,6 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp) unsigned long i; int ret; - if (kvm_is_vcpu_creation_in_progress(kvm)) - return -EBUSY; - ret = kvm_lock_all_vcpus(kvm); if (ret) return ret; @@ -2898,10 +2901,9 @@ int sev_vm_copy_enc_context_from(struct kvm *kvm, unsigned int source_fd) } mirror_sev = to_kvm_sev_info(kvm); - if (!zalloc_cpumask_var(&mirror_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(mirror_sev); + if (ret) goto e_unlock; - } /* * The mirror kvm holds an enc_context_owner ref so its asid can't @@ -2994,7 +2996,7 @@ void sev_vm_destroy(struct kvm *kvm) * Free the mask even if the VM is not *currently* an SEV VM, as it may * have been an SEV VM prior to intra-host migration. */ - free_cpumask_var(sev->have_run_cpus); + sev_free_have_run_cpus(sev); if (!sev_guest(kvm)) return; @@ -3630,7 +3632,6 @@ int pre_sev_run(struct vcpu_svm *svm, int cpu) sd->sev_vmcbs[asid] = svm->vmcb; svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ASID; - vmcb_mark_dirty(svm->vmcb, VMCB_ASID); return 0; } diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 7d59d301e1e543..fe0cba4731b2ef 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -673,16 +673,7 @@ static void clr_dr_intercepts(struct vcpu_svm *svm) static bool msr_write_intercepted(struct vcpu_svm *svm, u32 msr) { - /* - * For non-nested case: - * If the L01 MSR bitmap does not intercept the MSR, then we need to - * save it. - * - * For nested case: - * If the L02 MSR bitmap does not intercept the MSR, then we need to - * save it. - */ - void *msrpm = is_guest_mode(&svm->vcpu) ? svm->nested.msrpm : svm->msrpm; + void *msrpm = __va(__sme_clr(svm->vmcb->control.msrpm_base_pa)); return svm_test_msr_bitmap_write(msrpm, msr); } @@ -1902,8 +1893,7 @@ static void new_asid(struct vcpu_svm *svm, struct svm_cpu_data *sd) if (sd->next_asid > sd->max_asid) { ++sd->asid_generation; sd->next_asid = sd->min_asid; - svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; - vmcb_mark_dirty(svm->vmcb, VMCB_ASID); + sd->flush_all_asids = true; } svm->current_vmcb->asid_generation = sd->asid_generation; @@ -4528,6 +4518,9 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) svm->vmcb->control.asid = svm->asid; vmcb_mark_dirty(svm->vmcb, VMCB_ASID); } + if (this_cpu_ptr(&svm_data)->flush_all_asids) + svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; + svm->vmcb->save.cr2 = vcpu->arch.cr2; if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS) && @@ -4618,16 +4611,23 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) vcpu->arch.nested_run_pending = 0; } - svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING; + if (!svm_is_vmrun_failure(svm->vmcb->control.exit_code)) { + this_cpu_ptr(&svm_data)->flush_all_asids = false; + svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING; - /* - * Unconditionally mask off the CLEAR_RAP bit, the AND is just as cheap - * as the TEST+Jcc to avoid it. - */ - if (cpu_feature_enabled(X86_FEATURE_ERAPS)) - svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP; + /* + * Unconditionally mask off the CLEAR_RAP bit, the AND is just + * as cheap as the TEST+Jcc to avoid it. + */ + if (cpu_feature_enabled(X86_FEATURE_ERAPS)) + svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP; - vmcb_mark_all_clean(svm->vmcb); + vmcb_mark_all_clean(svm->vmcb); + + if (!msr_write_intercepted(svm, MSR_AMD64_PERF_CNTR_GLOBAL_CTL)) + rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, + vcpu_to_pmu(vcpu)->global_ctrl); + } /* if exit due to PF check for async PF */ if (svm->vmcb->control.exit_code == SVM_EXIT_EXCP_BASE + PF_VECTOR) @@ -4636,9 +4636,6 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) kvm_clear_available_registers(vcpu, SVM_REGS_LAZY_LOAD_SET); - if (!msr_write_intercepted(svm, MSR_AMD64_PERF_CNTR_GLOBAL_CTL)) - rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, vcpu_to_pmu(vcpu)->global_ctrl); - trace_kvm_exit(vcpu, KVM_ISA_SVM); svm_complete_interrupts(vcpu); diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index e958943b81627e..84f19026d3e8bb 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -376,6 +376,7 @@ struct svm_cpu_data { u32 next_asid; u32 min_asid; + bool flush_all_asids; bool bp_spec_reduce_set; struct vmcb *save_area; diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 40c1a5f6fa8a59..b252168627408b 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -1754,6 +1754,9 @@ static void copy_vmcs12_to_shadow(struct vcpu_vmx *vmx) static void copy_enlightened_to_vmcs12(struct vcpu_vmx *vmx, u32 hv_clean_fields) { #ifdef CONFIG_KVM_HYPERV + const u64 runtime_controls = HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1 | + HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2 | + HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_PROC; struct vmcs12 *vmcs12 = vmx->nested.cached_vmcs12; struct hv_enlightened_vmcs *evmcs = nested_vmx_evmcs(vmx); struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(&vmx->vcpu); @@ -1762,6 +1765,9 @@ static void copy_enlightened_to_vmcs12(struct vcpu_vmx *vmx, u32 hv_clean_fields vmcs12->tpr_threshold = evmcs->tpr_threshold; vmcs12->guest_rip = evmcs->guest_rip; + if ((hv_clean_fields & runtime_controls) != runtime_controls) + vmx->nested.force_msr_bitmap_recalc = true; + if (unlikely(!(hv_clean_fields & HV_VMX_ENLIGHTENED_CLEAN_FIELD_ENLIGHTENMENTSCONTROL))) { hv_vcpu->nested.pa_page_gpa = evmcs->partition_assist_page; diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 6c842e9191a500..00850a3a77d66a 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -2728,11 +2728,6 @@ static tdx_vm_state_guard_t tdx_acquire_vm_state_locks(struct kvm *kvm) mutex_lock(&kvm->lock); - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) { - r = -EBUSY; - goto out_err; - } - r = kvm_lock_all_vcpus(kvm); if (r) goto out_err; diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index cb03f5a2b24338..4a105f283cfbb6 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -705,47 +705,37 @@ int pmd_clear_huge(pmd_t *pmd) } #ifdef CONFIG_X86_64 -/** - * pud_free_pmd_page - Clear PUD entry and free PMD page - * @pud: Pointer to a PUD - * @addr: Virtual address associated with PUD - * - * Context: The PUD range has been unmapped and TLB purged. - * Return: 1 if clearing the entry succeeded. 0 otherwise. - * - * NOTE: Callers must allow a single page allocation. +/* + * Given a PUD poitner, detach and free the pointed-to + * PMD page and any PTE page children. The entire range + * under the PUD must not have any valid translations + * and the TLB must have already been flushed. */ int pud_free_pmd_page(pud_t *pud, unsigned long addr) { - pmd_t *pmd, *pmd_sv; struct ptdesc *pt; + pmd_t *pmd; int i; pmd = pud_pgtable(*pud); - pmd_sv = (pmd_t *)__get_free_page(GFP_KERNEL); - if (!pmd_sv) - return 0; - - for (i = 0; i < PTRS_PER_PMD; i++) { - pmd_sv[i] = pmd[i]; - if (!pmd_none(pmd[i])) - pmd_clear(&pmd[i]); - } + /* Detach the PMD page: */ pud_clear(pud); - /* INVLPG to clear all paging-structure caches */ + /* + * PMD and all its descendents are unreachable + * via normal page walks. Make them unreachable + * in cached mid-level walks too: + */ flush_tlb_kernel_range(addr, addr + PAGE_SIZE-1); for (i = 0; i < PTRS_PER_PMD; i++) { - if (!pmd_none(pmd_sv[i])) { - pt = page_ptdesc(pmd_page(pmd_sv[i])); + if (!pmd_none(pmd[i])) { + pt = page_ptdesc(pmd_page(pmd[i])); pagetable_dtor_free(pt); } } - free_page((unsigned long)pmd_sv); - pmd_free(&init_mm, pmd); return 1; diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index 2853e87797a784..6bca87457e872f 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -3217,16 +3217,21 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog, } static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_node *node, int stack_size, int run_ctx_off, bool save_ret, void *image, void *rw_image) { u8 *prog = *pprog; - u8 *jmp_insn; + u8 *jmp_insn, *skip; int ctx_cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); struct bpf_prog *p = node->link->prog; u64 cookie = node->cookie; + /* nop, patched to skip this prog when it is detached */ + skip = image + (prog - (u8 *)rw_image); + emit_nops(&prog, X86_PATCH_SIZE); + /* mov rdi, cookie */ emit_mov_imm64(&prog, BPF_REG_1, (long) cookie >> 32, (u32) (long) cookie); @@ -3301,6 +3306,8 @@ static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog, if (emit_rsb_call(&prog, bpf_trampoline_exit(p), image + (prog - (u8 *)rw_image))) return -EINVAL; + bpf_tramp_image_add_skip(im, p, skip, image + (prog - (u8 *)rw_image)); + *pprog = prog; return 0; } @@ -3332,6 +3339,7 @@ static int emit_cond_near_jump(u8 **pprog, void *func, void *ip, u8 jmp_cond) } static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tl, int stack_size, int run_ctx_off, int func_meta_off, bool save_ret, void *image, void *rw_image, u64 func_meta, @@ -3346,7 +3354,7 @@ static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, func_meta | (cur_cookie << BPF_TRAMP_COOKIE_INDEX_SHIFT)); cur_cookie--; } - if (invoke_bpf_prog(m, &prog, tl->nodes[i], stack_size, + if (invoke_bpf_prog(m, &prog, im, tl->nodes[i], stack_size, run_ctx_off, save_ret, image, rw_image)) return -EINVAL; } @@ -3355,6 +3363,7 @@ static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, } static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tl, int stack_size, int run_ctx_off, u8 **branches, void *image, void *rw_image) @@ -3368,7 +3377,7 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog, emit_mov_imm32(&prog, false, BPF_REG_0, 0); emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8); for (i = 0; i < tl->nr_nodes; i++) { - if (invoke_bpf_prog(m, &prog, tl->nodes[i], stack_size, run_ctx_off, true, + if (invoke_bpf_prog(m, &prog, im, tl->nodes[i], stack_size, run_ctx_off, true, image, rw_image)) return -EINVAL; @@ -3640,7 +3649,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im } if (fentry->nr_nodes) { - if (invoke_bpf(m, &prog, fentry, regs_off, run_ctx_off, func_meta_off, + if (invoke_bpf(m, &prog, im, fentry, regs_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, image, rw_image, func_meta, cookie_off)) return -EINVAL; @@ -3652,7 +3661,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im if (!branches) return -ENOMEM; - if (invoke_bpf_mod_ret(m, &prog, fmod_ret, regs_off, + if (invoke_bpf_mod_ret(m, &prog, im, fmod_ret, regs_off, run_ctx_off, branches, image, rw_image)) { ret = -EINVAL; goto cleanup; @@ -3682,8 +3691,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im } /* remember return value in a stack for bpf prog to access */ emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8); - im->ip_after_call = image + (prog - (u8 *)rw_image); - emit_nops(&prog, X86_PATCH_SIZE); } if (fmod_ret->nr_nodes) { @@ -3708,7 +3715,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta); if (fexit->nr_nodes) { - if (invoke_bpf(m, &prog, fexit, regs_off, run_ctx_off, func_meta_off, + if (invoke_bpf(m, &prog, im, fexit, regs_off, run_ctx_off, func_meta_off, false, image, rw_image, func_meta, cookie_off)) { ret = -EINVAL; goto cleanup; @@ -3723,7 +3730,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im * restored to R0. */ if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = image + (prog - (u8 *)rw_image); /* arg1: mov rdi, im */ emit_mov_imm64(&prog, BPF_REG_1, (long) im >> 32, (u32) (long) im); if (emit_rsb_call(&prog, __bpf_tramp_exit, image + (prog - (u8 *)rw_image))) { @@ -3811,7 +3817,7 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; void *image; int ret; diff --git a/arch/x86/um/asm/ptrace.h b/arch/x86/um/asm/ptrace.h index 2641d28d115ca6..439c4151f6b72e 100644 --- a/arch/x86/um/asm/ptrace.h +++ b/arch/x86/um/asm/ptrace.h @@ -13,9 +13,7 @@ enum { }; #include -#ifndef CONFIG_X86_32 -#define __FRAME_OFFSETS /* Needed to get the R* macros */ -#endif +#define __FRAME_OFFSETS /* Needed to get the register macros */ #include #define user_mode(r) UPT_IS_USER(&(r)->regs) diff --git a/arch/x86/um/ptrace_32.c b/arch/x86/um/ptrace_32.c index 3af3cb821524bf..9e9155b0e91828 100644 --- a/arch/x86/um/ptrace_32.c +++ b/arch/x86/um/ptrace_32.c @@ -7,6 +7,7 @@ #include #include #include +#define __FRAME_OFFSETS #include #include #include diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 2b5c29434e4264..b56db1cc6778f2 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -2091,6 +2091,7 @@ static inline struct blkcg_gq *blkg_tryget_closest(struct bio *bio, struct request_queue *q = bio->bi_bdev->bd_queue; struct blkcg *blkcg = css_to_blkcg(css); struct blkcg_gq *blkg; + unsigned long flags; rcu_read_lock(); blkg = blkg_lookup(blkcg, q); @@ -2105,11 +2106,11 @@ static inline struct blkcg_gq *blkg_tryget_closest(struct bio *bio, * Fast path failed, we're probably issuing IO in this cgroup the first * time, hold lock to create new blkg. */ - spin_lock_irq(&q->queue_lock); + spin_lock_irqsave(&q->queue_lock, flags); blkg = blkg_lookup_create(blkcg, bio->bi_bdev->bd_disk); if (blkg) blkg = blkg_lookup_tryget(blkg); - spin_unlock_irq(&q->queue_lock); + spin_unlock_irqrestore(&q->queue_lock, flags); return blkg; } diff --git a/block/blk-mq.c b/block/blk-mq.c index a26a11c73ee3e0..025a799f35b924 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -447,16 +447,6 @@ static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data, WRITE_ONCE(rq->deadline, 0); req_ref_set(rq, 1); - if (rq->rq_flags & RQF_USE_SCHED) { - struct elevator_queue *e = data->q->elevator; - - INIT_HLIST_NODE(&rq->hash); - RB_CLEAR_NODE(&rq->rb_node); - - if (e->type->ops.prepare_request) - e->type->ops.prepare_request(rq); - } - return rq; } @@ -498,6 +488,12 @@ __blk_mq_alloc_requests_batch(struct blk_mq_alloc_data *data) return rq_list_pop(data->cached_rqs); } +static bool blk_op_bypass_sched(blk_opf_t opf) +{ + return (opf & REQ_OP_MASK) == REQ_OP_FLUSH || + blk_op_is_passthrough(opf); +} + static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) { struct elevator_mq_ops *ops; @@ -518,12 +514,10 @@ static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) * Flush/passthrough requests are special and go directly to the * dispatch list, they are not subject to the async_depth limit. */ - if ((data->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH || - blk_op_is_passthrough(data->cmd_flags)) + if (blk_op_bypass_sched(data->cmd_flags)) return; WARN_ON_ONCE(data->flags & BLK_MQ_REQ_RESERVED); - data->rq_flags |= RQF_USE_SCHED; /* * By default, sync requests have no limit, and async requests are @@ -534,6 +528,29 @@ static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) ops->limit_depth(data->cmd_flags, data); } +/* + * Finish initializing a request once it has been claimed for an operation. + * Cached requests are allocated before that operation is known. + */ +static void blk_mq_rq_late_init(struct request *rq, u64 alloc_time_ns) +{ + struct elevator_queue *e; + + blk_mq_rq_time_init(rq, alloc_time_ns); + + if (!(rq->rq_flags & RQF_SCHED_TAGS) || (rq->rq_flags & RQF_RESV) || + blk_op_bypass_sched(rq->cmd_flags)) + return; + + rq->rq_flags |= RQF_USE_SCHED; + INIT_HLIST_NODE(&rq->hash); + RB_CLEAR_NODE(&rq->rb_node); + + e = rq->q->elevator; + if (e->type->ops.prepare_request) + e->type->ops.prepare_request(rq); +} + static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) { struct request_queue *q = data->q; @@ -562,7 +579,7 @@ static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) if (data->nr_tags > 1) { rq = __blk_mq_alloc_requests_batch(data); if (rq) { - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); return rq; } data->nr_tags = 1; @@ -590,7 +607,7 @@ static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) if (!(data->rq_flags & RQF_SCHED_TAGS)) blk_mq_inc_active_requests(data->hctx); rq = blk_mq_rq_ctx_init(data, blk_mq_tags_from_data(data), tag); - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); return rq; } @@ -637,8 +654,6 @@ static struct request *blk_mq_alloc_cached_request(struct request_queue *q, if (plug->nr_ios == 1) return NULL; rq = blk_mq_rq_cache_fill(q, plug, opf, flags); - if (!rq) - return NULL; } else { rq = rq_list_peek(&plug->cached_rqs); if (!rq || rq->q != q) @@ -646,15 +661,13 @@ static struct request *blk_mq_alloc_cached_request(struct request_queue *q, if (blk_mq_get_hctx_type(opf) != rq->mq_hctx->type) return NULL; - if (op_is_flush(rq->cmd_flags) != op_is_flush(opf)) - return NULL; rq_list_pop(&plug->cached_rqs); - blk_mq_rq_time_init(rq, blk_time_get_ns()); + rq->cmd_flags = opf; + INIT_LIST_HEAD(&rq->queuelist); + blk_mq_rq_late_init(rq, blk_time_get_ns()); } - rq->cmd_flags = opf; - INIT_LIST_HEAD(&rq->queuelist); return rq; } @@ -766,7 +779,7 @@ struct request *blk_mq_alloc_request_hctx(struct request_queue *q, if (!(data.rq_flags & RQF_SCHED_TAGS)) blk_mq_inc_active_requests(data.hctx); rq = blk_mq_rq_ctx_init(&data, blk_mq_tags_from_data(&data), tag); - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); rq->__data_len = 0; rq->phys_gap_bit = 0; rq->__sector = (sector_t) -1; @@ -3060,8 +3073,6 @@ static struct request *blk_mq_get_cached_request(struct blk_plug *plug, if (type != rq->mq_hctx->type && (type != HCTX_TYPE_READ || rq->mq_hctx->type != HCTX_TYPE_DEFAULT)) return NULL; - if (op_is_flush(rq->cmd_flags) != op_is_flush(opf)) - return NULL; rq_list_pop(&plug->cached_rqs); return rq; } @@ -3163,9 +3174,9 @@ void blk_mq_submit_bio(struct bio *bio) new_request: if (rq) { rq_qos_throttle(rq->q, bio); - blk_mq_rq_time_init(rq, blk_time_get_ns()); rq->cmd_flags = bio->bi_opf; INIT_LIST_HEAD(&rq->queuelist); + blk_mq_rq_late_init(rq, blk_time_get_ns()); } else { rq = blk_mq_get_new_requests(q, plug, bio); if (unlikely(!rq)) { diff --git a/block/fops.c b/block/fops.c index 2ce7c6c4714e42..c915ec0126d7f9 100644 --- a/block/fops.c +++ b/block/fops.c @@ -402,6 +402,11 @@ static ssize_t blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter) if (blkdev_dio_invalid(bdev, iocb, iter)) return -EINVAL; + /* HIPRI needs private as bio; HAS_METADATA keeps it as uio_meta */ + if ((iocb->ki_flags & IOCB_HIPRI) && + (iocb->ki_flags & IOCB_HAS_METADATA)) + return -EOPNOTSUPP; + if (iov_iter_rw(iter) == WRITE) { u16 max_write_streams = bdev_max_write_streams(bdev); diff --git a/block/mq-deadline.c b/block/mq-deadline.c index 5f643c0ce2a863..e5db1ee097c35d 100644 --- a/block/mq-deadline.c +++ b/block/mq-deadline.c @@ -685,7 +685,7 @@ static void dd_insert_requests(struct blk_mq_hw_ctx *hctx, blk_mq_free_requests(&free); } -/* Callback from inside blk_mq_rq_ctx_init(). */ +/* Callback from inside blk_mq_rq_late_init(). */ static void dd_prepare_request(struct request *rq) { rq->elv.priv[0] = NULL; diff --git a/crypto/aes.c b/crypto/aes.c index 94791f481e9842..51eee78396ead5 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -637,7 +637,18 @@ static struct skcipher_alg skcipher_algs[] = { .decrypt = crypto_aes_cbc_decrypt, }, #endif -#if IS_ENABLED(CONFIG_CRYPTO_CTS) + /* + * Don't register library-based "cts(cbc(aes))" on architectures where + * it might block a "better" implementation from being instantiated via + * the "cts" template. These exclusions are temporary and will go away + * as the arch-optimized AES code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_CTS) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC)) { .base.cra_name = "cts(cbc(aes))", .base.cra_driver_name = "cts-cbc-aes-lib", @@ -687,7 +698,13 @@ static struct skcipher_alg skcipher_algs[] = { .decrypt = crypto_aes_xctr_crypt, }, #endif -#if IS_ENABLED(CONFIG_CRYPTO_XTS) + /* + * Don't register library-based "xts(aes)" on architectures where it + * might block a "better" implementation from being instantiated via the + * "xts" template. This exclusion is temporary and will go away when + * the library AES-XTS is optimized for SPARC. + */ +#if IS_ENABLED(CONFIG_CRYPTO_XTS) && !IS_ENABLED(CONFIG_SPARC) { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", @@ -980,7 +997,20 @@ static __maybe_unused int crypto_aes_ccm_decrypt(struct aead_request *req) } static struct aead_alg aead_algs[] = { -#if IS_ENABLED(CONFIG_CRYPTO_GCM) + /* + * Don't register library-based "gcm(aes)" and "rfc4106(gcm(aes))" on + * architectures where they might block a "better" implementation from + * being instantiated via the "gcm" and "rfc4106" templates. These + * exclusions are temporary and will go away as the arch-optimized AES + * code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_GCM) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_RISCV) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC)) { .base.cra_name = "gcm(aes)", .base.cra_driver_name = "gcm-aes-lib", @@ -1012,7 +1042,20 @@ static struct aead_alg aead_algs[] = { .chunksize = AES_BLOCK_SIZE, }, #endif /* CONFIG_CRYPTO_GCM */ -#if IS_ENABLED(CONFIG_CRYPTO_CCM) + /* + * Don't register library-based "ccm(aes)" on architectures where it + * might block a "better" implementation from being instantiated via the + * "ccm" template. These exclusions are temporary and will go away as + * the arch-optimized AES code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_CCM) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_RISCV) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC) || \ + IS_ENABLED(CONFIG_X86)) { .base.cra_name = "ccm(aes)", .base.cra_driver_name = "ccm-aes-lib", diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c index 06b43b678d6ea6..5a3e375f4800f2 100644 --- a/drivers/accessibility/braille/braille_console.c +++ b/drivers/accessibility/braille/braille_console.c @@ -62,14 +62,50 @@ static void braille_write(u16 *buf) { static u16 lastwrite[WIDTH]; unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c; + struct nbcon_write_context wctxt = { }; + unsigned long flags; u16 out; int i; if (!braille_co) return; + /* + * Braille console is not registered in console_list. Instead, it + * is integrated with VT and shows what appears on the graphical + * console under console_lock(). From this POV it is a legacy + * console. But it calls serial console driver which might be + * converted to the NBCON API. It is similar to + * nbcon_legacy_emit_next_record() except that we should try + * harder to get the lock. Otherwise, the Braille device won't show + * everything what is displayed on the terminal. + * + * In short, simulate the original locking using NBCON API. + */ + if (braille_co->flags & CON_NBCON) { + if (panic_on_this_cpu()) { + /* + * This should be good enough in practice. Most/all + * serial console drivers have the atomic callback. + */ + if (!braille_co->write_atomic) + return; + + local_irq_save(flags); + /* NBCON API strictly requires the ownership. */ + if (!nbcon_braille_try_acquire(braille_co, &wctxt)) { + local_irq_restore(flags); + return; + } + } else { + braille_co->device_lock(braille_co, &flags); + while (!nbcon_braille_try_acquire(braille_co, &wctxt)) + cpu_relax(); + } + } + if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) - return; + goto unlock_nbcon; memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); #define SOH 1 @@ -102,7 +138,24 @@ static void braille_write(u16 *buf) *c++ = csum; *c++ = ETX; - braille_co->write(braille_co, data, c - data); + if (braille_co->flags & CON_NBCON) { + nbcon_write_context_set_buf(&wctxt, (char *)data, c - data); + if (panic_on_this_cpu()) + braille_co->write_atomic(braille_co, &wctxt); + else + braille_co->write_thread(braille_co, &wctxt); + } else { + braille_co->write(braille_co, data, c - data); + } + +unlock_nbcon: + if (braille_co->flags & CON_NBCON) { + nbcon_braille_release(&wctxt); + if (panic_on_this_cpu()) + local_irq_restore(flags); + else + braille_co->device_unlock(braille_co, flags); + } } /* Follow the VC cursor*/ diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 8f2ef1bd539f18..bc8bc9ee43a542 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -2930,8 +2930,9 @@ static int binder_proc_transaction(struct binder_transaction *t, t_outdated->buffer = NULL; buffer->transaction = NULL; trace_binder_transaction_update_buffer_release(buffer); - binder_release_entire_buffer(proc, NULL, buffer, false); + binder_release_entire_buffer(proc, NULL, buffer, true); binder_alloc_free_buf(&proc->alloc, buffer); + binder_free_txn_fixups(t_outdated); kfree(t_outdated); binder_stats_deleted(BINDER_STAT_TRANSACTION); } diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 0a82af14cda3ca..8dc3e3f2b834a9 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -51,9 +51,9 @@ pub(crate) struct CouldNotDeliverCriticalIncrement; /// about to drop the weak reference, then the strong increment could be processed after the /// other thread has already exited, which would be too late. /// -/// Note that trying to create a `ListArc` to the node can succeed even if `has_normal_push` is +/// Note that trying to create a `ListArc` to the node can succeed even if `has_pushed_node` is /// set. This is because another thread might just have popped the node from a todo list, but not -/// yet called `do_work`. However, if `has_normal_push` is false, then creating a `ListArc` should +/// yet called `do_work`. However, if `has_pushed_node` is false, then creating a `ListArc` should /// always succeed. /// /// Like the other fields in `NodeInner`, the delivery state is protected by the process lock. @@ -738,7 +738,21 @@ impl DeliverToRead for Node { self.do_work_locked(writer, owner_inner) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + let _drop_outside_lock; + let mut owner_inner = self.owner.inner.lock(); + + // We only do something on BINDER_THREAD_EXIT, not process exit. + if owner_inner.is_dead { + return; + } + + // If BINDER_THREAD_EXIT is invoked on a thread with a pending node refcount update, we + // should move ourselves to ensure the refcount update is still delivered. + if let Some(node) = ListArc::try_from_arc_borrow(self.as_arc_borrow()) { + _drop_outside_lock = owner_inner.push_work(&self.owner, node); + } + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs index 6e4ca01c941ab0..886626ca0d4252 100644 --- a/drivers/android/binder/node/wrapper.rs +++ b/drivers/android/binder/node/wrapper.rs @@ -57,7 +57,39 @@ impl DeliverToRead for NodeWrapper { node.do_work_locked(writer, owner_inner) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + let _drop_outside_lock; + let node = &self.node; + let mut owner_inner = node.owner.inner.lock(); + + // We only do something on BINDER_THREAD_EXIT, not process exit. + if owner_inner.is_dead { + return; + } + + // We transfer the responsibility of the node refcount update to the scheduled Node because + // NodeWrapper has no way to re-create the ListArc. + let inner = node.inner.access_mut(&mut owner_inner); + + let ds = &mut inner.delivery_state; + assert!(ds.has_pushed_wrapper); + assert!(ds.has_strong_zero2one); + ds.has_pushed_wrapper = false; + + // We are changing the state to one where the Node is the strong zero2one update instead of + // the wrapper. + ds.has_weak_zero2one = false; + + if !ds.has_pushed_node { + if let Some(node2) = ListArc::try_from_arc_borrow(node.as_arc_borrow()) { + ds.has_pushed_node = true; + _drop_outside_lock = owner_inner.push_work(&node.owner, node2); + } else { + // This can't actually happen. + ds.has_strong_zero2one = false; + } + } + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index 300cc65562d1e9..46a37f1858dd4c 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -72,6 +72,7 @@ struct binder_features { bool oneway_spam_detection; bool extended_error; bool freeze_notification; + bool transaction_report; }; static const struct constant_table binderfs_param_stats[] = { @@ -89,6 +90,7 @@ static struct binder_features binder_features = { .oneway_spam_detection = true, .extended_error = true, .freeze_notification = true, + .transaction_report = true, }; static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) @@ -566,6 +568,12 @@ static int init_binder_features(struct super_block *sb) if (IS_ERR(dentry)) return PTR_ERR(dentry); + dentry = rust_binderfs_create_file(dir, "transaction_report", + &binder_features_fops, + &binder_features.transaction_report); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + return 0; } diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 18a14aa8a835ae..24f7b5be1b0ec5 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -686,6 +686,12 @@ impl Thread { self.inner.lock().push_return_work(reply); } + pub(crate) fn pop_work_even_if_deferred(&self) -> Option> { + let mut thread_inner = self.inner.lock(); + thread_inner.process_work_list = true; + thread_inner.pop_work() + } + fn translate_object( &self, obj_index: usize, @@ -1678,7 +1684,7 @@ impl Thread { self.unwind_transaction_stack(); // Cancel all pending work items. - while let Ok(Some(work)) = self.get_work_local(false) { + while let Some(work) = self.pop_work_even_if_deferred() { work.into_arc().cancel(); } } diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 361d69f756f50e..1ca31b9a058362 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -189,12 +189,11 @@ static int binderfs_binder_device_create(struct inode *ref_inode, goto err; } inode->i_private = device; + binder_add_device(device); d_make_persistent(dentry, inode); fsnotify_create(root->d_inode, dentry); simple_done_creating(dentry); - binder_add_device(device); - return 0; err: diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 8f9aa97a519d11..fb5517d51095da 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -3059,10 +3059,15 @@ static void atapi_qc_complete(struct ata_queued_cmd *qc) if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL && qc->dev->sdev) qc->dev->sdev->locked = 0; - if (cmd->result) - ata_scsi_qc_done(qc, false, 0); - else - ata_scsi_qc_done(qc, true, SAM_STAT_CHECK_CONDITION); + /* + * Report CHECK CONDITION unless the command was terminated + * with a host byte set. Only set the status byte, so that the + * SCSI midlayer internal byte, which scsi_check_sense() may + * have set from the sense data, is preserved. + */ + if (get_host_byte(cmd) == DID_OK) + set_status_byte(cmd, SAM_STAT_CHECK_CONDITION); + ata_scsi_qc_done(qc, false, 0); goto schedule_deferred; } diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 4c80db5d6f6f32..3b1c2f28d5852f 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -22,6 +22,8 @@ static const struct regcache_ops *cache_types[] = { ®cache_flat_ops, }; +static int regcache_default_sync(struct regmap *map, unsigned int min, unsigned int max); + static int regcache_defaults_cmp(const void *a, const void *b) { const struct reg_default *x = a; @@ -56,12 +58,15 @@ static int regcache_count_cacheable_registers(struct regmap *map) return count; } -static int regcache_hw_init(struct regmap *map) +static int regcache_hw_init(struct regmap *map, int count) { int ret; unsigned int reg, val; void *tmp_buf; + if (!count) + return 0; + if (!map->reg_defaults_raw) { bool cache_bypass = map->cache_bypass; dev_dbg(map->dev, "No cache defaults, reading back from HW\n"); @@ -121,6 +126,39 @@ static void regcache_hw_exit(struct regmap *map) kfree(map->reg_defaults_raw); } +static int regcache_locked_op(struct regmap *map, + int (*op)(struct regmap *map), + const char *action) +{ + if (!op) + return 0; + + dev_dbg(map->dev, "%s %s cache\n", action, map->cache_ops->name); + guard(regmap)(map); + return op(map); +} + +static void regcache_locked_exit(struct regmap *map) +{ + if (!map->cache_ops->exit) + return; + + dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); + guard(regmap)(map); + map->cache_ops->exit(map); +} + +static int __regcache_sync(struct regmap *map, unsigned int min, unsigned int max) +{ + if (!map->cache_dirty) + return 0; + + if (map->cache_ops->sync) + return map->cache_ops->sync(map, min, max); + + return regcache_default_sync(map, min, max); +} + int regcache_init(struct regmap *map, const struct regmap_config *config) { bool sort_defaults = false; @@ -139,15 +177,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) return 0; } - if (config->reg_defaults && !config->num_reg_defaults) { - dev_err(map->dev, - "Register defaults are set without the number!\n"); - return -EINVAL; - } - - if (config->num_reg_defaults && !config->reg_defaults) { - dev_err(map->dev, - "Register defaults number are set without the reg!\n"); + if (!!config->reg_defaults != !!config->num_reg_defaults) { + dev_err(map->dev, "reg_defaults and num_reg_defaults must both be specified\n"); return -EINVAL; } @@ -180,9 +211,7 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->cache = NULL; map->cache_ops = cache_types[i]; - if (!map->cache_ops->read || - !map->cache_ops->write || - !map->cache_ops->name) + if (!map->cache_ops->read || !map->cache_ops->write || !map->cache_ops->name) return -EINVAL; /* We still need to ensure that the reg_defaults @@ -222,47 +251,32 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->max_register_is_set = true; } - if (map->cache_ops->init) { - dev_dbg(map->dev, "Initializing %s cache\n", - map->cache_ops->name); - map->lock(map->lock_arg); - ret = map->cache_ops->init(map); - map->unlock(map->lock_arg); - if (ret) - goto err_free_reg_defaults; - } + ret = regcache_locked_op(map, map->cache_ops->init, "Initializing"); + if (ret) + goto err_free_reg_defaults; /* * Some devices such as PMICs don't have cache defaults, * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. */ - if (count) { - ret = regcache_hw_init(map); - if (ret) - goto err_exit; - } + ret = regcache_hw_init(map, count); + if (ret) + goto err_exit; + + if (!map->num_reg_defaults && !map->reg_default_cb) + return 0; + + ret = regcache_locked_op(map, map->cache_ops->populate, "Populating"); + if (ret) + goto err_free; - if (map->cache_ops->populate && - (map->num_reg_defaults || map->reg_default_cb)) { - dev_dbg(map->dev, "Populating %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - ret = map->cache_ops->populate(map); - map->unlock(map->lock_arg); - if (ret) - goto err_free; - } return 0; err_free: regcache_hw_exit(map); err_exit: - if (map->cache_ops->exit) { - dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - map->cache_ops->exit(map); - map->unlock(map->lock_arg); - } + regcache_locked_exit(map); err_free_reg_defaults: kfree(map->reg_defaults); @@ -277,15 +291,10 @@ void regcache_exit(struct regmap *map) BUG_ON(!map->cache_ops); regcache_hw_exit(map); - - if (map->cache_ops->exit) { - dev_dbg(map->dev, "Destroying %s cache\n", - map->cache_ops->name); - scoped_guard(regmap, map) - map->cache_ops->exit(map); - } + regcache_locked_exit(map); kfree(map->reg_defaults); + map->reg_defaults = NULL; } /** @@ -460,11 +469,7 @@ int regcache_sync(struct regmap *map) } map->cache_bypass = false; - if (map->cache_ops->sync) - sync_ret = map->cache_ops->sync(map, 0, map->max_register); - else - sync_ret = regcache_default_sync(map, 0, map->max_register); - + sync_ret = __regcache_sync(map, 0, map->max_register); if (sync_ret == 0) map->cache_dirty = false; @@ -544,17 +549,10 @@ int regcache_sync_region(struct regmap *map, unsigned int min, trace_regcache_sync(map, name, "start region"); - if (!map->cache_dirty) - goto out; - map->async = true; - if (map->cache_ops->sync) - ret = map->cache_ops->sync(map, min, max); - else - ret = regcache_default_sync(map, min, max); + ret = __regcache_sync(map, min, max); -out: /* Restore the bypass state */ map->cache_bypass = bypass; map->async = false; @@ -759,8 +757,8 @@ int regcache_lookup_reg(struct regmap *map, unsigned int reg) if (r) return r - map->reg_defaults; - else - return -ENOENT; + + return -ENOENT; } static bool regcache_reg_present(unsigned long *cache_present, unsigned int idx) @@ -897,7 +895,7 @@ int regcache_sync_block(struct regmap *map, void *block, if (regmap_can_raw_write(map) && !map->use_single_write) return regcache_sync_block_raw(map, block, cache_present, block_base, start, end); - else - return regcache_sync_block_single(map, block, cache_present, - block_base, start, end); + + return regcache_sync_block_single(map, block, cache_present, + block_base, start, end); } diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index ec207548a6bbe7..b62a5dce027b43 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -38,10 +38,9 @@ static ssize_t regmap_name_read_file(struct file *file, { struct regmap *map = file->private_data; const char *name = "nodev"; + char *buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); int ret; - char *buf; - buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -49,14 +48,10 @@ static ssize_t regmap_name_read_file(struct file *file, name = map->dev->driver->name; ret = snprintf(buf, PAGE_SIZE, "%s\n", name); - if (ret >= PAGE_SIZE) { - kfree(buf); + if (ret >= PAGE_SIZE) return ret; - } - ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); - kfree(buf); - return ret; + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); } static const struct file_operations regmap_name_fops = { @@ -112,7 +107,7 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, * If we don't have a cache build one so we don't have to do a * linear scan each time. */ - mutex_lock(&map->cache_lock); + guard(mutex)(&map->cache_lock); i = base; if (list_empty(&map->debugfs_off_cache)) { for (; i <= map->max_register; i += map->reg_stride) { @@ -134,7 +129,6 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, c = kzalloc_obj(*c); if (!c) { regmap_debugfs_free_dump_cache(map); - mutex_unlock(&map->cache_lock); return base; } c->min = p; @@ -167,14 +161,12 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, fpos_offset = from - c->min; reg_offset = fpos_offset / map->debugfs_tot_len; *pos = c->min + (reg_offset * map->debugfs_tot_len); - mutex_unlock(&map->cache_lock); return c->base_reg + (reg_offset * map->reg_stride); } *pos = c->max; ret = c->max_reg; } - mutex_unlock(&map->cache_lock); return ret; } @@ -196,10 +188,10 @@ static int regmap_next_readable_reg(struct regmap *map, int reg) struct regmap_debugfs_off_cache *c; int ret = -EINVAL; - if (regmap_printable(map, reg + map->reg_stride)) { - ret = reg + map->reg_stride; - } else { - mutex_lock(&map->cache_lock); + if (regmap_printable(map, reg + map->reg_stride)) + return reg + map->reg_stride; + + scoped_guard(mutex, &map->cache_lock) { list_for_each_entry(c, &map->debugfs_off_cache, list) { if (reg > c->max_reg) continue; @@ -208,8 +200,8 @@ static int regmap_next_readable_reg(struct regmap *map, int reg) break; } } - mutex_unlock(&map->cache_lock); } + return ret; } @@ -366,8 +358,6 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, struct regmap_debugfs_off_cache *c; loff_t p = 0; size_t buf_pos = 0; - char *buf; - char *entry; int ret; unsigned int entry_len; @@ -377,15 +367,13 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, if (count > (PAGE_SIZE << MAX_PAGE_ORDER)) count = PAGE_SIZE << MAX_PAGE_ORDER; - buf = kmalloc(count, GFP_KERNEL); + char *buf __free(kfree) = kmalloc(count, GFP_KERNEL); if (!buf) return -ENOMEM; - entry = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!entry) { - kfree(buf); + char *entry __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!entry) return -ENOMEM; - } /* While we are at it, build the register dump cache * now so the read() operation on the `registers' file @@ -398,31 +386,27 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, /* Reset file pointer as the fixed-format of the `registers' * file is not compatible with the `range' file */ p = 0; - mutex_lock(&map->cache_lock); - list_for_each_entry(c, &map->debugfs_off_cache, list) { - entry_len = snprintf(entry, PAGE_SIZE, "%x-%x\n", - c->base_reg, c->max_reg); - if (p >= *ppos) { - if (buf_pos + entry_len > count) - break; - memcpy(buf + buf_pos, entry, entry_len); - buf_pos += entry_len; + scoped_guard(mutex, &map->cache_lock) { + list_for_each_entry(c, &map->debugfs_off_cache, list) { + entry_len = snprintf(entry, PAGE_SIZE, "%x-%x\n", + c->base_reg, c->max_reg); + if (p >= *ppos) { + if (buf_pos + entry_len > count) + break; + memcpy(buf + buf_pos, entry, entry_len); + buf_pos += entry_len; + } + p += entry_len; } - p += entry_len; } - mutex_unlock(&map->cache_lock); - kfree(entry); ret = buf_pos; - if (copy_to_user(user_buf, buf, buf_pos)) { - ret = -EFAULT; - goto out_buf; - } + if (copy_to_user(user_buf, buf, buf_pos)) + return -EFAULT; *ppos += buf_pos; -out_buf: - kfree(buf); + return ret; } @@ -557,9 +541,8 @@ void regmap_debugfs_init(struct regmap *map) if (!node) return; node->map = map; - mutex_lock(®map_debugfs_early_lock); - list_add(&node->link, ®map_debugfs_early_list); - mutex_unlock(®map_debugfs_early_lock); + scoped_guard(mutex, ®map_debugfs_early_lock) + list_add(&node->link, ®map_debugfs_early_list); return; } @@ -658,29 +641,29 @@ void regmap_debugfs_init(struct regmap *map) void regmap_debugfs_exit(struct regmap *map) { + struct regmap_debugfs_node *node, *tmp; + if (map->debugfs) { debugfs_remove_recursive(map->debugfs); - mutex_lock(&map->cache_lock); - regmap_debugfs_free_dump_cache(map); - mutex_unlock(&map->cache_lock); + scoped_guard(mutex, &map->cache_lock) + regmap_debugfs_free_dump_cache(map); if (map->debugfs_dummy_id >= 0) { ida_free(&dummy_ida, map->debugfs_dummy_id); map->debugfs_dummy_id = -1; } kfree(map->debugfs_name); map->debugfs_name = NULL; - } else { - struct regmap_debugfs_node *node, *tmp; - mutex_lock(®map_debugfs_early_lock); - list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, - link) { + return; + } + + scoped_guard(mutex, ®map_debugfs_early_lock) { + list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) { if (node->map == map) { list_del(&node->link); kfree(node); } } - mutex_unlock(®map_debugfs_early_lock); } } @@ -690,11 +673,10 @@ void regmap_debugfs_initcall(void) regmap_debugfs_root = debugfs_create_dir("regmap", NULL); - mutex_lock(®map_debugfs_early_lock); + guard(mutex)(®map_debugfs_early_lock); list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) { regmap_debugfs_init(node->map); list_del(&node->link); kfree(node); } - mutex_unlock(®map_debugfs_early_lock); } diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 0b40e1e7817c55..b9830afa6d8ef1 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -3379,16 +3379,13 @@ int regmap_register_patch(struct regmap *map, const struct reg_sequence *regs, num_regs)) return 0; - p = krealloc(map->patch, - sizeof(struct reg_sequence) * (map->patch_regs + num_regs), - GFP_KERNEL); - if (p) { - memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); - map->patch = p; - map->patch_regs += num_regs; - } else { + p = krealloc_array(map->patch, map->patch_regs + num_regs, sizeof(*p), GFP_KERNEL); + if (!p) return -ENOMEM; - } + + memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); + map->patch = p; + map->patch_regs += num_regs; scoped_guard(regmap, map) { bypass = map->cache_bypass; diff --git a/drivers/block/drbd/drbd_nl_gen.c b/drivers/block/drbd/drbd_nl_gen.c index 9753dc789bdea9..a3b24aa582977e 100644 --- a/drivers/block/drbd/drbd_nl_gen.c +++ b/drivers/block/drbd/drbd_nl_gen.c @@ -638,10 +638,6 @@ const struct genl_split_ops drbd_nl_ops[32] = { }, }; -static const struct genl_multicast_group drbd_nl_mcgrps[] = { - [DRBD_NLGRP_EVENTS] = { "events", }, -}; - static int __drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, struct nlattr ***ret_nested_attribute_table, struct genl_info *info) diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c index 32bf3ba07a9dcd..c8bfbf8435ac7e 100644 --- a/drivers/block/virtio_blk.c +++ b/drivers/block/virtio_blk.c @@ -743,6 +743,7 @@ static int virtblk_read_zoned_limits(struct virtio_blk *vblk, dev_warn(&vdev->dev, "zero write granularity reported\n"); return -ENODEV; } + lim->zone_write_granularity = wg; lim->physical_block_size = wg; lim->io_min = wg; diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index f0ca221524db63..394dc2408ed735 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -800,6 +800,27 @@ static void zloop_cmd_workfn(struct work_struct *work) current->flags = orig_flags; } +static void zloop_fill_zero_rq(struct request *rq, unsigned int zero_from) +{ + struct req_iterator iter; + struct bio_vec bv; + unsigned int pos = 0; + unsigned int end, off; + + rq_for_each_segment(bv, rq, iter) { + end = pos + bv.bv_len; + if (end > zero_from) { + if (pos < zero_from) + off = zero_from - pos; + else + off = 0; + memzero_page(bv.bv_page, bv.bv_offset + off, + bv.bv_len - off); + } + pos = end; + } +} + static void zloop_complete_rq(struct request *rq) { struct zloop_cmd *cmd = blk_mq_rq_to_pdu(rq); @@ -814,13 +835,9 @@ static void zloop_complete_rq(struct request *rq) pr_err("Zone %u: failed read sector %llu, %llu sectors\n", zone_no, cmd->sector, cmd->nr_sectors); - if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) { - /* short read */ - struct bio *bio; - - __rq_for_each_bio(bio, rq) - zero_fill_bio(bio); - } + /* Short read. Zero out the unread tail beyond cmd->ret. */ + if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) + zloop_fill_zero_rq(rq, cmd->ret); break; case REQ_OP_WRITE: case REQ_OP_ZONE_APPEND: diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 909a265fd9067e..c69a5d226e8821 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -289,7 +289,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) goto unlock; } - bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); + bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), + (char *)(skb->data + 1)); kfree_skb(skb); @@ -408,8 +409,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) /* DDC file contains one or more DDC structure which has * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). */ - while (fw->size > fw_ptr - fw->data) { - u8 cmd_plen = fw_ptr[0] + sizeof(u8); + while (fw->size > (size_t)(fw_ptr - fw->data)) { + size_t remaining = fw->size - (fw_ptr - fw->data); + unsigned int cmd_plen = fw_ptr[0] + 1U; + + if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { + bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", + cmd_plen, remaining); + release_firmware(fw); + return -EINVAL; + } skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, HCI_INIT_TIMEOUT); diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 2819e001797b67..ac71934007699b 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -401,7 +401,7 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, tfd_index = data->ia.tr_hia[BTINTEL_PCIE_TXQ_NUM]; - if (tfd_index > txq->count) + if (tfd_index >= txq->count) return -ERANGE; if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { @@ -1197,7 +1197,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, { int ret; u8 pkt_type; - u16 plen; + u32 plen; u32 pcie_pkt_type; void *pdata; struct hci_dev *hdev = data->hdev; @@ -1777,7 +1777,7 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) { int err; - int num_irqs, i; + int num_irqs, i, j; for (i = 0; i < BTINTEL_PCIE_MSIX_VEC_MAX; i++) data->msix_entries[i].entry = i; @@ -1806,6 +1806,10 @@ static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) KBUILD_MODNAME, msix_entry); if (err) { + for (j = 0; j < i; j++) + devm_free_irq(&data->pdev->dev, + data->msix_entries[j].vector, + &data->msix_entries[j]); pci_free_irq_vectors(data->pdev); data->alloc_vecs = 0; return err; @@ -1877,6 +1881,13 @@ static int btintel_pcie_config_pcie(struct pci_dev *pdev, if (IS_ERR(data->base_addr)) return PTR_ERR(data->base_addr); + /* Do shared hardware reset to ensure a clean start before + * configuring interrupts. + */ + err = btintel_pcie_reset_bt(data); + if (err) + return err; + err = btintel_pcie_setup_irq(data); if (err) return err; @@ -2219,6 +2230,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) return -ENODEV; + /* Account for the 4-byte PCIe type header prepended before the + * DMA copy. Written as a subtraction to avoid wrap-around on + * attacker-controlled skb->len. + */ + if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { + bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, + BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); + return -EMSGSIZE; + } + /* Due to the fw limitation, the type header of the packet should be * 4 bytes unlike 1 byte for UART. In UART, the firmware can read * the first byte to get the packet type and redirect the rest of data @@ -3128,7 +3149,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) struct msix_entry *msix_entry; msix_entry = &data->msix_entries[i]; - free_irq(msix_entry->vector, msix_entry); + devm_free_irq(&pdev->dev, msix_entry->vector, msix_entry); } pci_free_irq_vectors(pdev); diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 63253d1de5d70b..5b93c92c2cf194 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -503,7 +503,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc) #ifndef CONFIG_MMU return -ENOSYS; #endif - if (vma_desc_test(desc, VMA_SHARED_BIT)) + if (vma_desc_test(desc, VMA_MAYSHARE_BIT)) return shmem_zero_setup_desc(desc); /* diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index f745a098908b37..1ccdbde98b69a8 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -268,7 +268,7 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_buf *buf, int err; ssize_t len; - len = tpm_transmit(chip, buf->data, PAGE_SIZE); + len = tpm_transmit(chip, buf->data, buf->capacity); if (len < 0) return len; diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 48cec39995fe22..ae22295df7981a 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -281,8 +281,10 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) } tpm_buf_append_u16(buf, num_bytes); err = tpm_buf_fill_hmac_session(chip, buf); - if (err) + if (err) { + tpm2_end_auth_session(chip); return err; + } err = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, @@ -305,7 +307,7 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) out = (struct tpm2_get_random_out *)&buf->data[offset]; recd = min_t(u32, be16_to_cpu(out->size), num_bytes); if (tpm_buf_length(buf) < - TPM_HEADER_SIZE + + offset + offsetof(struct tpm2_get_random_out, buffer) + recd) { tpm2_end_auth_session(chip); diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index cf8f1fd6790ba6..ca1e2bf424e140 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -975,6 +975,7 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key) /* Deduce from the name change TPM interference: */ dev_err(&chip->dev, "null key integrity check failed\n"); tpm2_flush_context(chip, tmp_null_key); + rc = -ENODEV; err: if (rc) { diff --git a/drivers/clk/qcom/gcc-eliza.c b/drivers/clk/qcom/gcc-eliza.c index 3e26c7a1e5b546..fe18942a59ac45 100644 --- a/drivers/clk/qcom/gcc-eliza.c +++ b/drivers/clk/qcom/gcc-eliza.c @@ -3010,8 +3010,6 @@ static const u32 gcc_eliza_critical_cbcrs[] = { 0x26034, /* GCC_CAMERA_XO_CLK */ 0x27004, /* GCC_DISP_AHB_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32038, /* GCC_VIDEO_XO_CLK */ }; @@ -3045,6 +3043,10 @@ static const struct regmap_config gcc_eliza_regmap_config = { static void clk_eliza_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core and gcc ufs phy axi clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_axi_clk, true); diff --git a/drivers/clk/qcom/gcc-hawi.c b/drivers/clk/qcom/gcc-hawi.c index 6fb5a3db958640..f1b95753c3a35f 100644 --- a/drivers/clk/qcom/gcc-hawi.c +++ b/drivers/clk/qcom/gcc-hawi.c @@ -3692,8 +3692,6 @@ static const u32 gcc_hawi_critical_cbcrs[] = { 0x67084, /* GCC_PCIE_1_RSC_CORE_CLK */ 0x43014, /* GCC_PCIE_LINK_XO_CLK */ 0x6b088, /* GCC_PCIE_RSC_CORE_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32028, /* GCC_VIDEO_XO_CLK */ }; @@ -3763,6 +3761,10 @@ static const struct regmap_config gcc_hawi_regmap_config = { static void clk_hawi_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } diff --git a/drivers/clk/qcom/gcc-kaanapali.c b/drivers/clk/qcom/gcc-kaanapali.c index 842c1a70c69168..5bcbcd17865774 100644 --- a/drivers/clk/qcom/gcc-kaanapali.c +++ b/drivers/clk/qcom/gcc-kaanapali.c @@ -3464,8 +3464,6 @@ static const u32 gcc_kaanapali_critical_cbcrs[] = { 0x9f004, /* GCC_EVA_AHB_CLK */ 0x9f024, /* GCC_EVA_XO_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32040, /* GCC_VIDEO_XO_CLK */ }; @@ -3480,6 +3478,10 @@ static const struct regmap_config gcc_kaanapali_regmap_config = { static void clk_kaanapali_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } diff --git a/drivers/clk/qcom/gpucc-glymur.c b/drivers/clk/qcom/gpucc-glymur.c index 001b2454786a0c..cac51de20b2950 100644 --- a/drivers/clk/qcom/gpucc-glymur.c +++ b/drivers/clk/qcom/gpucc-glymur.c @@ -510,7 +510,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_glymur_clocks[] = { diff --git a/drivers/clk/qcom/gpucc-kaanapali.c b/drivers/clk/qcom/gpucc-kaanapali.c index ae5563e516f677..f75f2caa2205c4 100644 --- a/drivers/clk/qcom/gpucc-kaanapali.c +++ b/drivers/clk/qcom/gpucc-kaanapali.c @@ -380,7 +380,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_kaanapali_clocks[] = { diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index 92b930d5ff3057..39df15c8a3e6d9 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -30,14 +30,87 @@ static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { }; static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), + CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), + CCU_PLLA_RATE(2050000000UL, 0x0b2a2d55, 0x00005500, 0xa0558686), + CCU_PLLA_RATE(2100000000UL, 0x0b2b3000, 0x00000000, 0xa0558787), + CCU_PLLA_RATE(2150000000UL, 0x0b2c32aa, 0x0000ab00, 0xa0558787), CCU_PLLA_RATE(2200000000UL, 0x0b2d3555, 0x00005500, 0xa0558787), + CCU_PLLA_RATE(2250000000UL, 0x0b2e3800, 0x00000000, 0xa0558888), + CCU_PLLA_RATE(2300000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0558888), + CCU_PLLA_RATE(2350000000UL, 0x0b303d55, 0x00005500, 0xa0558888), + CCU_PLLA_RATE(2400000000UL, 0x0b320000, 0x00000000, 0xa0558989), }; static const struct ccu_pll_rate_tbl pll4_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), + CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), + CCU_PLLA_RATE(2050000000UL, 0x0b2a2d55, 0x00005500, 0xa0558686), + CCU_PLLA_RATE(2100000000UL, 0x0b2b3000, 0x00000000, 0xa0558787), + CCU_PLLA_RATE(2150000000UL, 0x0b2c32aa, 0x0000ab00, 0xa0558787), CCU_PLLA_RATE(2200000000UL, 0x0b2d3555, 0x00005500, 0xa0558787), + CCU_PLLA_RATE(2250000000UL, 0x0b2e3800, 0x00000000, 0xa0558888), + CCU_PLLA_RATE(2300000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0558888), + CCU_PLLA_RATE(2350000000UL, 0x0b303d55, 0x00005500, 0xa0558888), + CCU_PLLA_RATE(2400000000UL, 0x0b320000, 0x00000000, 0xa0558989), }; static const struct ccu_pll_rate_tbl pll5_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), }; @@ -50,6 +123,25 @@ static const struct ccu_pll_rate_tbl pll7_rate_tbl[] = { }; static const struct ccu_pll_rate_tbl pll8_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), }; diff --git a/drivers/clk/ti/composite.c b/drivers/clk/ti/composite.c index 83c3592cd179e1..c379bbdae25aa0 100644 --- a/drivers/clk/ti/composite.c +++ b/drivers/clk/ti/composite.c @@ -52,7 +52,7 @@ static const struct clk_ops ti_composite_gate_ops = { struct component_clk { int num_parents; - struct clk_parent_data *parent_data; + const char **parent_names; struct device_node *node; int type; struct clk_hw *hw; @@ -116,7 +116,7 @@ static void __init _register_composite(void *user, struct clk_hw_omap_comp *cclk = to_clk_hw_comp(hw); struct component_clk *comp; int num_parents = 0; - struct clk_parent_data *parent_data = NULL; + const char **parent_names = NULL; const char *name; int i; int ret; @@ -155,7 +155,7 @@ static void __init _register_composite(void *user, continue; if (comp->num_parents) { num_parents = comp->num_parents; - parent_data = comp->parent_data; + parent_names = comp->parent_names; break; } } @@ -166,8 +166,8 @@ static void __init _register_composite(void *user, } name = ti_dt_clk_name(node); - clk = clk_register_composite_pdata(NULL, name, - parent_data, num_parents, + clk = clk_register_composite(NULL, name, + parent_names, num_parents, _get_hw(cclk, CLK_COMPONENT_TYPE_MUX), &ti_clk_mux_ops, _get_hw(cclk, CLK_COMPONENT_TYPE_DIVIDER), @@ -190,7 +190,7 @@ static void __init _register_composite(void *user, if (!cclk->comp_clks[i]) continue; list_del(&cclk->comp_clks[i]->link); - kfree(cclk->comp_clks[i]->parent_data); + kfree(cclk->comp_clks[i]->parent_names); kfree(cclk->comp_clks[i]); } @@ -237,9 +237,8 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, int type) { unsigned int num_parents; - struct clk_parent_data *parent_data; + const char **parent_names; struct component_clk *clk; - unsigned int i; num_parents = of_clk_get_parent_count(node); @@ -248,21 +247,20 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, return -EINVAL; } - parent_data = kzalloc_objs(*parent_data, num_parents); - if (!parent_data) + parent_names = kcalloc(num_parents, sizeof(char *), GFP_KERNEL); + if (!parent_names) return -ENOMEM; - for (i = 0; i < num_parents; i++) - parent_data[i].index = i; + of_clk_parent_fill(node, parent_names, num_parents); clk = kzalloc_obj(*clk); if (!clk) { - kfree(parent_data); + kfree(parent_names); return -ENOMEM; } clk->num_parents = num_parents; - clk->parent_data = parent_data; + clk->parent_names = parent_names; clk->hw = hw; clk->node = node; clk->type = type; diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 96515880b4ac1c..54dde8419bdc4b 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2590,8 +2590,8 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) cpu = cpumask_first(policy->related_cpus); max_freq = arch_scale_freq_ref(cpu); - if (!max_freq) - max_freq = policy->cpuinfo.max_freq; + if (!max_freq && cpufreq_driver->scale_freq_ref) + max_freq = cpufreq_driver->scale_freq_ref(policy); capped_freq = policy->max; diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index ceb340f7a110ba..17158cb25dd89b 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1135,6 +1135,14 @@ static bool hybrid_clear_max_perf_cpu(void) return ret; } +static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) +{ + if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) + return policy->cpuinfo.max_freq; + + return 0; +} + static void intel_pstate_update_freq_limits(struct cpudata *cpu) { int scaling = cpu->pstate.scaling; @@ -3088,6 +3096,7 @@ static struct cpufreq_driver intel_pstate = { .offline = intel_pstate_cpu_offline, .online = intel_pstate_cpu_online, .update_limits = intel_pstate_update_limits, + .scale_freq_ref = intel_pstate_scale_freq_ref, .name = "intel_pstate", }; @@ -3411,6 +3420,7 @@ static struct cpufreq_driver intel_cpufreq = { .suspend = intel_cpufreq_suspend, .resume = intel_pstate_resume, .update_limits = intel_pstate_update_limits, + .scale_freq_ref = intel_pstate_scale_freq_ref, .name = "intel_cpufreq", }; diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 68846f583eeefe..bb95dab847b3c7 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -453,15 +453,6 @@ static int altr_sdram_probe(struct platform_device *pdev) return res; } -static void altr_sdram_remove(struct platform_device *pdev) -{ - struct mem_ctl_info *mci = platform_get_drvdata(pdev); - - edac_mc_del_mc(&pdev->dev); - edac_mc_free(mci); - platform_set_drvdata(pdev, NULL); -} - /* * If you want to suspend, need to disable EDAC by removing it * from the device tree or defconfig. @@ -481,13 +472,13 @@ static const struct dev_pm_ops altr_sdram_pm_ops = { static struct platform_driver altr_sdram_edac_driver = { .probe = altr_sdram_probe, - .remove = altr_sdram_remove, .driver = { .name = "altr_sdram_edac", #ifdef CONFIG_PM .pm = &altr_sdram_pm_ops, #endif .of_match_table = altr_sdram_ctrl_of_match, + .suppress_bind_attrs = true, }, }; @@ -517,6 +508,7 @@ static struct platform_driver altr_edac_driver = { .driver = { .name = "socfpga_ecc_manager", .of_match_table = altr_edac_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_driver); @@ -803,22 +795,12 @@ static int altr_edac_device_probe(struct platform_device *pdev) return res; } -static void altr_edac_device_remove(struct platform_device *pdev) -{ - struct edac_device_ctl_info *dci = platform_get_drvdata(pdev); - struct altr_edac_device_dev *drvdata = dci->pvt_info; - - debugfs_remove_recursive(drvdata->debugfs_dir); - edac_device_del_device(&pdev->dev); - edac_device_free_ctl_info(dci); -} - static struct platform_driver altr_edac_device_driver = { .probe = altr_edac_device_probe, - .remove = altr_edac_device_remove, .driver = { .name = "altr_edac_device", .of_match_table = altr_edac_device_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_device_driver); @@ -944,7 +926,7 @@ static int __maybe_unused altr_init_memory_port(void __iomem *ioaddr, int port) return ret; } -static __init int __maybe_unused +static int __maybe_unused altr_init_a10_ecc_block(struct device_node *np, u32 irq_mask, u32 ecc_ctrl_en_mask, bool dual_port) { @@ -1019,7 +1001,7 @@ altr_init_a10_ecc_block(struct device_node *np, u32 irq_mask, static int validate_parent_available(struct device_node *np); static const struct of_device_id altr_edac_a10_device_of_match[]; -static int __init __maybe_unused altr_init_a10_ecc_device_type(char *compat) +static int __maybe_unused altr_init_a10_ecc_device_type(char *compat) { int irq; struct device_node *child, *np; @@ -1348,7 +1330,7 @@ static const struct edac_device_prv_data a10_l2ecc_data = { #ifdef CONFIG_EDAC_ALTERA_ETHERNET -static int __init socfpga_init_ethernet_ecc(struct altr_edac_device_dev *dev) +static int socfpga_init_ethernet_ecc(struct altr_edac_device_dev *dev) { int ret; @@ -1378,7 +1360,7 @@ static const struct edac_device_prv_data a10_enetecc_data = { #ifdef CONFIG_EDAC_ALTERA_NAND -static int __init socfpga_init_nand_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_nand_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1408,7 +1390,7 @@ static const struct edac_device_prv_data a10_nandecc_data = { #ifdef CONFIG_EDAC_ALTERA_DMA -static int __init socfpga_init_dma_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_dma_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1438,7 +1420,7 @@ static const struct edac_device_prv_data a10_dmaecc_data = { #ifdef CONFIG_EDAC_ALTERA_USB -static int __init socfpga_init_usb_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_usb_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1468,7 +1450,7 @@ static const struct edac_device_prv_data a10_usbecc_data = { #ifdef CONFIG_EDAC_ALTERA_QSPI -static int __init socfpga_init_qspi_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_qspi_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1524,6 +1506,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name, 1, ecc_name, 1, 0, edac_idx); if (!dci) { + of_node_put(np); edac_printk(KERN_ERR, EDAC_DEVICE, "%s: Unable to allocate PortB EDAC device\n", ecc_name); @@ -1534,8 +1517,11 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) altdev = dci->pvt_info; *altdev = *device; - if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) + if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) { + edac_device_free_ctl_info(dci); + of_node_put(np); return -ENOMEM; + } /* Update PortB specific values */ altdev->edac_dev_name = ecc_name; @@ -1600,6 +1586,8 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENOMEM; goto err_release_group_1; } + of_node_put(np); + altr_create_edacdev_dbgfs(dci, prv); list_add(&altdev->next, &altdev->edac->a10_ecc_devices); @@ -1609,14 +1597,20 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) return 0; err_release_group_1: - edac_device_free_ctl_info(dci); + /* + * Release the devres group first so the managed IRQs are + * unregistered before dci (which contains the IRQ handler's + * data via dci->pvt_info) is freed, avoiding a use-after-free. + */ devres_release_group(device->edac->dev, altr_portb_setup); + edac_device_free_ctl_info(dci); + of_node_put(np); edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); return rc; } -static int __init socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) { int rc = -ENODEV; struct device_node *child; @@ -1638,7 +1632,7 @@ static int __init socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) goto exit; /* Setup portB */ - return altr_portb_setup(device); + rc = altr_portb_setup(device); exit: of_node_put(child); @@ -2013,9 +2007,17 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, return 0; err_release_group1: + /* + * Release the devres group first so the managed IRQs are + * unregistered before dci (which contains the IRQ handler's + * data via dci->pvt_info) is freed, avoiding a use-after-free. + */ + devres_release_group(edac->dev, NULL); edac_device_free_ctl_info(dci); + goto err_print; err_release_group: devres_release_group(edac->dev, NULL); +err_print: edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); @@ -2214,6 +2216,7 @@ static struct platform_driver altr_edac_a10_driver = { .driver = { .name = "socfpga_a10_ecc_manager", .of_match_table = altr_edac_a10_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_a10_driver); diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index 475235c402e888..0c0d11c7292598 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -2796,13 +2796,13 @@ static inline void decode_bus_error(int node_id, struct mce *m) * the instance_id. For example, instance_id=0xYXXXXX where Y is the channel * number. * - * For DRAM ECC errors, the Chip Select number is given in bits [2:0] of + * For DRAM ECC errors, the Chip Select number is given in bits [1:0] of * the MCA_SYND[ErrorInformation] field. */ static void umc_get_err_info(struct mce *m, struct err_info *err) { err->channel = (m->ipid & GENMASK(31, 0)) >> 20; - err->csrow = m->synd & 0x7; + err->csrow = m->synd & 0x3; } static void decode_umc_error(int node_id, struct mce *m) diff --git a/drivers/edac/versalnet_edac.c b/drivers/edac/versalnet_edac.c index 9e65c4b1d99dd3..eae9fea60db156 100644 --- a/drivers/edac/versalnet_edac.c +++ b/drivers/edac/versalnet_edac.c @@ -953,6 +953,7 @@ static void mc_remove(struct platform_device *pdev) remove_versalnet(priv); rproc_shutdown(priv->mcdi->r5_rproc); cdx_mcdi_finish(priv->mcdi); + rproc_put(priv->mcdi->r5_rproc); kfree(priv->mcdi); } diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 04015280ac5da3..844c68036975f6 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -2960,6 +2960,8 @@ int cs_dsp_run(struct cs_dsp *dsp) return 0; err: + dsp->running = false; + if (dsp->ops->stop_core) dsp->ops->stop_core(dsp); if (dsp->ops->disable_core) diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c index e5a389808e5f2e..b6e141ec409f33 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c @@ -36,6 +36,8 @@ struct cs_dsp_test_local { int num_post_stop; int num_watchdog_expired; + bool fail_post_run; + struct cs_dsp_coeff_ctl *passed_ctl[16]; struct cs_dsp *passed_dsp; }; @@ -106,6 +108,9 @@ static int cs_dsp_test_post_run_callback(struct cs_dsp *dsp) local->passed_dsp = dsp; local->num_post_run++; + if (local->fail_post_run) + return -EIO; + return 0; } @@ -198,6 +203,33 @@ static void cs_dsp_test_run_stop_callbacks(struct kunit *test) local->passed_dsp = NULL; } +static void cs_dsp_test_run_error_clears_running(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp_test_local *local = priv->local; + struct firmware *wmfw; + + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + + KUNIT_EXPECT_EQ(test, + cs_dsp_power_up(priv->dsp, wmfw, "wmfw", NULL, NULL, "misc"), + 0); + + /* + * By the time the post_run hook runs, the core has been started. A + * failure there stops and disables the core again, so the DSP must not + * be left looking like it is running. + */ + local->fail_post_run = true; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), -EIO); + KUNIT_EXPECT_FALSE(test, priv->dsp->running); + + /* and the next run must be able to start it again */ + local->fail_post_run = false; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), 0); + KUNIT_EXPECT_TRUE(test, priv->dsp->running); +} + static void cs_dsp_test_ctl_v1_callbacks(struct kunit *test) { struct cs_dsp_test *priv = test->priv; @@ -609,6 +641,7 @@ KUNIT_ARRAY_PARAM(cs_dsp_no_callbacks, static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v1_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -617,6 +650,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -625,6 +659,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { static struct kunit_case cs_dsp_callbacks_halo_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), diff --git a/drivers/gpio/gpio-exar.c b/drivers/gpio/gpio-exar.c index 9053662f18177f..f629cc526f3ee7 100644 --- a/drivers/gpio/gpio-exar.c +++ b/drivers/gpio/gpio-exar.c @@ -199,6 +199,7 @@ static int gpio_exar_probe(struct platform_device *pdev) index = ida_alloc(&ida_index, GFP_KERNEL); if (index < 0) return index; + exar_gpio->index = index; ret = devm_add_action_or_reset(dev, exar_devm_ida_free, exar_gpio); if (ret) @@ -214,7 +215,6 @@ static int gpio_exar_probe(struct platform_device *pdev) exar_gpio->gpio_chip.set = exar_set_value; exar_gpio->gpio_chip.base = -1; exar_gpio->gpio_chip.ngpio = ngpios; - exar_gpio->index = index; exar_gpio->first_pin = first_pin; ret = devm_gpiochip_add_data(dev, &exar_gpio->gpio_chip, exar_gpio); diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 30e8009b5fcc7c..61c5ca96314b7b 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ struct mpsse_priv { raw_spinlock_t irq_spin; /* protects worker list */ atomic_t irq_type[16]; /* pin -> edge detection type */ atomic_t irq_enabled; + atomic_t dying; /* no new workers after disconnect */ int id; u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ @@ -46,6 +48,7 @@ struct mpsse_worker { atomic_t cancelled; struct list_head list; /* linked list */ struct list_head destroy; /* teardown linked list */ + struct rcu_head rcu; /* deferred free for dying path */ }; struct bulk_desc { @@ -525,10 +528,24 @@ static void gpio_mpsse_irq_enable(struct irq_data *irqd) worker->priv = priv; INIT_LIST_HEAD(&worker->list); INIT_WORK(&worker->work, gpio_mpsse_poll); - schedule_work(&worker->work); - scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) { + if (atomic_read(&priv->dying)) { + /* + * Cannot kfree() here: this callback + * runs with the IRQ descriptor's raw + * spinlock held and kfree() may sleep + * on PREEMPT_RT. The worker is not + * yet published, so the deferred free + * is unobservable. + */ + kfree_rcu(worker, rcu); + return; + } + list_add(&worker->list, &priv->workers); + schedule_work(&worker->work); + } } } @@ -704,6 +721,9 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) { struct mpsse_priv *priv = usb_get_intfdata(intf); + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + atomic_set(&priv->dying, 1); + /* * Lock prevents double-free of worker from here and the teardown * step at the beginning of gpio_mpsse_poll diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 532205175827a0..9406ce85f93308 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -271,14 +271,7 @@ static void xgpio_save_regs(struct xgpio_instance *chip) static int xgpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void xgpio_free(struct gpio_chip *chip, unsigned int offset) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c index 9014678d75abff..be4d00ac96b3d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -318,7 +318,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) r = device_for_each_child(adev->acp.parent, &adev->acp.acp_genpd->gpd, acp_genpd_add_device); if (r) - goto failure; + goto failure_remove_mfd; break; } default: @@ -443,7 +443,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) r = device_for_each_child(adev->acp.parent, &adev->acp.acp_genpd->gpd, acp_genpd_add_device); if (r) - goto failure; + goto failure_remove_mfd; } /* Assert Soft reset of ACP */ @@ -461,7 +461,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); r = -ETIMEDOUT; - goto failure; + goto failure_remove_mfd; } udelay(100); } @@ -479,7 +479,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); r = -ETIMEDOUT; - goto failure; + goto failure_remove_mfd; } udelay(100); } @@ -489,6 +489,11 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) cgs_write_register(adev->acp.cgs_device, mmACP_SOFT_RESET, val); return 0; +failure_remove_mfd: + device_for_each_child(adev->acp.parent, NULL, + acp_genpd_remove_device); + mfd_remove_devices(adev->acp.parent); + failure: kfree(adev->acp.i2s_pdata); kfree(adev->acp.acp_res); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 054870e9078d73..ca5860e493a605 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -598,14 +598,18 @@ int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, /* first get metadata_size by buffer = NULL */ r = amdgpu_bo_get_metadata(bo, NULL, 0, metadata_size, NULL); + if (r) + goto out_put; /* user buf_size is bigger than bo metadata_size * allocate a buf at kernel space and copy */ if (*metadata_size <= buffer_size) { *metadata_buffer = kzalloc(*metadata_size, GFP_KERNEL); - if (!*metadata_buffer) - return -ENOMEM; + if (!*metadata_buffer) { + r = -ENOMEM; + goto out_put; + } r = amdgpu_bo_get_metadata(bo, *metadata_buffer, *metadata_size, NULL, &metadata_flags); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index aca1a8045afaba..ab7ccfaea22ae8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -121,6 +121,20 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, *pos &= (1UL << 22) - 1; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (!read && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -152,28 +166,16 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, mutex_lock(&adev->pm.mutex); while (size) { - uint32_t value; - - if (read) { - value = RREG32(*pos >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, value, 0); - } - if (r) { - result = r; - goto end; - } + if (read) + data[result >> 2] = RREG32(*pos >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, data[result >> 2], 0); result += 4; - buf += 4; *pos += 4; size -= 4; } -end: if (use_bank) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0); mutex_unlock(&adev->grbm_idx_mutex); @@ -188,6 +190,10 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (read && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } @@ -238,11 +244,24 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off struct amdgpu_device *adev = rd->adev; ssize_t result = 0; int r; - uint32_t value; if (size & 0x3 || offset & 0x3) return -EINVAL; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (write_en && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -281,24 +300,16 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off mutex_lock(&adev->pm.mutex); while (size) { - if (!write_en) { - value = RREG32(offset >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, value, rd->id.xcc_id); - } - if (r) { - result = r; - goto end; - } + if (!write_en) + data[result >> 2] = RREG32(offset >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, data[result >> 2], + rd->id.xcc_id); offset += 4; size -= 4; result += 4; - buf += 4; } -end: + if (rd->id.use_grbm) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, rd->id.xcc_id); mutex_unlock(&adev->grbm_idx_mutex); @@ -317,6 +328,10 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (!write_en && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 62f5c5cbd7f946..7ab2ad2c5ba997 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1325,6 +1325,8 @@ static int amdgpu_discovery_sysfs_ips(struct amdgpu_device *adev, ip_hw_instance->kobj.kset = &ip_hw_id->hw_id_kset; res = kobject_add(&ip_hw_instance->kobj, NULL, "%d", ip_hw_instance->num_instance); + if (res) + kobject_put(&ip_hw_instance->kobj); next_ip: if (reg_base_64) ip_offset += struct_size(ip, base_address_64, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 5c33c19fd9bc5d..d9d794a7db9648 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2664,6 +2664,14 @@ static int amdgpu_pmops_suspend_noirq(struct device *dev) struct amdgpu_device *adev = drm_to_adev(drm_dev); int r; + /* + * A GPU parked by vga_switcheroo has no power and no PCIe link, so the + * ASIC reset below would fail and abort the whole noirq suspend phase. + * Bail out like amdgpu_device_prepare/suspend/resume() already do. + */ + if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + if (amdgpu_acpi_should_gpu_reset(adev)) { amdgpu_device_lock_reset_domain(adev->reset_domain); r = amdgpu_asic_reset(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 3043ad041bb422..3017ddcefbe28b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -724,8 +724,14 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, bool is_guilty_fence; bool is_guilty_context; - last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; - seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + last_seq = amdgpu_fence_read(ring); + seq = ring->fence_drv.sync_seq; + + /* Hardware completion can precede the interrupt which signals fences. */ + if (last_seq == seq) { + amdgpu_fence_process(ring); + return; + } /* If there is nothing to reemit, return early and set an error on the fence * if applicable. If all of the fences are siganlled, this will be a nop. @@ -733,7 +739,9 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, * we are skipping it on purpose. */ if (!ring->ring_backup_entries_to_copy) { - amdgpu_fence_driver_force_completion(ring, &guilty_fence->base); + if (guilty_fence) + amdgpu_fence_driver_force_completion(ring, + &guilty_fence->base); return; } ring->reemit = true; @@ -741,9 +749,8 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, spin_lock_irqsave(&ring->fence_drv.lock, flags); do { last_seq++; - last_seq &= ring->fence_drv.num_fences_mask; - ptr = &ring->fence_drv.fences[last_seq]; + ptr = &ring->fence_drv.fences[last_seq & ring->fence_drv.num_fences_mask]; rcu_read_lock(); unprocessed = rcu_dereference(*ptr); @@ -800,26 +807,24 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, struct amdgpu_fence *fence; u32 seq, last_seq; - last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; - seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + last_seq = amdgpu_fence_read(ring); + seq = ring->fence_drv.sync_seq; ring->ring_backup_entries_to_copy = 0; - /* if we've already seen this fence, return early. - * ring->ring_backup_entries_to_copy is set to 0 so - * the reemit helper will return early as well to - * avoid getting stuck in a reemit loop. + /* + * Avoid replay loops for a repeated guilty fence. NULL identifies a + * collateral ring, whose pending commands still need backup. */ - if (ring->guilty_fence == guilty_fence) { + if (guilty_fence && ring->guilty_fence == guilty_fence) { ring->guilty_fence = NULL; return; } ring->guilty_fence = guilty_fence; - do { + while (last_seq != seq) { last_seq++; - last_seq &= ring->fence_drv.num_fences_mask; - ptr = &ring->fence_drv.fences[last_seq]; + ptr = &ring->fence_drv.fences[last_seq & ring->fence_drv.num_fences_mask]; rcu_read_lock(); unprocessed = rcu_dereference(*ptr); @@ -829,7 +834,7 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, amdgpu_ring_backup_unprocessed_command(ring, fence); } rcu_read_unlock(); - } while (last_seq != seq); + } } struct amdgpu_fence * diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 3dc8faa091d7e5..61dc2a6be7e774 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -1080,20 +1080,17 @@ void amdgpu_gmc_init_vga_resv_regions(struct amdgpu_device *adev) case CHIP_RENOIR: adev->mman.keep_stolen_vga_memory = true; break; - case CHIP_POLARIS10: - case CHIP_POLARIS11: - case CHIP_POLARIS12: - /* MacBookPros with switchable graphics put VRAM at 0 when - * the iGPU is enabled which results in cursor issues if - * the cursor ends up at 0. Reserve vram at 0 in that case. - */ - if (adev->gmc.vram_start == 0) - adev->mman.keep_stolen_vga_memory = true; - break; default: adev->mman.keep_stolen_vga_memory = false; break; } + /* MacBookPros with switchable graphics put VRAM at 0 when + * the iGPU is enabled which results in cursor issues if + * the cursor ends up at 0. Reserve vram at 0 in that case. + */ + if ((adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE) && + (adev->gmc.vram_start == 0)) + adev->mman.keep_stolen_vga_memory = true; if (amdgpu_sriov_vf(adev) || !amdgpu_device_has_display_hardware(adev)) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 242c48e85912f2..7c081b338fe2e5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "amdgpu_uvd.h" #include "amdgpu_vce.h" #include "atom.h" @@ -1660,6 +1661,9 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_ERROR("Invalid crtc %u\n", pipe); return -EINVAL; } + if (!down_read_trylock(&adev->reset_domain->sem)) + return drm_crtc_vblank_count(crtc); + /* The hw increments its frame counter at start of vsync, not at start * of vblank, as is required by DRM core vblank counter handling. @@ -1705,6 +1709,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n"); } + up_read(&adev->reset_domain->sem); return count; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 016957cac1f29b..3838e464703c4b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2342,7 +2342,7 @@ void amdgpu_ttm_fini(struct amdgpu_device *adev) void amdgpu_ttm_enable_buffer_funcs(struct amdgpu_device *adev) { struct ttm_resource_manager *man = ttm_manager_type(&adev->mman.bdev, TTM_PL_VRAM); - u32 num_clear_entities, num_move_entities; + u32 num_clear_entities, num_move_entities, sdma_ip_version; int r, i, j; if (!adev->mman.initialized || amdgpu_in_reset(adev) || @@ -2367,6 +2367,12 @@ void amdgpu_ttm_enable_buffer_funcs(struct amdgpu_device *adev) num_clear_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); num_move_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); + /* TODO: workaround for DCC corruption when moving BOs from multiple queues at + * the same time: use a single queue until the root cause is identified and fixed. + */ + sdma_ip_version = amdgpu_ip_version(adev, SDMA0_HWIP, 0); + if (sdma_ip_version == IP_VERSION(7, 0, 0) || sdma_ip_version == IP_VERSION(7, 0, 1)) + num_move_entities = 1; adev->mman.clear_entities = kzalloc_objs(struct amdgpu_ttm_buffer_entity, num_clear_entities); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index cc8e7af18834cd..9316423f934024 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -472,6 +472,7 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, u64 doorbell_index; struct drm_gem_object *gobj; struct amdgpu_userq_obj *db_obj = db_info->db_obj; + struct amdgpu_bo *abo; int r, db_size; gobj = drm_gem_object_lookup(filp, db_info->doorbell_handle); @@ -480,7 +481,17 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, return -EINVAL; } - db_obj->obj = amdgpu_bo_ref(gem_to_amdgpu_bo(gobj)); + /* + * Pinning a regular BO into the doorbell domain would discard its + * contents, possibly those of a buffer shared by another client. + */ + abo = gem_to_amdgpu_bo(gobj); + if (!(abo->preferred_domains & AMDGPU_GEM_DOMAIN_DOORBELL)) { + drm_gem_object_put(gobj); + return -EINVAL; + } + + db_obj->obj = amdgpu_bo_ref(abo); drm_gem_object_put(gobj); r = amdgpu_bo_reserve(db_obj->obj, true); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 0ff5a80aa91833..7a006b22e4be06 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -3808,6 +3808,7 @@ static int gfx_v11_0_cp_gfx_resume(struct amdgpu_device *adev) rb_bufsz = order_base_2(ring->ring_size / 8); tmp = REG_SET_FIELD(0, CP_RB0_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, KMD_QUEUE, 1); WREG32_SOC15(GC, 0, regCP_RB0_CNTL, tmp); /* Initialize the ring buffer's write pointers */ @@ -4216,6 +4217,7 @@ static int gfx_v11_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, tmp = regCP_GFX_HQD_CNTL_DEFAULT; tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, KMD_QUEUE, prop->kernel_queue); #ifdef __BIG_ENDIAN tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, BUF_SWAP, 1); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index e2a81a55c63b36..d69c43f1e194c6 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -2767,6 +2767,7 @@ static int gfx_v12_0_cp_gfx_resume(struct amdgpu_device *adev) rb_bufsz = order_base_2(ring->ring_size / 8); tmp = REG_SET_FIELD(0, CP_RB0_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, KMD_QUEUE, 1); WREG32_SOC15(GC, 0, regCP_RB0_CNTL, tmp); /* Initialize the ring buffer's write pointers */ @@ -3059,6 +3060,7 @@ static int gfx_v12_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, tmp = regCP_GFX_HQD_CNTL_DEFAULT; tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, KMD_QUEUE, prop->kernel_queue); #ifdef __BIG_ENDIAN tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, BUF_SWAP, 1); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c index 563abcf718b715..493ccfa266559e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c @@ -322,6 +322,14 @@ static const u32 verde_rlc_save_restore_register_list[] = 0x00000000 }; +static void gfx_v6_0_free_microcode(struct amdgpu_device *adev) +{ + amdgpu_ucode_release(&adev->gfx.pfp_fw); + amdgpu_ucode_release(&adev->gfx.me_fw); + amdgpu_ucode_release(&adev->gfx.ce_fw); + amdgpu_ucode_release(&adev->gfx.rlc_fw); +} + static int gfx_v6_0_init_microcode(struct amdgpu_device *adev) { const char *chip_name; @@ -3249,6 +3257,7 @@ static int gfx_v6_0_sw_fini(struct amdgpu_ip_block *ip_block) amdgpu_ring_fini(&adev->gfx.compute_ring[i]); amdgpu_gfx_rlc_fini(adev); + gfx_v6_0_free_microcode(adev); return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c index b05465dc30b4bd..ba5b1a17af6120 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c @@ -327,7 +327,7 @@ static void gmc_v12_0_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, return; } - gmc_v12_0_flush_vm_hub(adev, vmid, vmhub, 0); + gmc_v12_0_flush_vm_hub(adev, vmid, vmhub, flush_type); return; } diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c index 5fe43f7eab29d3..5d0d316fa94e00 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c @@ -389,7 +389,7 @@ static void gmc_v12_1_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, return; } - gmc_v12_1_flush_vm_hub(adev, vmid, vmhub, 0); + gmc_v12_1_flush_vm_hub(adev, vmid, vmhub, flush_type); return; } diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 14a5abe42d1ffc..8d5dcaf4fea6b7 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -361,8 +361,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, compute_mqd = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(compute_mqd)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(compute_mqd); goto free_mqd; } @@ -406,8 +405,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_gfx_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_gfx_v11)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_gfx_v11); goto free_mqd; } @@ -451,8 +449,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_sdma_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_sdma_v11)) { - DRM_ERROR("Failed to read sdma user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_sdma_v11); goto free_mqd; } diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c index 18366e16ef3faa..1e4a3dcde1a2bd 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c @@ -1339,10 +1339,6 @@ static int sdma_v7_1_sw_init(struct amdgpu_ip_block *ip_block) else DRM_ERROR("Failed to allocated memory for SDMA IP Dump\n"); -#ifdef CONFIG_DRM_AMDGPU_NAVI3X_USERQ - adev->userq_funcs[AMDGPU_HW_IP_DMA] = &userq_mes_funcs; -#endif - return r; } diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index 352c2710330546..e926d09128e0b0 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1397,6 +1397,7 @@ struct vi_reset_quirk { static const struct vi_reset_quirk vi_reset_quirks[] = { { 0x67ef, PCI_VENDOR_ID_APPLE, 0x0190, 0xe3 }, /* Radeon Pro 555X */ { 0x67ef, PCI_VENDOR_ID_APPLE, 0x018f, 0xc2 }, /* Radeon Pro 560X */ + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x017a, 0xc7 }, /* Radeon Pro 555 */ }; static bool vi_need_reset_on_init(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 2f78395a0c31cb..4594824985e1ef 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -572,12 +572,32 @@ static int allocate_doorbell(struct qcm_process_device *qpd, * we need the physical sdma engine id in order to get the * correct doorbell offset. */ - uint32_t valid_id = idx_offset[qpd->dqm->dev->node_id * - get_num_all_sdma_engines(qpd->dqm) + - q->properties.sdma_engine_id] - + (q->properties.sdma_queue_id & 1) - * KFD_QUEUE_DOORBELL_MIRROR_OFFSET - + (q->properties.sdma_queue_id >> 1); + u32 engine_base = idx_offset[dev->node_id * + get_num_all_sdma_engines(qpd->dqm) + + q->properties.sdma_engine_id]; + u32 valid_id; + + /* + * With the aqua_vanjaram doorbell layout, a shader doorbell + * write whose 32-byte block starts in the previous engine's + * range rings that engine instead. Engine ranges are 10 + * indices apart (sdma_doorbell_range = 20 dwords, halved for + * 8-byte doorbells, in aqua_vanjaram_doorbell_index_init()), + * which puts odd engines 2 indices into a shared block. + * Aligning costs at most 2 indices, and 8 queues per engine use + * only 4 of the 10, so the aligned base stays in range. The + * 512-index mirror offset keeps the alignment. + */ + if (KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 3) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 4) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 5, 0)) + engine_base = ALIGN(engine_base, + KFD_SDMA_SHADER_DOORBELL_GRANULARITY); + + valid_id = engine_base + + (q->properties.sdma_queue_id & 1) * + KFD_QUEUE_DOORBELL_MIRROR_OFFSET + + (q->properties.sdma_queue_id >> 1); if (restore_id && *restore_id != valid_id) return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index d8631847f0eb93..435bf26929680c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -133,6 +133,13 @@ */ #define KFD_QUEUE_DOORBELL_MIRROR_OFFSET 512 +/* + * On GC 9.4.3, 9.4.4 and 9.5.0, doorbell writes issued from shaders are + * routed to an SDMA engine at 32-byte granularity, i.e. 4 indices with the + * 8-byte SDMA doorbells used on SOC15. + */ +#define KFD_SDMA_SHADER_DOORBELL_GRANULARITY 4 + /** * enum kfd_ioctl_flags - KFD ioctl flags * Various flags that can be set in &amdkfd_ioctl_desc.flags to control how diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index fa4054d51f6066..d7f2ff5c8f024a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1752,12 +1752,15 @@ static int svm_range_validate_and_map(struct mm_struct *mm, } /* - * If prange with always mapped flag, update mapping on GPUs with - * ACCESS attribute + * If prange with always mapped flag is not mapped or migrated + * yet, update mapping on GPUs with ACCESS attribute. Don't add + * them otherwise, ACCESS GPUs may be on different XGMI hives, + * that would force hmm_range_fault to migrate VRAM back to ram. */ - if (prange->flags & KFD_IOCTL_SVM_FLAG_GPU_ALWAYS_MAPPED) - bitmap_or(ctx->bitmap, ctx->bitmap, prange->bitmap_access, - MAX_GPU_INSTANCE); + if (bitmap_empty(ctx->bitmap, MAX_GPU_INSTANCE) && + prange->flags & KFD_IOCTL_SVM_FLAG_GPU_ALWAYS_MAPPED) + bitmap_copy(ctx->bitmap, prange->bitmap_access, + MAX_GPU_INSTANCE); } else { bitmap_or(ctx->bitmap, prange->bitmap_access, prange->bitmap_aip, MAX_GPU_INSTANCE); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 91fdf3de7202dc..47a0ff2ed03592 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -426,6 +426,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) init_data[i].use_custom_backlight_caps = false; init_data[i].custom_backlight_caps_config_no = 0; init_data[i].use_linear_backlight_curve = false; + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + init_data[i].use_linear_backlight_curve = true; init_data[i].def_varibright_enable = 0; init_data[i].varibright_level = 0; /* @@ -1582,9 +1584,14 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) dc_allow_idle_optimizations(adev->dm.dc, false); dm->cached_dc_state = dc_state_create_copy(dm->dc->current_state); + if (!dm->cached_dc_state) { + drm_err(adev_to_drm(adev), + "Failed to allocate cached DC state during suspend\n"); + mutex_unlock(&dm->dc_lock); + return -ENOMEM; + } - if (dm->cached_dc_state) - dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false); + dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false); res = amdgpu_dm_commit_zero_streams(dm->dc); if (res != DC_OK) { @@ -3701,6 +3708,13 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconn = (struct amdgpu_dm_connector *)acrtc_state->stream->dm_stream_context; + if (!acrtc_state->base.async_flip) { + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, false, + psr_event_immediate_flip, false); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, false, + replay_event_immediate_flip, false); + } + /* Decrement skip count when SR is enabled and we're doing fast updates. */ if (acrtc_state->update_type == UPDATE_TYPE_FAST && (psr->psr_feature_enabled || pr->replay_feature_enabled)) { @@ -4111,6 +4125,18 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } mutex_lock(&dm->dc_lock); + /* + * Tearing (immediate) flips cannot work with panel self-refresh + * features. The event is cleared by + * amdgpu_dm_enable_self_refresh() once async flips stop. + */ + if (immediate_flip) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, true, + psr_event_immediate_flip, true); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, true, + replay_event_immediate_flip, true); + } update_planes_and_stream_adapter(dm->dc, acrtc_state->update_type, planes_count, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index b9e90ea449ca31..703d0617df6d47 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -39,6 +39,7 @@ #include "modules/inc/mod_power.h" #include +#include #include #include #include @@ -118,6 +119,68 @@ static inline u32 scale_fw_to_input(int min, int max, u64 input) return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); } +static u32 backlight_signal_to_luminance(const struct amdgpu_dm_backlight_caps *caps, + u32 signal) +{ + u32 lower_signal, upper_signal; + u32 lower_lum, upper_lum; + int i; + + if (!caps->data_points) + return DIV_ROUND_CLOSEST(signal * 100, AMDGPU_MAX_BL_LEVEL); + + if (signal < caps->luminance_data[0].input_signal) { + lower_signal = 0; + lower_lum = 0; + upper_signal = caps->luminance_data[0].input_signal; + upper_lum = caps->luminance_data[0].luminance; + } else if (signal >= + caps->luminance_data[caps->data_points - 1].input_signal) { + lower_signal = caps->luminance_data[caps->data_points - 1].input_signal; + lower_lum = caps->luminance_data[caps->data_points - 1].luminance; + upper_signal = caps->max_input_signal; + upper_lum = 100; + } else { + for (i = 1; + i < caps->data_points && + signal > caps->luminance_data[i].input_signal; + i++) + ; + + lower_signal = caps->luminance_data[i - 1].input_signal; + upper_signal = caps->luminance_data[i].input_signal; + lower_lum = caps->luminance_data[i - 1].luminance; + upper_lum = caps->luminance_data[i].luminance; + } + + if (upper_signal == lower_signal) + return upper_lum; + + return lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * + (signal - lower_signal), + upper_signal - lower_signal); +} + +static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps, + u32 user_brightness) +{ + unsigned int min, max; + u32 signal, luminance; + + if (!get_brightness_range(caps, &min, &max)) + return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, + MAX_BACKLIGHT_LEVEL); + + user_brightness = clamp(user_brightness, min, max); + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, max); + + signal = scale_input_to_fw(max, user_brightness); + luminance = backlight_signal_to_luminance(caps, signal); + + return min_t(u32, luminance * 1000, 100000); +} + STATIC_IFN_KUNIT void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, unsigned int min, unsigned int max, @@ -260,10 +323,10 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, struct amdgpu_dm_backlight_caps *caps; struct dc_link *link; u32 brightness = 0; + u32 millipercent = 0; bool rc = false, reallow_idle = false; struct drm_connector *connector; struct dc_stream_state *stream; - unsigned int min, max; list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); @@ -286,12 +349,21 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, /* update scratch register */ if (bl_idx == 0) amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); - brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); + link = (struct dc_link *)dm->backlight_link[bl_idx]; + stream = dm_find_stream_with_link(dm, link); + if (!stream) + return; - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |= caps->brightness_mask; + if (caps->aux_support) { + brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); + /* Apply brightness quirk */ + if (caps->brightness_mask) + brightness |= caps->brightness_mask; + } else { + millipercent = convert_brightness_to_millipercent(caps, + user_brightness); + } if (trace_amdgpu_dm_brightness_enabled()) { trace_amdgpu_dm_brightness(__builtin_return_address(0), @@ -301,10 +373,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, power_supply_is_system_supplied() > 0); } - stream = dm_find_stream_with_link(dm, link); - if (!stream) - return; - mutex_lock(&dm->dc_lock); if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { dc_allow_idle_optimizations(dm->dc, false); @@ -316,10 +384,8 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); } else { /* power module uses millipercent */ - get_brightness_range(caps, &min, &max); - brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; rc = mod_power_set_backlight_percent(dm->power_module, stream, - brightness, 0, false); + millipercent, 0, false); } /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 154e1f35dcb190..45a69bf43971fa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -732,6 +732,24 @@ get_aspect_ratio(const struct drm_display_mode *mode_in) } EXPORT_IF_KUNIT(get_aspect_ratio); +/* + * CTA-861 5.1: RGB video formats default to limited range except 640x480 (VIC 1). + * With Broadcast RGB left at Automatic follow that default, as i915 does. + */ +static bool rgb_output_is_limited_range(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state) +{ + switch (connector_state->hdmi.broadcast_rgb) { + case DRM_HDMI_BROADCAST_RGB_FULL: + return false; + case DRM_HDMI_BROADCAST_RGB_LIMITED: + return true; + default: + return connector_state->connector->display_info.is_hdmi && + dc_crtc_timing->vic > 1; + } +} + enum dc_color_space amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, const struct drm_connector_state *connector_state) @@ -757,7 +775,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, case DRM_MODE_COLORIMETRY_BT2020_RGB: case DRM_MODE_COLORIMETRY_BT2020_YCC: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_2020_RGB_LIMITEDRANGE; else color_space = COLOR_SPACE_2020_RGB_FULLRANGE; @@ -769,7 +787,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, default: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { color_space = COLOR_SPACE_SRGB; - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_SRGB_LIMITED; /* * 27030khz is the separation point between HDTV and SDTV @@ -994,10 +1012,15 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled) { + /* Inserted modes copy the native sync; a sink mode with equal totals may not */ if (scale_enabled || ( native_mode->clock == drm_mode->clock && native_mode->htotal == drm_mode->htotal && - native_mode->vtotal == drm_mode->vtotal)) { + native_mode->vtotal == drm_mode->vtotal && + native_mode->hsync_start == drm_mode->hsync_start && + native_mode->hsync_end == drm_mode->hsync_end && + native_mode->vsync_start == drm_mode->vsync_start && + native_mode->vsync_end == drm_mode->vsync_end)) { if (native_mode->crtc_clock) copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); } else { @@ -2256,8 +2279,8 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, * below gate which of these entries are actually attempted. */ static const enum dc_pixel_encoding encoding_order[] = { - PIXEL_ENCODING_YCBCR444, PIXEL_ENCODING_RGB, + PIXEL_ENCODING_YCBCR444, PIXEL_ENCODING_YCBCR422, PIXEL_ENCODING_YCBCR420, }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 045a7f88b75466..7261317d4c0f49 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -1231,7 +1231,8 @@ static bool get_conv_frl_bw(struct amdgpu_dm_connector *aconnector, unsigned int max_sink_bw_in_kbps = 0; unsigned int dsc_max_sink_bw_in_kbps = 0; - if (aconnector->dc_link->dc->caps.dp_hdmi21_pcon_support && + if (aconnector->dc_sink && + aconnector->dc_link->dc->caps.dp_hdmi21_pcon_support && aconnector->mst_downstream_port_caps.bytes.byte0.bits.DWN_STRM_PORTX_TYPE == DOWN_STREAM_DETAILED_HDMI) { max_conv_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data( aconnector->dc_link->dc, @@ -1996,6 +1997,9 @@ enum dc_status dm_dp_mst_is_port_support_mode( struct dc_dsc_config_options dsc_options = {0}; uint32_t stream_kbps; + if (!aconnector->dc_sink) + return DC_FAIL_BANDWIDTH_VALIDATE; + /* DSC unnecessary case * Check if timing could be supported within end-to-end BW */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e13b96358208c7..a2b8f70336160f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -112,25 +112,7 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state if (plane_state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI || plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) { - static const uint32_t alpha_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_ARGB16161616, - DRM_FORMAT_ABGR16161616, - DRM_FORMAT_ARGB16161616F, - }; - uint32_t format = plane_state->fb->format->format; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(alpha_formats); ++i) { - if (format == alpha_formats[i]) { - *per_pixel_alpha = true; - break; - } - } + *per_pixel_alpha = plane_state->fb->format->has_alpha; if (*per_pixel_alpha && plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) *pre_multiplied_alpha = false; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 212a7536e65b7f..b61771749ca06d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -409,7 +409,9 @@ static void dm_test_output_color_space_default_rgb_full(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; @@ -559,7 +561,9 @@ static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; @@ -584,6 +588,110 @@ static void dm_test_output_color_space_bt2020_rgb_limited(struct kunit *test) (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); } +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_cta - Automatic follows the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB_LIMITED); +} + +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_vga - 640x480 (VIC 1) stays full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_vga(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 1; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_auto_dp_cta - Automatic on a non-HDMI sink is full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_dp_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_full_hdmi_cta - Full overrides the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_full_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta - BT.2020 RGB follows the same default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc * @test: The KUnit test context @@ -647,7 +755,9 @@ static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *tes { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; @@ -1462,6 +1572,46 @@ static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test) KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); } +static void dm_test_decide_crtc_timing_sink_mode_same_totals(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode = { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 4184, 4272, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 4096); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hsync_start, 4184); +} + +static void dm_test_decide_crtc_timing_inserted_mode(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode = native_mode; + drm_mode.hdisplay = 1920; + drm_mode.vdisplay = 1080; + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 3840); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 2160); +} + /* Tests for amdgpu_dm_connector_funcs_reset() */ static const struct drm_connector_funcs dm_test_connector_funcs = { @@ -3425,6 +3575,7 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) ctx->conn_state = drmm_kzalloc(ctx->drm, sizeof(*ctx->conn_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->conn_state->connector = &ctx->aconnector->base; ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->stream); ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -3818,6 +3969,7 @@ static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->base.connector = &ctx->aconnector->base; ctx->dm_state->scaling = RMX_OFF; ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -5458,6 +5610,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_limited), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_vga), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_dp_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_full_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), @@ -5518,6 +5675,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), KUNIT_CASE(dm_test_decide_crtc_timing_no_copy), KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock), + KUNIT_CASE(dm_test_decide_crtc_timing_sink_mode_same_totals), + KUNIT_CASE(dm_test_decide_crtc_timing_inserted_mode), /* amdgpu_dm_connector_funcs_reset */ KUNIT_CASE(dm_test_funcs_reset_sets_defaults), KUNIT_CASE(dm_test_funcs_reset_edp_abm_level), diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 705c500ad71143..871063f809dcff 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1905,6 +1905,7 @@ struct dc_scratch_space { struct ddc_service *ddc; enum dp_panel_mode panel_mode; + bool panel_mode_initialized; bool aux_mode; /* Private to DC core */ diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 91465ac05c9717..eb4ce5b1ecfbbc 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -29,7 +29,7 @@ dml_ccflags := $(CC_FLAGS_FPU) dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) + ifneq ($(filter y,$(CONFIG_KASAN) $(CONFIG_KCSAN) $(CONFIG_UBSAN)),) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 39ee2d1999dd1b..289bac08c82c1b 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -28,7 +28,7 @@ dml2_ccflags := $(CC_FLAGS_FPU) dml2_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) + ifneq ($(filter y,$(CONFIG_KASAN) $(CONFIG_KCSAN) $(CONFIG_UBSAN)),) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index 16951a9550f25d..21dd91fc801074 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -93,6 +93,7 @@ void dp_set_panel_mode(struct dc_link *link, enum dp_panel_mode panel_mode) } link->panel_mode = panel_mode; + link->panel_mode_initialized = true; DC_LOG_DETECTION_DP_CAPS("%d eDP panel mode supported: %d, enabled: %d\n", link->link_index, link->dpcd_caps.panel_mode_edp, @@ -1297,7 +1298,8 @@ void edp_set_panel_assr(struct dc_link *link, struct pipe_ctx *pipe_ctx, result = cp_psp->funcs.enable_assr(cp_psp->handle, link); - if (!result && link->panel_mode != DP_PANEL_MODE_EDP) + if (!result && link->panel_mode_initialized && + link->panel_mode != DP_PANEL_MODE_EDP) *panel_mode = DP_PANEL_MODE_DEFAULT; } } diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c index d2d56a1c4b8b3a..65c7dae3ff66d2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c @@ -448,6 +448,12 @@ static const struct dce_abm_mask abm_mask = { ABM_MASK_SH_LIST_DCE110(_MASK) }; +static const struct dc_debug_options debug_defaults = { 0 }; + +static const struct dc_check_config config_defaults = { + .enable_legacy_fast_update = true, +}; + #define CTX ctx #define REG(reg) mm ## reg @@ -937,6 +943,8 @@ static bool dce60_construct( dc->caps.max_cursor_size = 64; dc->caps.dual_link_dvi = true; dc->caps.extended_aux_timeout_support = false; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * @@ -1134,6 +1142,8 @@ static bool dce61_construct( dc->caps.i2c_speed_in_khz = 40; dc->caps.max_cursor_size = 64; dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * @@ -1331,7 +1341,8 @@ static bool dce64_construct( dc->caps.max_downscale_ratio = 200; dc->caps.i2c_speed_in_khz = 40; dc->caps.max_cursor_size = 64; - dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c index 26820d35478a39..2f4de697ec4bb8 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c @@ -1148,6 +1148,8 @@ static bool dce81_construct( dc->caps.max_cursor_size = 128; dc->caps.min_horizontal_blanking_period = 80; dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index 2f9690e65ca95d..d900be8cb5fc5e 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -328,6 +328,7 @@ bool mod_power_add_stream(struct mod_power *mod_power, core_power->map[core_power->num_entities].psr_enabled = 0; core_power->map[core_power->num_entities].psr_events = psr_event_vsync; core_power->map[core_power->num_entities].psr_power_opt = 0; + core_power->map[core_power->num_entities].replay_events = replay_event_vsync; core_power->num_entities++; return true; } @@ -387,6 +388,7 @@ bool mod_power_remove_stream(struct mod_power *mod_power, core_power->map[i].psr_enabled = core_power->map[i + 1].psr_enabled; core_power->map[i].psr_events = core_power->map[i + 1].psr_events; core_power->map[i].psr_power_opt = core_power->map[i + 1].psr_power_opt; + core_power->map[i].replay_events = core_power->map[i + 1].replay_events; memcpy(core_power->map[i].psr_context, core_power->map[i + 1].psr_context, sizeof(struct mod_power_psr_context)); memset(core_power->map[i + 1].psr_context, 0, sizeof(struct mod_power_psr_context)); diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 012227c7060017..d26be73637e32e 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -5263,7 +5263,8 @@ static int si_init_smc_table(struct amdgpu_device *adev) break; } - if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC) + if ((adev->flags & AMD_IS_MOBILITY) && + (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)) table->systemFlags |= PPSMC_SYSTEMFLAG_GPIO_DC; if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_REGULATOR_HOT) { @@ -7237,6 +7238,19 @@ static void si_parse_pplib_non_clock_info(struct amdgpu_device *adev, adev->pm.dpm.uvd_ps = rps; } +static void si_update_limits(struct amdgpu_clock_and_voltage_limits *limits, + struct rv7xx_pl *pl) +{ + if (pl->sclk > limits->sclk) + limits->sclk = pl->sclk; + if (pl->mclk > limits->mclk) + limits->mclk = pl->mclk; + if (pl->vddc > limits->vddc) + limits->vddc = pl->vddc; + if (pl->vddci > limits->vddci) + limits->vddci = pl->vddci; +} + static void si_parse_pplib_clock_info(struct amdgpu_device *adev, struct amdgpu_ps *rps, int index, union pplib_clock_info *clock_info) @@ -7245,7 +7259,6 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, struct evergreen_power_info *eg_pi = evergreen_get_pi(adev); struct si_power_info *si_pi = si_get_pi(adev); struct si_ps *ps = si_get_ps(rps); - struct amdgpu_clock_and_voltage_limits *limits; u16 leakage_voltage; struct rv7xx_pl *pl = &ps->performance_levels[index]; int ret; @@ -7310,26 +7323,14 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, * VBIOS can contain conflicting values between: * - the maximum allowed clocks and voltages on AC or DC * - the clocks and voltages in power states on AC or DC + * + * Assume that the AC limits are the maximum of all power states, + * and the DC limits are the maximum of battery power states. */ + si_update_limits(&adev->pm.dpm.dyn_state.max_clock_voltage_on_ac, pl); if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == - ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) - limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_ac; - else if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) - limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_dc; - else - limits = NULL; - - if (limits) { - if (pl->sclk > limits->sclk) - limits->sclk = pl->sclk; - if (pl->mclk > limits->mclk) - limits->mclk = pl->mclk; - if (pl->vddc > limits->vddc) - limits->vddc = pl->vddc; - if (pl->vddci > limits->vddci) - limits->vddci = pl->vddci; - } + si_update_limits(&adev->pm.dpm.dyn_state.max_clock_voltage_on_dc, pl); } union pplib_power_state { diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c index d4577663a1f0ef..aa36b79d0f6c42 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c @@ -189,13 +189,12 @@ static int aspeed_gfx_load(struct drm_device *drm) } reset_control_deassert(priv->rst); - priv->clk = devm_clk_get(drm->dev, NULL); + priv->clk = devm_clk_get_enabled(drm->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, - "missing or invalid clk device tree entry"); + "failed to get and enable clock\n"); return PTR_ERR(priv->clk); } - clk_prepare_enable(priv->clk); /* Sanitize control registers */ writel(0, priv->base + CRT_CTRL1); diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index cbea8b14cd4b0f..2fa7f2d46b3dcd 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -135,8 +135,8 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi)) - return PTR_ERR(hdmi); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); platform_set_drvdata(pdev, hdmi); @@ -145,9 +145,9 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) static void th1520_dw_hdmi_remove(struct platform_device *pdev) { - struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + struct th1520_hdmi *hdmi = platform_get_drvdata(pdev); - dw_hdmi_remove(hdmi); + dw_hdmi_remove(hdmi->dw_hdmi); } static const struct of_device_id th1520_dw_hdmi_of_table[] = { diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index e623ccb4c1d8ab..488fd123a01adb 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -4634,7 +4634,9 @@ static int dp_aux_backlight_update_status(struct backlight_device *bd) if (!backlight_is_blank(bd)) { if (!bl->enabled) { - drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + ret = drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + if (ret) + return ret; bl->enabled = true; return 0; } diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 83f1809a5d376d..a52d5f5b9f052f 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -454,8 +454,10 @@ void drm_bridge_add(struct drm_bridge *bridge) * in bridge_lingering_list. Remove it or bridge_lingering_list will be * corrupted when adding this bridge to bridge_list below. */ + mutex_lock(&bridge_lock); if (!list_empty(&bridge->list)) list_del_init(&bridge->list); + mutex_unlock(&bridge_lock); mutex_init(&bridge->hpd_state_mutex); mutex_init(&bridge->hpd_mutex); @@ -593,8 +595,10 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, if (!bridge->container) DRM_WARN("DRM bridge corrupted or not allocated by devm_drm_bridge_alloc()\n"); - if (list_empty(&bridge->list)) - DRM_WARN("Missing drm_bridge_add() before attach\n"); + scoped_guard(mutex, &bridge_lock) { + if (list_empty(&bridge->list)) + DRM_WARN("Missing drm_bridge_add() before attach\n"); + } drm_bridge_get(bridge); diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d4664ed468b26f..0817b639b3faaf 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1724,10 +1724,10 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper) ret = drm_fb_helper_single_fb_probe(fb_helper); if (ret < 0) { - if (ret == -EAGAIN) { - fb_helper->deferred_setup = true; + /* modesets are probed but fb_helper->fb isn't; defer restore too */ + fb_helper->deferred_setup = true; + if (ret == -EAGAIN) ret = 0; - } mutex_unlock(&fb_helper->lock); goto err_drm_fb_helper_release_info; diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index aa7792966287bf..5aa5ac2f37274a 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -507,6 +507,7 @@ int gud_plane_atomic_check(struct drm_plane *plane, for_each_new_connector_in_state(state, connector, connector_state, i) { if (connector_state->crtc) break; + connector_state = NULL; } /* diff --git a/drivers/gpu/drm/i915/display/intel_display_params.c b/drivers/gpu/drm/i915/display/intel_display_params.c index 2aed110c5b090b..ca0ef466bb1039 100644 --- a/drivers/gpu/drm/i915/display/intel_display_params.c +++ b/drivers/gpu/drm/i915/display/intel_display_params.c @@ -120,6 +120,10 @@ intel_display_param_named_unsafe(enable_psr, int, 0400, "(0=disabled, 1=enable up to PSR1, 2=enable up to PSR2) " "Default: -1 (use per-chip default)"); +intel_display_param_named_unsafe(enable_dc_balance, bool, 0400, + "Enable VRR DC balance (0=disabled, 1=enabled). " + "Default: 0 (disabled)"); + intel_display_param_named_unsafe(enable_panel_replay, int, 0400, "Enable Panel Replay (0=disabled, 1=enabled). Default: -1 (use per-chip default)"); diff --git a/drivers/gpu/drm/i915/display/intel_display_params.h b/drivers/gpu/drm/i915/display/intel_display_params.h index b95ecf728daab7..16759e94c02536 100644 --- a/drivers/gpu/drm/i915/display/intel_display_params.h +++ b/drivers/gpu/drm/i915/display/intel_display_params.h @@ -46,6 +46,7 @@ struct drm_printer; param(bool, enable_dp_mst, true, 0600) \ param(int, enable_fbc, -1, 0600) \ param(int, enable_psr, -1, 0600) \ + param(bool, enable_dc_balance, false, 0600) \ param(int, enable_panel_replay, -1, 0600) \ param(bool, psr_safest_params, false, 0400) \ param(bool, enable_psr2_sel_fetch, true, 0400) \ diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c index 51e4f3309b8b16..cfc0cfbff017a1 100644 --- a/drivers/gpu/drm/i915/display/intel_vrr.c +++ b/drivers/gpu/drm/i915/display/intel_vrr.c @@ -386,10 +386,12 @@ static bool intel_vrr_dc_balance_possible(const struct intel_crtc_state *crtc_st static void intel_vrr_dc_balance_compute_config(struct intel_crtc_state *crtc_state) { + struct intel_display *display = to_intel_display(crtc_state); int guardband_usec, adjustment_usec; struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; - if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) + if (!intel_vrr_dc_balance_possible(crtc_state) || + !crtc_state->vrr.enable || !display->params.enable_dc_balance) return; crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 1f303d4eaa4d4f..d5aadced7da0cf 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -1766,6 +1766,7 @@ static int eb_reinit_userptr(struct i915_execbuffer *eb) static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb) { + const unsigned int count = eb->buffer_count; bool have_copy = false; struct eb_vma *ev; int err = 0; @@ -1868,7 +1869,6 @@ static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb) out: if (have_copy) { - const unsigned int count = eb->buffer_count; unsigned int i; for (i = 0; i < count; i++) { diff --git a/drivers/gpu/drm/i915/gt/intel_rps.c b/drivers/gpu/drm/i915/gt/intel_rps.c index a33b19c04737a7..0f9b4f1dd3cb6d 100644 --- a/drivers/gpu/drm/i915/gt/intel_rps.c +++ b/drivers/gpu/drm/i915/gt/intel_rps.c @@ -1904,7 +1904,8 @@ static void rps_work(struct work_struct *work) out: spin_lock_irq(gt->irq_lock); - gen6_gt_pm_unmask_irq(gt, rps->pm_events); + if (intel_rps_is_active(rps)) + gen6_gt_pm_unmask_irq(gt, rps->pm_events); spin_unlock_irq(gt->irq_lock); } diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 62eae7fcd5a26a..67e73d7d5d0e97 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -2337,7 +2337,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. * @device_addr: Virtual device address at the start of the requested mapping. - * @sgt_offset: Start offset of the requested device-virtual memory mapping. + * @sgt_offset: Offset into @sgt of the start of the requested mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. * @@ -2350,7 +2350,6 @@ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size) { - u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2365,18 +2364,18 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* - * The number of page table objects we need to prealloc is - * indicated by the mapping size, start address and the sizes - * of the areas mapped per PT or PD. The range calculation is - * identical to that for the index into a table for a device - * address, so we reuse those functions here. + * The page tables needed are set by the device-virtual range + * being mapped: one level 1 table per 1GiB region and one + * level 0 table per 2MiB region the range touches. Tables that + * already exist are not consumed, so this is an upper bound. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); - const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); - const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); - const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); - const u32 l0_count = l0_end_idx - l0_start_idx + 1; + const u64 last_addr = device_addr + size - 1; + const u64 l1_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) + 1; + const u64 l0_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) + 1; /* * Alloc and push page table entries until we have enough of diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index 55cc999f3708b6..cbdd15ed74fdbb 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -418,6 +418,8 @@ pvr_vm_gpuva_unmap(struct drm_gpuva_op *op, void *op_ctx) static int pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) { + /* The split parts belong to the object of the mapping being split. */ + struct drm_gpuvm_bo *vm_bo = op->remap.unmap->va->vm_bo; struct pvr_vm_bind_op *ctx = op_ctx; u64 va_start = 0, va_range = 0; int err; @@ -433,14 +435,12 @@ pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) drm_gpuva_remap(&ctx->prev_va->base, &ctx->next_va->base, &op->remap); if (op->remap.prev) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->prev_va->base.gem.obj)); - drm_gpuva_link(&ctx->prev_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->prev_va->base, vm_bo); ctx->prev_va = NULL; } if (op->remap.next) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->next_va->base.gem.obj)); - drm_gpuva_link(&ctx->next_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->next_va->base, vm_bo); ctx->next_va = NULL; } diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index c86a3f54f35bcc..dc8e3e917dd771 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -638,8 +638,10 @@ static int mtk_drm_bind(struct device *dev) private->mtk_drm_bound = true; private->dev = dev; - if (!mtk_drm_get_all_drm_priv(dev)) + if (!mtk_drm_get_all_drm_priv(dev)) { + put_device(private->mutex_dev); return 0; + } drm = drm_dev_alloc(&mtk_drm_driver, dev); if (IS_ERR(drm)) { @@ -1060,7 +1062,7 @@ static int mtk_drm_probe(struct platform_device *pdev) struct mtk_mmsys_driver_data *mtk_drm_data; struct device_node *node; struct component_match *match = NULL; - struct platform_device *ovl_adaptor; + struct platform_device *ovl_adaptor = NULL; int ret; int i; @@ -1112,6 +1114,9 @@ static int mtk_drm_probe(struct platform_device *pdev) PLATFORM_DEVID_AUTO, (void *)private->mmsys_dev, sizeof(*private->mmsys_dev)); + if (IS_ERR(ovl_adaptor)) + return PTR_ERR(ovl_adaptor); + private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR].dev = &ovl_adaptor->dev; mtk_ddp_comp_init(dev, NULL, &private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR], DDP_COMPONENT_DRM_OVL_ADAPTOR); @@ -1205,6 +1210,8 @@ static int mtk_drm_probe(struct platform_device *pdev) err_pm: pm_runtime_disable(dev); err_node: + if (ovl_adaptor) + platform_device_unregister(ovl_adaptor); of_node_put(private->mutex_node); for (i = 0; i < DDP_COMPONENT_DRM_ID_MAX; i++) of_node_put(private->comp_node[i]); @@ -1214,9 +1221,15 @@ static int mtk_drm_probe(struct platform_device *pdev) static void mtk_drm_remove(struct platform_device *pdev) { struct mtk_drm_private *private = platform_get_drvdata(pdev); + struct device *ovl_adaptor_dev; int i; + ovl_adaptor_dev = + private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR].dev; + component_master_del(&pdev->dev, &mtk_drm_ops); + if (ovl_adaptor_dev) + platform_device_unregister(to_platform_device(ovl_adaptor_dev)); pm_runtime_disable(&pdev->dev); of_node_put(private->mutex_node); for (i = 0; i < DDP_COMPONENT_DRM_ID_MAX; i++) diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c index 31e81a6de6d850..1ec973d3f52c5e 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c @@ -376,8 +376,10 @@ static int mtk_hdmi_ddc_v2_probe(struct platform_device *pdev) pm_runtime_get_sync(dev); ret = devm_i2c_add_adapter(dev, &ddc->adap); - if (ret < 0) + if (ret < 0) { + pm_runtime_put_noidle(dev); return dev_err_probe(dev, ret, "Cannot add DDC I2C adapter\n"); + } platform_set_drvdata(pdev, ddc); return 0; diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h index 521b35c7e14d37..bf3ebf5492d15f 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h @@ -173,7 +173,7 @@ #define TPI_AUDIO_LOOKUP_EN BIT(2) /* Video downsampling configuration */ -#define VID_DOWNSAMPLE_CONFIG 0x8d0 +#define VID_DOWNSAMPLE_CONFIG 0x8f0 #define C444_C422_CONFIG_ENABLE BIT(0) #define C422_C420_CONFIG_ENABLE BIT(4) #define C422_C420_CONFIG_BYPASS BIT(5) diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 4542d5f4ded89f..b776030460f771 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -488,7 +488,7 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS) goto done; break; case NV_DEVICE_INFO_V0_TURING: - if (engine != NV_DEVICE_HOST_RUNLIST_ENGINES_NVDEC) { + if (engine == NV_DEVICE_HOST_RUNLIST_ENGINES_GR) { ret = nvif_object_ctor(&chan->chan->user, "abi16CeWar", 0, TURING_DMA_COPY_A, NULL, 0, &chan->ce); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index f78e6b9b429231..b841d66f8bb34c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -201,7 +201,7 @@ nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) } *args = data; int ret = -ENOSYS; - if (!gsp) { + if (!gsp || !gsp->rm) { args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; return 0; } diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 5778e23e20a087..653c6755896c7e 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3674,6 +3674,9 @@ int panthor_group_create(struct panthor_file *pfile, hweight64(group_args->tiler_core_mask) < group_args->max_tiler_cores) return -EINVAL; + if (group_args->queues.count > sched->cs_slot_count) + return -EINVAL; + group = kzalloc_obj(*group); if (!group) return -ENOMEM; diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 8595f4c6e2e5cb..1da6bf1c17ff34 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -49,6 +49,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) { uint8_t __iomem *bios; + uint8_t val1, val2; resource_size_t vram_base; resource_size_t size = 256 * 1024; /* ??? */ @@ -63,7 +64,10 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) return false; } - if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + val1 = readb(&bios[0]); + val2 = readb(&bios[1]); + + if (size == 0 || val1 != 0x55 || val2 != 0xaa) { iounmap(bios); return false; } diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 49ea2ed0996b4c..7b8b3364000fce 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -204,7 +204,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, struct list_head *new_list; uint32_t i; - new_list = kmalloc_objs(struct list_head, new_size); + new_list = kvmalloc_objs(struct list_head, new_size); if (!new_list) return NULL; @@ -224,7 +224,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, for (i = vc4->bo_cache.size_list_size; i < new_size; i++) INIT_LIST_HEAD(&new_list[i]); - kfree(vc4->bo_cache.size_list); + kvfree(vc4->bo_cache.size_list); vc4->bo_cache.size_list = new_list; vc4->bo_cache.size_list_size = new_size; } @@ -1052,6 +1052,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); + kvfree(vc4->bo_cache.size_list); for (i = 0; i < vc4->num_labels; i++) { if (vc4->bo_labels[i].num_allocated) { diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 616caf9d9915b9..7b64e1ed02d4c8 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -360,8 +360,11 @@ static int vc4_drm_bind(struct device *dev) } ret = aperture_remove_all_conflicting_devices(driver->name); - if (ret) + if (ret) { + if (firmware) + rpi_firmware_put(firmware); goto err; + } if (firmware) { ret = rpi_firmware_property(firmware, diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 0f5958c1e6b601..b0e82c2dc1bae7 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -1051,7 +1051,7 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, /* vc4_v3d.c */ extern struct platform_driver vc4_v3d_driver; extern const struct of_device_id vc4_v3d_dt_match[]; -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout); int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used); void vc4_v3d_bin_bo_put(struct vc4_dev *vc4); int vc4_v3d_pm_get(struct vc4_dev *vc4); diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index e231c906709c1b..6ee4bd77d4331a 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -877,11 +877,17 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) drm_gem_object_put(&bo->base.base); } - /* Free up the allocation of any bin slots we used. */ + /* Free up the allocation of any bin slots we used. Jobs that ran to + * completion had their slots released in vc4_irq_finish_render_job(). + * Only jobs that never completed still have slots to be released here. + */ spin_lock_irqsave(&vc4->job_lock, irqflags); vc4->bin_alloc_used &= ~exec->bin_slots; spin_unlock_irqrestore(&vc4->job_lock, irqflags); + /* Let anyone waiting on the binner pool retry. */ + wake_up_all(&vc4->job_wait_queue); + /* Release the reference on the binner BO if needed. */ if (exec->bin_bo_used) vc4_v3d_bin_bo_put(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 7877d493d80e62..dc10d3a4d90f20 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -74,7 +74,7 @@ vc4_overflow_mem_work(struct work_struct *work) bo = vc4->bin_bo; - bin_bo_slot = vc4_v3d_get_bin_slot(vc4); + bin_bo_slot = vc4_v3d_get_bin_slot(vc4, msecs_to_jiffies(500)); if (bin_bo_slot < 0) { drm_err(&vc4->base, "Couldn't allocate binner overflow mem\n"); goto complete; @@ -165,6 +165,10 @@ vc4_irq_finish_render_job(struct drm_device *dev) trace_vc4_rcl_end_irq(dev, exec->seqno); vc4->finished_seqno++; + + vc4->bin_alloc_used &= ~exec->bin_slots; + exec->bin_slots = 0; + list_move_tail(&exec->head, &vc4->job_done_list); nextbin = vc4_first_bin_job(vc4); @@ -276,7 +280,7 @@ vc4_irq_disable(struct drm_device *dev) V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); /* Finish any interrupt handler still in flight. */ - synchronize_irq(vc4->irq); + disable_irq(vc4->irq); cancel_work_sync(&vc4->overflow_mem_work); } @@ -284,7 +288,6 @@ vc4_irq_disable(struct drm_device *dev) int vc4_irq_install(struct drm_device *dev, int irq) { struct vc4_dev *vc4 = to_vc4_dev(dev); - int ret; if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; @@ -298,19 +301,8 @@ int vc4_irq_install(struct drm_device *dev, int irq) init_waitqueue_head(&vc4->job_wait_queue); INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work); - /* Clear any pending interrupts someone might have left around - * for us. - */ - V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); - - ret = devm_request_irq(dev->dev, irq, vc4_irq, 0, - dev_name(dev->dev), dev); - if (ret) - return ret; - - vc4_irq_enable(dev); - - return 0; + return devm_request_irq(dev->dev, irq, vc4_irq, IRQF_NO_AUTOEN, + dev_name(dev->dev), dev); } void vc4_irq_uninstall(struct drm_device *dev) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index f32410420d3e4d..6844e8f3948cf8 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -172,46 +173,42 @@ static void vc4_v3d_init_hw(struct drm_device *dev) V3D_WRITE(V3D_VPMBASE, 0); } -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4) +static int bin_slot_try_alloc(struct vc4_dev *vc4) { - struct drm_device *dev = &vc4->base; - unsigned long irqflags; int slot; - uint64_t seqno = 0; - struct vc4_exec_info *exec; - if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) - return -ENODEV; + guard(spinlock_irqsave)(&vc4->job_lock); -try_again: - spin_lock_irqsave(&vc4->job_lock, irqflags); slot = ffs(~vc4->bin_alloc_used); if (slot != 0) { /* Switch from ffs() bit index to a 0-based index. */ slot--; vc4->bin_alloc_used |= BIT(slot); - spin_unlock_irqrestore(&vc4->job_lock, irqflags); - return slot; + } else { + slot = -ENOMEM; } - /* Couldn't find an open slot. Wait for render to complete - * and try again. - */ - exec = vc4_last_render_job(vc4); - if (exec) - seqno = exec->seqno; - spin_unlock_irqrestore(&vc4->job_lock, irqflags); + return slot; +} - if (seqno) { - int ret = vc4_wait_for_seqno(dev, seqno, ~0ull, true); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout) +{ + int slot; + long ret; - if (ret == 0) - goto try_again; + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) + return -ENODEV; + /* If the pool is full, wait for a job to release its slots. */ + ret = wait_event_interruptible_timeout(vc4->job_wait_queue, + (slot = bin_slot_try_alloc(vc4)) >= 0, + timeout); + if (ret < 0) return ret; - } + if (ret == 0) + return -ENOMEM; - return -ENOMEM; + return slot; } /* @@ -396,6 +393,7 @@ static int vc4_v3d_runtime_resume(struct device *dev) vc4_v3d_init_hw(&vc4->base); vc4_irq_enable(&vc4->base); + enable_irq(vc4->irq); return 0; } @@ -452,6 +450,12 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) return ret; vc4->irq = ret; + ret = vc4_irq_install(drm, vc4->irq); + if (ret) { + drm_err(drm, "Failed to install IRQ handler\n"); + return ret; + } + ret = devm_pm_runtime_enable(dev); if (ret) return ret; @@ -473,12 +477,6 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); - ret = vc4_irq_install(drm, vc4->irq); - if (ret) { - drm_err(drm, "Failed to install IRQ handler\n"); - goto err_put_runtime_pm; - } - pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_put_autosuspend(dev); @@ -497,8 +495,15 @@ static void vc4_v3d_unbind(struct device *dev, struct device *master, struct drm_device *drm = data; struct vc4_dev *vc4 = to_vc4_dev(drm); + /* A straggler vc4_reset() re-enables the interrupt. */ + timer_shutdown_sync(&vc4->hangcheck.timer); + cancel_work_sync(&vc4->hangcheck.reset_work); + vc4_irq_uninstall(drm); + /* Flush rather than cancel, so queued completions release their jobs. */ + flush_work(&vc4->job_done_work); + /* Disable the binner's overflow memory address, so the next * driver probe (if any) doesn't try to reuse our old * allocation. diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index d2a65c968b1f90..565227657b43b4 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -402,7 +402,7 @@ validate_tile_binning_config(VALIDATE_ARGS) return -EINVAL; } - bin_slot = vc4_v3d_get_bin_slot(vc4); + bin_slot = vc4_v3d_get_bin_slot(vc4, MAX_SCHEDULE_TIMEOUT); if (bin_slot < 0) { if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) { drm_err(dev, "Failed to allocate binner memory: %d\n", diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c index d1e7df50019030..5a69606a0593bc 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c @@ -324,6 +324,7 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, unsigned long kmap_num; SVGA3dCopyBox *box; u32 box_count; + u64 src_extent; void *virtual; bool is_iomem; struct vmw_dma_cmd { @@ -335,6 +336,8 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, vmw_surface_get_desc(VMW_CURSOR_SNOOP_FORMAT); const u32 image_pitch = VMW_CURSOR_SNOOP_WIDTH * desc->pitchBytesPerBlock; + BUILD_BUG_ON(sizeof(*srf->snooper.image) != desc->pitchBytesPerBlock); + cmd = container_of(header, struct vmw_dma_cmd, header); /* No snooper installed, nothing to copy */ @@ -372,8 +375,19 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, return; } + if (box->w == 0 || box->h == 0) + return; + + src_extent = (u64)(box->h - 1) * cmd->dma.guest.pitch + + (u64)box->w * desc->pitchBytesPerBlock; + if (src_extent > bo->base.size) { + DRM_ERROR("Cursor snoop source of %llu bytes exceeds the %zu byte buffer\n", + src_extent, bo->base.size); + return; + } + kmap_offset = cmd->dma.guest.ptr.offset >> PAGE_SHIFT; - kmap_num = (VMW_CURSOR_SNOOP_HEIGHT * image_pitch) >> PAGE_SHIFT; + kmap_num = PFN_UP(src_extent); ret = ttm_bo_reserve(bo, true, false, NULL); if (unlikely(ret != 0)) { @@ -387,14 +401,16 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, virtual = ttm_kmap_obj_virtual(&map, &is_iomem); - if (box->w == VMW_CURSOR_SNOOP_WIDTH && cmd->dma.guest.pitch == image_pitch) { + if (box->w == VMW_CURSOR_SNOOP_WIDTH && + box->h == VMW_CURSOR_SNOOP_HEIGHT && + cmd->dma.guest.pitch == image_pitch) { memcpy(srf->snooper.image, virtual, VMW_CURSOR_SNOOP_HEIGHT * image_pitch); } else { - /* Image is unsigned pointer. */ + /* The destination stride is in pixels. */ for (i = 0; i < box->h; i++) - memcpy(srf->snooper.image + i * image_pitch, - virtual + i * cmd->dma.guest.pitch, + memcpy(srf->snooper.image + i * VMW_CURSOR_SNOOP_WIDTH, + virtual + (size_t)i * cmd->dma.guest.pitch, box->w * desc->pitchBytesPerBlock); } srf->snooper.id++; diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c index 1b407b61f68386..31d3ee0825ca86 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c @@ -20,10 +20,18 @@ #include #include #include +#include void vmw_du_init(struct vmw_display_unit *du) { vmw_vkms_crtc_init(&du->crtc); + drm_plane_create_blend_mode_property(&du->primary, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI)); + // Check that the cursor plane was actually initialized + if (du->crtc.cursor == &du->cursor.base) + drm_plane_create_blend_mode_property(&du->cursor.base, + BIT(DRM_MODE_BLEND_PREMULTI)); } void vmw_du_cleanup(struct vmw_display_unit *du) diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c index 73469ea5f333d3..f1dbdc802df81a 100644 --- a/drivers/gpu/drm/xe/display/xe_fb_pin.c +++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c @@ -356,7 +356,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, drm_exec_retry_on_contention(&exec); xe_validation_retry_on_oom(&ctx, &ret); if (!ret) - ttm_bo_pin(&bo->ttm); + xe_bo_pin_account(bo); } if (ret) goto err; @@ -373,7 +373,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, err_unpin: ttm_bo_reserve(&bo->ttm, false, false, NULL); - ttm_bo_unpin(&bo->ttm); + xe_bo_unpin_account(bo); ttm_bo_unreserve(&bo->ttm); err: kfree(vma); @@ -393,7 +393,7 @@ static void __xe_unpin_fb_vma(struct i915_vma *vma) xe_ggtt_node_remove(vma->node, false); ttm_bo_reserve(&vma->bo->ttm, false, false, NULL); - ttm_bo_unpin(&vma->bo->ttm); + xe_bo_unpin_account(vma->bo); ttm_bo_unreserve(&vma->bo->ttm); kfree(vma); } diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index b4921a627ff370..7b95ce0445a95f 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -455,6 +455,100 @@ static void xe_ttm_tt_account_subtract(struct xe_device *xe, struct ttm_tt *tt) xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages, 0); } +static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) +{ + dma_resv_assert_held(tbo->base.resv); + + return tbo->ttm && + (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == + TTM_TT_FLAG_EXTERNAL; +} + +/* + * Account @bo's pages as pinned for the shrinker. Removes @bo's pages + * from the shrinker's shrinkable / purgeable counts on the transition + * from unpinned to pinned. Must be called with @bo's dma-resv held, + * after &ttm_buffer_object.pin_count has been incremented by + * ttm_bo_pin(). Safe to call unconditionally regardless of which pin + * path (kernel, external, framebuffer, backup object, ...) is pinning + * @bo, since it only acts on the true 0->1 pin_count transition. + * + * Imported bos (xe_ttm_bo_is_imported()) are excluded: for those, + * xe_ttm_tt_populate() never actually populates the tt or calls + * xe_ttm_tt_account_add(), but TTM core's ttm_tt_populate() still + * unconditionally marks the tt as populated on driver-hook success. + * Relying on ttm_tt_is_populated() for these would subtract pages that + * were never added, underflowing the shrinker's counts. + */ +static void xe_bo_account_pin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + + if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); +} + +/* + * Counterpart to xe_bo_account_pin(). Must be called with @bo's dma-resv + * held, before &ttm_buffer_object.pin_count is decremented by + * ttm_bo_unpin(), so that the check against the true 1->0 transition sees + * the pin count that is about to be released. See xe_bo_account_pin() for + * why imported bos are excluded. + * + * On the true last unpin, also removes @bo from whichever pinned-bo list + * (external or kernel_bo_present) it may currently be linked on, since a + * bo's final unpin can happen through a pin path (e.g. framebuffer, + * backup object) that has no notion of, or ownership over, that list. + * This is safe and list-agnostic: list_del_init() only needs the node + * itself, not knowledge of which list it is threaded through, and is a + * no-op if @bo is not linked. + */ +static void xe_bo_account_unpin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + bool last_unpin = bo->ttm.pin_count == 1; + + if (last_unpin && !list_empty(&bo->pinned_link)) { + spin_lock(&xe->pinned.lock); + list_del_init(&bo->pinned_link); + spin_unlock(&xe->pinned.lock); + } + + if (last_unpin && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_add(xe, bo->ttm.ttm); +} + +/** + * xe_bo_pin_account() - Pin a bo and account its pages for the shrinker + * @bo: The buffer object to pin. + * + * Pins @bo via ttm_bo_pin() and updates the shrinker's shrinkable / + * purgeable page accounting to match. Must be called with @bo's dma-resv + * held. Safe to call regardless of which pin path (kernel, external, + * framebuffer, backup object, ...) is pinning @bo. + */ +void xe_bo_pin_account(struct xe_bo *bo) +{ + ttm_bo_pin(&bo->ttm); + xe_bo_account_pin(bo); +} + +/** + * xe_bo_unpin_account() - Unpin a bo and account its pages for the shrinker + * @bo: The buffer object to unpin. + * + * Counterpart to xe_bo_pin_account(). Updates the shrinker's shrinkable / + * purgeable page accounting to match, then unpins @bo via ttm_bo_unpin(). + * Must be called with @bo's dma-resv held. + */ +void xe_bo_unpin_account(struct xe_bo *bo) +{ + xe_bo_account_unpin(bo); + ttm_bo_unpin(&bo->ttm); +} + static void update_global_total_pages(struct ttm_device *ttm_dev, long num_pages) { @@ -1389,7 +1483,8 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo) } backup->parent_obj = xe_bo_get(bo); /* Released by bo_destroy */ - ttm_bo_pin(&backup->ttm); + /* Note: XE_BO_FLAG_SYSTEM resolves to XE_PL_TT so already populated. */ + xe_bo_pin_account(backup); bo->backup_obj = backup; } @@ -1409,7 +1504,7 @@ int xe_bo_notifier_unprepare_pinned(struct xe_bo *bo) { xe_bo_lock(bo, false); if (bo->backup_obj) { - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } @@ -1620,7 +1715,7 @@ int xe_bo_restore_pinned(struct xe_bo *bo) xe_bo_vunmap(backup); if (!bo->backup_obj) { if (xe_bo_is_pinned(backup)) - ttm_bo_unpin(&backup->ttm); + xe_bo_unpin_account(backup); xe_bo_put(backup); } out_unlock_bo: @@ -2012,15 +2107,6 @@ static vm_fault_t xe_err_to_fault_t(int err) return VM_FAULT_SIGBUS; } -static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) -{ - dma_resv_assert_held(tbo->base.resv); - - return tbo->ttm && - (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == - TTM_TT_FLAG_EXTERNAL; -} - static vm_fault_t xe_bo_cpu_fault_fastpath(struct vm_fault *vmf, struct xe_device *xe, struct xe_bo *bo, bool needs_rpm) { @@ -3141,12 +3227,13 @@ uint64_t vram_region_gpu_offset(struct ttm_resource *res) int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) { struct xe_device *xe = xe_bo_device(bo); + bool first_pin = !xe_bo_is_pinned(bo); int err; xe_assert(xe, !bo->vm); xe_assert(xe, xe_bo_is_user(bo)); - if (!xe_bo_is_pinned(bo)) { + if (first_pin) { if (!in_place) { err = xe_bo_validate(bo, NULL, false, exec); if (err) @@ -3158,9 +3245,7 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3216,9 +3301,7 @@ int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3247,14 +3330,7 @@ void xe_bo_unpin_external(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); xe_assert(xe, xe_bo_is_user(bo)); - spin_lock(&xe->pinned.lock); - if (bo->ttm.pin_count == 1 && !list_empty(&bo->pinned_link)) - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); - - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3272,21 +3348,16 @@ void xe_bo_unpin(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); if (mem_type_is_vram(place->mem_type) || bo->flags & XE_BO_FLAG_GGTT) { - spin_lock(&xe->pinned.lock); xe_assert(xe, !list_empty(&bo->pinned_link)); - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj)) - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } } - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); } /** diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 152bfcffe39959..53a701d3d242e9 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -229,6 +229,8 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec); int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec); void xe_bo_unpin_external(struct xe_bo *bo); void xe_bo_unpin(struct xe_bo *bo); +void xe_bo_pin_account(struct xe_bo *bo); +void xe_bo_unpin_account(struct xe_bo *bo); int xe_bo_validate(struct xe_bo *bo, struct xe_vm *vm, bool allow_res_evict, struct drm_exec *exec); diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index f4f3819882897f..b82caaff80d7bf 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -324,12 +324,15 @@ static void xe_i2c_remove(void *data) xe_i2c_irq_reset(xe); xe_amc_exit(i2c); + /* Stop the notifier from arming the client work before teardown. */ + bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); + cancel_work_sync(&i2c->work); + for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { i2c_unregister_device(i2c->client[i]); i2c->client[i] = NULL; } - bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); xe->i2c = NULL; } diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c index 35b4e8289b5fee..e4388a9604909a 100644 --- a/drivers/gpu/drm/xe/xe_lrc.c +++ b/drivers/gpu/drm/xe/xe_lrc.c @@ -1099,8 +1099,7 @@ static void xe_lrc_finish(struct xe_lrc *lrc) #define CONTEXT_ACTIVE XE_LRC_CTX_TIMESTAMP_ACTIVE static ssize_t setup_utilization_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, - size_t max_len) + u32 *batch, size_t max_len, bool indirect) { u32 *cmd = batch; @@ -1131,19 +1130,25 @@ static ssize_t setup_utilization_wa(struct xe_lrc *lrc, } static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, bool indirect) { const u32 ts_addr = __xe_lrc_ctx_timestamp_ggtt_addr(lrc); u32 *cmd = batch; - if (!XE_GT_WA(lrc->gt, 16010904313) || - !(hwe->class == XE_ENGINE_CLASS_RENDER || - hwe->class == XE_ENGINE_CLASS_COMPUTE || - hwe->class == XE_ENGINE_CLASS_COPY || + if (!XE_GT_WA(lrc->gt, 16010904313)) + return 0; + + if (!indirect && + !(hwe->class == XE_ENGINE_CLASS_COPY || hwe->class == XE_ENGINE_CLASS_VIDEO_DECODE || hwe->class == XE_ENGINE_CLASS_VIDEO_ENHANCE)) return 0; + if (indirect && + !(hwe->class == XE_ENGINE_CLASS_RENDER || + hwe->class == XE_ENGINE_CLASS_COMPUTE)) + return 0; + if (xe_gt_WARN_ON(lrc->gt, max_len < 12)) return -ENOSPC; @@ -1169,7 +1174,8 @@ static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1198,7 +1204,8 @@ static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1227,7 +1234,8 @@ static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { u32 *cmd = batch; @@ -1247,7 +1255,8 @@ static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_gt *gt = lrc->gt; u32 *(*emit)(struct xe_gt *gt, u32 *cmd) = @@ -1264,7 +1273,7 @@ static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct bo_setup { ssize_t (*setup)(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_size); + u32 *batch, size_t max_size, bool indirect); }; struct bo_setup_state { @@ -1273,6 +1282,7 @@ struct bo_setup_state { struct xe_hw_engine *hwe; size_t max_size; size_t reserve_dw; + bool indirect; unsigned int offset; const struct bo_setup *funcs; unsigned int num_funcs; @@ -1298,7 +1308,8 @@ static int setup_bo(struct bo_setup_state *state) for (size_t i = 0; i < state->num_funcs; i++) { ssize_t len = state->funcs[i].setup(state->lrc, state->hwe, - state->ptr, remain); + state->ptr, remain, + state->indirect); remain -= len; @@ -1404,6 +1415,7 @@ setup_indirect_ctx(struct xe_lrc *lrc, struct xe_hw_engine *hwe) struct bo_setup_state state = { .lrc = lrc, .hwe = hwe, + .indirect = true, .max_size = (63 * 64) /* max 63 cachelines */, .buffer = NULL, .offset = __xe_lrc_indirect_ctx_offset(lrc), diff --git a/drivers/gpu/drm/xe/xe_mmio.c b/drivers/gpu/drm/xe/xe_mmio.c index 7fa18dfcb5a282..455a07d79cd999 100644 --- a/drivers/gpu/drm/xe/xe_mmio.c +++ b/drivers/gpu/drm/xe/xe_mmio.c @@ -320,6 +320,7 @@ u64 xe_mmio_read64_2x32(struct xe_mmio *mmio, struct xe_reg reg) return (u64)udw << 32 | ldw; } +#define __XE_MMIO_WAIT_MAX_BACKOFF_100MS (100 * USEC_PER_MSEC) static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u32 val, u32 timeout_us, u32 *out_val, bool atomic, bool expect_match) { @@ -350,10 +351,10 @@ static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u wait = ktime_us_delta(end, cur); if (atomic) - udelay(wait); + udelay(min_t(s64, wait, (MAX_UDELAY_MS * 1000))); else usleep_range(wait, wait << 1); - wait <<= 1; + wait = min_t(s64, wait << 1, __XE_MMIO_WAIT_MAX_BACKOFF_100MS); } if (ret != 0) { diff --git a/drivers/gpu/drm/xe/xe_pci_sriov.c b/drivers/gpu/drm/xe/xe_pci_sriov.c index 35e6b53e18ce23..31abcee5521150 100644 --- a/drivers/gpu/drm/xe/xe_pci_sriov.c +++ b/drivers/gpu/drm/xe/xe_pci_sriov.c @@ -82,16 +82,33 @@ static void pf_engine_activity_stats(struct xe_device *xe, unsigned int num_vfs, } } -static int resize_vf_vram_bar(struct xe_device *xe, int num_vfs) +static int pf_resize_vf_vram_bar(struct xe_device *xe, int num_vfs) { struct pci_dev *pdev = to_pci_dev(xe->drm.dev); + char buf[10]; u32 sizes; + int size; + int err; + + if (!IS_DGFX(xe)) + return 0; sizes = pci_iov_vf_bar_get_sizes(pdev, VF_LMEM_BAR, num_vfs); if (!sizes) return 0; - return pci_iov_vf_bar_set_size(pdev, VF_LMEM_BAR, __fls(sizes)); + size = __fls(sizes); + string_get_size(pci_rebar_size_to_bytes(size), 1, STRING_UNITS_2, buf, sizeof(buf)); + + err = pci_iov_vf_bar_set_size(pdev, VF_LMEM_BAR, size); + if (err) { + xe_sriov_notice(xe, "Failed to resize VF BAR %u to %s (%pe)\n", + VF_LMEM_BAR, buf, ERR_PTR(err)); + return err; + } + + xe_sriov_dbg(xe, "VF LMEM BAR resized to %s\n", buf); + return 0; } static int pf_prepare_vfs_enabling(struct xe_device *xe) @@ -142,11 +159,7 @@ static int pf_enable_vfs(struct xe_device *xe, int num_vfs) if (err < 0) goto failed; - if (IS_DGFX(xe)) { - err = resize_vf_vram_bar(xe, num_vfs); - if (err) - xe_sriov_info(xe, "Failed to set VF LMEM BAR size: %d\n", err); - } + pf_resize_vf_vram_bar(xe, num_vfs); err = pci_enable_sriov(pdev, num_vfs); if (err < 0) @@ -164,6 +177,7 @@ static int pf_enable_vfs(struct xe_device *xe, int num_vfs) return num_vfs; failed: + pf_resize_vf_vram_bar(xe, xe->sriov.pf.device_total_vfs); xe_sriov_pf_unprovision_vfs(xe, num_vfs); xe_pm_runtime_put(xe); pf_finish_vfs_enabling(xe); @@ -191,6 +205,8 @@ static int pf_disable_vfs(struct xe_device *xe) pci_disable_sriov(pdev); + pf_resize_vf_vram_bar(xe, xe->sriov.pf.device_total_vfs); + xe_sriov_pf_reprovision_default(xe); pf_reset_vfs(xe, num_vfs); diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c index 72baf1aa4b3ad6..a0179e62abaaf0 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c @@ -19,20 +19,20 @@ #include "xe_sysctrl_mailbox_types.h" struct xe_sysctrl_mailbox_msg_hdr { - __le32 data; -} __packed; + u32 data; +}; #define XE_SYSCTRL_HDR_GROUP_ID(hdr) \ - FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_COMMAND(hdr) \ - FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_IS_RESPONSE(hdr) \ - FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, (hdr)->data) #define XE_SYSCTRL_HDR_RESULT(hdr) \ - FIELD_GET(SYSCTRL_HDR_RESULT_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_RESULT_MASK, (hdr)->data) static bool sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask, unsigned int timeout_ms) @@ -126,8 +126,8 @@ static int sysctrl_prepare_command(struct xe_device *xe, return -ENOMEM; hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer; - hdr->data = cpu_to_le32(FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | - FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command)); + hdr->data = FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | + FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command); if (data_in && data_in_len) memcpy(buffer + sizeof(*hdr), data_in, data_in_len); diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index d46779b63660b3..6b467c74601cb3 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -453,7 +453,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, (u64)(long)ctx, true); /* prevent infinite recursions */ - if (ret > size) + if (ret > (ssize_t)size) ret = size; if (ret > 0) memcpy(buf, dma_data, ret); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 9a473f72c8cb1a..cb7f16d75fce6c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1510,6 +1510,9 @@ #define USB_DEVICE_ID_ASUS_MD_5110 0x5110 #define USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART 0x7100 +#define USB_VENDOR_ID_TURTLE_BEACH 0x10f5 +#define USB_DEVICE_ID_TB_VELOCITYONE_RACE 0x7077 + #define USB_VENDOR_ID_TWINHAN 0x6253 #define USB_DEVICE_ID_TWINHAN_IR_REMOTE 0x0100 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 1d619d2345e12f..390d106d5dc131 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -121,7 +121,6 @@ enum recvr_type { recvr_type_27mhz, recvr_type_bluetooth, recvr_type_dinovo, - recvr_type_bolt, }; struct dj_report { @@ -1157,10 +1156,6 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev, logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); workitem.reports_supported |= STD_KEYBOARD; break; - case 0x10: - device_type = "Bolt"; - logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); - break; } /* custom receiver device (eg. powerplay) */ @@ -1750,24 +1745,6 @@ static int logi_dj_hidpp_event(struct hid_device *hdev, dj_dev = djrcv_dev->paired_dj_devices[device_index]; - /* - * Bolt receivers send explicit unpair notifications as HID++ events; - * queue device removal when we receive one. - */ - if (djrcv_dev->type == recvr_type_bolt && - hidpp_report->report_id == REPORT_ID_HIDPP_SHORT && - hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) { - struct dj_workitem workitem = { - .device_index = device_index, - .type = WORKITEM_TYPE_UNPAIRED, - }; - - kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem)); - schedule_work(&djrcv_dev->work); - spin_unlock_irqrestore(&djrcv_dev->lock, flags); - return false; - } - /* * With 27 MHz receivers, we do not get an explicit unpair event, * remove the old device if the user has paired a *different* device. @@ -1907,9 +1884,6 @@ static int logi_dj_probe(struct hid_device *hdev, * treat these as logitech-dj interfaces then this causes input events * reported through this extra interface to not be reported correctly. * To avoid this, we treat these as generic-hid devices. - * - * Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver - * reporting. Treat all other Bolt interfaces as generic-hid devices. */ switch (id->driver_data) { case recvr_type_dj: no_dj_interfaces = 3; break; @@ -1923,20 +1897,10 @@ static int logi_dj_probe(struct hid_device *hdev, } if (hid_is_usb(hdev)) { intf = to_usb_interface(hdev->dev.parent); - if (intf) { - bool generic_hid_interface; - - if (id->driver_data == recvr_type_bolt) - generic_hid_interface = - intf->altsetting->desc.bInterfaceNumber != - LOGITECH_DJ_INTERFACE_NUMBER; - else - generic_hid_interface = - intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces; - if (generic_hid_interface) { - hdev->quirks |= HID_QUIRK_INPUT_PER_APP; - return hid_hw_start(hdev, HID_CONNECT_DEFAULT); - } + if (intf && intf->altsetting->desc.bInterfaceNumber >= + no_dj_interfaces) { + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); } } @@ -2139,10 +2103,6 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3), .driver_data = recvr_type_gaming_hidpp_ls_1_3}, - { /* Logitech Bolt receiver (0xc548) */ - HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, - USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), - .driver_data = recvr_type_bolt}, { /* Logitech lightspeed receiver (0xc54d) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4), diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 493763a12518ea..9646380febacaf 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -988,8 +988,7 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) } /* the device might not be connected */ - if (ret == HIDPP_ERROR_CONNECT_FAIL || - ret == HIDPP_ERROR_RESOURCE_ERROR || + if (ret == HIDPP_ERROR_RESOURCE_ERROR || ret == HIDPP_ERROR_UNKNOWN_DEVICE) return -EIO; @@ -4405,50 +4404,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return ret; } -static bool hidpp_is_bolt_child(struct hid_device *hdev) -{ - struct device *parent = hdev->dev.parent; - struct hid_device *receiver_hdev; - - if (!parent) - return false; - - receiver_hdev = to_hid_device(parent); - return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH && - receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER; -} - -static int hidpp_bolt_init(struct hidpp_device *hidpp) -{ - struct hid_device *hdev = hidpp->hid_dev; - char *name; - int ret; - - ret = hidpp_serial_init(hidpp); - if (ret) - return ret; - - name = hidpp_get_device_name(hidpp); - if (!name) - return -EIO; - - snprintf(hdev->name, sizeof(hdev->name), "%s", name); - dbg_hid("HID++ Bolt: Got name: %s\n", name); - - kfree(name); - return 0; -} - -static int hidpp_receiver_init(struct hidpp_device *hidpp) -{ - if (hidpp_is_bolt_child(hidpp->hid_dev)) - return hidpp_bolt_init(hidpp); - - return hidpp_unifying_init(hidpp); -} - /* Get name + serial for USB and Bluetooth HID++ devices */ -static void hidpp_non_receiver_init(struct hidpp_device *hidpp) +static void hidpp_non_unifying_init(struct hidpp_device *hidpp) { struct hid_device *hdev = hidpp->hid_dev; char *name; @@ -4800,9 +4757,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Get name + serial, store in hdev->name + hdev->uniq */ if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) - hidpp_receiver_init(hidpp); + hidpp_unifying_init(hidpp); else - hidpp_non_receiver_init(hidpp); + hidpp_non_unifying_init(hidpp); if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) connect_mask &= ~HID_CONNECT_HIDINPUT; diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index ab4ac261fe765a..4e19a0c4d2381d 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2249,7 +2249,7 @@ static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); - timer_delete_sync(&td->release_timer); + timer_shutdown_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); hid_hw_stop(hdev); diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c index 60180467a0bfd9..f1854cfe42d0d9 100644 --- a/drivers/hid/hid-universal-pidff.c +++ b/drivers/hid/hid-universal-pidff.c @@ -187,6 +187,7 @@ static const struct hid_device_id universal_pidff_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TURTLE_BEACH, USB_DEVICE_ID_TB_VELOCITYONE_RACE)}, {} }; MODULE_DEVICE_TABLE(hid, universal_pidff_devices); diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 0d2ad7bc3648a2..2537288b50261d 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -606,7 +606,8 @@ static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev) */ max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), SZ_4K); - qcdev->input_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + qcdev->input_buf = devm_kzalloc(qcdev->dev, + HIDI2C_PACKET_LEN(max_report_len), GFP_KERNEL); if (!qcdev->input_buf) return -ENOMEM; @@ -616,7 +617,8 @@ static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev) max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_output_len), max_report_len); - qcdev->report_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + qcdev->report_buf = devm_kzalloc(qcdev->dev, + HIDI2C_PACKET_LEN(max_report_len), GFP_KERNEL); if (!qcdev->report_buf) return -ENOMEM; diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index 89226f5ce45e09..ad676fde211b6b 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -552,7 +552,8 @@ static int quickspi_alloc_report_buf(struct quickspi_device *qsdev) max_input_len = max(le16_to_cpu(qsdev->dev_desc.rep_desc_len), le16_to_cpu(qsdev->dev_desc.max_input_len)); - qsdev->input_buf = devm_kzalloc(qsdev->dev, max_input_len, GFP_KERNEL); + qsdev->input_buf = devm_kzalloc(qsdev->dev, + HIDSPI_INPUT_BODY_SIZE(max_input_len), GFP_KERNEL); if (!qsdev->input_buf) return -ENOMEM; diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c index 68238cc8aee0d1..e0c3de5e82033f 100644 --- a/drivers/i2c/busses/i2c-at91-master.c +++ b/drivers/i2c/busses/i2c-at91-master.c @@ -921,8 +921,14 @@ int at91_twi_probe_master(struct platform_device *pdev, at91_calc_twi_clock(dev); rc = at91_init_twi_recovery_info(pdev, dev); - if (rc == -EPROBE_DEFER) + if (rc == -EPROBE_DEFER) { + /* + * The DMA channels were already set up above, release them + * before deferring the probe so they are not leaked. + */ + at91_twi_dma_release(dev); return rc; + } dev->adapter.algo = &at91_twi_algorithm; dev->adapter.quirks = &at91_twi_quirks; diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 6a39e21d5ed4fb..ed6495ef811cf5 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1003,13 +1003,14 @@ static int geni_i2c_xfer(struct i2c_adapter *adap, ret = gi2c->dev_data->set_rate(&gi2c->se, gi2c->clk_freq_out); if (ret) - return ret; + goto out_pm; if (gi2c->gpi_mode) ret = geni_i2c_gpi_xfer(gi2c, msgs, num); else ret = geni_i2c_fifo_xfer(gi2c, msgs, num); +out_pm: pm_runtime_put_autosuspend(gi2c->se.dev); gi2c->cur = NULL; gi2c->err = 0; diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 3e7735e1dae009..5e397a7e63f6ad 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -73,6 +73,9 @@ enum i2c_scl_freq { * @prev_msg_tx: Previous message is Tx * @quirks: To hold platform specific bug info * @smbus_block_read: Flag to handle block read + * @smbus_actual_len: Valid byte count (length + payload + optional PEC) of a + * padded SMBus block read. msg->len is trimmed to this on completion. + * Zero when no trimming is needed. * @input_clk: Input clock to I2C controller * @i2c_clk: I2C SCL frequency * @atomic: Mode of transfer @@ -98,6 +101,7 @@ struct xiic_i2c { bool prev_msg_tx; u32 quirks; bool smbus_block_read; + unsigned int smbus_actual_len; unsigned long input_clk; unsigned int i2c_clk; bool atomic; @@ -539,30 +543,38 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c) /* Check if received length is valid */ if (rxmsg_len <= I2C_SMBUS_BLOCK_MAX) { + unsigned int pec_len = i2c->rx_msg->len - 1; + /* Set Receive fifo depth */ - if (rxmsg_len > IIC_RX_FIFO_DEPTH) { + if (rxmsg_len + pec_len > IIC_RX_FIFO_DEPTH) { /* - * When Rx msg len greater than or equal to Rx fifo capacity - * Receive fifo depth should set to Rx fifo capacity minus 1 + * Trailing bytes (payload plus any PEC) exceed Rx FIFO + * capacity, so drain in chunks. This also keeps the + * else branch below from pushing rfd_set past the + * 4-bit XIIC_RFD_REG_OFFSET field. */ rfd_set = IIC_RX_FIFO_DEPTH - 1; - i2c->rx_msg->len = rxmsg_len + 1; - } else if ((rxmsg_len == 1) || - (rxmsg_len == 0)) { + i2c->rx_msg->len = rxmsg_len + 1 + pec_len; + } else if (1 + rxmsg_len + pec_len < SMBUS_BLOCK_READ_MIN_LEN) { /* - * Minimum of 3 bytes required to exit cleanly. 1 byte - * already received, Second byte is being received. Have - * to set NACK in read_rx before receiving the last byte + * The HW needs SMBUS_BLOCK_READ_MIN_LEN bytes on the + * bus to exit cleanly: by the time the ISR reads the + * length byte the second byte is already being clocked + * in, too late to NACK. Pad the drain target and record + * the real length, trimmed back on completion so the + * PEC check sees the right byte. */ rfd_set = 0; i2c->rx_msg->len = SMBUS_BLOCK_READ_MIN_LEN; + i2c->smbus_actual_len = 1 + rxmsg_len + pec_len; } else { /* - * When Rx msg len less than Rx fifo capacity - * Receive fifo depth should set to Rx msg len minus 2 + * All trailing bytes fit in the Rx FIFO. Defer RX_FULL + * until every one of them is buffered, so the drain + * takes xiic_read_rx()'s bytes_rem == 0 path. */ - rfd_set = rxmsg_len - 2; - i2c->rx_msg->len = rxmsg_len + 1; + rfd_set = rxmsg_len + pec_len - 1; + i2c->rx_msg->len = rxmsg_len + 1 + pec_len; } xiic_setreg8(i2c, XIIC_RFD_REG_OFFSET, rfd_set); @@ -575,6 +587,16 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c) dev_err(i2c->adap.dev.parent, "smbus_block_read Invalid msg length\n"); } +/* + * Undo the setup-time padding of a short SMBus block read before rx_msg is + * cleared, so the PEC check sees the right byte. + */ +static void xiic_smbus_trim_len(struct xiic_i2c *i2c) +{ + if (i2c->rx_msg && i2c->smbus_actual_len) + i2c->rx_msg->len = i2c->smbus_actual_len; +} + static void xiic_read_rx(struct xiic_i2c *i2c) { u8 bytes_in_fifo, cr = 0, bytes_to_read = 0; @@ -797,6 +819,8 @@ static irqreturn_t xiic_process(int irq, void *dev_id) xiic_read_rx(i2c); if (xiic_rx_space(i2c) == 0) { + xiic_smbus_trim_len(i2c); + /* this is the last part of the message */ i2c->rx_msg = NULL; @@ -865,8 +889,11 @@ static irqreturn_t xiic_process(int irq, void *dev_id) if (i2c->tx_msg && i2c->smbus_block_read) { i2c->smbus_block_read = false; - /* Set requested message len=1 to indicate STATE_DONE */ - i2c->tx_msg->len = 1; + /* + * Drive xiic_tx_space() to 0 to signal STATE_DONE + * without truncating the rx_msg length. + */ + i2c->tx_pos = i2c->tx_msg->len; } if (!i2c->tx_msg) @@ -938,6 +965,7 @@ static void xiic_recv_atomic(struct xiic_i2c *i2c) return; } + xiic_smbus_trim_len(i2c); i2c->rx_msg = NULL; xiic_irq_clr_en(i2c, XIIC_INTR_TX_ERROR_MASK); @@ -955,6 +983,9 @@ static void xiic_start_recv(struct xiic_i2c *i2c) u8 cr = 0, rfd_set = 0; struct i2c_msg *msg = i2c->rx_msg = i2c->tx_msg; + /* A stale value from an aborted block read would truncate this msg. */ + i2c->smbus_actual_len = 0; + if (!i2c->atomic) dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index 02dd1db7a6463c..9ff70a3731413b 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -649,7 +649,7 @@ static int kx022a_read_raw(struct iio_dev *idev, if (ret) return ret; - if ((regval & KX022A_MASK_ODR) > + if ((regval & KX022A_MASK_ODR) >= ARRAY_SIZE(kx022a_accel_samp_freq_table)) { dev_err(data->dev, "Invalid ODR\n"); return -EINVAL; @@ -980,26 +980,44 @@ static int kx022a_fifo_enable(struct kx022a_data *data) guard(mutex)(&data->mutex); ret = __kx022a_turn_on_off(data, false); if (ret) - return ret; + goto err_free_out; /* Update watermark to HW */ ret = kx022a_fifo_set_wmi(data); if (ret) - return ret; + goto err_turn_on_out; /* Enable buffer */ ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2, KX022A_MASK_BUF_EN); if (ret) - return ret; + goto err_turn_on_out; data->state |= KX022A_STATE_FIFO; ret = regmap_set_bits(data->regmap, data->ien_reg, KX022A_MASK_WMI); if (ret) - return ret; + goto err_buf_disable_out; - return __kx022a_turn_on_off(data, true); + ret = __kx022a_turn_on_off(data, true); + if (ret) + goto err_wmi_clear_out; + + return ret; + +err_wmi_clear_out: + regmap_clear_bits(data->regmap, data->ien_reg, + KX022A_MASK_WMI); +err_buf_disable_out: + regmap_clear_bits(data->regmap, data->chip_info->buf_cntl2, + KX022A_MASK_BUF_EN); + data->state &= ~KX022A_STATE_FIFO; +err_turn_on_out: + __kx022a_turn_on_off(data, true); +err_free_out: + kfree(data->fifo_buffer); + + return ret; } static int kx022a_buffer_postenable(struct iio_dev *idev) diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 166fb786425f8d..8994c9e8e048d0 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -1030,7 +1030,7 @@ static int kxcjk1013_write_event_config(struct iio_dev *indio_dev, struct kxcjk1013_data *data = iio_priv(indio_dev); int ret; - if (state && data->ev_enable_state) + if (state == data->ev_enable_state) return 0; mutex_lock(&data->mutex); diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index a92a563f6a4bf0..8f1ac1d3305ed5 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -638,7 +638,7 @@ static int sca3000_write_raw_samp_freq(struct sca3000_state *st, int val) if (val == base_freq / 2) ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_2; - if (val == base_freq / 4) + else if (val == base_freq / 4) ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_4; else if (val != base_freq) return -EINVAL; diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c index e97400a1aa4812..877b06007396f0 100644 --- a/drivers/iio/adc/ad4030.c +++ b/drivers/iio/adc/ad4030.c @@ -746,14 +746,21 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev, static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val) { struct ad4030_state *st = iio_priv(dev); - unsigned int avg_log2 = ilog2(avg_val); unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1; + unsigned int avg_log2; int freq_hz; int ret; - if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx]) + /* Reject unsupported modes */ + if (avg_val > ad4030_average_modes[last_avg_idx]) + return -EINVAL; + + /* Avoid invalid values for logarithm since it's undefined */ + if (avg_val < 1) return -EINVAL; + avg_log2 = ilog2(avg_val); + if (st->offload_trigger) { /* * The sample averaging and sampling frequency configurations diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index eb47175a052870..faebb7b34a1519 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -775,9 +775,9 @@ static int ad7173_load_config(struct ad7173_state *st, return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | - FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, - post_filter_enable) | FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, post_filter_select) | FIELD_PREP(AD7173_FILTER_ORDER, 0) | FIELD_PREP(AD7173_FILTER_ODR_MASK, diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c index 1b410291da5378..119ddc5a13e27c 100644 --- a/drivers/iio/adc/ad_sigma_delta.c +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -498,7 +498,6 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type; struct spi_transfer *xfer = sigma_delta->sample_xfer; unsigned int i, slot, channel; - u8 *samples_buf; int ret; if (sigma_delta->num_slots == 1) { @@ -530,7 +529,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) xfer[1].bits_per_word = scan_type->realbits; xfer[1].len = spi_bpw_to_bytes(scan_type->realbits); } else { - unsigned int samples_buf_size, scan_size; + unsigned int scan_size; if (sigma_delta->active_slots > 1) { ret = ad_sigma_delta_append_status(sigma_delta, true); @@ -538,17 +537,6 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) return ret; } - samples_buf_size = - ALIGN(slot * BITS_TO_BYTES(scan_type->storagebits), - sizeof(s64)); - samples_buf_size += sizeof(s64); - samples_buf = devm_krealloc(&sigma_delta->spi->dev, - sigma_delta->samples_buf, - samples_buf_size, GFP_KERNEL); - if (!samples_buf) - return -ENOMEM; - - sigma_delta->samples_buf = samples_buf; scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); /* For 24-bit data, there is an extra byte of padding. */ xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0]; @@ -855,6 +843,23 @@ int devm_ad_sd_setup_buffer_and_trigger(struct device *dev, struct iio_dev *indi indio_dev->setup_ops = &ad_sd_buffer_setup_ops; } else { + const struct iio_scan_type *scan_type = + &indio_dev->channels[0].scan_type; + unsigned int samples_buf_size; + + /* + * Worst-case size: all sequencer slots can be active, capped + * at num_slots by ad_sd_validate_scan_mask(). + */ + samples_buf_size = + ALIGN(sigma_delta->num_slots * + BITS_TO_BYTES(scan_type->storagebits), + sizeof(s64)); + samples_buf_size += sizeof(s64); + sigma_delta->samples_buf = devm_kzalloc(dev, samples_buf_size, GFP_KERNEL); + if (!sigma_delta->samples_buf) + return -ENOMEM; + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, &ad_sd_trigger_handler, diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index c6c3ea953feaee..4fc0eb7e70a887 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -219,7 +219,7 @@ #define ADE9000_ST1_ERROR2_BIT BIT(30) #define ADE9000_ST1_ERROR3_BIT BIT(31) #define ADE9000_ST1_CROSSING_FIRST 6 -#define ADE9000_ST1_CROSSING_DEPTH 25 +#define ADE9000_ST1_CROSSING_DEPTH 26 #define ADE9000_WFB_TRG_DIP_BIT BIT(0) #define ADE9000_WFB_TRG_SWELL_BIT BIT(1) @@ -444,7 +444,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { BIT(IIO_CHAN_INFO_CALIBSCALE), \ .event_spec = ade9000_current_events, \ .num_event_specs = ARRAY_SIZE(ade9000_current_events), \ - .scan_index = num, \ + .scan_index = num * 2, \ .indexed = 1, \ .scan_type = { \ .sign = 's', \ @@ -464,7 +464,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { BIT(IIO_CHAN_INFO_FREQUENCY), \ .event_spec = ade9000_voltage_events, \ .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \ - .scan_index = num + 1, /* interleave with current channels */ \ + .scan_index = num * 2 + 1, /* interleave with current channels */ \ .indexed = 1, \ .scan_type = { \ .sign = 's', \ @@ -1647,8 +1647,9 @@ static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) return 0; /* CLKOUT passes through CLKIN with divider of 1 */ - clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin), - CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL); + clkout_hw = devm_clk_hw_register_fixed_factor(dev, "clkout", + __clk_get_name(st->clkin), + CLK_SET_RATE_PARENT, 1, 1); if (IS_ERR(clkout_hw)) return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout"); @@ -1705,18 +1706,6 @@ static int ade9000_probe(struct spi_device *spi) if (ret) return ret; - ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); - if (ret) - return ret; - - ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); - if (ret) - return ret; - - ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); - if (ret) - return ret; - /* External CMOS clock input (optional - crystal can be used instead) */ st->clkin = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(st->clkin)) @@ -1736,6 +1725,19 @@ static int ade9000_probe(struct spi_device *spi) return dev_err_probe(&spi->dev, ret, "Failed to get and enable vdd regulator\n"); + /* Request IRQs only after powering the chip; the handlers do SPI access. */ + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + indio_dev->channels = ade9000_channels; indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 589f03618ecf3a..fc7cc92b35f0ac 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -714,6 +714,10 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (!st) return -ENOMEM; + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index 6169ce28261e12..452c30cf04f284 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -585,7 +585,9 @@ static int aspeed_adc_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(data->rst), "invalid or missing reset controller device tree entry"); - reset_control_deassert(data->rst); + ret = reset_control_deassert(data->rst); + if (ret) + return ret; ret = devm_add_action_or_reset(dev, aspeed_adc_reset_assert, data->rst); if (ret) diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c index c8283279c47756..c98863d612ade8 100644 --- a/drivers/iio/adc/axp288_adc.c +++ b/drivers/iio/adc/axp288_adc.c @@ -207,6 +207,14 @@ static const struct dmi_system_id axp288_adc_ts_bias_override[] = { }, .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, }, + { + /* Haier HV103H */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Haier"), + DMI_MATCH(DMI_PRODUCT_NAME, "HV103H"), + }, + .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, + }, { } }; diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 65a2d92bb1124c..629c53af6472ec 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -404,6 +404,14 @@ static int max1363_read_single_chan(struct iio_dev *indio_dev, data = rxbuf[0]; } + + /* + * Differential channels are bipolar and the device returns the sample + * in two's complement, so sign-extend it from the resolution bit. + */ + if (chan->differential) + data = sign_extend32(data, st->chip_info->bits - 1); + *val = data; return 0; diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 23055405a6e0cb..de59dc27c44059 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -1108,6 +1108,10 @@ static int pac1934_acpi_parse_channel_config(struct i2c_client *client, devm_kmemdup(dev, rez->package.elements[i].string.pointer, (size_t)rez->package.elements[i].string.length + 1, GFP_KERNEL); + if (!info->labels[idx]) { + ACPI_FREE(rez); + return -ENOMEM; + } info->labels[idx][rez->package.elements[i].string.length] = '\0'; info->shunts[idx] = rez->package.elements[i + 1].integer.value * 1000; info->active_channels[idx] = (info->shunts[idx] != 0); diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index ed542728896199..8091a138cca855 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -200,7 +200,7 @@ static int bd79124gpo_set_multiple(struct gpio_chip *gc, unsigned long *mask, if (ret) return ret; - if (all_gpos ^ *mask) { + if (*mask & ~all_gpos) { dev_dbg(data->dev, "Invalid mux config. Can't set value.\n"); return -EINVAL; @@ -381,6 +381,9 @@ static int bd79124_start_measurement(struct bd79124_data *data, int chan) /* See if already started */ ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &val); + if (ret) + return ret; + if (val & BIT(chan)) return 0; @@ -420,11 +423,16 @@ static int bd79124_stop_measurement(struct bd79124_data *data, int chan) /* See if already stopped */ ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &enabled_chans); + if (ret) + return ret; + if (!(enabled_chans & BIT(chan))) return 0; ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, BD79124_MSK_SEQ_START); + if (ret) + return ret; /* Clear the channel from the measured channels */ enabled_chans &= ~BIT(chan); @@ -524,7 +532,7 @@ static int bd79124_enable_event(struct bd79124_data *data, return ret; if (dir == IIO_EV_DIR_RISING) { - limit = &data->alarm_f_limit[channel]; + limit = &data->alarm_r_limit[channel]; reg = BD79124_GET_HIGH_LIMIT_REG(channel); } else { limit = &data->alarm_f_limit[channel]; @@ -919,13 +927,13 @@ static int bd79124_chan_init(struct bd79124_data *data, int channel) { int ret; - ret = regmap_write(data->map, BD79124_GET_HIGH_LIMIT_REG(channel), - BD79124_HIGH_LIMIT_MAX); + ret = bd79124_write_int_to_reg(data, BD79124_GET_HIGH_LIMIT_REG(channel), + BD79124_HIGH_LIMIT_MAX); if (ret) return ret; - return regmap_write(data->map, BD79124_GET_LOW_LIMIT_REG(channel), - BD79124_LOW_LIMIT_MIN); + return bd79124_write_int_to_reg(data, BD79124_GET_LOW_LIMIT_REG(channel), + BD79124_LOW_LIMIT_MIN); } static int bd79124_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 5c6c06b269be6e..90f0e257e6d1b6 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -1608,11 +1608,16 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev, ret = stm32_adc_single_conv(indio_dev, chan, val); else ret = -EINVAL; + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; - if (mask == IIO_CHAN_INFO_PROCESSED) + if (mask == IIO_CHAN_INFO_PROCESSED) { + if (*val == 0) + return -EINVAL; *val = STM32_ADC_VREFINT_VOLTAGE * adc->vrefint.vrefint_cal / *val; + } - iio_device_release_direct(indio_dev); return ret; case IIO_CHAN_INFO_SCALE: @@ -2263,33 +2268,37 @@ static int stm32_adc_populate_int_ch(struct iio_dev *indio_dev, const char *ch_n for (i = 0; i < STM32_ADC_INT_CH_NB; i++) { if (!strncmp(stm32_adc_ic[i].name, ch_name, STM32_ADC_CH_SZ)) { + bool na; + /* Check internal channel availability */ switch (i) { case STM32_ADC_INT_CH_VDDCORE: - if (!adc->cfg->regs->or_vddcore.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddcore.reg; break; case STM32_ADC_INT_CH_VDDCPU: - if (!adc->cfg->regs->or_vddcpu.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddcpu.reg; break; case STM32_ADC_INT_CH_VDDQ_DDR: - if (!adc->cfg->regs->or_vddq_ddr.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddq_ddr.reg; break; case STM32_ADC_INT_CH_VREFINT: - if (!adc->cfg->regs->ccr_vref.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->ccr_vref.reg; break; case STM32_ADC_INT_CH_VBAT: - if (!adc->cfg->regs->ccr_vbat.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->ccr_vbat.reg; break; + default: + return -EINVAL; + } + + if (na) { + /* + * Channel label matches an internal STM32 ADC channel. + * Warn about it, as there's normally no restriction on the + * name but that's not among available internal channels. + */ + dev_warn(&indio_dev->dev, "no %s internal channel\n", ch_name); + return 0; } if (stm32_adc_ic[i].idx != STM32_ADC_INT_CH_VREFINT) { diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c index 203459ca99079f..ac5ba76b979e6d 100644 --- a/drivers/iio/adc/sun4i-gpadc-iio.c +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -638,10 +638,10 @@ static int sun4i_gpadc_probe(struct platform_device *pdev) * thermal because no thermal DT node is found. */ if (IS_ERR(info->tzd) && PTR_ERR(info->tzd) != -ENODEV) { - dev_err(&pdev->dev, - "could not register thermal sensor: %ld\n", - PTR_ERR(info->tzd)); - return PTR_ERR(info->tzd); + ret = dev_err_probe(&pdev->dev, + PTR_ERR(info->tzd), + "could not register thermal sensor\n"); + goto err_map; } } @@ -657,7 +657,6 @@ static int sun4i_gpadc_probe(struct platform_device *pdev) if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) iio_map_array_unregister(indio_dev); - pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); return ret; @@ -668,7 +667,6 @@ static void sun4i_gpadc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct sun4i_gpadc_iio *info = iio_priv(indio_dev); - pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ENABLED(CONFIG_THERMAL_OF)) diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index cab66bb8cc1cca..66202b3a3abbc1 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -1401,13 +1401,14 @@ static int xadc_probe(struct platform_device *pdev) } if (irq > 0) { - ret = devm_request_irq(dev, irq, xadc->ops->interrupt_handler, - 0, dev_name(dev), indio_dev); + /* Clear out any work queued by the interrupt */ + ret = devm_add_action_or_reset(dev, xadc_cancel_delayed_work, + &xadc->zynq_unmask_work); if (ret) return ret; - ret = devm_add_action_or_reset(dev, xadc_cancel_delayed_work, - &xadc->zynq_unmask_work); + ret = devm_request_irq(dev, irq, xadc->ops->interrupt_handler, + 0, dev_name(dev), indio_dev); if (ret) return ret; } diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 1bd82d3db5ea5c..31ed2e3e7f52be 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -104,10 +104,13 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (block->sg_table) { unsigned long flags; - sgl = block->sg_table->sgl; - nents = sg_nents_for_len(sgl, block->bytes_used); - if (nents < 0) - return nents; + /* + * Only the first sgt->nents entries carry a valid + * sg_dma_address()/sg_dma_len() pair as mapping the table may + * have coalesced entries, in which case nents is smaller than + * orig_nents. + */ + nents = block->sg_table->nents; vecs = kmalloc_objs(*vecs, nents, GFP_ATOMIC); if (!vecs) @@ -115,7 +118,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, len_total = block->bytes_used; - for (i = 0; i < nents; i++) { + sgl = block->sg_table->sgl; + for (i = 0; i < nents && len_total; i++) { vecs[i].addr = sg_dma_address(sgl); vecs[i].len = min(sg_dma_len(sgl), len_total); len_total -= vecs[i].len; @@ -123,6 +127,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, sgl = sg_next(sgl); } + nents = i; + if (block->cyclic) flags = DMA_PREP_REPEAT; else diff --git a/drivers/iio/cdc/ad7150.c b/drivers/iio/cdc/ad7150.c index 2f35c6d2f9cee5..b36ac4e2dae800 100644 --- a/drivers/iio/cdc/ad7150.c +++ b/drivers/iio/cdc/ad7150.c @@ -636,11 +636,13 @@ static const struct i2c_device_id ad7150_id[] = { MODULE_DEVICE_TABLE(i2c, ad7150_id); static const struct of_device_id ad7150_of_match[] = { - { "adi,ad7150" }, - { "adi,ad7151" }, - { "adi,ad7156" }, + { .compatible = "adi,ad7150" }, + { .compatible = "adi,ad7151" }, + { .compatible = "adi,ad7156" }, { } }; +MODULE_DEVICE_TABLE(of, ad7150_of_match); + static struct i2c_driver ad7150_driver = { .driver = { .name = "ad7150", diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c index 88a82d1370c504..8c9e81a77e6887 100644 --- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -167,11 +167,16 @@ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, valid = inv_validate_period(ts, period); } - /* if interrupt interval is valid, sync with interrupt timestamp */ + /* + * If interrupt interval is valid, sync with interrupt timestamp. + * Otherwise, use estimated value while ensuring interrupt timestamp + * remains the maximum possible value. + */ + period = inv_align_timestamp_it(ts, sample_nb); if (valid) - ts->period = inv_align_timestamp_it(ts, sample_nb); + ts->period = period; else - ts->period = ts->mult * ts->chip_period.val; + ts->period = min(ts->mult * ts->chip_period.val, period); } EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, "IIO_INV_SENSORS_TIMESTAMP"); diff --git a/drivers/iio/dac/mcp47a1.c b/drivers/iio/dac/mcp47a1.c index 0bf994aa0e4f58..3ed306f3060f81 100644 --- a/drivers/iio/dac/mcp47a1.c +++ b/drivers/iio/dac/mcp47a1.c @@ -26,7 +26,7 @@ struct mcp47a1_data { int vref_mV; }; -static const int mcp47a1_raw_avail[] = { 0, 1, MCP47A1_MAX_STEPS - 1 }; +static const int mcp47a1_raw_avail[] = { 0, 1, MCP47A1_MAX_STEPS }; static const struct iio_chan_spec mcp47a1_channel = { .type = IIO_VOLTAGE, @@ -46,7 +46,7 @@ static int mcp47a1_write(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (!in_range(val, 0, MCP47A1_MAX_STEPS)) + if (!in_range(val, 0, MCP47A1_MAX_STEPS + 1)) return -EINVAL; return i2c_smbus_write_byte_data(data->client, MCP47A1_CMD_CODE, diff --git a/drivers/iio/dac/rohm-bd79703.c b/drivers/iio/dac/rohm-bd79703.c index 6e2e7787f8a472..127ccc2c4a2293 100644 --- a/drivers/iio/dac/rohm-bd79703.c +++ b/drivers/iio/dac/rohm-bd79703.c @@ -73,6 +73,9 @@ static int bd79703_write_raw(struct iio_dev *idev, { struct bd79703_data *data = iio_priv(idev); + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + if (val < 0 || val >= 1 << BD79703_DAC_BITS) return -EINVAL; diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 4dd19a9aa9943c..7385be62fa6a32 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -988,10 +988,10 @@ static const struct clk_ops adf4377_clk_ops = { static int adf4377_clk_register(struct adf4377_state *st) { + struct clk_parent_data parent_data = { }; struct spi_device *spi = st->spi; struct device *dev = &spi->dev; - struct clk_init_data init; - struct clk_parent_data parent_data; + struct clk_init_data init = { }; int ret; if (!device_property_present(dev, "#clock-cells")) diff --git a/drivers/iio/frequency/admv1013.c b/drivers/iio/frequency/admv1013.c index c9baaab574e3f3..078ea3e5aa0301 100644 --- a/drivers/iio/frequency/admv1013.c +++ b/drivers/iio/frequency/admv1013.c @@ -195,7 +195,7 @@ static int admv1013_read_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_CALIBBIAS: - switch (chan->channel) { + switch (chan->channel2) { case IIO_MOD_I: addr = ADMV1013_REG_OFFSET_ADJUST_I; break; @@ -601,11 +601,14 @@ static int admv1013_probe(struct spi_device *spi) "failed to get the LO input clock\n"); st->nb.notifier_call = admv1013_freq_change; - ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + + ret = devm_mutex_init(dev, &st->lock); if (ret) return ret; - mutex_init(&st->lock); + ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + if (ret) + return ret; ret = admv1013_init(st, vcm_uv); if (ret) diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c index ff335f81e9ae98..b687588ada03bd 100644 --- a/drivers/iio/gyro/adis16136.c +++ b/drivers/iio/gyro/adis16136.c @@ -145,7 +145,7 @@ static int adis16136_debugfs_init(struct iio_dev *indio_dev) struct adis16136 *adis16136 = iio_priv(indio_dev); struct dentry *d = iio_get_debugfs_dentry(indio_dev); - debugfs_create_file_unsafe("serial_number", 0400, + debugfs_create_file("serial_number", 0400, d, adis16136, &adis16136_serial_fops); debugfs_create_file_unsafe("product_id", 0400, d, adis16136, &adis16136_product_id_fops); diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index c37316c86f140a..aee96167f01e4e 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -290,9 +290,15 @@ static irqreturn_t max30102_interrupt_handler(int irq, void *private) { struct iio_dev *indio_dev = private; struct max30102_data *data = iio_priv(indio_dev); - unsigned int measurements = bitmap_weight(indio_dev->active_scan_mask, - iio_get_masklength(indio_dev)); - int ret, cnt = 0; + unsigned int measurements; + int ret, cnt; + + cnt = max30102_fifo_count(data); + if (cnt <= 0) + return IRQ_HANDLED; + + measurements = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); mutex_lock(&data->lock); diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index 4842346f9f0c0e..8f664d055e9d56 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -280,7 +280,7 @@ static void adis16400_debugfs_init(struct iio_dev *indio_dev) return; if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) - debugfs_create_file_unsafe("serial_number", 0400, + debugfs_create_file("serial_number", 0400, d, st, &adis16400_serial_number_fops); if (st->variant->flags & ADIS16400_HAS_PROD_ID) debugfs_create_file_unsafe("product_id", 0400, diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c index 5187566c18763c..d51786874e74c4 100644 --- a/drivers/iio/imu/adis16480.c +++ b/drivers/iio/imu/adis16480.c @@ -309,9 +309,9 @@ static void adis16480_debugfs_init(struct iio_dev *indio_dev) if (!IS_ENABLED(CONFIG_DEBUG_FS)) return; - debugfs_create_file_unsafe("firmware_revision", 0400, + debugfs_create_file("firmware_revision", 0400, d, adis16480, &adis16480_firmware_revision_fops); - debugfs_create_file_unsafe("firmware_date", 0400, + debugfs_create_file("firmware_date", 0400, d, adis16480, &adis16480_firmware_date_fops); debugfs_create_file_unsafe("serial_number", 0400, d, adis16480, &adis16480_serial_number_fops); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 190e998f7b8ef2..f4ef75da22c762 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -537,9 +537,8 @@ static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st) return 0; } -static void inv_icm42607_sensors_off(void *_data) +static int inv_icm42607_sensors_off(struct inv_icm42607_state *st) { - struct inv_icm42607_state *st = _data; const struct device *dev = regmap_get_device(st->map); int ret; @@ -552,6 +551,13 @@ static void inv_icm42607_sensors_off(void *_data) st->conf.accel.mode); if (ret) dev_err(dev, "Unable to turn off sensors\n"); + + return ret; +} + +static void inv_icm42607_sensors_off_action(void *data) +{ + inv_icm42607_sensors_off(data); } static void inv_icm42607_disable_vddio_reg(void *_data) @@ -619,7 +625,7 @@ int inv_icm42607_core_probe(struct regmap *regmap, * Ensure if sensors get turned on at some point, they're turned off * as part of teardown. */ - ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off, st); + ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off_action, st); if (ret) return ret; @@ -658,9 +664,8 @@ static int inv_icm42607_suspend(struct device *dev) return 0; } -static int inv_icm42607_resume(struct device *dev) +static int inv_icm42607_resume_core(struct inv_icm42607_state *st) { - struct inv_icm42607_state *st = dev_get_drvdata(dev); int ret; ret = inv_icm42607_enable_vddio_reg(st); @@ -669,9 +674,25 @@ static int inv_icm42607_resume(struct device *dev) /* Sync the regcache again after regulator shutdown. */ regcache_mark_dirty(st->map); - ret = regcache_sync(st->map); - if (ret) + + return regcache_sync(st->map); +} + +static int inv_icm42607_resume(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + int ret; + + ret = inv_icm42607_resume_core(st); + if (ret) { + int rc; + + rc = pm_runtime_force_resume(dev); + if (rc) + dev_warn(dev, "Failed to restore runtime PM state: %d\n", rc); + return ret; + } return pm_runtime_force_resume(dev); } @@ -688,8 +709,7 @@ static int inv_icm42607_runtime_suspend(struct device *dev) * however the tradeoff is that an unused sensor won't be * turned off until the entire chip is no longer in use. */ - inv_icm42607_sensors_off(st); - return 0; + return inv_icm42607_sensors_off(st); } EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42607_pm_ops, IIO_ICM42607) = { diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 2c9ec93dff475e..4294101810fd71 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -1377,6 +1377,10 @@ EXPORT_SYMBOL_GPL(iio_update_buffers); void iio_disable_all_buffers(struct iio_dev *indio_dev) { + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + guard(mutex)(&iio_dev_opaque->mlock); + iio_disable_buffers(indio_dev); iio_buffer_deactivate_all(indio_dev); } @@ -2462,7 +2466,8 @@ int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev, bb = devm_krealloc(&indio_dev->dev, iio_dev_opaque->bounce_buffer, - indio_dev->scan_bytes, GFP_KERNEL); + indio_dev->scan_bytes, + GFP_KERNEL | __GFP_ZERO); if (!bb) return -ENOMEM; iio_dev_opaque->bounce_buffer = bb; diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c index 17781c12bc858e..76926fee0f84d9 100644 --- a/drivers/iio/industrialio-trigger.c +++ b/drivers/iio/industrialio-trigger.c @@ -509,6 +509,8 @@ static void iio_trig_release(struct device *device) struct iio_trigger *trig = to_iio_trigger(device); int i; + cancel_work_sync(&trig->reenable_work); + if (trig->subirq_base) { for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { irq_modify_status(trig->subirq_base + i, diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c index 63591b7ecb892d..b435dc5e8706f0 100644 --- a/drivers/iio/light/gp2ap020a00f.c +++ b/drivers/iio/light/gp2ap020a00f.c @@ -1497,6 +1497,7 @@ static int gp2ap020a00f_probe(struct i2c_client *client) iio_trigger_unregister(data->trig); error_free_irq: free_irq(client->irq, indio_dev); + irq_work_sync(&data->work); error_uninit_buffer: iio_triggered_buffer_cleanup(indio_dev); error_regulator_disable: @@ -1519,6 +1520,7 @@ static void gp2ap020a00f_remove(struct i2c_client *client) iio_device_unregister(indio_dev); iio_trigger_unregister(data->trig); free_irq(client->irq, indio_dev); + irq_work_sync(&data->work); iio_triggered_buffer_cleanup(indio_dev); regulator_disable(data->vled_reg); } diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c index 28d111ac8c0a7b..92fca33387bbf2 100644 --- a/drivers/iio/light/rohm-bu27034.c +++ b/drivers/iio/light/rohm-bu27034.c @@ -137,6 +137,7 @@ static const struct iio_gain_sel_pair bu27034_gains[] = { #define BU27034_MEAS_MODE_200MS 2 #define BU27034_MEAS_MODE_400MS 4 +#define BU27034_INT_TIME_US_MIN (55 * USEC_PER_MSEC) static const struct iio_itime_sel_mul bu27034_itimes[] = { GAIN_SCALE_ITIME_US(400000, BU27034_MEAS_MODE_400MS, 8), GAIN_SCALE_ITIME_US(200000, BU27034_MEAS_MODE_200MS, 4), @@ -296,7 +297,7 @@ static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain) return 0; } -static int bu27034_get_int_time(struct bu27034_data *data) +static int bu27034_get_int_time(struct bu27034_data *data, int *itime) { int ret, sel; @@ -304,24 +305,30 @@ static int bu27034_get_int_time(struct bu27034_data *data) if (ret) return ret; - return iio_gts_find_int_time_by_sel(&data->gts, - sel & BU27034_MASK_MEAS_MODE); + ret = iio_gts_find_int_time_by_sel(&data->gts, + sel & BU27034_MASK_MEAS_MODE); + if (ret < 0) + return ret; + + *itime = ret; + + return 0; } static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val, int *val2) { - int gain, ret; + int gain, itime, ret; ret = bu27034_get_gain(data, channel, &gain); if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &itime); + if (ret) return ret; - return iio_gts_get_scale(&data->gts, gain, ret, val, val2); + return iio_gts_get_scale(&data->gts, gain, itime, val, val2); } static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val, @@ -397,12 +404,10 @@ static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) int ret, int_time_old, i; guard(mutex)(&data->mutex); - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &int_time_old); + if (ret) return ret; - int_time_old = ret; - if (!iio_gts_valid_time(&data->gts, time_us)) { dev_err(data->dev, "Unsupported integration time %u\n", time_us); @@ -415,7 +420,7 @@ static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) for (i = 0; i < numg; i++) { ret = bu27034_get_gain(data, gains[i].chan, &gains[i].old_gain); if (ret) - return 0; + return ret; ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, gains[i].old_gain, @@ -841,7 +846,7 @@ static int bu27034_meas_set(struct bu27034_data *data, bool en) static int bu27034_get_single_result(struct bu27034_data *data, int chan, int *val) { - int ret; + int ret, itime; if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA1) return -EINVAL; @@ -850,11 +855,11 @@ static int bu27034_get_single_result(struct bu27034_data *data, int chan, if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &itime); + if (ret) return ret; - msleep(ret / 1000); + msleep(itime / 1000); return bu27034_read_result(data, chan, val); } @@ -904,12 +909,10 @@ static int bu27034_calc_mlux(struct bu27034_data *data, __le16 *res, int *val) if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &meastime); + if (ret) return ret; - meastime = ret; - d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100; helper64 = (u64)ch1 * (u64)gain0 * 100LLU; @@ -970,9 +973,9 @@ static int bu27034_read_raw(struct iio_dev *idev, switch (mask) { case IIO_CHAN_INFO_INT_TIME: *val = 0; - *val2 = bu27034_get_int_time(data); - if (*val2 < 0) - return *val2; + ret = bu27034_get_int_time(data, val2); + if (ret) + return ret; return IIO_VAL_INT_PLUS_MICRO; @@ -1157,11 +1160,20 @@ static int bu27034_buffer_thread(void *arg) { struct iio_dev *idev = arg; struct bu27034_data *data; - int wait_ms; + int wait_ms, ret; data = iio_priv(idev); - wait_ms = bu27034_get_int_time(data); + /* + * If reading the integration time fails, default to the minimum so we + * don't lose samples. This may waste CPU cycles, but as a hardening + * against theoretical, once-in-a-blue-moon error, this should be Ok. + */ + wait_ms = BU27034_INT_TIME_US_MIN; + ret = bu27034_get_int_time(data, &wait_ms); + if (ret) + dev_warn(data->dev, "Failed to get integration time\n"); + wait_ms /= 1000; wait_ms -= BU27034_MEAS_WAIT_PREMATURE_MS; diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index 1066792b908738..3940b8e61a06a1 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -839,7 +839,7 @@ static int bmp280_write_sampling_frequency(struct bmp280_data *data, int val, int val2) { const int (*avail)[2] = data->chip_info->sampling_freq_avail; - const int n = data->chip_info->num_sampling_freq_avail; + const int n = data->chip_info->num_sampling_freq_avail / 2; int ret, prev; int i; diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c index 57941fb4a53593..f9deb48e6a41cf 100644 --- a/drivers/iio/pressure/rohm-bm1390.c +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -289,7 +289,7 @@ static int bm1390_read_data(struct bm1390_data *data, if (warn) dev_warn(data->dev, "Failed to stop measurement (%d)\n", warn); - return 0; + return ret; } static int bm1390_read_raw(struct iio_dev *idev, diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c index 8fbb755dcae040..8352d51e512881 100644 --- a/drivers/iio/proximity/aw96103.c +++ b/drivers/iio/proximity/aw96103.c @@ -24,6 +24,7 @@ #define AW96103_BIN_VALID_DATA_OFFSET 64 #define AW96103_BIN_DATA_LEN_OFFSET 16 #define AW96103_BIN_DATA_REG_NUM_SIZE 4 +#define AW96103_BIN_REG_SIZE 6 #define AW96103_BIN_CHIP_TYPE_SIZE 8 #define AW96103_BIN_CHIP_TYPE_OFFSET 24 @@ -229,14 +230,27 @@ static const struct aw_chip_info aw_chip_info_tbl[] = { }, }; -static void aw96103_parsing_bin_file(struct aw_bin *bin) +static int aw96103_parsing_bin_file(struct aw_bin *bin) { + u32 data_len; + + if (bin->len < AW96103_BIN_VALID_DATA_OFFSET) + return -EINVAL; + + data_len = get_unaligned_le32(bin->data + AW96103_BIN_DATA_LEN_OFFSET); + if (data_len < AW96103_BIN_DATA_REG_NUM_SIZE) + return -EINVAL; + + bin->valid_data_len = data_len - AW96103_BIN_DATA_REG_NUM_SIZE; + if (bin->valid_data_len > bin->len - AW96103_BIN_VALID_DATA_OFFSET || + bin->valid_data_len % AW96103_BIN_REG_SIZE) + return -EINVAL; + bin->valid_data_addr = AW96103_BIN_VALID_DATA_OFFSET; - bin->valid_data_len = - *(unsigned int *)(bin->data + AW96103_BIN_DATA_LEN_OFFSET) - - AW96103_BIN_DATA_REG_NUM_SIZE; memcpy(bin->chip_type, bin->data + AW96103_BIN_CHIP_TYPE_OFFSET, AW96103_BIN_CHIP_TYPE_SIZE); + + return 0; } static const struct regmap_config aw96103_regmap_confg = { @@ -500,7 +514,7 @@ static int aw96103_bin_valid_loaded(struct aw96103 *aw96103, int ret; for (i = 0; i < aw_bin_data_s->valid_data_len; - i += 6, start_addr += 6) { + i += AW96103_BIN_REG_SIZE, start_addr += AW96103_BIN_REG_SIZE) { reg_addr = get_unaligned_le16(aw_bin_data_s->data + start_addr); reg_data = get_unaligned_le32(aw_bin_data_s->data + start_addr + 2); @@ -550,6 +564,8 @@ static int aw96103_para_loaded(struct aw96103 *aw96103) static int aw96103_cfg_all_loaded(const struct firmware *cont, struct aw96103 *aw96103) { + int ret; + if (!cont) return -EINVAL; @@ -561,7 +577,9 @@ static int aw96103_cfg_all_loaded(const struct firmware *cont, aw_bin->len = cont->size; memcpy(aw_bin->data, cont->data, cont->size); release_firmware(cont); - aw96103_parsing_bin_file(aw_bin); + ret = aw96103_parsing_bin_file(aw_bin); + if (ret) + return ret; return aw96103_bin_valid_loaded(aw96103, aw_bin); } diff --git a/drivers/iio/proximity/isl29501.c b/drivers/iio/proximity/isl29501.c index 95fb7238f67887..a98a9975323c60 100644 --- a/drivers/iio/proximity/isl29501.c +++ b/drivers/iio/proximity/isl29501.c @@ -226,7 +226,7 @@ static int isl29501_register_read(struct isl29501_private *isl29501, return ret; } -static u32 isl29501_register_write(struct isl29501_private *isl29501, +static int isl29501_register_write(struct isl29501_private *isl29501, enum isl29501_register_name name, u32 value) { diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 400477b4c740e3..8a9ee21f2bf6aa 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -294,12 +294,14 @@ static int lidar_probe(struct i2c_client *client) ret = pm_runtime_set_active(&client->dev); if (ret) - goto error_unreg_buffer; + goto error_unreg_dev; pm_runtime_enable(&client->dev); pm_runtime_idle(&client->dev); return 0; +error_unreg_dev: + iio_device_unregister(indio_dev); error_unreg_buffer: iio_triggered_buffer_cleanup(indio_dev); diff --git a/drivers/iio/proximity/sx9324.c b/drivers/iio/proximity/sx9324.c index 13b4ef2896e35c..255a36976ac41f 100644 --- a/drivers/iio/proximity/sx9324.c +++ b/drivers/iio/proximity/sx9324.c @@ -222,7 +222,7 @@ static const struct iio_chan_spec_ext_info sx9324_channel_ext_info[] = { .scan_index = idx, \ .scan_type = { \ .sign = 's', \ - .realbits = 12, \ + .realbits = 16, \ .storagebits = 16, \ .endianness = IIO_BE, \ }, \ diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c index 6b0d7a9038d90e..768870127ff874 100644 --- a/drivers/iio/proximity/vcnl3020.c +++ b/drivers/iio/proximity/vcnl3020.c @@ -584,16 +584,26 @@ static irqreturn_t vcnl3020_handle_irq_thread(int irq, void *p) return IRQ_HANDLED; } - if (!(isr & VCNL_ICR_THRES_EN)) + if (!(isr & (VCNL_INT_TH_HI | VCNL_INT_TH_LOW))) return IRQ_NONE; - iio_push_event(indio_dev, - IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_RISING), - iio_get_time_ns(indio_dev)); + if (isr & VCNL_INT_TH_HI) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (isr & VCNL_INT_TH_LOW) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } - rc = regmap_write(data->regmap, VCNL_ISR, isr & VCNL_ICR_THRES_EN); + rc = regmap_write(data->regmap, VCNL_ISR, isr & (VCNL_INT_TH_HI | VCNL_INT_TH_LOW)); if (rc) dev_err(data->dev, "Error (%d) writing in reg (0x%x)\n", rc, VCNL_ISR); diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c index 9fe14ceb8be793..173e69753df7db 100644 --- a/drivers/iio/proximity/vl53l0x-i2c.c +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -219,7 +219,10 @@ static int vl53l0x_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; ret = vl53l0x_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); if (ret < 0) return ret; diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 6406ceab988ade..810e52782ae891 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -302,6 +302,8 @@ static const struct xpad_device { { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b1f, "Corsair Novablade Pro (Wired)", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b2b, "Corsair Novablade Pro (2.4 GHz Receiver)", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0002, "Harmonix Rock Band Guitar", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0003, "Harmonix Rock Band Drumkit", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, { 0x1bad, 0x0130, "Ion Drum Rocker", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, @@ -379,6 +381,7 @@ static const struct xpad_device { { 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE }, { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2dc8, 0x201e, "8BitDo Pro 2 Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, { 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 }, @@ -545,6 +548,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ + XPAD_XBOX360_VENDOR(0x1b1c), /* Corsair controllers */ XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */ XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */ XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */ diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 93650c98206d6b..b5ee5697a13676 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), + }, + .callback = atkbd_deactivate_fixup, + }, { /* Xiaomi Book Pro 14 (TM2424) */ .matches = { diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c index 8db2dca84975ca..3e0f874e9b16fc 100644 --- a/drivers/input/misc/ati_remote2.c +++ b/drivers/input/misc/ati_remote2.c @@ -672,8 +672,7 @@ static ssize_t ati_remote2_show_channel_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%04x\n", ar2->channel_mask); @@ -683,8 +682,7 @@ static ssize_t ati_remote2_store_channel_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int r; @@ -720,8 +718,7 @@ static ssize_t ati_remote2_show_mode_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%02x\n", ar2->mode_mask); @@ -731,8 +728,7 @@ static ssize_t ati_remote2_store_mode_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int err; diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index a8ce89d41faa1a..1ed9261bff2b25 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { "LEN2054", /* E480 */ "LEN2055", /* E580 */ "LEN2058", /* E490 */ + "LEN205b", /* T490 */ "LEN2068", /* T14 Gen 1 */ "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ "SYN3003", /* HP EliteBook 850 G1 */ @@ -1839,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, return -ENXIO; } - /* Disable intertouch on known-broken board revisions */ - if (info->board_id == 2722) { + /* Disable intertouch on known-broken T440p board revisions */ + if (info->board_id == 2722 && + psmouse_matches_pnp_id(psmouse, + (const char * const []){"LEN0036", NULL})) { psmouse_info(psmouse, "Disabling intertouch for board id %u\n", info->board_id); diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 54eb3687000a59..1b1c5d4ac01c9e 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_NOAUX) }, + { + /* Fujitsu LIFEBOOK U7410 */ + /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), + }, + .driver_data = (void *)(SERIO_QUIRK_NOMUX) + }, { /* Gigabyte M912 */ .matches = { diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c index 0f24a9b730635f..899c179fb2d311 100644 --- a/drivers/input/touchscreen/s6sy761.c +++ b/drivers/input/touchscreen/s6sy761.c @@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) static int s6sy761_resume(struct device *dev) { struct s6sy761_data *sdata = dev_get_drvdata(dev); + int err; + + err = s6sy761_power_on(sdata); + if (err) + return err; enable_irq(sdata->client->irq); - return s6sy761_power_on(sdata); + guard(mutex)(&sdata->input->mutex); + + if (!input_device_enabled(sdata->input)) + return 0; + + err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); + + return err; } static const struct dev_pm_ops s6sy761_pm_ops = { diff --git a/drivers/interconnect/qcom/x1e80100.c b/drivers/interconnect/qcom/x1e80100.c index 8075e0ff205973..2ba2823c7860e9 100644 --- a/drivers/interconnect/qcom/x1e80100.c +++ b/drivers/interconnect/qcom/x1e80100.c @@ -173,13 +173,6 @@ static struct qcom_icc_node qhm_qspi = { .name = "qhm_qspi", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -188,13 +181,6 @@ static struct qcom_icc_node qhm_qup1 = { .name = "qhm_qup1", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -203,13 +189,6 @@ static struct qcom_icc_node xm_sdc4 = { .name = "xm_sdc4", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -218,13 +197,6 @@ static struct qcom_icc_node xm_ufs_mem = { .name = "xm_ufs_mem", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xe000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -233,13 +205,6 @@ static struct qcom_icc_node qhm_qup0 = { .name = "qhm_qup0", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x16000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -248,13 +213,6 @@ static struct qcom_icc_node qhm_qup2 = { .name = "qhm_qup2", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x11000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -263,13 +221,6 @@ static struct qcom_icc_node qxm_crypto = { .name = "qxm_crypto", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x12000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -286,13 +237,6 @@ static struct qcom_icc_node xm_qdss_etr_0 = { .name = "xm_qdss_etr_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x13000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -301,13 +245,6 @@ static struct qcom_icc_node xm_qdss_etr_1 = { .name = "xm_qdss_etr_1", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x14000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -316,13 +253,6 @@ static struct qcom_icc_node xm_sdc2 = { .name = "xm_sdc2", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x15000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -407,13 +337,6 @@ static struct qcom_icc_node alm_gpu_tcu = { .name = "alm_gpu_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x271000 }, - .prio = 1, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -422,13 +345,6 @@ static struct qcom_icc_node alm_pcie_tcu = { .name = "alm_pcie_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27d000 }, - .prio = 3, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -437,13 +353,6 @@ static struct qcom_icc_node alm_sys_tcu = { .name = "alm_sys_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x273000 }, - .prio = 6, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -461,13 +370,6 @@ static struct qcom_icc_node qnm_gpu = { .name = "qnm_gpu", .channels = 4, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 4, - .port_offsets = { 0x51000, 0x58000, 0xd1000, 0xd8000 }, - .prio = 0, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -476,13 +378,6 @@ static struct qcom_icc_node qnm_lpass = { .name = "qnm_lpass", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x275000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -492,13 +387,6 @@ static struct qcom_icc_node qnm_mnoc_hf = { .name = "qnm_mnoc_hf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x53000, 0xd3000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -507,13 +395,6 @@ static struct qcom_icc_node qnm_mnoc_sf = { .name = "qnm_mnoc_sf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x55000, 0xd5000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -522,13 +403,6 @@ static struct qcom_icc_node qnm_nsp_noc = { .name = "qnm_nsp_noc", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x57000, 0xd7000 }, - .prio = 0, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -538,13 +412,6 @@ static struct qcom_icc_node qnm_pcie = { .name = "qnm_pcie", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x277000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -553,13 +420,6 @@ static struct qcom_icc_node qnm_snoc_sf = { .name = "qnm_snoc_sf", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27b000 }, - .prio = 2, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -569,13 +429,6 @@ static struct qcom_icc_node xm_gic = { .name = "xm_gic", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27f000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_llcc }, }; @@ -616,13 +469,6 @@ static struct qcom_icc_node qnm_av1_enc = { .name = "qnm_av1_enc", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x2f000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -631,13 +477,6 @@ static struct qcom_icc_node qnm_camnoc_hf = { .name = "qnm_camnoc_hf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x28000, 0x29000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_hf }, }; @@ -646,13 +485,6 @@ static struct qcom_icc_node qnm_camnoc_icp = { .name = "qnm_camnoc_icp", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x2a000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -661,13 +493,6 @@ static struct qcom_icc_node qnm_camnoc_sf = { .name = "qnm_camnoc_sf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x2b000, 0x2c000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -676,13 +501,6 @@ static struct qcom_icc_node qnm_eva = { .name = "qnm_eva", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x33000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -691,13 +509,6 @@ static struct qcom_icc_node qnm_mdp = { .name = "qnm_mdp", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x2d000, 0x2e000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_hf }, }; @@ -706,13 +517,6 @@ static struct qcom_icc_node qnm_video = { .name = "qnm_video", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x30000, 0x31000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -721,13 +525,6 @@ static struct qcom_icc_node qnm_video_cv_cpu = { .name = "qnm_video_cv_cpu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x32000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -736,13 +533,6 @@ static struct qcom_icc_node qnm_video_v_cpu = { .name = "qnm_video_v_cpu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x34000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -783,13 +573,6 @@ static struct qcom_icc_node xm_pcie_3 = { .name = "xm_pcie_3", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x7000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -798,13 +581,6 @@ static struct qcom_icc_node xm_pcie_4 = { .name = "xm_pcie_4", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x8000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -813,13 +589,6 @@ static struct qcom_icc_node xm_pcie_5 = { .name = "xm_pcie_5", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x9000 }, - .prio = 3, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -828,13 +597,6 @@ static struct qcom_icc_node xm_pcie_0 = { .name = "xm_pcie_0", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x9000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -843,13 +605,6 @@ static struct qcom_icc_node xm_pcie_1 = { .name = "xm_pcie_1", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xa000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -858,13 +613,6 @@ static struct qcom_icc_node xm_pcie_2 = { .name = "xm_pcie_2", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -873,13 +621,6 @@ static struct qcom_icc_node xm_pcie_6a = { .name = "xm_pcie_6a", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -888,13 +629,6 @@ static struct qcom_icc_node xm_pcie_6b = { .name = "xm_pcie_6b", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -919,13 +653,6 @@ static struct qcom_icc_node qnm_gic = { .name = "qnm_gic", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x1c000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_gemnoc_sf }, }; @@ -958,13 +685,6 @@ static struct qcom_icc_node xm_usb2_0 = { .name = "xm_usb2_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x6000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_north_snoc }, }; @@ -973,13 +693,6 @@ static struct qcom_icc_node xm_usb3_mp = { .name = "xm_usb3_mp", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x7000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_north_snoc }, }; @@ -988,13 +701,6 @@ static struct qcom_icc_node xm_usb3_0 = { .name = "xm_usb3_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xa000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1003,13 +709,6 @@ static struct qcom_icc_node xm_usb3_1 = { .name = "xm_usb3_1", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1018,13 +717,6 @@ static struct qcom_icc_node xm_usb3_2 = { .name = "xm_usb3_2", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1033,13 +725,6 @@ static struct qcom_icc_node xm_usb4_0 = { .name = "xm_usb4_0", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1048,13 +733,6 @@ static struct qcom_icc_node xm_usb4_1 = { .name = "xm_usb4_1", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xe000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1063,13 +741,6 @@ static struct qcom_icc_node xm_usb4_2 = { .name = "xm_usb4_2", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xf000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1795,21 +1466,11 @@ static struct qcom_icc_node * const aggre1_noc_nodes[] = { [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, }; -static const struct regmap_config x1e80100_aggre1_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x14400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_aggre1_noc = { - .config = &x1e80100_aggre1_noc_regmap_config, .nodes = aggre1_noc_nodes, .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), .bcms = aggre1_noc_bcms, .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), - .qos_requires_clocks = true, }; static struct qcom_icc_bcm * const aggre2_noc_bcms[] = { @@ -1827,16 +1488,7 @@ static struct qcom_icc_node * const aggre2_noc_nodes[] = { [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, }; -static const struct regmap_config x1e80100_aggre2_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x1c400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_aggre2_noc = { - .config = &x1e80100_aggre2_noc_regmap_config, .nodes = aggre2_noc_nodes, .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), .bcms = aggre2_noc_bcms, @@ -1921,16 +1573,7 @@ static struct qcom_icc_node * const cnoc_cfg_nodes[] = { [SLAVE_TCU] = &xs_sys_tcu_cfg, }; -static const struct regmap_config x1e80100_cnoc_cfg_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x6600, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_cnoc_cfg = { - .config = &x1e80100_cnoc_cfg_regmap_config, .nodes = cnoc_cfg_nodes, .num_nodes = ARRAY_SIZE(cnoc_cfg_nodes), .bcms = cnoc_cfg_bcms, @@ -1960,16 +1603,7 @@ static struct qcom_icc_node * const cnoc_main_nodes[] = { [SLAVE_PCIE_6B] = &xs_pcie_6b, }; -static const struct regmap_config x1e80100_cnoc_main_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x14400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_cnoc_main = { - .config = &x1e80100_cnoc_main_regmap_config, .nodes = cnoc_main_nodes, .num_nodes = ARRAY_SIZE(cnoc_main_nodes), .bcms = cnoc_main_bcms, @@ -1999,16 +1633,7 @@ static struct qcom_icc_node * const gem_noc_nodes[] = { [SLAVE_MEM_NOC_PCIE_SNOC] = &qns_pcie, }; -static const struct regmap_config x1e80100_gem_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x311200, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_gem_noc = { - .config = &x1e80100_gem_noc_regmap_config, .nodes = gem_noc_nodes, .num_nodes = ARRAY_SIZE(gem_noc_nodes), .bcms = gem_noc_bcms, @@ -2023,16 +1648,7 @@ static struct qcom_icc_node * const lpass_ag_noc_nodes[] = { [SLAVE_LPASS_GEM_NOC] = &qns_lpass_ag_noc_gemnoc, }; -static const struct regmap_config x1e80100_lpass_ag_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xe080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_ag_noc = { - .config = &x1e80100_lpass_ag_noc_regmap_config, .nodes = lpass_ag_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_ag_noc_nodes), .bcms = lpass_ag_noc_bcms, @@ -2048,16 +1664,7 @@ static struct qcom_icc_node * const lpass_lpiaon_noc_nodes[] = { [SLAVE_LPIAON_NOC_LPASS_AG_NOC] = &qns_lpass_aggnoc, }; -static const struct regmap_config x1e80100_lpass_lpiaon_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x19080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_lpiaon_noc = { - .config = &x1e80100_lpass_lpiaon_noc_regmap_config, .nodes = lpass_lpiaon_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_lpiaon_noc_nodes), .bcms = lpass_lpiaon_noc_bcms, @@ -2072,16 +1679,7 @@ static struct qcom_icc_node * const lpass_lpicx_noc_nodes[] = { [SLAVE_LPICX_NOC_LPIAON_NOC] = &qns_lpi_aon_noc, }; -static const struct regmap_config x1e80100_lpass_lpicx_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x3a200, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_lpicx_noc = { - .config = &x1e80100_lpass_lpicx_noc_regmap_config, .nodes = lpass_lpicx_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_lpicx_noc_nodes), .bcms = lpass_lpicx_noc_bcms, @@ -2126,16 +1724,7 @@ static struct qcom_icc_node * const mmss_noc_nodes[] = { [SLAVE_SERVICE_MNOC] = &srvc_mnoc, }; -static const struct regmap_config x1e80100_mmss_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x5b800, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_mmss_noc = { - .config = &x1e80100_mmss_noc_regmap_config, .nodes = mmss_noc_nodes, .num_nodes = ARRAY_SIZE(mmss_noc_nodes), .bcms = mmss_noc_bcms, @@ -2151,16 +1740,7 @@ static struct qcom_icc_node * const nsp_noc_nodes[] = { [SLAVE_CDSP_MEM_NOC] = &qns_nsp_gemnoc, }; -static const struct regmap_config x1e80100_nsp_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xe080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_nsp_noc = { - .config = &x1e80100_nsp_noc_regmap_config, .nodes = nsp_noc_nodes, .num_nodes = ARRAY_SIZE(nsp_noc_nodes), .bcms = nsp_noc_bcms, @@ -2177,16 +1757,7 @@ static struct qcom_icc_node * const pcie_center_anoc_nodes[] = { [SLAVE_ANOC_PCIE_GEM_NOC] = &qns_pcie_mem_noc, }; -static const struct regmap_config x1e80100_pcie_center_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x7000, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_center_anoc = { - .config = &x1e80100_pcie_center_anoc_regmap_config, .nodes = pcie_center_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_center_anoc_nodes), .bcms = pcie_center_anoc_bcms, @@ -2203,16 +1774,7 @@ static struct qcom_icc_node * const pcie_north_anoc_nodes[] = { [SLAVE_PCIE_NORTH] = &qns_pcie_north_gem_noc, }; -static const struct regmap_config x1e80100_pcie_north_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x9080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_north_anoc = { - .config = &x1e80100_pcie_north_anoc_regmap_config, .nodes = pcie_north_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_north_anoc_nodes), .bcms = pcie_north_anoc_bcms, @@ -2231,16 +1793,7 @@ static struct qcom_icc_node * const pcie_south_anoc_nodes[] = { [SLAVE_PCIE_SOUTH] = &qns_pcie_south_gem_noc, }; -static const struct regmap_config x1e80100_pcie_south_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xd080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_south_anoc = { - .config = &x1e80100_pcie_south_anoc_regmap_config, .nodes = pcie_south_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_south_anoc_nodes), .bcms = pcie_south_anoc_bcms, @@ -2262,16 +1815,7 @@ static struct qcom_icc_node * const system_noc_nodes[] = { [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf, }; -static const struct regmap_config x1e80100_system_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x1c080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_system_noc = { - .config = &x1e80100_system_noc_regmap_config, .nodes = system_noc_nodes, .num_nodes = ARRAY_SIZE(system_noc_nodes), .bcms = system_noc_bcms, @@ -2287,16 +1831,7 @@ static struct qcom_icc_node * const usb_center_anoc_nodes[] = { [SLAVE_USB_NOC_SNOC] = &qns_aggre_usb_snoc, }; -static const struct regmap_config x1e80100_usb_center_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x8800, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_center_anoc = { - .config = &x1e80100_usb_center_anoc_regmap_config, .nodes = usb_center_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_center_anoc_nodes), .bcms = usb_center_anoc_bcms, @@ -2312,21 +1847,11 @@ static struct qcom_icc_node * const usb_north_anoc_nodes[] = { [SLAVE_AGGRE_USB_NORTH] = &qns_aggre_usb_north_snoc, }; -static const struct regmap_config x1e80100_usb_north_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x7080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_north_anoc = { - .config = &x1e80100_usb_north_anoc_regmap_config, .nodes = usb_north_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_north_anoc_nodes), .bcms = usb_north_anoc_bcms, .num_bcms = ARRAY_SIZE(usb_north_anoc_bcms), - .qos_requires_clocks = true, }; static struct qcom_icc_bcm * const usb_south_anoc_bcms[] = { @@ -2342,21 +1867,11 @@ static struct qcom_icc_node * const usb_south_anoc_nodes[] = { [SLAVE_AGGRE_USB_SOUTH] = &qns_aggre_usb_south_snoc, }; -static const struct regmap_config x1e80100_usb_south_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xf080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_south_anoc = { - .config = &x1e80100_usb_south_anoc_regmap_config, .nodes = usb_south_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_south_anoc_nodes), .bcms = usb_south_anoc_bcms, .num_bcms = ARRAY_SIZE(usb_south_anoc_bcms), - .qos_requires_clocks = true, }; static const struct of_device_id qnoc_of_match[] = { diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 58c624513cd430..68ee57800fefc1 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -1228,9 +1228,9 @@ dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size, * If both the physical buffer start address and size are page aligned, * we don't need to use a bounce page. */ - if (dev_use_swiotlb(dev, size, dir) && + if (!(attrs & DMA_ATTR_MMIO) && dev_use_swiotlb(dev, size, dir) && iova_unaligned(iovad, phys, size)) { - if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT)) + if (attrs & DMA_ATTR_REQUIRE_COHERENT) return DMA_MAPPING_ERROR; phys = iommu_dma_map_swiotlb(dev, phys, size, dir, attrs); diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 9e170de50ad323..0bd22bda3e2deb 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -2894,8 +2894,13 @@ static int crypt_ctr_cipher_new(struct dm_target *ti, char *cipher_in, char *key } } - if (*ivmode && !strcmp(*ivmode, "lmk")) + if (*ivmode && !strcmp(*ivmode, "lmk")) { + if (crypt_integrity_aead(cc)) { + ti->error = "AEAD transforms not supported for LMK"; + return -EINVAL; + } cc->tfms_count = 64; + } if (*ivmode && !strcmp(*ivmode, "essiv")) { if (!*ivopts) { diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 92970e12267ab4..802b377613b06b 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -180,6 +180,7 @@ struct dm_integrity_c { struct dm_bufio_client *bufio; struct workqueue_struct *metadata_wq; struct superblock *sb; + struct superblock *sb_copy; unsigned int journal_pages; unsigned int n_bitmap_blocks; @@ -2721,7 +2722,7 @@ static void dm_integrity_inline_recheck(struct work_struct *w) outgoing_data = dio->integrity_payload + PAGE_SIZE; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; int r; struct bio_integrity_payload *bip; struct bio_vec bv; @@ -2788,7 +2789,7 @@ static inline bool dm_integrity_check(struct dm_integrity_c *ic, struct dm_integ unsigned pos = 0; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; struct bio_vec bv = bio_iter_iovec(bio, dio->bio_details.bi_iter); char *mem = integrity_kmap(ic, bv.bv_page); integrity_sector_checksum(ic, &dio->ahash_req, dio->bio_details.bi_iter.bi_sector, mem, bv.bv_offset, digest); @@ -3858,6 +3859,35 @@ static void dm_integrity_postsuspend(struct dm_target *ti) ic->journal_uptodate = true; } +/* + * The superblock is re-read from the device on every resume, so that we pick + * up the flags and the recalculate position. The geometry described by the + * superblock must not change though - the in-memory structures (and the + * journal in particular) were sized according to the superblock that was + * validated in the constructor. Reject a superblock that was modified behind + * our back. + * + * Only the fields that the driver never rewrites may be tested here. In + * particular, "version" is recalculated by sb_set_version on every superblock + * write and it depends on SB_FLAG_RECALCULATING and SB_FLAG_DIRTY_BITMAP, and + * SB_FLAG_DISCARD_KEYED may be set by dm_integrity_resume itself. + */ +static bool superblock_changed(struct dm_integrity_c *ic) +{ + const __le32 immutable_flags = cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC | + SB_FLAG_FIXED_PADDING | + SB_FLAG_FIXED_HMAC | + SB_FLAG_INLINE); + + return memcmp(ic->sb->magic, ic->sb_copy->magic, sizeof(ic->sb->magic)) != 0 || + ic->sb->log2_interleave_sectors != ic->sb_copy->log2_interleave_sectors || + ic->sb->integrity_tag_size != ic->sb_copy->integrity_tag_size || + ic->sb->journal_sections != ic->sb_copy->journal_sections || + ic->sb->log2_sectors_per_block != ic->sb_copy->log2_sectors_per_block || + ((ic->sb->flags ^ ic->sb_copy->flags) & immutable_flags) != 0 || + memcmp(ic->sb->salt, ic->sb_copy->salt, SALT_SIZE) != 0; +} + static void dm_integrity_resume(struct dm_target *ti) { struct dm_integrity_c *ic = ti->private; @@ -3876,6 +3906,18 @@ static void dm_integrity_resume(struct dm_target *ti) if (r) dm_integrity_io_error(ic, "reading superblock", r); + if (unlikely(superblock_changed(ic))) { + /* + * Restore the superblock that we validated in the constructor, + * so that the rest of the driver doesn't operate on values + * that don't match the in-memory structures. + */ + memcpy(ic->sb, ic->sb_copy, sizeof(struct superblock)); + DMERR("The superblock was changed while the device was suspended"); + dm_integrity_io_error(ic, "superblock check", -EINVAL); + goto skip_writes; + } + if (ic->mode == 'R') goto skip_writes; @@ -5427,6 +5469,13 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv ic->just_formatted = true; } + ic->sb_copy = kmemdup(ic->sb, sizeof(struct superblock), GFP_KERNEL); + if (!ic->sb_copy) { + ti->error = "Cannot allocate superblock copy"; + r = -ENOMEM; + goto bad; + } + if (!ic->meta_dev && ic->mode != 'I') { r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors); if (r) @@ -5525,6 +5574,7 @@ static void dm_integrity_dtr(struct dm_target *ti) kvfree(ic->journal_tree); if (ic->sb) free_pages_exact(ic->sb, SB_SECTORS << SECTOR_SHIFT); + kfree(ic->sb_copy); if (ic->internal_shash) crypto_free_shash(ic->internal_shash); diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a6b8e97755cd5e..8f945f0aeda5d6 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -656,10 +656,16 @@ static void *get_result_buffer(struct dm_ioctl *param, size_t param_size, { param->data_start = align_ptr(param + 1) - (void *) param; - if (param->data_start < param_size) + if (param->data_start < param_size) { *len = param_size - param->data_start; - else + } else { + /* + * The buffer can be as small as offsetof(*param, data), which + * is less than sizeof(*param), so don't run off the end of it. + */ *len = 0; + param->data_start = param_size; + } return ((void *) param) + param->data_start; } @@ -1267,6 +1273,7 @@ static int do_resume(struct dm_ioctl *param) /* Do we need to load a new map ? */ if (new_map) { sector_t old_size, new_size; + blk_mode_t new_map_mode; dm_ima_context_table_op(md, ima_context, DM_IMA_TABLE_SAVE); /* Suspend if it isn't already suspended */ @@ -1299,6 +1306,7 @@ static int do_resume(struct dm_ioctl *param) } } + new_map_mode = dm_table_get_mode(new_map); old_size = dm_get_size(md); old_map = dm_swap_table(md, new_map); if (IS_ERR(old_map)) { @@ -1314,7 +1322,7 @@ static int do_resume(struct dm_ioctl *param) if (old_size && new_size && old_size != new_size) need_resize_uevent = true; - if (dm_table_get_mode(new_map) & BLK_OPEN_WRITE) + if (new_map_mode & BLK_OPEN_WRITE) set_disk_ro(dm_disk(md), 0); else set_disk_ro(dm_disk(md), 1); diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index c8a9ca1eeae768..7c3725e4ac54a2 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -1292,6 +1292,20 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad_store; } + /* + * snapshot-merge requires the optional prepare_merge/commit_merge + * callbacks; the transient exception store does not implement them. + * The handover path checks this in __validate_exception_handover(), + * but a directly constructed snapshot-merge never goes through it. + */ + if (dm_target_is_snapshot_merge(ti) && + (!s->store->type->prepare_merge || + !s->store->type->commit_merge)) { + ti->error = "Snapshot exception store does not support snapshot-merge."; + r = -EINVAL; + goto bad_hash_tables; + } + argv += args_used; argc -= args_used; diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index c418add65bb514..79af154767c17f 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -2416,7 +2416,7 @@ static int em28xx_v4l2_fini(struct em28xx *dev) if (video_is_registered(&v4l2->radio_dev)) { dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", video_device_node_name(&v4l2->radio_dev)); - vb2_video_unregister_device(&v4l2->radio_dev); + video_unregister_device(&v4l2->radio_dev); } if (video_is_registered(&v4l2->vbi_dev)) { dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", @@ -3082,7 +3082,7 @@ static int em28xx_v4l2_init(struct em28xx *dev) dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", video_device_node_name(&v4l2->radio_dev)); - vb2_video_unregister_device(&v4l2->radio_dev); + video_unregister_device(&v4l2->radio_dev); } if (video_is_registered(&v4l2->vbi_dev)) { dev_info(&dev->intf->dev, diff --git a/drivers/memstick/core/memstick.c b/drivers/memstick/core/memstick.c index e03989c4e99ea1..0d15f48ef51d4f 100644 --- a/drivers/memstick/core/memstick.c +++ b/drivers/memstick/core/memstick.c @@ -370,9 +370,7 @@ int memstick_set_rw_addr(struct memstick_dev *card) { card->next_request = h_memstick_set_rw_addr; memstick_new_req(card->host); - if (!wait_for_completion_timeout(&card->mrq_complete, - msecs_to_jiffies(500))) - card->current_mrq.error = -ETIMEDOUT; + wait_for_completion(&card->mrq_complete); return card->current_mrq.error; } @@ -405,9 +403,7 @@ static struct memstick_dev *memstick_alloc_card(struct memstick_host *host) card->next_request = h_memstick_read_dev_id; memstick_new_req(host); - if (!wait_for_completion_timeout(&card->mrq_complete, - msecs_to_jiffies(500))) - card->current_mrq.error = -ETIMEDOUT; + wait_for_completion(&card->mrq_complete); if (card->current_mrq.error) goto err_out; diff --git a/drivers/memstick/host/rtsx_usb_ms.c b/drivers/memstick/host/rtsx_usb_ms.c index beadc389f15f39..d5b3a96fc609a1 100644 --- a/drivers/memstick/host/rtsx_usb_ms.c +++ b/drivers/memstick/host/rtsx_usb_ms.c @@ -27,7 +27,6 @@ struct rtsx_usb_ms { struct memstick_host *msh; struct memstick_request *req; - struct mutex host_mutex; struct work_struct handle_req; struct delayed_work poll_card; @@ -514,6 +513,13 @@ static void rtsx_usb_ms_handle_req(struct work_struct *work) struct memstick_host *msh = host->msh; int rc; + /* Fail requests after eject so their waiters are released. */ + if (host->eject) { + while (!memstick_next_req(msh, &host->req)) + host->req->error = -ENOMEDIUM; + return; + } + if (!host->req) { pm_runtime_get_sync(ms_dev(host)); do { @@ -547,8 +553,7 @@ static void rtsx_usb_ms_request(struct memstick_host *msh) dev_dbg(ms_dev(host), "--> %s\n", __func__); - if (!host->eject) - schedule_work(&host->handle_req); + schedule_work(&host->handle_req); } static int rtsx_usb_ms_set_param(struct memstick_host *msh, @@ -781,7 +786,6 @@ static int rtsx_usb_ms_drv_probe(struct platform_device *pdev) host->power_mode = MEMSTICK_POWER_OFF; platform_set_drvdata(pdev, host); - mutex_init(&host->host_mutex); INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req); INIT_DELAYED_WORK(&host->poll_card, rtsx_usb_ms_poll_card); @@ -812,27 +816,12 @@ static void rtsx_usb_ms_drv_remove(struct platform_device *pdev) { struct rtsx_usb_ms *host = platform_get_drvdata(pdev); struct memstick_host *msh = host->msh; - int err; host->eject = true; msh->removing = true; - cancel_work_sync(&host->handle_req); + flush_work(&host->handle_req); cancel_delayed_work_sync(&host->poll_card); - mutex_lock(&host->host_mutex); - if (host->req) { - dev_dbg(ms_dev(host), - "%s: Controller removed during transfer\n", - dev_name(&msh->dev)); - host->req->error = -ENOMEDIUM; - do { - err = memstick_next_req(msh, &host->req); - if (!err) - host->req->error = -ENOMEDIUM; - } while (!err); - } - mutex_unlock(&host->host_mutex); - /* Balance possible unbalanced usage count * e.g. unconditional module removal */ @@ -841,6 +830,8 @@ static void rtsx_usb_ms_drv_remove(struct platform_device *pdev) pm_runtime_disable(ms_dev(host)); memstick_remove_host(msh); + /* No card, no new requests; wait for the last failed one to finish. */ + cancel_work_sync(&host->handle_req); dev_dbg(ms_dev(host), ": Realtek USB Memstick controller has been removed\n"); memstick_free_host(msh); diff --git a/drivers/mmc/host/cavium-octeon.c b/drivers/mmc/host/cavium-octeon.c index 1bfc1a4f9fe9c6..671dd1a36a74ad 100644 --- a/drivers/mmc/host/cavium-octeon.c +++ b/drivers/mmc/host/cavium-octeon.c @@ -295,9 +295,12 @@ static void octeon_mmc_remove(struct platform_device *pdev) u64 dma_cfg; int i; - for (i = 0; i < CAVIUM_MAX_MMC; i++) + for (i = 0; i < CAVIUM_MAX_MMC; i++) { if (host->slot[i]) cvm_mmc_of_slot_remove(host->slot[i]); + if (host->slot_pdev[i]) + of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); + } dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); dma_cfg &= ~MIO_EMM_DMA_CFG_EN; diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c index 84ff6d82ae3c0a..2635ac94ec5da3 100644 --- a/drivers/mmc/host/cavium-thunderx.c +++ b/drivers/mmc/host/cavium-thunderx.c @@ -173,9 +173,12 @@ static void thunder_mmc_remove(struct pci_dev *pdev) u64 dma_cfg; int i; - for (i = 0; i < CAVIUM_MAX_MMC; i++) + for (i = 0; i < CAVIUM_MAX_MMC; i++) { if (host->slot[i]) cvm_mmc_of_slot_remove(host->slot[i]); + if (host->slot_pdev[i]) + of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); + } dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); dma_cfg &= ~MIO_EMM_DMA_CFG_EN; diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index 01ea3adbdf3b61..5b160fdad3f02d 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -3216,6 +3216,7 @@ static void msdc_drv_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); mmc_remove_host(mmc); + cancel_delayed_work_sync(&host->req_timeout); msdc_deinit_hw(host); msdc_gate_clock(host); diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c index 3584a2b314a9f9..0c8f389fe58471 100644 --- a/drivers/mmc/host/sdhci-sprd.c +++ b/drivers/mmc/host/sdhci-sprd.c @@ -890,6 +890,10 @@ static void sdhci_sprd_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + sdhci_remove_host(host, 0); clk_disable_unprepare(sprd_host->clk_sdio); diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 6049ba2d6bcb66..60d82a1acb0116 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -1716,12 +1716,24 @@ static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t le } +/* + * Keep noinline: inlined, the map_word temporaries put do_write_buffer() over + * the frame-size limit with MTD_MAP_BANK_WIDTH_32 and KASAN_STACK. + */ +static noinline void __xipram cfi_write_cmd(struct map_info *map, + unsigned long cmd, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + + map_write(map, CMD(cmd), adr); +} + static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, unsigned long adr, const struct kvec **pvec, unsigned long *pvec_seek, int len) { struct cfi_private *cfi = map->fldrv_priv; - map_word status, write_cmd, datum; + map_word status, datum; unsigned long cmd_adr; int ret, wbufsize, word_gap, words; const struct kvec *vec; @@ -1740,9 +1752,6 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, if (is_LH28F640BF(cfi)) cmd_adr = adr; - /* Let's determine this according to the interleave only once */ - write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0xe8) : CMD(0xe9); - mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_adr, FL_WRITING); if (ret) { @@ -1759,7 +1768,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, So we must check here and reset those bits if they're set. Otherwise we're just pissing in the wind */ if (chip->state != FL_STATUS) { - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; } status = map_read(map, cmd_adr); @@ -1767,21 +1776,22 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, xip_enable(map, chip, cmd_adr); printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %lx). Clearing.\n", status.x[0]); xip_disable(map, chip, cmd_adr); - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); } chip->state = FL_WRITING_TO_BUFFER; - map_write(map, write_cmd, cmd_adr); + cfi_write_cmd(map, (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? 0xe8 : 0xe9, + cmd_adr); ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0, 0); if (ret) { /* Argh. Not ready for write to buffer */ map_word Xstatus = map_read(map, cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; status = map_read(map, cmd_adr); - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); xip_enable(map, chip, cmd_adr); printk(KERN_ERR "%s: Chip not ready for buffer write. Xstatus = %lx, status = %lx\n", map->name, Xstatus.x[0], status.x[0]); @@ -1800,7 +1810,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, } /* Write length of data to come */ - map_write(map, CMD(words), cmd_adr ); + cfi_write_cmd(map, words, cmd_adr); /* Write data */ vec = *pvec; @@ -1837,7 +1847,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, *pvec_seek = vec_seek; /* GO GO GO */ - map_write(map, CMD(0xd0), cmd_adr); + cfi_write_cmd(map, 0xd0, cmd_adr); chip->state = FL_WRITING; ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, @@ -1845,7 +1855,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, chip->buffer_write_time, chip->buffer_write_time_max); if (ret) { - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; xip_enable(map, chip, cmd_adr); printk(KERN_ERR "%s: buffer write error (status timeout)\n", map->name); @@ -1858,8 +1868,8 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, unsigned long chipstatus = MERGESTATUS(status); /* reset status */ - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); xip_enable(map, chip, cmd_adr); if (chipstatus & 0x02) { diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 03e80b2c4f5a72..349fa07be314cc 100644 --- a/drivers/mtd/devices/block2mtd.c +++ b/drivers/mtd/devices/block2mtd.c @@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size, } size = bdev_nr_bytes(bdev); - if ((long)size % erase_size) { + if (!erase_size || (long)size % erase_size) { pr_err("erasesize must be a divisor of device size\n"); goto err_free_block2mtd; } diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c index a566e86eb5e3a0..55a945c1a67add 100644 --- a/drivers/mtd/devices/mtd_intel_dg.c +++ b/drivers/mtd/devices/mtd_intel_dg.c @@ -387,7 +387,7 @@ idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_a void __iomem *base2 = nvm->base2; void __iomem *base = nvm->base; const u32 block = 0x10; - u32 iter = 0; + u32 iter; u32 reg; u64 i; @@ -396,6 +396,7 @@ idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_a iowrite32(region << 24 | block, base + NVM_ERASE_REG); if (nvm->non_posted_erase) { /* Wait for Erase Done */ + iter = 0; reg = ioread32(base2 + NVM_DEBUG_REG); while (!(reg & NVM_NON_POSTED_ERASE_DONE) && ++iter < NVM_NON_POSTED_ERASE_DONE_ITER) { diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 16629382a787bd..491a27d0315fa5 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1083,6 +1083,7 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd) err: nvmem_unregister(mtd->otp_user_nvmem); + mtd->otp_user_nvmem = NULL; /* Don't report error if OTP is not supported. */ if (err == -EOPNOTSUPP) return 0; @@ -1332,7 +1333,7 @@ int __get_mtd_device(struct mtd_info *mtd) int err; if (master->_get_device) { - err = master->_get_device(mtd); + err = master->_get_device(master); if (err) return err; } diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 6ccdff3fc913be..1d809cbd47e6df 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -159,6 +159,13 @@ int nand_ecc_finish_io_req(struct nand_device *nand, } EXPORT_SYMBOL(nand_ecc_finish_io_req); +bool nand_ecc_is_pipelined(const struct nand_device *nand) +{ + return nand->ecc.engine && + nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED; +} +EXPORT_SYMBOL(nand_ecc_is_pipelined); + /* Define default OOB placement schemes for large and small page devices */ static int nand_ooblayout_ecc_sp(struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion) diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index d53b35a8b3cb24..2085632631c020 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -3143,6 +3143,9 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl) goto free_buf_desc; } + spin_lock_init(&cdns_ctrl->irq_lock); + init_completion(&cdns_ctrl->complete); + if (devm_request_irq(cdns_ctrl->dev, cdns_ctrl->irq, cadence_nand_isr, IRQF_SHARED, "cadence-nand-controller", cdns_ctrl)) { @@ -3151,9 +3154,6 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl) goto free_buf; } - spin_lock_init(&cdns_ctrl->irq_lock); - init_completion(&cdns_ctrl->complete); - ret = cadence_nand_hw_init(cdns_ctrl); if (ret) goto disable_irq; diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c index 9940681810cff2..1b9b370adfab61 100644 --- a/drivers/mtd/nand/raw/vf610_nfc.c +++ b/drivers/mtd/nand/raw/vf610_nfc.c @@ -505,6 +505,11 @@ static int vf610_nfc_exec_op(struct nand_chip *chip, check_only); } +static unsigned int vf610_nfc_spare_size(struct mtd_info *mtd) +{ + return min_t(unsigned int, mtd->oobsize, 64); +} + static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, uint8_t *oob, int page) { @@ -514,6 +519,7 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, u8 ecc_status; u8 ecc_count; int flips_threshold = nfc->chip.ecc.strength / 2; + int ret; ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff; ecc_count = ecc_status & ECC_STATUS_ERR_COUNT; @@ -521,16 +527,22 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, if (!(ecc_status & ECC_STATUS_MASK)) return ecc_count; + /* The failed decode leaves a bogus correction in SRAM; re-read without ECC */ nfc->data_access = true; - nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize); + ret = nand_read_page_op(&nfc->chip, page, 0, dat, mtd->writesize); + if (!ret) + ret = nand_read_oob_op(&nfc->chip, page, 0, oob, + vf610_nfc_spare_size(mtd)); nfc->data_access = false; + if (ret) + return ret; /* - * On an erased page, bit count (including OOB) should be zero or - * at least less then half of the ECC strength. + * Run the erased-page check with the driver's historic threshold + * of half the ECC strength. */ - return nand_check_erased_ecc_chunk(dat, nfc->chip.ecc.size, oob, - mtd->oobsize, NULL, 0, + return nand_check_erased_ecc_chunk(dat, mtd->writesize, oob, + vf610_nfc_spare_size(mtd), NULL, 0, flips_threshold); } @@ -551,7 +563,7 @@ static int vf610_nfc_read_page(struct nand_chip *chip, uint8_t *buf, { struct vf610_nfc *nfc = chip_to_nfc(chip); struct mtd_info *mtd = nand_to_mtd(chip); - int trfr_sz = mtd->writesize + mtd->oobsize; + int trfr_sz = mtd->writesize + vf610_nfc_spare_size(mtd); u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; int stat; @@ -577,11 +589,16 @@ static int vf610_nfc_read_page(struct nand_chip *chip, uint8_t *buf, */ vf610_nfc_rd_from_sram(buf, nfc->regs + NFC_MAIN_AREA(0), mtd->writesize, false); - if (oob_required) + if (oob_required) { + unsigned int spare = vf610_nfc_spare_size(mtd); + vf610_nfc_rd_from_sram(chip->oob_poi, nfc->regs + NFC_MAIN_AREA(0) + mtd->writesize, - mtd->oobsize, false); + spare, false); + /* Not transferred, and never written: reads back erased */ + memset(chip->oob_poi + spare, 0xff, mtd->oobsize - spare); + } stat = vf610_nfc_correct_data(chip, buf, chip->oob_poi, page); @@ -599,7 +616,7 @@ static int vf610_nfc_write_page(struct nand_chip *chip, const uint8_t *buf, { struct vf610_nfc *nfc = chip_to_nfc(chip); struct mtd_info *mtd = nand_to_mtd(chip); - int trfr_sz = mtd->writesize + mtd->oobsize; + int trfr_sz = mtd->writesize + vf610_nfc_spare_size(mtd); u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; u8 status; int ret; @@ -740,6 +757,49 @@ static void vf610_nfc_init_controller(struct vf610_nfc *nfc) } } +/* + * With 64 byte OOB chips the core's large page layout matches what + * U-Boot uses, and on chips with more, U-Boot and older kernels clamped + * mtd->oobsize to 64. Modifying the OOB size is no longer possible, the + * actual chip geometry must be respected, so to avoid breaking those + * existing setups use our own layout: the core's large page one, + * computed over the first 64 spare bytes only. + */ +static int vf610_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section || !total_ecc_bytes) + return -ERANGE; + + oobregion->length = total_ecc_bytes; + oobregion->offset = vf610_nfc_spare_size(mtd) - oobregion->length; + + return 0; +} + +static int vf610_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section) + return -ERANGE; + + oobregion->length = vf610_nfc_spare_size(mtd) - total_ecc_bytes - 2; + oobregion->offset = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops vf610_nfc_ooblayout_ops = { + .ecc = vf610_nfc_ooblayout_ecc, + .free = vf610_nfc_ooblayout_free, +}; + static int vf610_nfc_attach_chip(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); @@ -770,12 +830,7 @@ static int vf610_nfc_attach_chip(struct nand_chip *chip) return -ENXIO; } - /* Only 64 byte ECC layouts known */ - if (mtd->oobsize > 64) - mtd->oobsize = 64; - - /* Use default large page ECC layout defined in NAND core */ - mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + mtd_set_ooblayout(mtd, &vf610_nfc_ooblayout_ops); if (chip->ecc.strength == 32) { nfc->ecc_mode = ECC_60_BYTE; chip->ecc.bytes = 60; diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 8bf9301f25e752..95353777d7f749 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand) static int spinand_init_quad_enable(struct spinand_device *spinand, bool enable) { - return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, - enable ? CFG_QUAD_ENABLE : 0); + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int target; + int ret; + + /* + * QE is a per-die setting on some devices. Program each target + * individually when enabling or disabling quad I/O mode. + */ + for (target = 0; target < nand->memorg.ntargets; target++) { + ret = spinand_select_target(spinand, target); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, + enable ? CFG_QUAD_ENABLE : 0); + if (ret) + return ret; + } + + return 0; } static int spinand_ecc_enable(struct spinand_device *spinand, @@ -508,8 +526,7 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, else rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && - req->mode != MTD_OPS_RAW) + if (nand_ecc_is_pipelined(nand) && req->mode != MTD_OPS_RAW) rdesc->info.op_tmpl->data.ecc = true; else rdesc->info.op_tmpl->data.ecc = false; @@ -603,8 +620,7 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand, wdesc = spinand->dirmaps[req->pos.plane].wdesc; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && - req->mode != MTD_OPS_RAW) + if (nand_ecc_is_pipelined(nand) && req->mode != MTD_OPS_RAW) wdesc->info.op_tmpl->data.ecc = true; else wdesc->info.op_tmpl->data.ecc = false; @@ -1261,7 +1277,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, struct spi_mem_dirmap_desc *desc; bool enable_ecc = false, secondary_op = false; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED) + if (nand_ecc_is_pipelined(nand)) enable_ecc = true; if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache) @@ -1797,11 +1813,11 @@ static int spinand_configure_chip(struct spinand_device *spinand) spinand->ssdr_op_templates.write_cache->data.buswidth == 4 || spinand->ssdr_op_templates.update_cache->data.buswidth == 4) quad_enable = true; - } - ret = spinand_init_quad_enable(spinand, quad_enable); - if (ret) - return ret; + ret = spinand_init_quad_enable(spinand, quad_enable); + if (ret) + return ret; + } if (spinand->configure_chip) { ret = spinand->configure_chip(spinand, SSDR); @@ -1809,7 +1825,7 @@ static int spinand_configure_chip(struct spinand_device *spinand) return ret; } - return ret; + return 0; } static int spinand_init_flash(struct spinand_device *spinand) @@ -1980,12 +1996,17 @@ static int spinand_init(struct spinand_device *spinand) goto err_cleanup_ecc_engine; } - if (nand->ecc.engine) { - ret = mtd_ooblayout_count_freebytes(mtd); - if (ret < 0) - goto err_cleanup_ecc_engine; + if (!nand->ecc.engine) { + if (spinand->eccinfo.ooblayout) + mtd_set_ooblayout(mtd, spinand->eccinfo.ooblayout); + else + mtd_set_ooblayout(mtd, &spinand_noecc_ooblayout); } + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + goto err_cleanup_ecc_engine; + mtd->oobavail = ret; /* Propagate ECC information to mtd_info */ diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ccf4396cdcd047..8bc117b46e0281 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe) return false; diff --git a/drivers/net/amt.c b/drivers/net/amt.c index bddc24e1856de2..b53f8ec5566151 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -80,15 +80,6 @@ static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT; static struct mld2_grec mldv2_zero_grec; #endif -static struct amt_skb_cb *amt_skb_cb(struct sk_buff *skb) -{ - BUILD_BUG_ON(sizeof(struct amt_skb_cb) + sizeof(struct tc_skb_cb) > - sizeof_field(struct sk_buff, cb)); - - return (struct amt_skb_cb *)((void *)skb->cb + - sizeof(struct tc_skb_cb)); -} - static void __amt_source_gc_work(void) { struct amt_source_node *snode; @@ -791,6 +782,11 @@ static void amt_send_request(struct amt_dev *amt, bool v6) rcu_read_unlock(); } +static bool amt_send_membership_query(struct amt_dev *amt, + struct sk_buff *skb, + struct amt_tunnel_list *tunnel, + bool v6); + static void amt_send_igmp_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) { @@ -800,8 +796,11 @@ static void amt_send_igmp_gq(struct amt_dev *amt, if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, false)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #if IS_ENABLED(CONFIG_IPV6) @@ -885,8 +884,11 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, true)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #else static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) @@ -1186,7 +1188,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) #endif bool report = false; struct igmphdr *ih; - bool query = false; struct iphdr *iph; bool data = false; bool v6 = false; @@ -1204,9 +1205,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case IGMP_HOST_MEMBERSHIP_REPORT: report = true; break; - case IGMP_HOST_MEMBERSHIP_QUERY: - query = true; - break; default: goto free; } @@ -1228,9 +1226,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case ICMPV6_MLD2_REPORT: report = true; break; - case ICMPV6_MGM_QUERY: - query = true; - break; default: goto free; } @@ -1261,19 +1256,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) goto free; goto unlock; } else if (amt->mode == AMT_MODE_RELAY) { - if (query) { - tunnel = amt_skb_cb(skb)->tunnel; - if (!tunnel) { - WARN_ON(1); - goto free; - } - - /* Do not forward unexpected query */ - if (amt_send_membership_query(amt, skb, tunnel, v6)) - goto free; - goto unlock; - } - if (!data) goto free; list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) { diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index c3b5ed52d87789..b9fe54151e1a74 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -356,7 +356,7 @@ static int bareudp_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; - skb_set_inner_protocol(skb, bareudp->ethertype); + skb_set_inner_protocol(skb, skb->protocol); udp_tunnel_xmit_skb(rt, sk, skb, saddr, info->key.u.ipv4.dst, tos, ttl, df, sport, bareudp->port, !net_eq(bareudp->net, dev_net(bareudp->dev)), @@ -424,6 +424,7 @@ static int bareudp6_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; + skb_set_inner_protocol(skb, skb->protocol); daddr = info->key.u.ipv6.dst; udp_tunnel6_xmit_skb(dst, sk, skb, dev, &saddr, &daddr, prio, ttl, diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index de2489c3d9bf28..09aa45d71fabb5 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -5732,12 +5732,12 @@ static int bond_xdp_set(struct net_device *dev, struct bpf_prog *prog, bpf_prog_inc(prog); } - if (prog) { + if (prog && !old_prog) static_branch_inc(&bpf_master_redirect_enabled_key); - } else if (old_prog) { - bpf_prog_put(old_prog); + else if (!prog && old_prog) static_branch_dec(&bpf_master_redirect_enabled_key); - } + if (old_prog) + bpf_prog_put(old_prog); return 0; diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c index 95fcdc1026f808..14edec5afb57e4 100644 --- a/drivers/net/can/dev/skb.c +++ b/drivers/net/can/dev/skb.c @@ -210,6 +210,10 @@ static void init_can_skb(struct sk_buff *skb) { skb->pkt_type = PACKET_BROADCAST; skb->ip_summed = CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_reset_transport_header(skb); } struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf) diff --git a/drivers/net/can/usb/f81604.c b/drivers/net/can/usb/f81604.c index f12318268e46be..4c147b9d6d690a 100644 --- a/drivers/net/can/usb/f81604.c +++ b/drivers/net/can/usb/f81604.c @@ -169,7 +169,7 @@ struct f81604_int_data { u8 rxerr; u8 txerr; u8 val; -} __packed __aligned(4); +} __packed; struct f81604_sff { __be16 id; diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index 9e2e25d024717d..51148a81d1d9dc 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -79,6 +79,9 @@ static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) /* reset CAN GW hop counter */ csx->can_gw_hops = 0; + /* start with new CAN skb UID in the other namespace */ + csx->can_skb_uid = 0; + skb->pkt_type = PACKET_BROADCAST; skb->dev = peer; skb->ip_summed = CHECKSUM_UNNECESSARY; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index cbe98494578c37..f6104f714a8d16 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -853,7 +853,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) #define KSZ88_FAMILY_ID 0x88 #define KSZ8895_FAMILY_ID 0x95 -#define KSZ8_PORT_STATUS_0 0x08 +#define KSZ8_PORT_STATUS_0 0x18 #define KSZ8_PORT_FIBER_MODE BIT(7) #define SW_CHIP_ID_M GENMASK(7, 4) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 64619e9a704dd4..6ca57875b6d6e4 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2800,14 +2800,34 @@ static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel, FIELD_PREP(TRTCM_METER_GROUP_MASK, group) | FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) | FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode); + int i; - airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); - airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + /* + * HW might be in the middle of bucket handling and might + * require an additional try to actually apply the new TRTCM + * config. + */ + for (i = 0; i < TRTCM_CONFIG_RETRY_MAX; i++) { + u32 data = 0; - return read_poll_timeout(airoha_qdma_rr, val, - val & TRTCM_PARAM_RW_DONE_MASK, - USEC_PER_MSEC, 10 * USEC_PER_MSEC, true, - qdma, REG_TRTCM_CFG_PARAM(addr)); + airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); + airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + + if (read_poll_timeout(airoha_qdma_rr, data, + data & TRTCM_PARAM_RW_DONE_MASK, + USEC_PER_MSEC, 10 * USEC_PER_MSEC, + true, qdma, REG_TRTCM_CFG_PARAM(addr))) + continue; + + if (airoha_qdma_get_trtcm_param(qdma, channel, addr, param, + mode, &data, NULL)) + continue; + + if (data == val) + return 0; + } + + return -EBUSY; } static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel, diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8277c1c87bb328..338fde03a149fa 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -171,6 +171,8 @@ enum trtcm_param { #define TRTCM_TOKEN_RATE_MASK GENMASK(23, 6) #define TRTCM_TOKEN_RATE_FRACTION_MASK GENMASK(5, 0) +#define TRTCM_CONFIG_RETRY_MAX 2 + enum airoha_dma_map_type { AIROHA_DMA_UNMAPPED, AIROHA_DMA_MAP_SINGLE, diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 0ba67a55705d60..09b2abdfc9e841 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -300,7 +300,7 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, if (!req) { dev_err(db->dev, "alloc emac dma req error.\n"); ret = -ENOMEM; - goto alloc_req_err; + goto prepare_err; } desc->callback_param = req; @@ -319,9 +319,6 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, submit_err: emac_free_dma_req(req); -alloc_req_err: - dmaengine_desc_free(desc); - prepare_err: dma_unmap_single(db->dev, rxbuf, count, DMA_FROM_DEVICE); return ret; diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 4d06c6ba664188..78909f51682311 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -391,8 +391,10 @@ static void bcm_sysport_update_mib_counters(struct bcm_sysport_priv *priv) if (priv->is_lite) continue; - if (s->type != BCM_SYSPORT_STAT_MIB_RX) + if (s->type == BCM_SYSPORT_STAT_MIB_TX) offset = UMAC_MIB_STAT_OFFSET; + else if (s->type == BCM_SYSPORT_STAT_RUNT) + offset = 2 * UMAC_MIB_STAT_OFFSET; val = umac_readl(priv, UMAC_MIB_START + j + offset); break; case BCM_SYSPORT_STAT_RXCHK: @@ -482,10 +484,10 @@ static void bcm_sysport_get_stats(struct net_device *dev, s->type == BCM_SYSPORT_STAT_NETDEV64) { do { start = u64_stats_fetch_begin(syncp); - data[i] = *(u64 *)p; + data[j] = *(u64 *)p; } while (u64_stats_fetch_retry(syncp, start)); } else - data[i] = *(u32 *)p; + data[j] = *(u32 *)p; j++; } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d7728d0c5b6e63..15a8349ccf7b3f 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -486,7 +486,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *txq; int i; dma_addr_t mapping; - unsigned int length, pad = 0; + unsigned int length; u32 len, free_size, vlan_tag_flags, cfa_action, flags; struct bnxt_ptp_cfg *ptp = bp->ptp_cfg; struct pci_dev *pdev = bp->pdev; @@ -536,6 +536,16 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } + /* Pad after the SW USO branch: bnxt_sw_udp_gso_xmit() would + * otherwise account the padding as UDP payload. + * Must be done before skb_shinfo(skb)->nr_frags is sampled, + * because skb_put_padto() might linearize the skb. + */ + if (skb_put_padto(skb, BNXT_MIN_PKT_SIZE)) { + /* SKB already freed. */ + goto tx_kick_pending; + } + free_size = bnxt_tx_avail(bp, txr); if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) { /* We must have raced with NAPI cleanup */ @@ -672,14 +682,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) } normal_tx: - if (length < BNXT_MIN_PKT_SIZE) { - pad = BNXT_MIN_PKT_SIZE - length; - if (skb_pad(skb, pad)) - /* SKB already freed. */ - goto tx_kick_pending; - length = BNXT_MIN_PKT_SIZE; - } - mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(&pdev->dev, mapping))) @@ -759,10 +761,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) txbd->tx_bd_len_flags_type = cpu_to_le32(flags); } - flags &= ~TX_BD_LEN; - txbd->tx_bd_len_flags_type = - cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | - TX_BD_FLAGS_PACKET_END); + txbd->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_PACKET_END); netdev_tx_sent_queue(txq, skb->len); @@ -15547,7 +15546,7 @@ static void bnxt_fw_reset_task(struct work_struct *work) if (test_and_clear_bit(BNXT_STATE_FW_ACTIVATE_RESET, &bp->state) && !test_bit(BNXT_STATE_FW_ACTIVATE, &bp->state)) bnxt_dl_remote_reload(bp); - if (pci_enable_device(bp->pdev)) { + if (!pci_is_enabled(bp->pdev) && pci_enable_device(bp->pdev)) { netdev_err(bp->dev, "Cannot re-enable PCI device\n"); rc = -ENODEV; goto fw_reset_abort; @@ -17111,6 +17110,51 @@ void bnxt_print_device_info(struct bnxt *bp) pcie_print_link_status(bp->pdev); } +static void bnxt_clear_bars(struct pci_dev *pdev) +{ + int off; + + for (off = PCI_BASE_ADDRESS_0; off <= PCI_BASE_ADDRESS_5; off += 4) + pci_write_config_dword(pdev, off, 0); +} + +/* Clear any pending DMA transactions from crash kernel while loading driver in + * capture kernel. + */ +static int bnxt_kdump_reset(struct pci_dev *pdev) +{ + int rc, i; + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + if (pci_save_state(pdev)) + dev_warn(&pdev->dev, "Failed to save PCI state, PCI restore may be incomplete\n"); + + rc = pcie_flr(pdev); + if (rc) + dev_warn(&pdev->dev, "pcie_flr() failed (rc: %d), trying to continue\n", + rc); + + /* In case device is not returning CRS, wait 5 seconds longer */ + for (i = 0; i < 50; i++) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) + break; + msleep(100); + } + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, "PCI config space inaccessible after FLR, aborting\n"); + return -ENODEV; + } + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + return 0; +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17126,12 +17170,10 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) return -ENODEV; } - /* Clear any pending DMA transactions from crash kernel - * while loading driver in capture kernel. - */ if (is_kdump_kernel()) { - pci_clear_master(pdev); - pcie_flr(pdev); + rc = bnxt_kdump_reset(pdev); + if (rc) + return rc; } max_irqs = bnxt_get_max_irq(pdev); @@ -17565,10 +17607,8 @@ static pci_ers_result_t bnxt_io_error_detected(struct pci_dev *pdev, * so we disable bus master to prevent any potential bad DMAs before * freeing kernel memory. */ - if (state == pci_channel_io_frozen) { - set_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state); + if (state == pci_channel_io_frozen) bnxt_fw_fatal_close(bp); - } if (netif_running(netdev)) __bnxt_close_nic(bp, true, true); @@ -17598,69 +17638,80 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; - int off; + u16 cmd; netdev_info(bp->dev, "PCI Slot Reset\n"); - if (test_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state)) { - /* After DPC, the chip should return CRS when the vendor ID - * config register is read until it is ready. On all chips, - * this is not happening reliably so add a 5-second delay as a - * workaround. - */ - msleep(5000); - } + /* After a PCIe hot reset, the chip should return CRS when the + * vendor ID config register is read until it is ready. On all + * chips, this is not happening reliably so add a 5-second delay + * as a workaround. + */ + msleep(5000); netdev_lock(netdev); - if (pci_enable_device(pdev)) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (PCI_POSSIBLE_ERROR(cmd)) { dev_err(&pdev->dev, - "Cannot re-enable PCI device after reset.\n"); - } else { - pci_set_master(pdev); - /* Upon fatal error, our device internal logic that latches to - * BAR value is getting reset and will restore only upon - * rewriting the BARs. - * - * As pci_restore_state() does not re-write the BARs if the - * value is same as saved value earlier, driver needs to - * write the BARs to 0 to force restore, in case of fatal error. - */ - if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) { - for (off = PCI_BASE_ADDRESS_0; - off <= PCI_BASE_ADDRESS_5; off += 4) - pci_write_config_dword(bp->pdev, off, 0); - } - pci_restore_state(pdev); + "PCI config space inaccessible after reset\n"); + goto reset_exit; + } - bnxt_inv_fw_health_reg(bp); - bnxt_try_map_fw_health_reg(bp); + /* Upon PCIe error, our device internal logic that latches to + * BAR value is getting reset and will restore only upon + * rewriting the BARs. + * + * As pci_restore_state() does not re-write the BARs if the + * value is same as saved value earlier, driver needs to + * write the BARs to 0 to force restore. + */ + pci_clear_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); - /* In some PCIe AER scenarios, firmware may take up to - * 10 seconds to become ready in the worst case. - */ - do { - err = bnxt_try_recover_fw(bp); - if (!err) - break; - retry++; - } while (retry < BNXT_FW_SLOT_RESET_RETRY); + bnxt_clear_bars(pdev); + pci_restore_state(pdev); - if (err) { - dev_err(&pdev->dev, "Firmware not ready\n"); - goto reset_exit; + if (!pci_is_enabled(pdev) && pci_enable_device(pdev)) { + dev_err(&pdev->dev, + "Cannot re-enable PCI device after reset.\n"); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) { + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); } + goto reset_exit; + } + pci_set_master(pdev); - err = bnxt_hwrm_func_reset(bp); + bnxt_inv_fw_health_reg(bp); + bnxt_try_map_fw_health_reg(bp); + + /* In some PCIe AER scenarios, firmware may take up to + * 10 seconds to become ready in the worst case. + */ + do { + err = bnxt_try_recover_fw(bp); if (!err) - result = PCI_ERS_RESULT_RECOVERED; + break; + retry++; + } while (retry < BNXT_FW_SLOT_RESET_RETRY); - /* IRQ will be initialized later in bnxt_io_resume */ - bnxt_ulp_irq_stop(bp); - bnxt_clear_int_mode(bp); + if (err) { + dev_err(&pdev->dev, "Firmware not ready\n"); + goto reset_exit; } + err = bnxt_hwrm_func_reset(bp); + if (!err) + result = PCI_ERS_RESULT_RECOVERED; + + /* IRQ will be initialized later in bnxt_io_resume */ + bnxt_ulp_irq_stop(bp); + bnxt_clear_int_mode(bp); + reset_exit: clear_bit(BNXT_STATE_IN_FW_RESET, &bp->state); bnxt_clear_reservations(bp, true); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index c673b2ce4a0d22..41d05efd6307a4 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -2468,7 +2468,6 @@ struct bnxt { #define BNXT_STATE_ABORT_ERR 5 #define BNXT_STATE_FW_FATAL_COND 6 #define BNXT_STATE_DRV_REGISTERED 7 -#define BNXT_STATE_PCI_CHANNEL_IO_FROZEN 8 #define BNXT_STATE_NAPI_DISABLED 9 #define BNXT_STATE_FW_ACTIVATE 11 #define BNXT_STATE_RECOVER 12 diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 21668e41b6964a..e8908916558b12 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -57,6 +57,9 @@ */ #define GENET_RSB_PAD (sizeof(struct status_64) + 2) +/* RX buffer plus the skb_shared_info napi_build_skb() places behind it */ +#define GENET_RX_BUF_SIZE SKB_HEAD_ALIGN(RX_BUF_LENGTH) + /* Tx/Rx DMA register offset, skip 256 descriptors */ #define WORDS_PER_BD(p) (p->hw_params->words_per_bd) #define DMA_DESC_SIZE (WORDS_PER_BD(priv) * sizeof(u32)) @@ -2254,11 +2257,12 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring, struct enet_cb *cb) { struct bcmgenet_priv *priv = ring->priv; + unsigned int size = GENET_RX_BUF_SIZE; + unsigned int offset; dma_addr_t mapping; struct page *page; - page = page_pool_alloc_pages(ring->page_pool, - GFP_ATOMIC); + page = page_pool_dev_alloc(ring->page_pool, &offset, &size); if (!page) { priv->mib.alloc_rx_buff_failed++; netif_err(priv, rx_err, priv->dev, @@ -2267,9 +2271,13 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring, } /* page_pool handles DMA mapping via PP_FLAG_DMA_MAP */ - mapping = page_pool_get_dma_addr(page); + mapping = page_pool_get_dma_addr(page) + offset; + dma_sync_single_for_device(&priv->pdev->dev, mapping, RX_BUF_LENGTH, + DMA_FROM_DEVICE); cb->rx_page = page; + cb->rx_offset = offset; + cb->rx_size = size; dmadesc_set_addr(priv, cb->bd_addr, mapping); return 0; @@ -2323,6 +2331,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, while ((rxpktprocessed < rxpkttoprocess) && (rxpktprocessed < budget)) { + unsigned int rx_offset, rx_size; struct status_64 *status; struct page *rx_page; void *hard_start; @@ -2332,6 +2341,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Save the received page before refilling */ rx_page = cb->rx_page; + rx_offset = cb->rx_offset; + rx_size = cb->rx_size; if (bcmgenet_rx_refill(ring, cb)) { BCMGENET_STATS64_INC(stats, dropped); @@ -2341,10 +2352,10 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Sync the full buffer; the HW may have written anywhere * up to RX_BUF_LENGTH. */ - page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, 0, + page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, rx_offset, RX_BUF_LENGTH); - hard_start = page_address(rx_page); + hard_start = page_address(rx_page) + rx_offset; status = (struct status_64 *)hard_start; dma_length_status = status->length_status; @@ -2410,7 +2421,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Build SKB from the page - data starts at hard_start, * frame begins after RSB(64) + pad(2) = 66 bytes. */ - skb = napi_build_skb(hard_start, PAGE_SIZE); + skb = napi_build_skb(hard_start, rx_size); if (unlikely(!skb)) { BCMGENET_STATS64_INC(stats, dropped); page_pool_put_full_page(ring->page_pool, rx_page, @@ -2762,14 +2773,16 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv, struct bcmgenet_rx_ring *ring) { + /* Buffers share a page. bcmgenet_rx_refill() syncs each one for the + * device, PP_FLAG_DMA_SYNC_DEV would sync the whole page. + */ struct page_pool_params pp_params = { .order = 0, - .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, + .flags = PP_FLAG_DMA_MAP, .pool_size = ring->size, .nid = NUMA_NO_NODE, .dev = &priv->pdev->dev, .dma_dir = DMA_FROM_DEVICE, - .max_len = RX_BUF_LENGTH, }; int err; @@ -4138,8 +4151,11 @@ static int bcmgenet_probe(struct platform_device *pdev) netif_set_real_num_rx_queues(priv->dev, priv->hw_params->rx_queues + 1); /* Set default coalescing parameters */ - for (i = 0; i <= priv->hw_params->rx_queues; i++) + for (i = 0; i <= priv->hw_params->rx_queues; i++) { + priv->rx_rings[i].priv = priv; + priv->rx_rings[i].index = i; priv->rx_rings[i].rx_max_coalesced_frames = 1; + } /* Initialize u64 stats seq counter for 32bit machines */ for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 22a958ba99024b..86f2aed20dbe87 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -470,6 +470,8 @@ struct bcmgenet_rx_stats64 { struct enet_cb { struct sk_buff *skb; struct page *rx_page; + unsigned int rx_offset; + unsigned int rx_size; void __iomem *bd_addr; DEFINE_DMA_UNMAP_ADDR(dma_addr); DEFINE_DMA_UNMAP_LEN(dma_len); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c index 96d5d4f7f51fe8..984432952963ad 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c @@ -253,6 +253,17 @@ int bcmgenet_wol_power_up_cfg(struct bcmgenet_priv *priv, reg = bcmgenet_umac_readl(priv, UMAC_CMD); reg &= ~CMD_CRC_FWD; bcmgenet_umac_writel(priv, reg, UMAC_CMD); + + /* + * Mirror wol_power_down_cfg(). If only UMAC_RX + * is enabled, then we must place the UMAC back + * into SW_RESET. + */ + reg = bcmgenet_umac_readl(priv, UMAC_CMD); + if ((reg & CMD_RX_EN) && !(reg & CMD_TX_EN)) { + reg |= CMD_SW_RESET; + bcmgenet_umac_writel(priv, reg, UMAC_CMD); + } spin_unlock_bh(&priv->reg_lock); /* Resume link status tracking */ diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8e5c034dc3a487..261a7e87520a47 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2421,34 +2421,40 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +static bool macb_needs_sw_fcs(struct sk_buff *skb, struct net_device *netdev) +{ + return netdev->features & NETIF_F_HW_CSUM && + skb->ip_summed != CHECKSUM_PARTIAL && + !skb_shinfo(skb)->gso_size && !ptp_one_step_sync(skb); +} + /* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) +static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); int padlen = ETH_ZLEN - (*skb)->len; int tailroom = skb_tailroom(*skb); + bool shared = skb_shared(*skb); struct sk_buff *nskb; u32 fcs; - if (!(netdev->features & NETIF_F_HW_CSUM) || - !((*skb)->ip_summed != CHECKSUM_PARTIAL) || - skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) + if (!add_fcs) return 0; if (padlen <= 0) { - /* FCS could be appeded to tailroom. */ - if (tailroom >= ETH_FCS_LEN) + /* FCS could be appended to tailroom. */ + if (!shared && !skb_is_nonlinear(*skb) && + tailroom >= ETH_FCS_LEN) goto add_fcs; - /* No room for FCS, need to reallocate skb. */ - else - padlen = ETH_FCS_LEN; + /* Reallocate with room for the FCS. */ + padlen = ETH_FCS_LEN; } else { /* Add room for FCS. */ padlen += ETH_FCS_LEN; } - if (cloned || tailroom < padlen) { + if (shared || cloned || tailroom < padlen) { nskb = skb_copy_expand(*skb, 0, padlen, GFP_ATOMIC); if (!nskb) return -ENOMEM; @@ -2481,27 +2487,15 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, unsigned int desc_cnt, nr_frags, frag_size, f; struct macb_queue *queue = &bp->queues[q]; netdev_tx_t ret = NETDEV_TX_OK; - unsigned int hdrlen; + unsigned int hdrlen, tx_len; + bool add_fcs, is_lso; unsigned long flags; int fcs_len; - bool is_lso; - - if (macb_clear_csum(skb)) { - dev_kfree_skb_any(skb); - return ret; - } - - fcs_len = macb_pad_and_fcs(&skb, netdev); - if (fcs_len < 0) { - dev_kfree_skb_any(skb); - return ret; - } - - if (macb_dma_ptp(bp) && - (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + add_fcs = macb_needs_sw_fcs(skb, netdev); is_lso = (skb_shinfo(skb)->gso_size != 0); + tx_len = add_fcs ? max_t(unsigned int, skb->len, ETH_ZLEN) + + ETH_FCS_LEN : skb->len; if (is_lso) { /* length of headers */ @@ -2515,8 +2509,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } - } else + } else if (add_fcs) { + hdrlen = umin(tx_len, bp->max_tx_length); + } else { hdrlen = umin(skb_headlen(skb), bp->max_tx_length); + } #if defined(DEBUG) && defined(VERBOSE_DEBUG) netdev_vdbg(bp->netdev, @@ -2531,12 +2528,18 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, * socket buffer: skb fragments of jumbo frames may need to be * split into many buffer descriptors. */ - if (is_lso && (skb_headlen(skb) > hdrlen)) + if (add_fcs) { + /* macb_pad_and_fcs() linearizes the skb before adding the FCS. */ + desc_cnt = DIV_ROUND_UP(tx_len, bp->max_tx_length); + nr_frags = 0; + } else if (is_lso && (skb_headlen(skb) > hdrlen)) { /* extra header descriptor if also payload in first buffer */ desc_cnt = DIV_ROUND_UP((skb_headlen(skb) - hdrlen), bp->max_tx_length) + 1; - else + nr_frags = skb_shinfo(skb)->nr_frags; + } else { desc_cnt = DIV_ROUND_UP(skb_headlen(skb), bp->max_tx_length); - nr_frags = skb_shinfo(skb)->nr_frags; + nr_frags = skb_shinfo(skb)->nr_frags; + } for (f = 0; f < nr_frags; f++) { frag_size = skb_frag_size(&skb_shinfo(skb)->frags[f]); desc_cnt += DIV_ROUND_UP(frag_size, bp->max_tx_length); @@ -2554,6 +2557,21 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, goto unlock; } + if (macb_clear_csum(skb)) { + dev_kfree_skb_any(skb); + goto unlock; + } + + fcs_len = macb_pad_and_fcs(&skb, add_fcs); + if (fcs_len < 0) { + dev_kfree_skb_any(skb); + goto unlock; + } + + if (macb_dma_ptp(bp) && + (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + /* Map socket buffer for DMA transfer */ if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c index f5acd4be1e69dc..dbec95b4977736 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c +++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c @@ -389,7 +389,7 @@ static void chcr_ktls_dev_del(struct net_device *netdev, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); } port_stats = &tx_info->adap->ch_ktls_stats.ktls_port[tx_info->port_id]; @@ -577,7 +577,7 @@ static int chcr_ktls_dev_add(struct net_device *netdev, struct sock *sk, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); put_module: /* release module refcount */ diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 9cc343a1627125..a6c18e2431cc7e 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -1708,17 +1708,17 @@ static int gve_xsk_pool_disable(struct net_device *dev, } napi_rx = &priv->ntfy_blocks[priv->rx[qid].ntfy_id].napi; - napi_disable(napi_rx); /* make sure current rx poll is done */ + napi_disable_locked(napi_rx); /* make sure current rx poll is done */ tx_qid = gve_xdp_tx_queue_id(priv, qid); napi_tx = &priv->ntfy_blocks[priv->tx[tx_qid].ntfy_id].napi; - napi_disable(napi_tx); /* make sure current tx poll is done */ + napi_disable_locked(napi_tx); /* make sure current tx poll is done */ gve_unreg_xsk_pool(priv, qid); smp_mb(); /* Make sure it is visible to the workers on datapath */ - napi_enable(napi_rx); - napi_enable(napi_tx); + napi_enable_locked(napi_rx); + napi_enable_locked(napi_tx); if (gve_is_gqi(priv)) { if (gve_rx_work_pending(&priv->rx[qid])) napi_schedule(napi_rx); diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 5cf242b285577e..82ed5e48b00abd 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -113,6 +113,12 @@ static void gve_rx_reset_ring_dqo(struct gve_priv *priv, int idx) gve_free_to_page_pool(rx, bs, false); else gve_free_qpl_page_dqo(bs); + + if (gve_buf_state_is_allocated(rx, bs) && + bs->xsk_buff) { + xsk_buff_free(bs->xsk_buff); + bs->xsk_buff = NULL; + } } } @@ -836,7 +842,12 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } if (unlikely(compl_desc->rx_error)) { - gve_free_buffer(rx, buf_state); + if (buf_state->xsk_buff) { + xsk_buff_free(buf_state->xsk_buff); + gve_free_buf_state(rx, buf_state); + } else { + gve_free_buffer(rx, buf_state); + } return -EINVAL; } diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 616c1921aebeac..ad99cbb77e291b 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -595,25 +595,25 @@ static int gve_prep_tso(struct sk_buff *skb) err = skb_cow_head(skb, 0); if (err < 0) return err; + shinfo = skb_shinfo(skb); l4_start = skb_transport_offset(skb); paylen = skb->len - l4_start; - switch (shinfo->gso_type) { - case SKB_GSO_TCPV4: - case SKB_GSO_TCPV6: + /* gso_type is a bitmask: SKB_GSO_DODGY, SKB_GSO_TCP_ECN and + * SKB_GSO_TCP_FIXEDID may be set alongside the protocol bit. + */ + if (shinfo->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) { tcp = tcp_hdr(skb); csum_replace_by_diff(&tcp->check, (__force __wsum)htonl(paylen)); header_len = skb_tcp_all_headers(skb); - break; - case SKB_GSO_UDP_L4: + } else if (shinfo->gso_type & SKB_GSO_UDP_L4) { udp = udp_hdr(skb); csum_replace_by_diff(&udp->check, (__force __wsum)htonl(paylen)); header_len = sizeof(struct udphdr) + l4_start; - break; - default: + } else { return -EINVAL; } diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 73e051d26b9d88..abebdb1fc26289 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -553,10 +553,15 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx) { + void *ptr = adapter->tx_ltb_ptr[idx]; + + if (!ptr) + return; + + adapter->tx_ltb_ptr[idx] = NULL; dma_unmap_single(&adapter->vdev->dev, adapter->tx_ltb_dma[idx], adapter->tx_ltb_size, DMA_TO_DEVICE); - kfree(adapter->tx_ltb_ptr[idx]); - adapter->tx_ltb_ptr[idx] = NULL; + kfree(ptr); } static int ibmveth_allocate_tx_ltb(struct ibmveth_adapter *adapter, int idx) @@ -667,7 +672,7 @@ static int ibmveth_open(struct net_device *netdev) for (i = 0; i < netdev->real_num_tx_queues; i++) { if (ibmveth_allocate_tx_ltb(adapter, i)) - goto out_free_tx_ltb; + goto out_unmap_filter_list; } adapter->rx_queue.index = 0; @@ -718,10 +723,6 @@ static int ibmveth_open(struct net_device *netdev) if (rc != 0) { netdev_err(netdev, "unable to request irq 0x%x, rc %d\n", netdev->irq, rc); - do { - lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); - } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); - goto out_free_buffer_pools; } @@ -737,6 +738,9 @@ static int ibmveth_open(struct net_device *netdev) return 0; out_free_buffer_pools: + do { + lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); + } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); while (--i >= 0) { if (adapter->rx_buff_pool[i].active) ibmveth_free_buffer_pool(adapter, @@ -746,10 +750,8 @@ static int ibmveth_open(struct net_device *netdev) dma_unmap_single(dev, adapter->filter_list_dma, 4096, DMA_BIDIRECTIONAL); -out_free_tx_ltb: - while (--i >= 0) { + for (i = netdev->real_num_tx_queues - 1; i >= 0; i--) ibmveth_free_tx_ltb(adapter, i); - } out_unmap_buffer_list: dma_unmap_single(dev, adapter->buffer_list_dma, 4096, diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 844f31ab37ad44..c282f65199e49e 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -67,6 +67,13 @@ static const struct dmi_system_id disable_k1_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro 16 Plus PB16250"), }, }, + { + .ident = "Lenovo ThinkPad P14s Gen 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad P14s Gen 5"), + }, + }, {} }; diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c index a26c3d47ec1561..c6732a24b64002 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c @@ -4943,6 +4943,20 @@ int i40e_ndo_set_vf_spoofchk(struct net_device *netdev, int vf_id, bool enable) return ret; } +/** + * i40e_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void i40e_setup_vf_trust(struct i40e_vf *vf, bool setting) +{ + assign_bit(I40E_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * i40e_ndo_set_vf_trust * @netdev: network interface device structure of the pf @@ -4987,19 +5001,20 @@ int i40e_ndo_set_vf_trust(struct net_device *netdev, int vf_id, bool setting) set_bit(__I40E_MACVLAN_SYNC_PENDING, pf->state); pf->vsi[vf->lan_vsi_idx]->flags |= I40E_VSI_FLAG_FILTER_CHANGED; - i40e_vc_reset_vf(vf, true); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!setting && + (vf->adq_enabled || vf->num_cloud_filters > 0 || + test_bit(I40E_VF_STATE_UC_PROMISC, &vf->vf_states) || + test_bit(I40E_VF_STATE_MC_PROMISC, &vf->vf_states))) { + i40e_vc_reset_vf(vf, true); + i40e_del_all_cloud_filters(vf); + } else { + i40e_setup_vf_trust(vf, setting); + } + dev_info(&pf->pdev->dev, "VF %u is now %strusted\n", vf_id, setting ? "" : "un"); - if (vf->adq_enabled) { - if (!vf->trusted) { - dev_info(&pf->pdev->dev, - "VF %u no longer Trusted, deleting all cloud filters\n", - vf_id); - i40e_del_all_cloud_filters(vf); - } - } - out: clear_bit(__I40E_VIRTCHNL_OP_PENDING, pf->state); return ret; diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index dc31202b2a946e..8c45536fd50294 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -259,7 +259,6 @@ struct iavf_adapter { struct work_struct adminq_task; struct work_struct finish_config; wait_queue_head_t down_waitqueue; - wait_queue_head_t vc_waitqueue; struct iavf_q_vector *q_vectors; struct list_head vlan_filter_list; int num_vlan_filters; @@ -588,8 +587,9 @@ void iavf_configure_queues(struct iavf_adapter *adapter); void iavf_enable_queues(struct iavf_adapter *adapter); void iavf_disable_queues(struct iavf_adapter *adapter); void iavf_map_queues(struct iavf_adapter *adapter); -void iavf_add_ether_addrs(struct iavf_adapter *adapter); +int iavf_add_ether_addrs(struct iavf_adapter *adapter); void iavf_del_ether_addrs(struct iavf_adapter *adapter); +void iavf_mac_add_reject(struct iavf_adapter *adapter); void iavf_add_vlans(struct iavf_adapter *adapter); void iavf_del_vlans(struct iavf_adapter *adapter); void iavf_set_promiscuous(struct iavf_adapter *adapter); @@ -606,6 +606,13 @@ void iavf_disable_vlan_stripping(struct iavf_adapter *adapter); void iavf_virtchnl_completion(struct iavf_adapter *adapter, enum virtchnl_ops v_opcode, enum iavf_status v_retval, u8 *msg, u16 msglen); +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms); int iavf_config_rss(struct iavf_adapter *adapter); void iavf_cfg_queues_bw(struct iavf_adapter *adapter); void iavf_cfg_queues_quanta_size(struct iavf_adapter *adapter); diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index 29b8403a066bc8..2f8a80a1133626 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1029,6 +1029,60 @@ static bool iavf_is_mac_set_handled(struct net_device *netdev, return ret; } +/** + * iavf_mac_change_done - Check if MAC change completed + * @adapter: board private structure + * @data: MAC address being checked (as const void *) + * @v_op: virtchnl opcode from processed message + * + * Callback for iavf_poll_virtchnl_response() to check if MAC change completed. + * + * Return: true if MAC change completed, false otherwise + */ +static bool iavf_mac_change_done(struct iavf_adapter *adapter, + const void *data, enum virtchnl_ops v_op) +{ + const u8 *addr = data; + + return iavf_is_mac_set_handled(adapter->netdev, addr); +} + +/** + * iavf_set_mac_sync - Synchronously change MAC address + * @adapter: board private structure + * @addr: MAC address to set + * + * Send MAC change request to PF and poll admin queue for response. + * Caller must hold netdev_lock. This can sleep for up to 2.5 seconds. + * Event buffer is allocated before sending to avoid state mismatch if + * allocation fails after message is sent to PF. + * + * Return: 0 on success, negative on failure + */ +static int iavf_set_mac_sync(struct iavf_adapter *adapter, const u8 *addr) +{ + struct iavf_arq_event_info event; + int ret; + + netdev_assert_locked(adapter->netdev); + + event.buf_len = IAVF_MAX_AQ_BUF_SIZE; + event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL); + if (!event.msg_buf) + return -ENOMEM; + + ret = iavf_add_ether_addrs(adapter); + if (ret) + goto out; + + ret = iavf_poll_virtchnl_response(adapter, &event, + iavf_mac_change_done, addr, 2500); + +out: + kfree(event.msg_buf); + return ret; +} + /** * iavf_set_mac - NDO callback to set port MAC address * @netdev: network interface device structure @@ -1046,25 +1100,23 @@ static int iavf_set_mac(struct net_device *netdev, void *p) return -EADDRNOTAVAIL; ret = iavf_replace_primary_mac(adapter, addr->sa_data); - if (ret) return ret; - ret = wait_event_interruptible_timeout(adapter->vc_waitqueue, - iavf_is_mac_set_handled(netdev, addr->sa_data), - msecs_to_jiffies(2500)); - - /* If ret < 0 then it means wait was interrupted. - * If ret == 0 then it means we got a timeout. - * else it means we got response for set MAC from PF, - * check if netdev MAC was updated to requested MAC, - * if yes then set MAC succeeded otherwise it failed return -EACCES - */ - if (ret < 0) + ret = iavf_set_mac_sync(adapter, addr->sa_data); + if (ret) { + /* Rollback only if send failed (message never reached PF). + * Don't rollback on timeout (-EAGAIN) because the message was + * sent and PF will eventually respond. When the response arrives, + * iavf_virtchnl_completion() will handle rollback (on PF error) + * or acceptance (on PF success) automatically. + */ + if (ret != -EAGAIN) { + iavf_mac_add_reject(adapter); + ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); + } return ret; - - if (!ret) - return -EAGAIN; + } if (!ether_addr_equal(netdev->dev_addr, addr->sa_data)) return -EACCES; @@ -5394,9 +5446,6 @@ static int iavf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* Setup the wait queue for indicating transition to down status */ init_waitqueue_head(&adapter->down_waitqueue); - /* Setup the wait queue for indicating virtchannel events */ - init_waitqueue_head(&adapter->vc_waitqueue); - INIT_LIST_HEAD(&adapter->ptp.aq_cmds); init_waitqueue_head(&adapter->ptp.phc_time_waitqueue); mutex_init(&adapter->ptp.aq_cmd_lock); diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c index ec234cc8bd9db0..e6b7e8f82c7c1f 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c @@ -2,6 +2,7 @@ /* Copyright(c) 2013 - 2018 Intel Corporation. */ #include +#include #include "iavf.h" #include "iavf_ptp.h" @@ -555,20 +556,23 @@ iavf_set_mac_addr_type(struct virtchnl_ether_addr *virtchnl_ether_addr, * @adapter: adapter structure * * Request that the PF add one or more addresses to our filters. - **/ -void iavf_add_ether_addrs(struct iavf_adapter *adapter) + * + * Return: 0 on success, negative on failure + */ +int iavf_add_ether_addrs(struct iavf_adapter *adapter) { struct virtchnl_ether_addr_list *veal; struct iavf_mac_filter *f; int i = 0, count = 0; bool more = false; size_t len; + int ret; if (adapter->current_op != VIRTCHNL_OP_UNKNOWN) { /* bail because we already have a command pending */ dev_err(&adapter->pdev->dev, "Cannot add filters, command %d pending\n", adapter->current_op); - return; + return -EBUSY; } spin_lock_bh(&adapter->mac_vlan_list_lock); @@ -580,7 +584,7 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) if (!count) { adapter->aq_required &= ~IAVF_FLAG_AQ_ADD_MAC_FILTER; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return 0; } adapter->current_op = VIRTCHNL_OP_ADD_ETH_ADDR; @@ -594,8 +598,9 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) veal = kzalloc(len, GFP_ATOMIC); if (!veal) { + adapter->current_op = VIRTCHNL_OP_UNKNOWN; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return -ENOMEM; } veal->vsi_id = adapter->vsi_res->vsi_id; @@ -615,8 +620,15 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) spin_unlock_bh(&adapter->mac_vlan_list_lock); - iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); + ret = iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); kfree(veal); + if (ret) { + dev_err(&adapter->pdev->dev, + "Unable to send ADD_ETH_ADDR message to PF, error %d\n", ret); + adapter->current_op = VIRTCHNL_OP_UNKNOWN; + } + + return ret; } /** @@ -712,8 +724,8 @@ static void iavf_mac_add_ok(struct iavf_adapter *adapter) * @adapter: adapter structure * * Remove filters from list based on PF response. - **/ -static void iavf_mac_add_reject(struct iavf_adapter *adapter) + */ +void iavf_mac_add_reject(struct iavf_adapter *adapter) { struct net_device *netdev = adapter->netdev; struct iavf_mac_filter *f, *ftmp; @@ -2364,7 +2376,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, iavf_mac_add_reject(adapter); /* restore administratively set MAC address */ ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_DEL_ETH_ADDR: dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n", @@ -2555,7 +2566,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, eth_hw_addr_set(netdev, adapter->hw.mac.addr); netif_addr_unlock_bh(netdev); } - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_GET_STATS: { struct iavf_eth_stats *stats = @@ -2950,3 +2960,72 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, } /* switch v_opcode */ adapter->current_op = VIRTCHNL_OP_UNKNOWN; } + +/** + * iavf_poll_virtchnl_response - Poll admin queue for virtchnl response + * @adapter: adapter structure + * @event: pre-allocated event buffer to use for polling + * @condition: callback to check if desired response received + * @cond_data: context data passed to condition callback + * @timeout_ms: maximum time to wait in milliseconds + * + * Polls the admin queue and processes all incoming virtchnl messages. + * After processing each valid message, calls the condition callback to check + * if the expected response has been received. The callback receives the opcode + * of the processed message to identify which response was received. Continues + * polling until the callback returns true or timeout expires. + * + * Caller must allocate event buffer before sending any messages to PF to avoid + * state mismatch if allocation fails after message is sent. + * + * Caller must hold netdev_lock. This can sleep for up to timeout_ms while + * polling hardware. + * + * Return: 0 on success (condition met), -EAGAIN on timeout, or error code + */ +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms) +{ + struct iavf_hw *hw = &adapter->hw; + enum virtchnl_ops received_op; + unsigned long timeout; + int ret = -EAGAIN; + u16 pending = 0; + u32 v_retval; + + netdev_assert_locked(adapter->netdev); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + if (!pending) + usleep_range(50, 75); + + if (iavf_clean_arq_element(hw, event, &pending) == IAVF_SUCCESS) { + received_op = (enum virtchnl_ops)le32_to_cpu(event->desc.cookie_high); + if (received_op != VIRTCHNL_OP_UNKNOWN) { + v_retval = le32_to_cpu(event->desc.cookie_low); + + iavf_virtchnl_completion(adapter, received_op, + (enum iavf_status)v_retval, + event->msg_buf, event->msg_len); + + if (condition(adapter, cond_data, received_op)) { + ret = 0; + break; + } + } + + memset(event->msg_buf, 0, IAVF_MAX_AQ_BUF_SIZE); + + if (pending) + continue; + } + } while (time_before(jiffies, timeout)); + + return ret; +} diff --git a/drivers/net/ethernet/intel/ice/devlink/port.c b/drivers/net/ethernet/intel/ice/devlink/port.c index 2a2e56777f9f75..3ede2464900272 100644 --- a/drivers/net/ethernet/intel/ice/devlink/port.c +++ b/drivers/net/ethernet/intel/ice/devlink/port.c @@ -590,8 +590,8 @@ static void ice_dealloc_dynamic_port(struct ice_dynamic_port *dyn_port) xa_erase(&pf->sf_nums, devlink_port->attrs.pci_sf.sf); ice_eswitch_detach_sf(pf, dyn_port); - ice_vsi_free(dyn_port->vsi); xa_erase(&pf->dyn_ports, dyn_port->vsi->idx); + ice_vsi_free(dyn_port->vsi); kfree(dyn_port); } diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c index b069e6c514fb12..6e7bba4738989b 100644 --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c @@ -95,7 +95,7 @@ ice_eswitch_release_repr(struct ice_pf *pf, struct ice_repr *repr) return; ice_vsi_update_security(vsi, ice_vsi_ctx_set_antispoof); - metadata_dst_free(repr->dst); + dst_release(&repr->dst->dst); repr->dst = NULL; ice_fltr_add_mac_and_broadcast(vsi, repr->parent_mac, ICE_FWD_TO_VSI); diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c index e04de021559665..f893dff39aac60 100644 --- a/drivers/net/ethernet/intel/ice/ice_sriov.c +++ b/drivers/net/ethernet/intel/ice/ice_sriov.c @@ -1366,6 +1366,20 @@ int ice_set_vf_mac(struct net_device *netdev, int vf_id, u8 *mac) return __ice_set_vf_mac(ice_netdev_to_pf(netdev), vf_id, mac); } +/** + * ice_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void ice_setup_vf_trust(struct ice_vf *vf, bool setting) +{ + assign_bit(ICE_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * ice_set_vf_trust * @netdev: network interface device structure @@ -1401,11 +1415,19 @@ int ice_set_vf_trust(struct net_device *netdev, int vf_id, bool trusted) mutex_lock(&vf->cfg_lock); - while (!trusted && vf->num_mac_lldp) - ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); - - vf->trusted = trusted; - ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!trusted && + (vf->num_mac_lldp > 0 || + test_bit(ICE_VF_STATE_UC_PROMISC, vf->vf_states) || + test_bit(ICE_VF_STATE_MC_PROMISC, vf->vf_states))) { + while (vf->num_mac_lldp) + ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); + vf->trusted = trusted; + ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + } else { + vf->trusted = trusted; + ice_setup_vf_trust(vf, trusted); + } dev_info(ice_pf_to_dev(pf), "VF %u is now %strusted\n", vf_id, trusted ? "" : "un"); diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 31303ab5be175a..a2c7c4962882da 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1561,10 +1561,10 @@ ice_tx_map(struct ice_tx_ring *tx_ring, struct ice_tx_buf *first, } } tstamp_ring->next_to_use = j; - writel_relaxed(j, tstamp_ring->tail); + writel(j, tstamp_ring->tail); } else { ring_kick: - writel_relaxed(i, tx_ring->tail); + writel(i, tx_ring->tail); } return; diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 543e566425c1e5..4f479b6f731ebf 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -2340,7 +2340,7 @@ mvneta_swbm_rx_frame(struct mvneta_port *pp, /* Prefetch header */ prefetch(data); - xdp_buff_clear_frags_flag(xdp); + xdp_reinit_buff(xdp); xdp_prepare_buff(xdp, data, pp->rx_offset_correction + MVNETA_MH_SIZE, data_len, true); } diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c index 211c10aa5880f2..30c672d526a0cf 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c @@ -314,7 +314,7 @@ int mcs_clear_all_stats(struct mcs *mcs, u16 pcifunc, int dir) } /* Clear SC stats */ - for (id = 0; id < map->secy.max; id++) { + for (id = 0; id < map->sc.max; id++) { if (map->sc2pf_map[id] != pcifunc) continue; mcs_clear_stats(mcs, MCS_SC_STATS, id, dir); @@ -738,7 +738,7 @@ int mcs_free_all_rsrc(struct mcs *mcs, int dir, u16 pcifunc) } /* free sc entries */ - for (id = 0; id < map->secy.max; id++) { + for (id = 0; id < map->sc.max; id++) { if (map->sc2pf_map[id] != pcifunc) continue; mcs_free_rsrc(&map->sc, map->sc2pf_map, id, pcifunc); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index 8e41431c7f9c70..e92b77789f088c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -242,15 +242,6 @@ int cn20k_register_pfvf_mbox_intr(struct otx2_nic *pf, int numvfs) #define RQ_BP_LVL_AURA (255 - ((85 * 256) / 100)) /* BP when 85% is full */ -static u8 cn20k_aura_bpid_idx(struct otx2_nic *pfvf, int aura_id) -{ -#ifdef CONFIG_DCB - return pfvf->queue_to_pfc_map[aura_id]; -#else - return 0; -#endif -} - static int cn20k_tc_get_entry_index(struct otx2_flow_config *flow_cfg, struct otx2_tc_flow *node) { @@ -580,7 +571,7 @@ static int cn20k_aura_aq_init(struct otx2_nic *pfvf, int aura_id, if (pfvf->nix_blkaddr == BLKADDR_NIX1) aq->aura.bp_ena = 1; - bpid_idx = cn20k_aura_bpid_idx(pfvf, aura_id); + bpid_idx = otx2_get_bpid_idx(pfvf, aura_id); aq->aura.bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level for RQ's Aura */ diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 175992188c18a4..b11389a841d159 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -394,7 +394,7 @@ int otx2_rss_init(struct otx2_nic *pfvf) struct otx2_rss_info *rss = &pfvf->hw.rss_info; int idx, ret = 0; - rss->rss_size = sizeof(*rss->ind_tbl); + rss->rss_size = ARRAY_SIZE(rss->ind_tbl); /* Init RSS key if it is not setup already */ if (!rss->enable) @@ -1055,6 +1055,7 @@ int otx2_cq_init(struct otx2_nic *pfvf, u16 qidx) struct nix_aq_enq_req *aq; struct otx2_cq_queue *cq; struct otx2_pool *pool; + u8 bpid_idx; cq = &qset->cq[qidx]; cq->cq_idx = qidx; @@ -1132,11 +1133,8 @@ int otx2_cq_init(struct otx2_nic *pfvf, u16 qidx) if (!is_otx2_lbkvf(pfvf->pdev)) { /* Enable receive CQ backpressure */ aq->cq.bp_ena = 1; -#ifdef CONFIG_DCB - aq->cq.bpid = pfvf->bpid[pfvf->queue_to_pfc_map[qidx]]; -#else - aq->cq.bpid = pfvf->bpid[0]; -#endif + bpid_idx = otx2_get_bpid_idx(pfvf, qidx); + aq->cq.bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level is same as cq pass level */ aq->cq.bp = RQ_PASS_LVL_CQ(pfvf->hw.rq_skid, qset->rqe_cnt); @@ -1378,6 +1376,7 @@ int otx2_aura_aq_init(struct otx2_nic *pfvf, int aura_id, { struct npa_aq_enq_req *aq; struct otx2_pool *pool; + u8 bpid_idx; int err; pool = &pfvf->qset.pool[pool_id]; @@ -1433,11 +1432,8 @@ int otx2_aura_aq_init(struct otx2_nic *pfvf, int aura_id, */ if (pfvf->nix_blkaddr == BLKADDR_NIX1) aq->aura.bp_ena = 1; -#ifdef CONFIG_DCB - aq->aura.nix0_bpid = pfvf->bpid[pfvf->queue_to_pfc_map[aura_id]]; -#else - aq->aura.nix0_bpid = pfvf->bpid[0]; -#endif + bpid_idx = otx2_get_bpid_idx(pfvf, aura_id); + aq->aura.nix0_bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level for RQ's Aura */ aq->aura.bp = RQ_BP_LVL_AURA; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h index eecee612b7b2c9..44cd56a0c2c4ca 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h @@ -1029,6 +1029,15 @@ static inline int otx2_tc_flower_rule_cnt(struct otx2_nic *pfvf) return pfvf->flow_cfg->nr_flows; } +static inline u8 otx2_get_bpid_idx(struct otx2_nic *pfvf, int qidx) +{ +#ifdef CONFIG_DCB + if (pfvf->queue_to_pfc_map) + return pfvf->queue_to_pfc_map[qidx]; +#endif + return 0; +} + /* MSI-X APIs */ void otx2_free_cints(struct otx2_nic *pfvf, int n); void otx2_set_cints_affinity(struct otx2_nic *pfvf); diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 2ea5dfe85539b8..ad0fdc4133fb68 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -5341,6 +5341,22 @@ static int mtk_probe(struct platform_device *pdev) } } + /* we run 2 devices on the same DMA ring so we need a dummy device + * for NAPI to work. Allocate it before registering the netdevs so a + * concurrent ndo_open (e.g. netifd bringing the first netdev up the + * instant it is registered) never observes eth->dummy_dev == NULL in + * mtk_dma_init() -> mtk_rx_alloc() -> __xdp_rxq_info_reg() (net/core/xdp.c + * "Missing net_device from driver") and fails the first open. + */ + eth->dummy_dev = alloc_netdev_dummy(0); + if (!eth->dummy_dev) { + err = -ENOMEM; + dev_err(eth->dev, "failed to allocated dummy device\n"); + goto err_unreg_netdev; + } + netif_napi_add(eth->dummy_dev, ð->tx_napi, mtk_napi_tx); + netif_napi_add(eth->dummy_dev, ð->rx_napi, mtk_napi_rx); + for (i = 0; i < MTK_MAX_DEVS; i++) { if (!eth->netdev[i]) continue; @@ -5355,18 +5371,6 @@ static int mtk_probe(struct platform_device *pdev) eth->netdev[i]->base_addr, eth->irq[MTK_FE_IRQ_SHARED]); } - /* we run 2 devices on the same DMA ring so we need a dummy device - * for NAPI to work - */ - eth->dummy_dev = alloc_netdev_dummy(0); - if (!eth->dummy_dev) { - err = -ENOMEM; - dev_err(eth->dev, "failed to allocated dummy device\n"); - goto err_unreg_netdev; - } - netif_napi_add(eth->dummy_dev, ð->tx_napi, mtk_napi_tx); - netif_napi_add(eth->dummy_dev, ð->rx_napi, mtk_napi_rx); - platform_set_drvdata(pdev, eth); schedule_delayed_work(ð->reset.monitor_work, MTK_DMA_MONITOR_TIMEOUT); @@ -5376,6 +5380,16 @@ static int mtk_probe(struct platform_device *pdev) err_unreg_netdev: mtk_unreg_dev(eth); err_deinit_ppe: + /* A netdev opened while probe was still registering can have queued + * the frame-engine reset worker, which takes rtnl and drives the + * hardware; cancel it before anything below is torn down. + */ + cancel_work_sync(ð->pending_work); + if (eth->dummy_dev) { + netif_napi_del(ð->tx_napi); + netif_napi_del(ð->rx_napi); + free_netdev(eth->dummy_dev); + } mtk_ppe_deinit(eth); mtk_mdio_cleanup(eth); err_free_dev: diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index 77ea51bfbaae58..2cca20e1b9b78b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -293,6 +293,9 @@ static u64 mlx5e_xsk_fill_timestamp(void *_priv) struct mlx5e_xsk_tx_complete *priv = _priv; u64 ts; + if (!priv->cqe) + return 0; + ts = get_cqe_ts(priv->cqe); if (mlx5_is_real_time_rq(priv->cq->mdev) || mlx5_is_real_time_sq(priv->cq->mdev)) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 7bd0606a525385..903af3b3e7794e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -908,10 +908,12 @@ int mlx5e_poll_ico_cq(struct mlx5e_cq *cq) } while (!last_wqe); } while ((++i < MLX5E_TX_CQ_POLL_BUDGET) && (cqe = mlx5_cqwq_get_cqe(&cq->wq))); - sq->cc = sqcc; - mlx5_cqwq_update_db_record(&cq->wq); + /* ensure cq space is freed before enabling more cqes */ + dma_wmb(); + + sq->cc = sqcc; return i; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index 7950b82d8b8aec..3624c680a861a5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -11,26 +11,6 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, lockdep_assert_held(&dev->priv.eswitch->state_lock); - if (MLX5_CAP_ESW(dev, esw_vport_state_max_tx_speed)) { - u8 op_mod = MLX5_VPORT_STATE_OP_MOD_ESW_VPORT; - struct mlx5_vport *esw_vport; - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(dev, op_mod, vport, - true, &speed, NULL); - if (err) { - esw_vport = mlx5_eswitch_get_vport(dev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; - mlx5_core_dbg(dev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index dd14cdc378de01..4e173b08cb37af 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1432,30 +1432,40 @@ static bool mlx5_lag_should_disable_lag(struct mlx5_lag *ldev, bool do_bond) } #ifdef CONFIG_MLX5_ESWITCH -static int -mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, - int (*get_speed)(struct mlx5_core_dev *, u32 *)) +static int mlx5_lag_get_devices_oper_speed(struct mlx5_lag *ldev, + u32 *sum_speed) { - struct mlx5_core_dev *pf_mdev; - struct lag_func *pf; int pf_idx; - u32 speed; - int ret; *sum_speed = 0; mlx5_ldev_for_each(pf_idx, 0, ldev) { + u8 opmod = MLX5_VPORT_STATE_OP_MOD_VNIC_VPORT; + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + u8 state; + int ret; + pf = mlx5_lag_pf(ldev, pf_idx); if (!pf) continue; pf_mdev = pf->dev; if (!pf_mdev) continue; + ret = mlx5_query_vport_max_tx_speed(pf_mdev, opmod, 0, 0, + &speed, &state); + if (ret) { + mlx5_core_dbg(pf_mdev, "State query failed (err=%d)\n", + ret); + return ret; + } + if (state != VPORT_STATE_UP) + continue; - ret = get_speed(pf_mdev, &speed); + ret = mlx5_port_oper_linkspeed(pf_mdev, &speed); if (ret) { mlx5_core_dbg(pf_mdev, - "Failed to get device speed using %ps. Device %s speed is not available (err=%d)\n", - get_speed, dev_name(pf_mdev->device), + "Failed to get oper speed (err=%d)\n", ret); return ret; } @@ -1466,17 +1476,42 @@ mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, return 0; } -static int mlx5_lag_sum_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) +static int mlx5_lag_get_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) { - return mlx5_lag_sum_devices_speed(ldev, max_speed, - mlx5_port_max_linkspeed); -} + bool take_max; + int pf_idx; -static int mlx5_lag_sum_devices_oper_speed(struct mlx5_lag *ldev, - u32 *oper_speed) -{ - return mlx5_lag_sum_devices_speed(ldev, oper_speed, - mlx5_port_oper_linkspeed); + take_max = ldev->tracker.tx_type == NETDEV_LAG_TX_TYPE_ACTIVEBACKUP; + if (ldev->mode == MLX5_LAG_MODE_MPESW) + take_max = false; + + *max_speed = 0; + mlx5_ldev_for_each(pf_idx, 0, ldev) { + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + int ret; + + pf = mlx5_lag_pf(ldev, pf_idx); + if (!pf) + continue; + pf_mdev = pf->dev; + if (!pf_mdev) + continue; + + ret = mlx5_port_max_linkspeed(pf_mdev, &speed); + if (ret) { + mlx5_core_dbg(pf_mdev, + "Failed to get max speed (err=%d)\n", + ret); + return ret; + } + + *max_speed = take_max ? + max(*max_speed, speed) : *max_speed + speed; + } + + return 0; } static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, @@ -1525,7 +1560,7 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) int pf_idx; if (ldev->mode == MLX5_LAG_MODE_MPESW) { - if (mlx5_lag_sum_devices_oper_speed(ldev, &speed)) + if (mlx5_lag_get_devices_oper_speed(ldev, &speed)) return; } else { speed = ldev->tracker.bond_speed_mbps; @@ -1533,8 +1568,8 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) return; } - /* If speed is not set, use the sum of max speeds of all PFs */ - if (!speed && mlx5_lag_sum_devices_max_speed(ldev, &speed)) + /* If speed is not set, fall back to the max achievable speed */ + if (!speed && mlx5_lag_get_devices_max_speed(ldev, &speed)) return; speed = speed / MLX5_MAX_TX_SPEED_UNIT; @@ -1943,9 +1978,11 @@ static int mlx5_handle_changeupper_event(struct mlx5_lag *ldev, } } if (i < MLX5_MAX_PORTS) { - slave = bond_slave_get_rcu(ndev_tmp); - if (slave) - has_inactive |= bond_is_slave_inactive(slave); + if (netif_is_bond_master(upper)) { + slave = bond_slave_get_rcu(ndev_tmp); + if (slave) + has_inactive |= bond_is_slave_inactive(slave); + } bond_status |= (1 << idx); } @@ -2029,7 +2066,7 @@ static int mlx5_handle_changeinfodata_event(struct mlx5_lag *ldev, bool has_inactive = 0; int idx; - if (!netif_is_lag_master(ndev)) + if (!netif_is_bond_master(ndev)) return 0; rcu_read_lock(); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 5774ec11742877..78f3d15b599d00 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -93,30 +93,6 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, lockdep_assert_held(&mdev->priv.eswitch->state_lock); #endif - if (MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed) && - opmod == MLX5_VPORT_STATE_OP_MOD_ESW_VPORT && - vport != MLX5_VPORT_UPLINK) { - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(mdev, opmod, vport, - other_vport, &speed, NULL); - if (err) { -#ifdef CONFIG_MLX5_ESWITCH - struct mlx5_vport *esw_vport; - - esw_vport = mlx5_eswitch_get_vport(mdev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; -#endif - mlx5_core_dbg(mdev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index b03ff9e044f9e2..10af08ce72efaf 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -989,6 +989,8 @@ void mlxsw_sp_acl_ruleset_prio_get(struct mlxsw_sp_acl_ruleset *ruleset, struct mlxsw_sp_acl_rule_info * mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, struct mlxsw_afa_block *afa_block); +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei); void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_acl_rule_info *rulei); int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei); diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c index cb232accb296aa..f7be4f3a151842 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c @@ -340,8 +340,8 @@ mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, return ERR_PTR(err); } -void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, - struct mlxsw_sp_acl_rule_info *rulei) +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei) { if (rulei->action_created) mlxsw_afa_block_destroy(rulei->act_block); @@ -351,6 +351,12 @@ void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, if (rulei->dst_port_range_reg_valid) mlxsw_sp_port_range_reg_put(mlxsw_sp, rulei->dst_port_range_reg_index); +} + +void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, + struct mlxsw_sp_acl_rule_info *rulei) +{ + mlxsw_sp_acl_rulei_free(mlxsw_sp, rulei); kfree(rulei); } diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c index 353fd9ca89a68a..5e4632cb792d56 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c @@ -862,14 +862,19 @@ int mlxsw_sp_flower_tmplt_create(struct mlxsw_sp *mlxsw_sp, memset(&rulei, 0, sizeof(rulei)); err = mlxsw_sp_flower_parse(mlxsw_sp, block, &rulei, f); if (err) - return err; + goto out; + ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, block, f->common.chain_index, MLXSW_SP_ACL_PROFILE_FLOWER, &rulei.values.elusage); + err = PTR_ERR_OR_ZERO(ruleset); + +out: + mlxsw_sp_acl_rulei_free(mlxsw_sp, &rulei); /* keep the reference to the ruleset */ - return PTR_ERR_OR_ZERO(ruleset); + return err; } void mlxsw_sp_flower_tmplt_destroy(struct mlxsw_sp *mlxsw_sp, diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c index dad713e9ddd5bd..b530fb6d3d0c95 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c @@ -898,6 +898,7 @@ static int mchp_sparx5_probe(struct platform_device *pdev) dev_info(sparx5->dev, "MAC addr was not set, use random MAC\n"); eth_random_addr(sparx5->base_mac); sparx5->base_mac[5] = 0; + sparx5->base_mac_assign_type = NET_ADDR_RANDOM; } sparx5->fdma_irq = platform_get_irq_byname(sparx5->pdev, "fdma"); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h index eb57b86fbe22fa..0f7d8440f6149e 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h @@ -392,6 +392,7 @@ struct sparx5 { struct notifier_block switchdev_blocking_nb; /* Switch state */ u8 base_mac[ETH_ALEN]; + u8 base_mac_assign_type; /* Associated bridge device (when bridged) */ struct net_device *hw_bridge_dev; /* Bridged interfaces */ diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c index 4dedf5d9264167..b3725cf2dd44cd 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c @@ -294,6 +294,7 @@ struct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno) ndev->ethtool_ops = &sparx5_ethtool_ops; eth_hw_addr_gen(ndev, sparx5->base_mac, portno + 1); + ndev->addr_assign_type = sparx5->base_mac_assign_type; return ndev; } diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index a16ec8136d6d63..2826e48af1033e 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -622,18 +622,9 @@ int sparx5_ptp_init(struct sparx5 *sparx5) sparx5->ptp = 1; } - if (!sparx5->ptp) - return 0; - - for (i = 0; i < SPARX5_PHC_COUNT; ++i) { - err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); - if (err) - return err; - } - - spin_lock_init(&sparx5->ptp_clock_lock); - spin_lock_init(&sparx5->ptp_ts_id_lock); - mutex_init(&sparx5->ptp_lock); + /* The base, non-PTP-capable lan969x variants need the + * first TOD counter running to forward frames. + */ /* Disable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0), sparx5, PTP_PTP_DOM_CFG); @@ -657,6 +648,19 @@ int sparx5_ptp_init(struct sparx5 *sparx5) /* Enable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0x7), sparx5, PTP_PTP_DOM_CFG); + if (!sparx5->ptp) + return 0; + + for (i = 0; i < SPARX5_PHC_COUNT; ++i) { + err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); + if (err) + return err; + } + + spin_lock_init(&sparx5->ptp_clock_lock); + spin_lock_init(&sparx5->ptp_ts_id_lock); + mutex_init(&sparx5->ptp_lock); + for (i = 0; i < sparx5->data->consts->n_ports; i++) { port = sparx5->ports[i]; if (!port) @@ -673,6 +677,9 @@ void sparx5_ptp_deinit(struct sparx5 *sparx5) struct sparx5_port *port; int i; + if (!sparx5->ptp) + return; + if (sparx5->ptp_irq >= 0) { disable_irq(sparx5->ptp_irq); sparx5->ptp_irq = -ENXIO; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c index 702257979462d4..146d7f28e8f3dd 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c @@ -160,6 +160,7 @@ static int sparx5_tc_matchall_destroy(struct net_device *ndev, } list_del(&entry->list); + kfree(entry); return err; } diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c index 788c0728d76368..0f8d98f2094d0e 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api.c +++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c @@ -3527,8 +3527,8 @@ int vcap_filter_rule_keys(struct vcap_rule *rule, if (key == keylist[idx]) { list_del(&ckf->ctrl.list); kfree(ckf); - idx++; err = 0; + break; } } } diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 5c9961ee9747ab..b27fd9b8c4f6d8 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -91,7 +91,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq, goto out; xdp_init_buff(xdp, PAGE_SIZE, &rxq->xdp_rxq); - xdp_prepare_buff(xdp, buf_va, XDP_PACKET_HEADROOM, pkt_len, true); + xdp_prepare_buff(xdp, buf_va, rxq->headroom, pkt_len, true); act = bpf_prog_run_xdp(prog, xdp); @@ -183,9 +183,9 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (!old_prog && !prog) return 0; - if (prog && ndev->mtu > MANA_XDP_MTU_MAX) { + if (prog && ndev->mtu > MANA_XDP_MTU_MAX(ndev)) { netdev_err(ndev, "XDP: mtu:%u too large, mtu_max:%lu\n", - ndev->mtu, MANA_XDP_MTU_MAX); + ndev->mtu, MANA_XDP_MTU_MAX(ndev)); NL_SET_ERR_MSG_MOD(extack, "XDP: mtu too large"); return -EOPNOTSUPP; @@ -238,7 +238,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, bpf_prog_put(old_prog); if (prog) - ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX, + ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX(ndev), gc->adapter_mtu - ETH_HLEN); else ndev->max_mtu = gc->adapter_mtu - ETH_HLEN; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 591fb4191d90d3..a1fb06b239476a 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -758,6 +758,44 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +/* Reserve enough headroom to satisfy the skb_cow() in ip_forward() and avoid + * reallocation: the TX path keeps the SGE DMA mappings in struct mana_skb_head + * at skb->head, so the port advertises ndev->needed_headroom = MANA_HEADROOM. + */ +static u32 mana_get_rxbuf_headroom(struct mana_port_context *apc) +{ + if (mana_xdp_get(apc)) + return mana_xdp_headroom(apc->ndev); + + return LL_RESERVED_SPACE(apc->ndev); +} + +static u32 mana_get_rxbuf_size(struct mana_port_context *apc, u32 mtu) +{ + u32 len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + + mana_get_rxbuf_headroom(apc)); + + return ALIGN(len, MANA_RX_FRAG_ALIGNMENT); +} + +/* Returns true when one RX buffer per page is already required by XDP or by + * the buffer size implied by the MTU, i.e. regardless of the + * MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF private flag. + */ +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu) +{ + /* For xdp make sure only one packet fits per page. */ + if (mana_xdp_get(apc)) + return true; + + /* Only use the page_pool fragment path when at least two buffers, + * including the headroom each of them has to reserve, actually fit + * into one page. Otherwise the fragment path degenerates into one + * buffer per page while still paying the fragment accounting cost. + */ + return PAGE_SIZE / mana_get_rxbuf_size(apc, mtu) < 2; +} + static bool mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) { @@ -770,32 +808,27 @@ mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) return true; - /* For xdp and jumbo frames make sure only one packet fits per page. */ - if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) - return true; - - return false; + return mana_single_rxbuf_per_page_forced(apc, mtu); } -/* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ +/* Get RX buffer's data size, alloc size, headroom and frag count based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, u32 *headroom, u32 *frag_count) { - u32 len, buf_size; + u32 buf_size; /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; + *headroom = mana_get_rxbuf_headroom(apc); + if (mana_use_single_rxbuf_per_page(apc, mtu)) { - if (mana_xdp_get(apc)) { - *headroom = XDP_PACKET_HEADROOM; + if (mana_xdp_get(apc)) *alloc_size = PAGE_SIZE; - } else { - *headroom = 0; /* no support for XDP */ + else *alloc_size = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - } *frag_count = 1; @@ -809,11 +842,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, } /* Standard MTU case - optimize for multiple packets per page */ - *headroom = 0; - - /* Calculate base buffer size needed */ - len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - buf_size = ALIGN(len, MANA_RX_FRAG_ALIGNMENT); + buf_size = mana_get_rxbuf_size(apc, mtu); /* Calculate how many packets can fit in a page */ *frag_count = PAGE_SIZE / buf_size; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index ece7ff9cc409a8..33db79569d3b6e 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -815,12 +815,12 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) if (!apc->port_is_up) return 0; - /* If XDP is attached or MTU is jumbo, single-buffer-per-page - * is already forced regardless of this flag. Skip the - * expensive detach/attach cycle since nothing changes. + /* If XDP is attached or the MTU already forces one buffer per + * page, single-buffer-per-page is used regardless of this + * flag. Skip the expensive detach/attach cycle since nothing + * changes. */ - if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || - mana_xdp_get(apc)) + if (mana_single_rxbuf_per_page_forced(apc, ndev->mtu)) return 0; /* Block RDMA from grabbing the vport during detach/attach */ diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c index ec4fc21fa21fb0..ee1bc9bd04470c 100644 --- a/drivers/net/ethernet/realtek/r8169_main.c +++ b/drivers/net/ethernet/realtek/r8169_main.c @@ -5484,6 +5484,12 @@ static int r8169_mdio_register(struct rtl8169_private *tp) phy_disable_eee_mode(tp->phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT); + /* Keep EEE off but the version stays EEE-capable so that link-up + * still clears the MAC TX-LPI bits. + */ + if (tp->mac_version == RTL_GIGA_MAC_VER_46) + phy_disable_eee(tp->phydev); + /* PHY will be woken up in rtl_open() */ phy_suspend(tp->phydev); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 857f7562c6c6dc..5e2654c91b4171 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double); + bool is_svlan); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7582fca637419d..d2d387f45c107c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -344,7 +344,7 @@ struct stmmac_priv { void __iomem *ptpaddr; void __iomem *estaddr; unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_double_vlans; + unsigned int num_svlans; int sfty_irq; struct stmmac_msi *msi; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 3f34d491c95920..b837e8e27a352e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1629,6 +1629,16 @@ static void stmmac_clear_descriptors(struct stmmac_priv *priv, stmmac_clear_tx_descriptors(priv, dma_conf, queue); } +static bool stmmac_rx_check_buf2_cap(struct stmmac_priv *priv) +{ + /* Only cores that can program an independent secondary RX buffer + * (used for scatter-gather overflow or split-header payload) back + * buffer2. Legacy cores have no set_sec_addr op, so buffer2 is + * never handed to the hardware there. + */ + return priv->hw->desc && priv->hw->desc->set_sec_addr; +} + /** * stmmac_init_rx_buffers - init the RX descriptor buffer. * @priv: driver private structure @@ -1659,16 +1669,13 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, buf->page_offset = stmmac_rx_offset(priv); } - if (priv->sph_active && !buf->sec_page) { + if (stmmac_rx_check_buf2_cap(priv) && !buf->sec_page) { buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); if (!buf->sec_page) return -ENOMEM; buf->sec_addr = page_pool_get_dma_addr(buf->sec_page); stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); - } else { - buf->sec_page = NULL; - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); } buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; @@ -2256,13 +2263,8 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, pp_params.nid = dev_to_node(priv->device); pp_params.dev = priv->device; pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE; - pp_params.offset = stmmac_rx_offset(priv); - pp_params.max_len = dma_conf->dma_buf_sz; - - if (priv->sph_active) { - pp_params.offset = 0; - pp_params.max_len += stmmac_rx_offset(priv); - } + pp_params.offset = 0; + pp_params.max_len = dma_conf->dma_buf_sz + stmmac_rx_offset(priv); rx_q->page_pool = page_pool_create(&pp_params); if (IS_ERR(rx_q->page_pool)) { @@ -5099,7 +5101,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) break; } - if (priv->sph_active && !buf->sec_page) { + if (stmmac_rx_check_buf2_cap(priv) && !buf->sec_page) { buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); if (!buf->sec_page) break; @@ -5110,10 +5112,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; stmmac_set_desc_addr(priv, p, buf->addr); - if (priv->sph_active) - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); - else - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); + stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, + stmmac_rx_check_buf2_cap(priv)); stmmac_refill_desc3(priv, rx_q, p); rx_q->rx_count_frames++; @@ -5144,7 +5144,9 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, unsigned int plen = 0, hlen = 0; int coe = priv->hw->rx_csum; - /* Not first descriptor, buffer is always zero */ + /* Not first descriptor, SPH enabled: buffer1 only carries the + * split header of the first descriptor, so it is zero here. + */ if (priv->sph_active && len) return 0; @@ -5155,14 +5157,16 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, return hlen; } - /* First descriptor, not last descriptor and not split header */ + /* Not last descriptor and not split header: buffer1 is fully filled */ if (status & rx_not_ls) return priv->dma_conf.dma_buf_sz; plen = stmmac_get_rx_frame_len(priv, p, coe); - /* First descriptor and last descriptor and not split header */ - return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen); + /* Last descriptor and not split header: buffer1 holds the remaining + * bytes of the frame, up to dma_buf_sz + */ + return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen - len); } static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, @@ -5172,8 +5176,7 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, int coe = priv->hw->rx_csum; unsigned int plen = 0; - /* Not split header, buffer is not available */ - if (!priv->sph_active) + if (!stmmac_rx_check_buf2_cap(priv)) return 0; /* For GMAC4, when split header is enabled, in some rare cases, the @@ -5190,14 +5193,15 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, * Thus 'plen - len' always gives the correct length of buf2. */ - /* Not GMAC4 and not last descriptor */ - if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls)) + /* Not GMAC4, or non-SPH and not last descriptor */ + if ((priv->plat->core_type != DWMAC_CORE_GMAC4 || !priv->sph_active) && + (status & rx_not_ls)) return priv->dma_conf.dma_buf_sz; /* GMAC4 or last descriptor */ plen = stmmac_get_rx_frame_len(priv, p, coe); - return plen - len; + return plen > len ? plen - len : 0; } static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue, @@ -5720,6 +5724,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) unsigned int desc_size; struct sk_buff *skb = NULL; struct stmmac_xdp_buff ctx; + bool fcs_stripped = false; int xdp_status = 0; int bufsz; @@ -5809,24 +5814,25 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) len += buf2_len; /* ACS is disabled; strip manually. */ - if (likely(!(status & rx_not_ls))) { - if (buf2_len) { - buf2_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } else if (buf1_len) { - buf1_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } - } + if (likely(!(status & rx_not_ls))) + len -= ETH_FCS_LEN; if (!skb) { unsigned int pre_len, sync_len; + /* Each frame starts here: reset the FCS handling */ + fcs_stripped = false; + dma_sync_single_for_cpu(priv->device, buf->addr, buf1_len, dma_dir); net_prefetch(page_address(buf->page) + buf->page_offset); + if (stmmac_xdp_is_enabled(priv) && !buf2_len) { + buf1_len -= ETH_FCS_LEN; + fcs_stripped = true; + } + xdp_init_buff(&ctx.xdp, bufsz, &rx_q->xdp_rxq); xdp_prepare_buff(&ctx.xdp, page_address(buf->page), buf->page_offset, buf1_len, true); @@ -5927,6 +5933,15 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) /* Got entire packet into SKB. Finish it. */ + /* Remove FCS if needed */ + if (!fcs_stripped && pskb_trim(skb, len)) { + dev_kfree_skb_any(skb); + skb = NULL; + rx_dropped++; + count++; + continue; + } + stmmac_get_rx_hwtstamp(priv, p, np, skb); if (priv->hw->hw_vlan_en) @@ -6211,6 +6226,13 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_STAG_RX; + else + features &= ~NETIF_F_HW_VLAN_STAG_RX; + } + return features; } @@ -6835,32 +6857,21 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) +static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) { u32 crc, hash = 0; - u16 pmatch = 0; - int count = 0; u16 vid = 0; for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); - count++; - } - - if (!priv->dma_cap.vlhash) { - if (count > 2) /* VID = 0 always passes filter */ - return -EOPNOTSUPP; - - pmatch = vid; - hash = 0; } if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -6869,8 +6880,8 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6878,11 +6889,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; set_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans + is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans + is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { clear_bit(vid, priv->active_vlans); goto err_pm_put; @@ -6892,12 +6903,12 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto err_pm_put; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; err_pm_put: pm_runtime_put(priv->device); @@ -6911,8 +6922,8 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6920,11 +6931,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; clear_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans - is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans - is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { set_bit(vid, priv->active_vlans); goto del_vlan_error; @@ -6934,12 +6945,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto del_vlan_error; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; del_vlan_error: pm_runtime_put(priv->device); @@ -6955,7 +6966,7 @@ static void stmmac_vlan_restore(struct stmmac_priv *priv) if (priv->hw->num_vlan) stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); } static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) @@ -7994,14 +8005,18 @@ static int __stmmac_dvr_probe(struct device *device, ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ - ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; + ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_RX; + if (dwmac_is_xmac(priv->plat->core_type)) { ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; priv->hw->hw_vlan_en = true; } if (priv->dma_cap.vlhash) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; - ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; } if (priv->dma_cap.vlins) ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index c25dc9f89270cc..c485217ba880c2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -243,7 +243,7 @@ struct stmmac_test_priv { int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); bool capture_all; - int double_vlan; + int svlan; int vlan_id; int ok; }; @@ -285,7 +285,7 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (tpriv->packet->tcp) { @@ -936,7 +936,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, struct iphdr *ihdr; u16 proto; - proto = tpriv->double_vlan ? ETH_P_8021AD : ETH_P_8021Q; + proto = tpriv->svlan ? ETH_P_8021AD : ETH_P_8021Q; skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) @@ -963,7 +963,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (ihdr->protocol != IPPROTO_UDP) goto out; @@ -1080,7 +1080,7 @@ static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv) return ret; } -static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int __stmmac_test_svlanfilt(struct stmmac_priv *priv) { struct stmmac_packet_attrs attr = { }; struct stmmac_test_priv *tpriv; @@ -1092,7 +1092,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return -ENOMEM; tpriv->ok = false; - tpriv->double_vlan = true; + tpriv->svlan = true; init_completion(&tpriv->comp); tpriv->pt.type = htons(ETH_P_8021Q); @@ -1155,15 +1155,15 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return ret; } -static int stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt(struct stmmac_priv *priv) { if (!priv->dma_cap.vlhash) return -EOPNOTSUPP; - return __stmmac_test_dvlanfilt(priv); + return __stmmac_test_svlanfilt(priv); } -static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt_perfect(struct stmmac_priv *priv) { int ret, prev_cap = priv->dma_cap.vlhash; @@ -1171,7 +1171,7 @@ static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) return -EOPNOTSUPP; priv->dma_cap.vlhash = 0; - ret = __stmmac_test_dvlanfilt(priv); + ret = __stmmac_test_svlanfilt(priv); priv->dma_cap.vlhash = prev_cap; return ret; @@ -1372,7 +1372,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) proto = svlan ? ETH_P_8021AD : ETH_P_8021Q; tpriv->ok = false; - tpriv->double_vlan = svlan; + tpriv->svlan = svlan; init_completion(&tpriv->comp); tpriv->pt.type = svlan ? htons(ETH_P_8021Q) : htons(ETH_P_IP); @@ -1960,11 +1960,11 @@ static const struct stmmac_test { .name = "VLAN Filtering (perf) ", .fn = stmmac_test_vlanfilt_perfect, }, { - .name = "Double VLAN Filter ", - .fn = stmmac_test_dvlanfilt, + .name = "SVLAN Filtering ", + .fn = stmmac_test_svlanfilt, }, { - .name = "Double VLAN Filter (perf) ", - .fn = stmmac_test_dvlanfilt_perfect, + .name = "SVLAN Filtering (perf) ", + .fn = stmmac_test_svlanfilt_perfect, }, { .name = "Flexible RX Parser ", .fn = stmmac_test_rxp, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index e24efe3bfedbec..fb9aad748cb346 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -173,34 +173,17 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = VLAN_ETV; - - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -236,6 +219,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); + value |= VLAN_EDVLP; value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (hw->hw_vlan_en) @@ -251,7 +235,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; @@ -267,41 +251,16 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); @@ -311,8 +270,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -320,6 +278,14 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, } } +static void dwxlgmac2_set_hw_vlan_mode(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value = readl(ioaddr + VLAN_TAG); + + writel(value | VLAN_EDVLP, ioaddr + VLAN_TAG); +} + const struct stmmac_vlan_ops dwmac_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, @@ -333,6 +299,7 @@ const struct stmmac_vlan_ops dwmac_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, + .set_hw_vlan_mode = dwxlgmac2_set_hw_vlan_mode, }; const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index fcd3aaef27fc32..c63d99686d0bac 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -523,8 +523,9 @@ struct skbuf_dma_descriptor { * @stats_work: Work for reading the hardware statistics counters often enough * to catch overflows. * @dma_err_task: Work structure to process Axi DMA errors - * @stopping: Set when @dma_err_task shouldn't do anything because we are - * about to stop the device. + * @stopping: Set when we are about to stop the device: makes @dma_err_task + * a no-op (legacy DMA path) and fences RX descriptor + * resubmission in axienet_dma_rx_cb() (dmaengine path). * @tx_irq: Axidma TX IRQ number * @rx_irq: Axidma RX IRQ number * @eth_irq: Ethernet core IRQ number @@ -545,6 +546,7 @@ struct skbuf_dma_descriptor { * @tx_ring_tail: TX skb ring buffer tail index. * @rx_ring_head: RX skb ring buffer head index. * @rx_ring_tail: RX skb ring buffer tail index. + * @rx_submit_lock: Protects RX ring resubmission vs teardown in dmaengine path. */ struct axienet_local { struct net_device *ndev; @@ -626,6 +628,7 @@ struct axienet_local { int tx_ring_tail; int rx_ring_head; int rx_ring_tail; + spinlock_t rx_submit_lock; }; /** diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 1722b7038f3400..09443623a3e2aa 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -881,6 +881,7 @@ static void axienet_dma_tx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->tx_stat_sync); dma_unmap_sg(lp->dev, skbuf_dma->sgl, skbuf_dma->sg_len, DMA_TO_DEVICE); dev_consume_skb_any(skbuf_dma->skb); + skbuf_dma->skb = NULL; netif_txq_completed_wake(txq, 1, len, CIRC_SPACE(lp->tx_ring_head, lp->tx_ring_tail, TX_BD_NUM_MAX), 2); @@ -1171,6 +1172,7 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) &meta_max_len); dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size, DMA_FROM_DEVICE); + skbuf_dma->skb = NULL; if (IS_ERR(app_metadata)) { if (net_ratelimit()) @@ -1193,10 +1195,17 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->rx_stat_sync); rx_submit: + spin_lock(&lp->rx_submit_lock); + if (lp->stopping) { + spin_unlock(&lp->rx_submit_lock); + return; + } + for (i = 0; i < CIRC_SPACE(lp->rx_ring_head, lp->rx_ring_tail, RX_BUF_NUM_DEFAULT); i++) axienet_rx_submit_desc(lp->ndev); dma_async_issue_pending(lp->rx_chan); + spin_unlock(&lp->rx_submit_lock); } /** @@ -1541,6 +1550,7 @@ static int axienet_init_dmaengine(struct net_device *ndev) lp->tx_ring_head = 0; lp->rx_ring_tail = 0; lp->rx_ring_head = 0; + lp->stopping = false; lp->tx_skb_ring = kzalloc_objs(*lp->tx_skb_ring, TX_BD_NUM_MAX); if (!lp->tx_skb_ring) { ret = -ENOMEM; @@ -1752,20 +1762,42 @@ static int axienet_stop(struct net_device *ndev) free_irq(lp->rx_irq, ndev); axienet_dma_bd_release(ndev); } else { + struct skbuf_dma_descriptor *skbuf_dma; + + spin_lock_bh(&lp->rx_submit_lock); + lp->stopping = true; + spin_unlock_bh(&lp->rx_submit_lock); + dmaengine_terminate_sync(lp->tx_chan); - dmaengine_synchronize(lp->tx_chan); dmaengine_terminate_sync(lp->rx_chan); - dmaengine_synchronize(lp->rx_chan); - - for (i = 0; i < TX_BD_NUM_MAX; i++) - kfree(lp->tx_skb_ring[i]); - kfree(lp->tx_skb_ring); - for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) - kfree(lp->rx_skb_ring[i]); - kfree(lp->rx_skb_ring); dma_release_channel(lp->rx_chan); dma_release_channel(lp->tx_chan); + + /* Unmap and free any buffer the terminate did not reclaim, so it + * is not leaked; a non-NULL skb marks such a slot. + */ + for (i = 0; i < TX_BD_NUM_MAX; i++) { + skbuf_dma = lp->tx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_sg(lp->dev, skbuf_dma->sgl, + skbuf_dma->sg_len, DMA_TO_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->tx_skb_ring); + + for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) { + skbuf_dma = lp->rx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_single(lp->dev, skbuf_dma->dma_address, + lp->max_frm_size, DMA_FROM_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->rx_skb_ring); } netdev_reset_queue(ndev); @@ -3065,6 +3097,7 @@ static int axienet_probe(struct platform_device *pdev) spin_lock_init(&lp->rx_cr_lock); spin_lock_init(&lp->tx_cr_lock); + spin_lock_init(&lp->rx_submit_lock); INIT_WORK(&lp->rx_dim.work, axienet_rx_dim_work); lp->rx_dim_enabled = true; lp->rx_dim.profile_ix = 1; diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c index cb74861e823c8e..3aa5134670609e 100644 --- a/drivers/net/pcs/pcs-rzn1-miic.c +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -270,6 +270,9 @@ static struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs) static void miic_unlock_regs(struct miic *miic) { + /* Clear protection state */ + writel(0x0000, miic->base + MIIC_PRCMD); + /* Unprotect register writes */ writel(0x00A5, miic->base + MIIC_PRCMD); writel(0x0001, miic->base + MIIC_PRCMD); diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index e1cca779d2eccd..51c67c914fad15 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -65,6 +65,13 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) goto drop; unparsed = pfcp_hdr(skb); + if (unparsed->flags & PFCP_SEID_FLAG) { + if (unlikely(!pskb_may_pull(skb, PFCP_HLEN + + offsetofend(struct pfcphdr_session, + seid)))) + goto drop; + unparsed = pfcp_hdr(skb); + } ip_tunnel_flags_zero(flags); tun_dst = udp_tun_rx_dst(skb, sk->sk_family, flags, 0, @@ -105,6 +112,9 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) static void pfcp_del_sock(struct pfcp_dev *pfcp) { + if (!pfcp->sk) + return; + udp_tunnel_sock_release(pfcp->sk); pfcp->sk = NULL; } diff --git a/drivers/net/phy/aquantia/aquantia_main.c b/drivers/net/phy/aquantia/aquantia_main.c index e95d391a6dc233..096407bebd52a8 100644 --- a/drivers/net/phy/aquantia/aquantia_main.c +++ b/drivers/net/phy/aquantia/aquantia_main.c @@ -544,7 +544,7 @@ static int aqr_gen1_read_status(struct phy_device *phydev) if (ret) return ret; - if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE) + if (!phydev->link) return 0; /* The status register is not immediately correct on line side link up. @@ -594,6 +594,9 @@ static int aqr_gen1_read_status(struct phy_device *phydev) phydev->interface = aqr_translate_interface(phydev, interface); + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + /* Read rate from vendor register */ return aqr_gen1_read_rate(phydev); } diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index 6872dbf7785628..cacbadf1f48410 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -1051,11 +1051,15 @@ static int ipq5018_config_init(struct phy_device *phydev) if (priv->set_short_cable_dac) { /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */ phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC, - IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_MMD1_MDAC_VAL); + IPQ5018_PHY_DAC_MASK, + FIELD_PREP(IPQ5018_PHY_DAC_MASK, + IPQ5018_PHY_MMD1_MDAC_VAL)); /* setting EDAC (Error-detection and Correction) in debug register */ at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC, - IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_DEBUG_EDAC_VAL); + IPQ5018_PHY_DAC_MASK, + FIELD_PREP(IPQ5018_PHY_DAC_MASK, + IPQ5018_PHY_DEBUG_EDAC_VAL)); } return 0; diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index beffbe450612b3..ca75f08e3c5939 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -820,7 +820,8 @@ static struct list_head *__team_get_qom_list(struct team *team, u16 queue_id) /* * note: already called with rcu_read_lock */ -static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) +static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb, + bool *tx_success) { struct list_head *qom_list; struct team_port *port; @@ -828,11 +829,11 @@ static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) if (!team->queue_override_enabled || !skb->queue_mapping) return false; qom_list = __team_get_qom_list(team, skb->queue_mapping); - list_for_each_entry_rcu(port, qom_list, qom_list) { - if (!team_dev_queue_xmit(team, port, skb)) - return true; - } - return false; + port = list_first_or_null_rcu(qom_list, struct team_port, qom_list); + if (!port) + return false; + *tx_success = !team_dev_queue_xmit(team, port, skb); + return true; } static void __team_queue_override_port_del(struct team *team, @@ -1864,8 +1865,7 @@ static netdev_tx_t team_xmit(struct sk_buff *skb, struct net_device *dev) bool tx_success; unsigned int len = skb->len; - tx_success = team_queue_override_transmit(team, skb); - if (!tx_success) + if (!team_queue_override_transmit(team, skb, &tx_success)) tx_success = READ_ONCE(team->ops.transmit)(team, skb); if (tx_success) { struct team_pcpu_stats *pcpu_stats; diff --git a/drivers/net/team/team_mode_broadcast.c b/drivers/net/team/team_mode_broadcast.c index 61d7d79f0c3638..69dc25a5847a17 100644 --- a/drivers/net/team/team_mode_broadcast.c +++ b/drivers/net/team/team_mode_broadcast.c @@ -37,6 +37,8 @@ static bool bc_transmit(struct team *team, struct sk_buff *skb) ret = !team_dev_queue_xmit(team, last, skb); if (!sum_ret) sum_ret = ret; + } else { + dev_kfree_skb_any(skb); } return sum_ret; } diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68aa3..242899f7fd0711 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1897,12 +1897,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { - atomic_long_inc(&tun->rx_frame_errors); - err = -EINVAL; - goto free_skb; - } - + skb->dev = tun->dev; switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { @@ -1927,9 +1922,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - skb_reset_mac_header(skb); + skb_reset_network_header(skb); skb->protocol = pi.proto; - skb->dev = tun->dev; break; case IFF_TAP: if (!pskb_may_pull(skb, ETH_HLEN)) { @@ -1937,10 +1931,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; } - skb->protocol = eth_type_trans(skb, tun->dev); + skb_set_network_header(skb, ETH_HLEN); break; } + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { + atomic_long_inc(&tun->rx_frame_errors); + err = -EINVAL; + goto free_skb; + } + + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + skb->protocol = eth_type_trans(skb, tun->dev); + /* copy skb_ubuf_info for callback when skb has no error */ if (zerocopy) { skb_zcopy_init(skb, msg_control); @@ -2600,6 +2603,8 @@ static int tun_xdp_one(struct tun_struct *tun, features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + skb->dev = tun->dev; + skb_set_network_header(skb, ETH_HLEN); if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 81d8412ce8e2fa..cba1dc95d35942 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1457,11 +1457,11 @@ static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb) return 1; } - ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len); + ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len - 2); if (!ax_skb) return 0; - skb_put(ax_skb, pkt_len); - memcpy(ax_skb->data, skb->data + 2, pkt_len); + skb_put(ax_skb, pkt_len - 2); + memcpy(ax_skb->data, skb->data + 2, pkt_len - 2); ax88179_rx_checksum(ax_skb, pkt_hdr); usbnet_skb_return(dev, ax_skb); diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 0e2ab567dd4011..a44d1b9756a1d5 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1455,6 +1455,7 @@ static const struct usb_device_id products[] = { {QMI_QUIRK_SET_DTR(0x1546, 0x1312, 4)}, /* u-blox LARA-R6 01B */ {QMI_QUIRK_SET_DTR(0x1546, 0x1342, 4)}, /* u-blox LARA-L6 */ {QMI_QUIRK_SET_DTR(0x33f8, 0x0104, 4)}, /* Rolling RW101 RMNET */ + {QMI_QUIRK_SET_DTR(0x33f8, 0x9300, 8)}, /* Rolling RN947R RMNET */ {QMI_FIXED_INTF(0x2dee, 0x4d22, 5)}, /* MeiG Smart SRM825L */ {QMI_QUIRK_SET_DTR(0x2dee, 0x4d63, 0)}, /* MeiG SRM813Q w/ Modem(PPP) */ {QMI_QUIRK_SET_DTR(0x2dee, 0x4d64, 0)}, /* MeiG SRM813Q w/ NMEA */ diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 71227d0389aa5d..643b97dc52456d 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1415,6 +1415,7 @@ static int veth_open(struct net_device *dev) } veth_set_xdp_features(dev); + veth_set_xdp_features(peer); return 0; } diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index bf82ef9874abb4..daab43ac92ce42 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -2515,6 +2515,8 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue * goto frame_err; } + skb->dev = dev; + skb_set_network_header(skb, ETH_HLEN); if (virtio_net_hdr_tnl_to_skb(skb, &hdr->tnl_hdr, vi->rx_tnl, vi->rx_tnl_csum, virtio_is_little_endian(vi->vdev))) { diff --git a/drivers/net/wireguard/noise.c b/drivers/net/wireguard/noise.c index 9c0a09bf6c9562..ec2513d6dfab35 100644 --- a/drivers/net/wireguard/noise.c +++ b/drivers/net/wireguard/noise.c @@ -784,10 +784,11 @@ wg_noise_handshake_consume_response(struct message_handshake_response *src, /* Success! Copy everything to peer */ down_write(&handshake->lock); - /* It's important to check that the state is still the same, while we - * have an exclusive lock. + /* Check that the state is the same and that this is still the + * initiation we started with, while we have an exclusive lock. */ - if (handshake->state != state) { + if (handshake->state != state || + crypto_memneq(handshake->ephemeral_private, ephemeral_private, NOISE_PUBLIC_KEY_LEN)) { up_write(&handshake->lock); goto fail; } diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h index 79b6d70de236bc..663b19b7839533 100644 --- a/drivers/net/wireguard/queueing.h +++ b/drivers/net/wireguard/queueing.h @@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) u8 l4_hash = skb->l4_hash; u8 sw_hash = skb->sw_hash; u32 hash = skb->hash; + u8 tstamp_type = skb->tstamp_type; skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); if (encapsulating) { skb->l4_hash = l4_hash; skb->sw_hash = sw_hash; skb->hash = hash; + skb->tstamp_type = tstamp_type; } skb->queue_mapping = 0; skb->nohdr = 0; diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index ae91b57c8422f1..c1fa42edd3630a 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -6716,6 +6716,7 @@ static int ath11k_mac_op_start(struct ieee80211_hw *hw) ar->num_started_vdevs = 0; ar->num_created_vdevs = 0; ar->num_peers = 0; + ar->num_stations = 0; ar->allocated_vdev_map = 0; /* Configure monitor status ring with default rx_filter to get rx status diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b1f..b0107cde99ff30 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1776,29 +1776,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2214,18 +2191,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2250,8 +2222,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2260,7 +2230,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a98fc6e0699d76..93fd227b4d198f 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1139,8 +1138,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_qmi_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e915..aecdb0c5f2858d 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b538224..8978edb44ceca0 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1484,13 +1484,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1508,7 +1501,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index d3491ff08e6ef5..7d2f292e20c52b 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -14,6 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include "htc.h" @@ -328,11 +329,14 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM); for (i = 0; i < tx_skb_cnt; i++) { - nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue); + nskb = skb_peek(&hif_dev->tx.tx_skb_queue); + if (!nskb) + break; - /* Should never be NULL */ - BUG_ON(!nskb); + if (tx_buf->offset + nskb->len + 4 > MAX_TX_BUF_SIZE) + break; + nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue); hif_dev->tx.tx_skb_cnt--; buf = tx_buf->buf; @@ -342,13 +346,8 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG); buf += 4; memcpy(buf, nskb->data, nskb->len); - tx_buf->len = nskb->len + 4; - - if (i < (tx_skb_cnt - 1)) - tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4; - - if (i == (tx_skb_cnt - 1)) - tx_buf->len += tx_buf->offset; + tx_buf->len = tx_buf->offset + nskb->len + 4; + tx_buf->offset += round_up(nskb->len + 4, 4); __skb_queue_tail(&tx_buf->skb_queue, nskb); TX_STAT_INC(hif_dev, skb_queued); diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h index 6c33e898b30006..4d2b75d0212efa 100644 --- a/drivers/net/wireless/ath/ath9k/htc.h +++ b/drivers/net/wireless/ath/ath9k/htc.h @@ -281,7 +281,6 @@ struct ath9k_htc_rxbuf { struct ath9k_htc_rx { struct list_head rxbuf; spinlock_t rxbuflock; - bool initialized; }; #define ATH9K_HTC_TX_CLEANUP_INTERVAL 50 /* ms */ diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c index 6de78ae8572687..911eaa4f776abc 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c @@ -910,6 +910,9 @@ static int ath9k_init_device(struct ath9k_htc_priv *priv, ath9k_init_leds(priv); ath9k_start_rfkill_poll(priv); + /* signal completion to ath9k_htc_rxep() and ath9k_wmi_event_tasklet() */ + smp_store_release(&priv->initialized, true); + return 0; err_world: @@ -966,10 +969,6 @@ int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev, htc_handle->drv_priv = priv; - /* Allow ath9k_wmi_event_tasklet() to operate. */ - smp_wmb(); - priv->initialized = true; - return 0; err_init: diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c index bed7ea2425a0e2..95f48c7dca154b 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c @@ -1141,8 +1141,8 @@ void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb, struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL; unsigned long flags; - /* Check if ath9k_rx_init() completed. */ - if (!data_race(priv->rx.initialized)) + /* Check if ath9k_init_device() completed. */ + if (!smp_load_acquire(&priv->initialized)) goto err; spin_lock_irqsave(&priv->rx.rxbuflock, flags); @@ -1200,10 +1200,6 @@ int ath9k_rx_init(struct ath9k_htc_priv *priv) list_add_tail(&rxbuf->list, &priv->rx.rxbuf); } - /* Allow ath9k_htc_rxep() to operate. */ - smp_wmb(); - priv->rx.initialized = true; - return 0; err: diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c index 284e8c13b04309..4a08babb6d9d67 100644 --- a/drivers/net/wireless/ath/ath9k/wmi.c +++ b/drivers/net/wireless/ath/ath9k/wmi.c @@ -146,6 +146,12 @@ void ath9k_wmi_event_tasklet(struct tasklet_struct *t) unsigned long flags; u16 cmd_id; + /* Check if ath9k_init_device() completed. */ + if (!smp_load_acquire(&priv->initialized)) { + tasklet_schedule(t); + return; + } + do { spin_lock_irqsave(&wmi->wmi_lock, flags); skb = __skb_dequeue(&wmi->wmi_event_queue); @@ -155,12 +161,6 @@ void ath9k_wmi_event_tasklet(struct tasklet_struct *t) } spin_unlock_irqrestore(&wmi->wmi_lock, flags); - /* Check if ath9k_htc_probe_device() completed. */ - if (!data_race(priv->initialized)) { - kfree_skb(skb); - continue; - } - hdr = (struct wmi_cmd_hdr *) skb->data; cmd_id = be16_to_cpu(hdr->command_id); wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr)); @@ -206,9 +206,16 @@ static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb) { skb_pull(skb, sizeof(struct wmi_cmd_hdr)); - if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0) + if (wmi->cmd_rsp_buf && wmi->cmd_rsp_len) { + if (skb->len < wmi->cmd_rsp_len) { + wmi->cmd_rsp_status = -EMSGSIZE; + goto complete; + } memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len); + } + wmi->cmd_rsp_status = 0; +complete: complete(&wmi->cmd_wait); } @@ -300,6 +307,7 @@ static int ath9k_wmi_cmd_issue(struct wmi *wmi, /* record the rsp buffer and length */ wmi->cmd_rsp_buf = rsp_buf; wmi->cmd_rsp_len = rsp_len; + wmi->cmd_rsp_status = 0; wmi->last_seq_id = wmi->tx_seq_id; spin_unlock_irqrestore(&wmi->wmi_lock, flags); @@ -356,9 +364,13 @@ int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id, return -ETIMEDOUT; } + spin_lock_irqsave(&wmi->wmi_lock, flags); + ret = wmi->cmd_rsp_status; + spin_unlock_irqrestore(&wmi->wmi_lock, flags); + mutex_unlock(&wmi->op_mutex); - return 0; + return ret; out: ath_dbg(common, WMI, "WMI failure for: %s\n", wmi_cmd_to_name(cmd_id)); diff --git a/drivers/net/wireless/ath/ath9k/wmi.h b/drivers/net/wireless/ath/ath9k/wmi.h index 5c3b710b8f3190..0bb9d31f1a1cc4 100644 --- a/drivers/net/wireless/ath/ath9k/wmi.h +++ b/drivers/net/wireless/ath/ath9k/wmi.h @@ -157,6 +157,7 @@ struct wmi { u16 tx_seq_id; u8 *cmd_rsp_buf; u32 cmd_rsp_len; + int cmd_rsp_status; bool stopped; struct list_head pending_tx_events; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index d2ae679856067a..92ec269f43b232 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1489,8 +1489,31 @@ void brcmf_detach(struct device *dev) brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN); /* make sure primary interface removed last */ for (i = BRCMF_MAX_IFS - 1; i > -1; i--) { - if (drvr->iflist[i]) - brcmf_remove_interface(drvr->iflist[i], false); + struct brcmf_if *ifp = drvr->iflist[i]; + + if (!ifp) + continue; + + if (ifp->ndev) { + brcmf_remove_interface(ifp, false); + continue; + } + + /* The P2P device interface has no netdev. Its removal can + * race with NL80211_CMD_DEL_INTERFACE issued by a user space + * process that is exiting (e.g. wpa_supplicant), which ends + * in brcmf_p2p_del_vif() under RTNL and the wiphy mutex. If + * both paths remove the interface, the wdev is unregistered + * twice and the vif is used after being freed. Take the same + * locks and re-read iflist, so only one path removes it. + */ + rtnl_lock(); + wiphy_lock(drvr->wiphy); + ifp = drvr->iflist[i]; + if (ifp) + brcmf_remove_interface(ifp, true); + wiphy_unlock(drvr->wiphy); + rtnl_unlock(); } brcmf_bus_stop(drvr->bus_if); diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c index cbaf250626c5c3..f7ca182d14943a 100644 --- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c +++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c @@ -3807,6 +3807,7 @@ il3945_pci_remove(struct pci_dev *pdev) il_free_channel_map(il); il_free_geos(il); + il_eeprom_free(il); kfree(il->scan_cmd); dev_kfree_skb(il->beacon_skb); ieee80211_free_hw(il->hw); diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c index a3d9053f043c68..f028a7bfa73456 100644 --- a/drivers/net/wireless/intersil/p54/fwio.c +++ b/drivers/net/wireless/intersil/p54/fwio.c @@ -52,6 +52,13 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) u32 code = le32_to_cpu(bootrec->code); switch (code) { case BR_CODE_COMPONENT_ID: + if (len < sizeof(struct bootrec_comp_id) / + sizeof(*bootrec->data)) { + wiphy_err(priv->hw->wiphy, + "firmware component ID is too short\n"); + return -EINVAL; + } + priv->fw_interface = be32_to_cpup((__be32 *) bootrec->data); switch (priv->fw_interface) { @@ -72,16 +79,41 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) } break; case BR_CODE_COMPONENT_VERSION: + if (len < DIV_ROUND_UP(sizeof(struct bootrec_comp_ver), + sizeof(*bootrec->data))) { + wiphy_err(priv->hw->wiphy, + "firmware component version is too short\n"); + return -EINVAL; + } + /* 24 bytes should be enough for all firmwares */ - if (strnlen((unsigned char *) bootrec->data, 24) < 24) - fw_version = (unsigned char *) bootrec->data; + if (strnlen((unsigned char *)bootrec->data, + sizeof(struct bootrec_comp_ver)) < + sizeof(struct bootrec_comp_ver)) + fw_version = (unsigned char *)bootrec->data; break; case BR_CODE_DESCR: { struct bootrec_desc *desc = (struct bootrec_desc *)bootrec->data; - priv->rx_start = le32_to_cpu(desc->rx_start); - /* FIXME add sanity checking */ - priv->rx_end = le32_to_cpu(desc->rx_end) - 0x3500; + u32 rx_start, rx_end; + + /* 0xa is the shortest descriptor in supported firmware. */ + if (len < 0xa) { + wiphy_err(priv->hw->wiphy, + "firmware descriptor is too short\n"); + return -EINVAL; + } + + rx_start = le32_to_cpu(desc->rx_start); + rx_end = le32_to_cpu(desc->rx_end); + if (rx_end < 0x3500 || rx_end - 0x3500 <= rx_start) { + wiphy_err(priv->hw->wiphy, + "firmware descriptor has invalid RX range\n"); + return -EINVAL; + } + + priv->rx_start = rx_start; + priv->rx_end = rx_end - 0x3500; priv->headroom = desc->headroom; priv->tailroom = desc->tailroom; priv->privacy_caps = desc->privacy_caps; diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 13d08022e41415..27304a98787d6a 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,13 +117,6 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; - if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { - lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); - dev->stats.tx_dropped++; - dev->stats.tx_errors++; - goto free; - } - /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 62b41c8bb7c0d8..2ade848fe3bbbf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* peer-wide state uses the wcid of the primary link */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/init.c b/drivers/net/wireless/mediatek/mt76/mt7603/init.c index 10f2ec70c792b8..218ec406d2a878 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/init.c @@ -230,6 +230,7 @@ mt7603_mac_init(struct mt7603_dev *dev) eth_broadcast_addr(bc_addr); mt7603_wtbl_init(dev, MT7603_WTBL_RESERVED, -1, bc_addr); + mt76_wcid_init(&dev->global_sta.wcid, 0); dev->global_sta.wcid.idx = MT7603_WTBL_RESERVED; rcu_assign_pointer(dev->mt76.wcid[MT7603_WTBL_RESERVED], &dev->global_sta.wcid); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index ca46a203aa48be..c8e389db63a777 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1238,6 +1238,12 @@ static void mt7915_unregister_ext_phy(struct mt7915_dev *dev) static void mt7915_stop_hardware(struct mt7915_dev *dev) { + int i; + + mt76_for_each_q_rx(&dev->mt76, i) { + if (!mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + napi_disable(&dev->mt76.napi[i]); + } mt7915_mcu_exit(dev); mt76_connac2_tx_token_put(&dev->mt76); mt7915_dma_cleanup(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index a8286f8becf926..a03d89aabe8092 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -805,6 +805,7 @@ __mt7915_drop_sta(void *ptr, u8 *mac, struct ieee80211_vif *vif) msta = (struct mt7915_sta *)sta->drv_priv; mt7915_mcu_add_sta(data->dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; } @@ -871,6 +872,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 101f571b027f18..3b82c9de26c5bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -798,7 +798,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (band_idx) + /* 0xff means BAND_AUTO to the firmware, and TGID cannot encode it */ + if (band_idx && band_idx != 0xff) val |= FIELD_PREP(MT_TXD1_TGID, band_idx); txwi[1] = cpu_to_le32(val); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5993b31e1aae4d..549f9c06b36368 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2294,6 +2294,18 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mvif->valid_links = new_links; + /* Restore the legacy BSS after disabling MLO */ + if (old_links && !new_links) { + mt792x_mac_link_bss_remove(dev, &mvif->bss_conf, + &mvif->sta.deflink); + err = mt7925_mac_link_bss_add(dev, &vif->bss_conf, + &mvif->sta.deflink); + if (err < 0) { + mt792x_mutex_release(dev); + return err; + } + } + mt792x_mutex_release(dev); return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index b468051fbe6862..e87dfb0f1b3a66 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -108,68 +108,98 @@ mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) return ret; } +/* A table's layout is decided by the version byte in MTCL, but some firmware + * labels a version 2 table as version 1. Accept whichever layout the table + * actually has: for every accepted entry count the two do not overlap, since + * one is 6 + 5n bytes and the other 7 + 7n. + */ +static bool +mt792x_asar_fits(u8 *table, int len, int prelen, int sarlen, int nr_off, + int min, int max) +{ + int tblcnt; + + if (len < prelen || (len - prelen) % sarlen) + return false; + + tblcnt = (len - prelen) / sarlen; + if (tblcnt < min || tblcnt > max) + return false; + + return tblcnt == table[nr_off]; +} + /* MTDS : Dynamic SAR Power Table */ static int -mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; - bool enable; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); if (ret) return ret; + t = *table; + /* Table content validation */ - switch (version) { - case 1: - enable = ((struct mt792x_asar_dyn *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit); - prelen = sizeof(struct mt792x_asar_dyn); - break; - case 2: - enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); - prelen = sizeof(struct mt792x_asar_dyn_v2); - break; - default: + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn), + sizeof(struct mt792x_asar_dyn_limit), + offsetof(struct mt792x_asar_dyn, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn_v2), + sizeof(struct mt792x_asar_dyn_limit_v2), + offsetof(struct mt792x_asar_dyn_v2, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 2; + else return -EINVAL; - } - tblcnt = (len - prelen) / sarlen; - if (!enable || - tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) + if (!t[offsetof(struct mt792x_asar_dyn, enable)]) return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTDS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } /* MTGS : Geo SAR Power Table */ static int -mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); if (ret) return ret; + t = *table; + /* Table content validation */ - switch (version) { - case 1: - sarlen = sizeof(struct mt792x_asar_geo_limit); - prelen = sizeof(struct mt792x_asar_geo); - break; - case 2: - sarlen = sizeof(struct mt792x_asar_geo_limit_v2); - prelen = sizeof(struct mt792x_asar_geo_v2); - break; - default: + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo), + sizeof(struct mt792x_asar_geo_limit), + offsetof(struct mt792x_asar_geo, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo_v2), + sizeof(struct mt792x_asar_geo_limit_v2), + offsetof(struct mt792x_asar_geo_v2, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 2; + else return -EINVAL; - } - tblcnt = (len - prelen) / sarlen; - if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) - return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTGS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); return 0; } @@ -205,14 +235,16 @@ int mt792x_init_acpi_sar(struct mt792x_dev *dev) asar->countrylist = NULL; } - ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); + ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver, + &asar->dyn_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->dyn); asar->dyn = NULL; } /* MTGS is optional */ - ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); + ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver, + &asar->geo_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->geo); asar->geo = NULL; @@ -254,7 +286,7 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, break; } - if (asar->ver == 1) { + if (asar->geo_ver == 1) { band_pwr = &asar->geo->tbl[idx].band[0]; max = ARRAY_SIZE(asar->geo->tbl[idx].band); } else { @@ -279,10 +311,18 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, if (idx >= max) return dyn_power; - geo_power = (band_pwr + idx)->pwr; - dyn_power += (band_pwr + idx)->offset; + band_pwr += idx; + + /* Some OEM tables carry 0xff here; narrowed to s8 that is -1 and + * wins the min() below. Take it as no limit. + */ + if (band_pwr->pwr == 0xff) + return dyn_power; + + geo_power = band_pwr->pwr; - return min(geo_power, dyn_power); + return min_t(int, geo_power, + min_t(int, dyn_power + band_pwr->offset, 127)); } static s8 @@ -297,7 +337,7 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, if (!capa) return 127; - if (asar->ver == 1) { + if (asar->dyn_ver == 1) { limit = &asar->dyn->tbl[0].frp[0]; max = ARRAY_SIZE(asar->dyn->tbl[0].frp); } else { @@ -315,7 +355,9 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, else band = NL80211_BAND_2GHZ; - return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); + /* 0xff: no dynamic limit, see mt792x_asar_get_geo_pwr() */ + return mt792x_asar_get_geo_pwr(phy, band, + limit[idx] == 0xff ? 127 : limit[idx]); } int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h index 47403307383158..1d42f24b05f668 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h @@ -105,6 +105,8 @@ struct mt792x_asar_fg { struct mt792x_acpi_sar { u8 ver; + u8 dyn_ver; + u8 geo_ver; union { struct mt792x_asar_dyn *dyn; struct mt792x_asar_dyn_v2 *dyn_v2; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index fb635a09258408..c1d8600298012b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, CONNECTION_MONITOR); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ @@ -1247,7 +1248,7 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) adie_ver = u32_get_bits(regval, MT_ADIE_VERSION_MASK); idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE; is_7976c = adie_ver == 0x8a10 || adie_ver == 0x8b00 || - adie_ver == 0x8c10 || buf[idx] == 0xc; + adie_ver == 0x8c10 || (!ret && buf[idx] == 0xc); adie_id = u32_get_bits(regval, MT_ADIE_CHIP_ID_MASK); if (adie_id == 0x7975 || adie_id == 0x7979 || diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e218856b0c4547..dd7a01c02a7cab 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); - return 0; + + /* update the link address */ + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + msta_link, true); } mlink->idx = __ffs64(~dev->mt76.vif_mask); @@ -1426,6 +1433,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, else if (sta->mlo && links == BIT(link_id)) /* last link */ mt7996_mcu_teardown_mld_sta(dev, link, msta_link); + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3da7..1b272e904100ca 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -284,6 +284,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event { __le16 len; __le16 session_id; + __le16 wlan_id; + u8 tid; + u8 __rsv1; u8 __rsv2[2]; } __packed; diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 3707ee19e4ae7b..3c623df2e741ae 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* the hardware can report the completion on a different link */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { @@ -675,7 +680,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data_present(hdr->frame_control) && - (!ieee80211_is_bufferable_mmpdu(skb) || + (!(wcid->tx_info & MT_WCID_TX_INFO_SET) || + !ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index c2a54d781b42af..25b2e430f3f93a 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || !nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c index be7080f2a6ce7b..54933c42c960d2 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -168,6 +168,7 @@ nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); return -ENOENT; } diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index 65b628411543b8..87cf2ec8599146 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -205,11 +205,12 @@ void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) return; for (i = 0; i < MAX_NUM_TID; i++) { - guard(rcu)(); - list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { - if (!memcmp(tbl->ta, ta, ETH_ALEN)) { - INIT_LIST_HEAD(&tbl->tmp_list); - list_add_tail(&tbl->tmp_list, &to_delete); + scoped_guard(rcu) { + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } } } diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 86304ea3331c74..473e81304ca8a2 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, eth_zero_addr(deauth_mac); + rcu_read_lock(); sta_node = nxpwifi_get_sta_entry(priv, params->mac); if (sta_node) ether_addr_copy(deauth_mac, params->mac); + rcu_read_unlock(); if (is_valid_ether_addr(deauth_mac)) { ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); @@ -3323,6 +3325,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, struct nxpwifi_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct ieee80211_mgmt *mgmt; struct nxpwifi_txinfo *tx_info; @@ -3396,13 +3399,25 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, nxpwifi_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + - NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + frame_len = req->ie_len + req->auth_data_len + + sizeof(struct ieee80211_hdr_3addr) + NXPWIFI_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; + + /* nxpwifi_form_mgmt_frame() inserts address4, so the frame handed to + * the firmware is ETH_ALEN longer than the one built here. + */ + if (frame_len > U16_MAX - ETH_ALEN) { + nxpwifi_dbg(adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len + ETH_ALEN; - mgmt = kzalloc(pkt_len, GFP_KERNEL); + mgmt = kzalloc(frame_len, GFP_KERNEL); + if (!mgmt) + return -ENOMEM; skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3410,6 +3425,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, if (!skb) { nxpwifi_dbg(adapter, ERROR, "allocate skb failed for management frame\n"); + kfree(mgmt); return -ENOMEM; } @@ -3448,7 +3464,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, memcpy((u8 *)varptr, req->ie, req->ie_len); } - nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, frame_len); kfree(mgmt); priv->auth_flag = HOST_MLME_AUTH_PENDING; priv->auth_alg = auth_alg; @@ -3729,8 +3745,9 @@ int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) * additional active scan request for hidden SSIDs on passive channels. */ adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); - adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), - adapter->num_in_chan_stats)); + adapter->chan_stats = kcalloc(adapter->num_in_chan_stats, + sizeof(*adapter->chan_stats), + GFP_KERNEL); if (!adapter->chan_stats) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index 7c75dca5d0a831..3c4df37333d812 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -328,7 +328,7 @@ static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) static void nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) { - timer_delete(&adapter->wakeup_timer); + timer_delete_sync(&adapter->wakeup_timer); nxpwifi_cancel_all_pending_cmd(adapter); wake_up_interruptible(&adapter->cmd_wait_q.wait); wake_up_interruptible(&adapter->hs_activate_wait_q); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 55b962430f37e7..9e13348105c3b4 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -644,7 +644,7 @@ static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) goto done; err_add_intf: - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); err_init_chan_scan: wiphy_unregister(adapter->wiphy); wiphy_free(adapter->wiphy); @@ -1384,7 +1384,7 @@ static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) wiphy_free(adapter->wiphy); adapter->wiphy = NULL; - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); nxpwifi_free_cmd_buffers(adapter); } diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ab5c15686f549d..20b7b53db23618 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -75,6 +75,7 @@ nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); } nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); return 0; } diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index bbfefb81d8d3b1..ae8444a1410581 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, } } -/* Delete a station from list; called under cfg80211 mutex. */ +/* Delete a station from list. */ void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) { struct nxpwifi_sta_node *node; - list_for_each_entry_rcu(node, &priv->sta_list, list) { + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry(node, &priv->sta_list, list) { if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { list_del_rcu(&node->list); kfree_rcu(node, rcu); break; } } + + spin_unlock_bh(&priv->sta_list_spinlock); } /* Delete all stations from list. */ diff --git a/drivers/net/wireless/silabs/wfx/hif_tx.c b/drivers/net/wireless/silabs/wfx/hif_tx.c index 9f403d275cb136..9efb108bd877df 100644 --- a/drivers/net/wireless/silabs/wfx/hif_tx.c +++ b/drivers/net/wireless/silabs/wfx/hif_tx.c @@ -207,9 +207,11 @@ int wfx_hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, si dev_warn(wdev->dev, "%s: confirmation mismatch request\n", __func__); ret = -EIO; } - if (ret == -ENOMEM) + if (!ret && le16_to_cpu(reply->length) > val_len) { dev_err(wdev->dev, "buffer is too small to receive %s (%zu < %d)\n", wfx_get_mib_name(mib_id), val_len, le16_to_cpu(reply->length)); + ret = -EIO; + } if (!ret) memcpy(val, &reply->mib_data, le16_to_cpu(reply->length)); else diff --git a/drivers/net/wireless/st/cw1200/bh.c b/drivers/net/wireless/st/cw1200/bh.c index b034bab4b48979..9367a981f1dee9 100644 --- a/drivers/net/wireless/st/cw1200/bh.c +++ b/drivers/net/wireless/st/cw1200/bh.c @@ -344,6 +344,7 @@ static int cw1200_bh_tx_helper(struct cw1200_common *priv, int *tx_burst) { size_t tx_len; + size_t aligned_len; u8 *data; int ret; struct wsm_hdr *wsm; @@ -376,9 +377,14 @@ static int cw1200_bh_tx_helper(struct cw1200_common *priv, atomic_inc(&priv->bh_tx); - tx_len = priv->hwbus_ops->align_size( + aligned_len = priv->hwbus_ops->align_size( priv->hwbus_priv, tx_len); + /* Zero the bus alignment padding so no stale data is sent. */ + if (aligned_len > tx_len) + memset(data + tx_len, 0, aligned_len - tx_len); + tx_len = aligned_len; + /* Check if not exceeding CW1200 capabilities */ if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE)) pr_debug("Write aligned len: %zu\n", tx_len); diff --git a/drivers/net/wireless/st/cw1200/txrx.c b/drivers/net/wireless/st/cw1200/txrx.c index 084d52b11f5b03..e13695667a00b6 100644 --- a/drivers/net/wireless/st/cw1200/txrx.c +++ b/drivers/net/wireless/st/cw1200/txrx.c @@ -14,6 +14,7 @@ #include "wsm.h" #include "bh.h" #include "sta.h" +#include "hwbus.h" #include "debug.h" #define CW1200_INVALID_RATE_ID (0xFF) @@ -716,6 +717,7 @@ void cw1200_tx(struct ieee80211_hw *dev, struct ieee80211_sta *sta; struct wsm_tx *wsm; bool tid_update = false; + size_t pad_len; u8 flags = 0; int ret; @@ -764,6 +766,18 @@ void cw1200_tx(struct ieee80211_hw *dev, sta = t.sta; + /* + * The bus transfer is padded up to the bus alignment and the + * padding is transferred from the frame buffer. Make sure the + * buffer has room for the padding so the device never receives + * data past the end of the frame. + */ + pad_len = priv->hwbus_ops->align_size(priv->hwbus_priv, skb->len) - + skb->len; + if (skb_tailroom(skb) < pad_len && + pskb_expand_head(skb, 0, pad_len, GFP_ATOMIC)) + goto drop; + spin_lock_bh(&priv->ps_state_lock); { tid_update = cw1200_tx_h_pm_state(priv, &t); diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index edf6ca23c6c35f..26aabf4ea12785 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -6809,8 +6809,10 @@ void wlcore_remove(struct platform_device *pdev) if (pdev_data->family && pdev_data->family->nvs_name) wait_for_completion(&wl->nvs_loading_complete); - if (!wl->initialized) + if (!wl->initialized) { + pm_runtime_put_noidle(wl->dev); return; + } if (wl->wakeirq >= 0) { dev_pm_clear_wake_irq(wl->dev); diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index 2ed673649c4888..fdcb91042f2906 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1234,8 +1234,16 @@ static int handle_incoming_queue(struct netfront_queue *queue, while ((skb = __skb_dequeue(rxq)) != NULL) { int pull_to = NETFRONT_SKB_CB(skb)->pull_to; - if (pull_to > skb_headlen(skb)) - __pskb_pull_tail(skb, pull_to - skb_headlen(skb)); + /* pull_to comes from the first slot's length, which the + * backend controls. Make sure the head holds at least an + * Ethernet header for eth_type_trans(). + */ + if (!pskb_may_pull(skb, max(pull_to, ETH_HLEN))) { + kfree_skb(skb); + packets_dropped++; + queue->info->netdev->stats.rx_errors++; + continue; + } /* Ethernet work: Delayed to here as it peeks the header. */ skb->protocol = eth_type_trans(skb, queue->info->netdev); @@ -1338,8 +1346,10 @@ static int xennet_poll(struct napi_struct *napi, int budget) skb->data_len = rx->status; skb->len += rx->status; - if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) + if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) { + __skb_queue_head(&tmpq, skb); goto err; + } if (rx->flags & XEN_NETRXF_csum_blank) skb->ip_summed = CHECKSUM_PARTIAL; diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index beea23d04a702b..7eb0f9fe6b18d1 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2617,10 +2617,19 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk)); set_disk_ro(ns->head->disk, nvme_ns_is_readonly(ns, info)); nvme_mpath_revalidate_paths(ns->head); - ret = nvme_mpath_revalidate_zones(ns->head); unfreeze_head_queue: blk_mq_unfreeze_queue(ns->head->disk->queue, memflags); + + /* + * Wait until the head queue is unfrozen before revalidating + * its zones. blk_revalidate_disk_zones() takes the queue limits + * lock and then freezes the queue on its own, so it must not be + * called with the queue already frozen. This is also what + * nvme_update_ns_info_block() does for ns->disk. + */ + if (!ret) + ret = nvme_mpath_revalidate_zones(ns->head); } return ret; @@ -3195,9 +3204,16 @@ static void nvme_release_subsystem(struct device *dev) { struct nvme_subsystem *subsys = container_of(dev, struct nvme_subsystem, dev); + struct nvme_effects_log *cel; + unsigned long i; if (subsys->instance >= 0) ida_free(&nvme_instance_ida, subsys->instance); + xa_for_each(&subsys->cels, i, cel) { + xa_erase(&subsys->cels, i); + kfree(cel); + } + xa_destroy(&subsys->cels); kfree(subsys); } @@ -3311,6 +3327,7 @@ static int nvme_init_subsystem(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) kref_init(&subsys->ref); INIT_LIST_HEAD(&subsys->ctrls); INIT_LIST_HEAD(&subsys->nsheads); + xa_init(&subsys->cels); nvme_init_subnqn(subsys, ctrl, id); memcpy(subsys->serial, id->sn, sizeof(subsys->serial)); memcpy(subsys->model, id->mn, sizeof(subsys->model)); @@ -3413,7 +3430,7 @@ int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, static int nvme_get_effects_log(struct nvme_ctrl *ctrl, u8 csi, struct nvme_effects_log **log) { - struct nvme_effects_log *old, *cel = xa_load(&ctrl->cels, csi); + struct nvme_effects_log *old, *cel = xa_load(&ctrl->subsys->cels, csi); int ret; if (cel) @@ -3430,7 +3447,7 @@ static int nvme_get_effects_log(struct nvme_ctrl *ctrl, u8 csi, return ret; } - old = xa_store(&ctrl->cels, csi, cel, GFP_KERNEL); + old = xa_store(&ctrl->subsys->cels, csi, cel, GFP_KERNEL); if (xa_is_err(old)) { kfree(cel); return xa_err(old); @@ -3505,7 +3522,7 @@ static int nvme_init_effects_log(struct nvme_ctrl *ctrl, if (!effects) return -ENOMEM; - old = xa_store(&ctrl->cels, csi, effects, GFP_KERNEL); + old = xa_store(&ctrl->subsys->cels, csi, effects, GFP_KERNEL); if (xa_is_err(old)) { kfree(effects); return xa_err(old); @@ -3551,23 +3568,27 @@ static int nvme_init_effects(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) { int ret = 0; + mutex_lock(&ctrl->subsys->lock); + ctrl->effects = xa_load(&ctrl->subsys->cels, NVME_CSI_NVM); if (ctrl->effects) - return 0; + goto out_unlock; if (id->lpa & NVME_CTRL_LPA_CMD_EFFECTS_LOG) { ret = nvme_get_effects_log(ctrl, NVME_CSI_NVM, &ctrl->effects); if (ret < 0) - return ret; + goto out_unlock; } if (!ctrl->effects) { ret = nvme_init_effects_log(ctrl, NVME_CSI_NVM, &ctrl->effects); if (ret < 0) - return ret; + goto out_unlock; } nvme_init_known_nvm_effects(ctrl); - return 0; +out_unlock: + mutex_unlock(&ctrl->subsys->lock); + return ret; } static int nvme_check_ctrl_fabric_info(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) @@ -3931,9 +3952,9 @@ void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device) put_device(cdev_device); } -int nvme_cdev_add(const char *name, struct cdev *cdev, - struct device *cdev_device, - const struct file_operations *fops, struct module *owner) +int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, + const struct file_operations *fops, struct module *owner, + int ctrl, int head) { int minor, ret; @@ -3941,7 +3962,7 @@ int nvme_cdev_add(const char *name, struct cdev *cdev, if (minor < 0) return minor; - ret = dev_set_name(cdev_device, name); + ret = dev_set_name(cdev_device, "ng%dn%d", ctrl, head); if (ret) { ida_free(&nvme_ns_chr_minor_ida, minor); return ret; @@ -3982,17 +4003,14 @@ static const struct file_operations nvme_ns_chr_fops = { static void nvme_add_ns_cdev(struct nvme_ns *ns) { - char name[32]; - ns->cdev_device.parent = ns->ctrl->device; - snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance, - ns->head->instance); nvme_get_ns(ns); /* Undone in nvme_cdev_rel() */ - if (nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, - &nvme_ns_chr_fops, ns->ctrl->ops->module)) { - dev_err(ns->ctrl->device, "Unable to create the %s device\n", - name); + if (nvme_cdev_add(&ns->cdev, &ns->cdev_device, + &nvme_ns_chr_fops, ns->ctrl->ops->module, + ns->ctrl->instance, ns->head->instance)) { + dev_err(ns->ctrl->device, "Unable to create the ng%dn%d device\n", + ns->ctrl->instance, ns->head->instance); nvme_put_ns(ns); return; } @@ -5155,19 +5173,6 @@ void nvme_uninit_ctrl(struct nvme_ctrl *ctrl) } EXPORT_SYMBOL_GPL(nvme_uninit_ctrl); -static void nvme_free_cels(struct nvme_ctrl *ctrl) -{ - struct nvme_effects_log *cel; - unsigned long i; - - xa_for_each(&ctrl->cels, i, cel) { - xa_erase(&ctrl->cels, i); - kfree(cel); - } - - xa_destroy(&ctrl->cels); -} - static void nvme_free_ctrl(struct device *dev) { struct nvme_ctrl *ctrl = @@ -5180,7 +5185,6 @@ static void nvme_free_ctrl(struct device *dev) blk_put_queue(ctrl->fabrics_q); if (!subsys || ctrl->instance != subsys->instance) ida_free(&nvme_instance_ida, ctrl->instance); - nvme_free_cels(ctrl); nvme_mpath_uninit(ctrl); cleanup_srcu_struct(&ctrl->srcu); nvme_auth_stop(ctrl); @@ -5226,7 +5230,6 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, mutex_init(&ctrl->scan_lock); INIT_LIST_HEAD(&ctrl->namespaces); - xa_init(&ctrl->cels); ctrl->dev = dev; ctrl->ops = ops; ctrl->quirks = quirks; diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 48454cb7a0fcc9..3a4030bcfab7d9 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -3338,7 +3338,7 @@ nvme_fc_reconnect_or_delete(struct nvme_fc_ctrl *ctrl, int status) ctrl->cnum, min_t(int, portptr->dev_loss_tmo, (ctrl->ctrl.opts->max_reconnects * ctrl->ctrl.opts->reconnect_delay))); - WARN_ON(nvme_delete_ctrl(&ctrl->ctrl)); + nvme_delete_ctrl(&ctrl->ctrl); } } diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 75dbb58286a32c..99ecc73393e585 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -646,17 +646,15 @@ static const struct file_operations nvme_ns_head_chr_fops = { static void nvme_add_ns_head_cdev(struct nvme_ns_head *head) { - char name[32]; - head->cdev_device.parent = &head->subsys->dev; - snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance, - head->instance); nvme_get_ns_head(head); /* Undone in nvme_cdev_rel() */ - if (nvme_cdev_add(name, &head->cdev, &head->cdev_device, - &nvme_ns_head_chr_fops, THIS_MODULE)) { + if (nvme_cdev_add(&head->cdev, &head->cdev_device, + &nvme_ns_head_chr_fops, THIS_MODULE, + head->subsys->instance, head->instance)) { dev_err(disk_to_dev(head->disk), - "Unable to create the %s device\n", name); + "Unable to create the ng%dn%d device\n", + head->subsys->instance, head->instance); nvme_put_ns_head(head); return; } @@ -763,8 +761,6 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) lim.dma_alignment = 3; lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT | BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES | BLK_FEAT_PCI_P2PDMA; - if (head->ids.csi == NVME_CSI_ZNS) - lim.features |= BLK_FEAT_ZONED; head->disk = blk_alloc_disk(&lim, ctrl->numa_node); if (IS_ERR(head->disk)) @@ -845,7 +841,8 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, u32 nr_nsids; size_t nsid_buf_size; - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - sizeof(*desc))) + if (WARN_ON_ONCE(offset > ctrl->ana_log_size || + sizeof(*desc) > ctrl->ana_log_size - offset)) return -EINVAL; nr_nsids = le32_to_cpu(desc->nnsids); @@ -861,7 +858,7 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, return -EINVAL; offset += sizeof(*desc); - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - nsid_buf_size)) + if (WARN_ON_ONCE(nsid_buf_size > ctrl->ana_log_size - offset)) return -EINVAL; error = cb(ctrl, desc, data); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 2cff9fcbf7404e..a643c4723a69b9 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -415,7 +415,6 @@ struct nvme_ctrl { unsigned long quirks; struct nvme_id_power_state psd[32]; struct nvme_effects_log *effects; - struct xarray cels; struct work_struct scan_work; struct work_struct async_event_work; struct delayed_work ka_work; @@ -509,6 +508,8 @@ struct nvme_subsystem { __guarded_by(&nvme_subsystems_lock); struct list_head nsheads __guarded_by(&lock); + /* Command effects logs, indexed by CSI and protected by lock. */ + struct xarray cels; char subnqn[NVMF_NQN_SIZE]; char serial[20]; char model[40]; @@ -1013,9 +1014,9 @@ int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, void nvme_get_ns_head(struct nvme_ns_head *head); bool nvme_tryget_ns_head(struct nvme_ns_head *head); void nvme_put_ns_head(struct nvme_ns_head *head); -int nvme_cdev_add(const char *name, struct cdev *cdev, - struct device *cdev_device, - const struct file_operations *fops, struct module *owner); +int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, + const struct file_operations *fops, struct module *owner, + int ctrl, int head); void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device); int nvme_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned int cmd, unsigned long arg); diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 5440cf18b55b28..77317e5d00f9fb 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -4196,6 +4196,8 @@ static const struct pci_device_id nvme_id_table[] = { .driver_data = NVME_QUIRK_IGNORE_DEV_SUBNQN }, { PCI_DEVICE(0x1344, 0x6001), /* Micron Nitro NVMe */ .driver_data = NVME_QUIRK_BOGUS_NID, }, + { PCI_DEVICE(0x1344, 0x6004), /* Micron 4100AT */ + .driver_data = NVME_QUIRK_DMAPOOL_ALIGN_512, }, { PCI_DEVICE(0x1c5c, 0x1504), /* SK Hynix PC400 */ .driver_data = NVME_QUIRK_DISABLE_WRITE_ZEROES, }, { PCI_DEVICE(0x1c5c, 0x174a), /* SK Hynix P31 SSD */ diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 02a2490a9ed75f..e1e3dcfd084be4 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -679,7 +679,7 @@ static ssize_t cntrltype_show(struct device *dev, if (ctrl->cntrltype > NVME_CTRL_ADMIN || !type[ctrl->cntrltype]) return sysfs_emit(buf, "reserved\n"); - return sysfs_emit(buf, type[ctrl->cntrltype]); + return sysfs_emit(buf, "%s", type[ctrl->cntrltype]); } static DEVICE_ATTR_RO(cntrltype); @@ -696,7 +696,7 @@ static ssize_t dctype_show(struct device *dev, if (ctrl->dctype > NVME_DCTYPE_CDC || !type[ctrl->dctype]) return sysfs_emit(buf, "reserved\n"); - return sysfs_emit(buf, type[ctrl->dctype]); + return sysfs_emit(buf, "%s", type[ctrl->dctype]); } static DEVICE_ATTR_RO(dctype); diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 921934028e0b22..7eec983c0eaded 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -144,11 +144,6 @@ struct nvme_tcp_queue { void (*state_change)(struct sock *); void (*data_ready)(struct sock *); void (*write_space)(struct sock *); - -#ifdef CONFIG_DEBUG_LOCK_ALLOC - struct lock_class_key nvme_tcp_sk_key; - struct lock_class_key nvme_tcp_slock_key; -#endif }; static DEFINE_MUTEX(nvme_tcp_ctrl_mutex); @@ -185,9 +180,12 @@ static int nvme_tcp_try_send(struct nvme_tcp_queue *queue); * a separate class prevents lockdep from conflating nvme-tcp socket use with * user-space socket API use. */ -static void nvme_tcp_reclassify_socket(struct nvme_tcp_queue *queue) +static struct lock_class_key nvme_tcp_sk_key[2]; +static struct lock_class_key nvme_tcp_slock_key[2]; + +static void nvme_tcp_reclassify_socket(struct socket *sock) { - struct sock *sk = queue->sock->sk; + struct sock *sk = sock->sk; if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) return; @@ -195,20 +193,22 @@ static void nvme_tcp_reclassify_socket(struct nvme_tcp_queue *queue) switch (sk->sk_family) { case AF_INET: sock_lock_init_class_and_name(sk, "slock-AF_INET-NVME", - &queue->nvme_tcp_slock_key, + &nvme_tcp_slock_key[0], "sk_lock-AF_INET-NVME", - &queue->nvme_tcp_sk_key); + &nvme_tcp_sk_key[0]); break; case AF_INET6: sock_lock_init_class_and_name(sk, "slock-AF_INET6-NVME", - &queue->nvme_tcp_slock_key, + &nvme_tcp_slock_key[1], "sk_lock-AF_INET6-NVME", - &queue->nvme_tcp_sk_key); + &nvme_tcp_sk_key[1]); break; default: WARN_ON_ONCE(1); } } +#else +static void nvme_tcp_reclassify_socket(struct socket *sock) { } #endif static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl) @@ -1523,11 +1523,6 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); mutex_destroy(&queue->pf_cache_lock); - -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_unregister_key(&queue->nvme_tcp_sk_key); - lockdep_unregister_key(&queue->nvme_tcp_slock_key); -#endif } static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) @@ -1875,12 +1870,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, sk_net_refcnt_upgrade(queue->sock->sk); -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_register_key(&queue->nvme_tcp_sk_key); - lockdep_register_key(&queue->nvme_tcp_slock_key); - nvme_tcp_reclassify_socket(queue); -#endif - /* Single syn retry */ tcp_sock_set_syncnt(queue->sock->sk, 1); @@ -1968,6 +1957,8 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, goto err_init_connect; } + nvme_tcp_reclassify_socket(queue->sock); + ret = nvme_tcp_init_connection(queue); if (ret) goto err_init_connect; @@ -1984,10 +1975,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, /* Use sync variant - see nvme_tcp_free_queue() for explanation */ __fput_sync(queue->sock->file); queue->sock = NULL; -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_unregister_key(&queue->nvme_tcp_sk_key); - lockdep_unregister_key(&queue->nvme_tcp_slock_key); -#endif err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 6286e38436ddf8..b03b5d1c2dc3d2 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -534,6 +534,7 @@ static ssize_t nvmet_ns_device_path_store(struct config_item *item, struct nvmet_subsys *subsys = ns->subsys; size_t len; int ret; + char *new_path = NULL; mutex_lock(&subsys->lock); ret = -EBUSY; @@ -545,12 +546,14 @@ static ssize_t nvmet_ns_device_path_store(struct config_item *item, if (!len) goto out_unlock; - kfree(ns->device_path); ret = -ENOMEM; - ns->device_path = kmemdup_nul(page, len, GFP_KERNEL); - if (!ns->device_path) + new_path = kmemdup_nul(page, len, GFP_KERNEL); + if (!new_path) goto out_unlock; + kfree(ns->device_path); + ns->device_path = new_path; + mutex_unlock(&subsys->lock); return count; diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 43871a8f56ca36..07a74c2e1f5874 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -446,20 +446,27 @@ u16 nvmet_req_find_ns(struct nvmet_req *req) { u32 nsid = le32_to_cpu(req->cmd->common.nsid); struct nvmet_subsys *subsys = nvmet_req_subsys(req); + u16 status = NVME_SC_SUCCESS; + rcu_read_lock(); req->ns = xa_load(&subsys->namespaces, nsid); - if (unlikely(!req->ns || !req->ns->enabled)) { + if (unlikely(!req->ns) || + !test_bit(NVMET_NS_IO_LIVE, &req->ns->flags) || + !percpu_ref_tryget_live_rcu(&req->ns->ref)) { req->error_loc = offsetof(struct nvme_common_command, nsid); - if (!req->ns) /* ns doesn't exist! */ - return NVME_SC_INVALID_NS | NVME_STATUS_DNR; + if (!req->ns) { /* ns doesn't exist! */ + status = NVME_SC_INVALID_NS | NVME_STATUS_DNR; + goto unlock; + } /* ns exists but it's disabled */ req->ns = NULL; - return NVME_SC_INTERNAL_PATH_ERROR; + status = NVME_SC_INTERNAL_PATH_ERROR; } +unlock: + rcu_read_unlock(); - percpu_ref_get(&req->ns->ref); - return NVME_SC_SUCCESS; + return status; } static void nvmet_destroy_namespace(struct percpu_ref *ref) @@ -623,6 +630,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns) ns->enabled = true; xa_set_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); nvmet_debugfs_ns_setup(ns); + set_bit(NVMET_NS_IO_LIVE, &ns->flags); ret = 0; out_unlock: mutex_unlock(&subsys->lock); @@ -643,11 +651,11 @@ void nvmet_ns_disable(struct nvmet_ns *ns) struct nvmet_subsys *subsys = ns->subsys; struct nvmet_ctrl *ctrl; + if (!test_and_clear_bit(NVMET_NS_IO_LIVE, &ns->flags)) + return; + mutex_lock(&subsys->lock); - if (!ns->enabled) - goto out_unlock; - ns->enabled = false; xa_clear_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); nvmet_debugfs_ns_free(ns); @@ -675,7 +683,7 @@ void nvmet_ns_disable(struct nvmet_ns *ns) mutex_lock(&subsys->lock); nvmet_ns_changed(subsys, ns->nsid); nvmet_ns_dev_disable(ns); -out_unlock: + ns->enabled = false; mutex_unlock(&subsys->lock); } @@ -1317,7 +1325,7 @@ static int nvmet_req_alloc_p2pmem_sgls(struct pci_dev *p2p_dev, return 0; out_free_sg: - pci_p2pmem_free_sgl(req->p2p_dev, req->sg); + pci_p2pmem_free_sgl(p2p_dev, req->sg); out_err: return -ENOMEM; } @@ -1648,6 +1656,8 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) INIT_DELAYED_WORK(&ctrl->ka_work, nvmet_keep_alive_timer); memcpy(ctrl->hostnqn, args->hostnqn, NVMF_NQN_SIZE); + if (args->hostid) + uuid_copy(&ctrl->hostid, args->hostid); kref_init(&ctrl->ref); ctrl->subsys = subsys; @@ -1706,9 +1716,6 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) nvmet_start_keep_alive_timer(ctrl); - if (args->hostid) - uuid_copy(&ctrl->hostid, args->hostid); - dhchap_status = nvmet_setup_auth(ctrl, args->sq, false); if (dhchap_status) { pr_err("Failed to setup authentication, dhchap status %u\n", diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 92f8a76f10ff50..dccdb55a367c50 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -427,7 +427,7 @@ static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al) struct nvmet_ctrl *ctrl = req->sq->ctrl; int ret = 0; int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id); - int data_size = sizeof(*d) + hash_len; + int data_size = sizeof(*data) + hash_len; if (ctrl->dh_tfm) data_size += ctrl->dh_keysize; diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index dbda55895f4fb9..162e2fdd848e00 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -112,6 +112,8 @@ struct nvmet_ns { bool buffered_io; bool enabled; +#define NVMET_NS_IO_LIVE 0 + unsigned long flags; struct nvmet_subsys *subsys; const char *device_path; diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 9c28367435a599..c683450df42278 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -978,6 +978,9 @@ nvmet_pci_epf_get_sgl_segment(struct nvmet_pci_epf_ctrl *ctrl, int nr_descs, ret; void *buf; + if (length < sizeof(struct nvme_sgl_desc)) + return NULL; + buf = kmalloc(length, GFP_KERNEL); if (!buf) return NULL; diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index 09d8c63f568077..81f48cb00aeee9 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -51,6 +51,8 @@ u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask) if (nsid != U32_MAX) { status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; if (!req->ns->pr.enable) @@ -75,6 +77,8 @@ u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req) u16 status; status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; diff --git a/drivers/of/base.c b/drivers/of/base.c index d71ae044936092..f63a9486b13055 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -96,9 +96,17 @@ static bool __of_node_is_type(const struct device_node *np, const char *type) return !strcmp(match, type); } +static bool of_coreboot_present(void) +{ + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, "coreboot"); + + return np; +} + #define EXCLUDED_DEFAULT_CELLS_PLATFORMS ( \ IS_ENABLED(CONFIG_SPARC) || \ - of_find_compatible_node(NULL, NULL, "coreboot") \ + of_coreboot_present() \ ) int of_bus_n_addr_cells(struct device_node *np) diff --git a/drivers/of/irq.c b/drivers/of/irq.c index ec035367c9500c..ef1ed9743907b4 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -754,6 +754,8 @@ void __init of_irq_init(const struct of_device_id *matches) list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); + of_node_put(desc->interrupt_parent); + of_node_put(desc->dev); kfree(desc); } err: diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 08d5351746be65..83c56dd9b5d83e 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -256,6 +256,9 @@ static struct property *dup_and_fixup_symbol_prop( if (!target_path) return NULL; target_path_len = strlen(target_path); + /* a root target renders as "/"; drop it to avoid "//" results */ + if (target_path_len == 1 && target_path[0] == '/' && path_tail_len) + target_path_len = 0; new_prop = kzalloc_obj(*new_prop); if (!new_prop) @@ -358,12 +361,13 @@ static int add_changeset_property(struct overlay_changeset *ovcs, return -ENOMEM; if (!prop) { - if (!target->in_livetree) { + ret = of_changeset_add_property(&ovcs->cset, target->np, + new_prop); + /* the detached node owns the property until the apply */ + if (!ret && !target->in_livetree) { new_prop->next = target->np->deadprops; target->np->deadprops = new_prop; } - ret = of_changeset_add_property(&ovcs->cset, target->np, - new_prop); } else { ret = of_changeset_update_property(&ovcs->cset, target->np, new_prop); @@ -853,6 +857,10 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, err_out: pr_err("%s() failed, ret = %d\n", __func__, ret); + /* let free_overlay_changeset() put the fragments set up so far */ + if (ovcs->fragments) + ovcs->count = cnt; + return ret; } @@ -863,7 +871,8 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) if (ovcs->cset.entries.next) of_changeset_destroy(&ovcs->cset); - if (ovcs->id) { + /* a failed idr_alloc() leaves its negative error in ovcs->id */ + if (ovcs->id > 0) { idr_remove(&ovcs_idr, ovcs->id); list_del(&ovcs->ovcs_list); ovcs->id = 0; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2879a6be5f808..8cc6a89130b53f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3769,8 +3769,21 @@ int pci_enable_atomic_ops_to_root(struct pci_dev *dev, u32 cap_mask) } root = pcie_find_root_port(dev); - if (!root) + if (!root) { + + /* + * A hypervisor may expose a topology with the root port + * not visible to the guest. If the hypervisor has already + * set AtomicOp Requester Enable in the endpoint, we assume + * it has verified support in the root port, so it is safe + * for the driver to use AtomicOps. + */ + pcie_capability_read_dword(dev, PCI_EXP_DEVCTL2, &ctl2); + if (ctl2 & PCI_EXP_DEVCTL2_ATOMIC_REQ) + return 0; + return -EINVAL; + } pcie_capability_read_dword(root, PCI_EXP_DEVCAP2, &cap); if ((cap & cap_mask) != cap_mask) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index d8dcd238fda1f0..922a726a52a506 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1360,8 +1360,10 @@ static DEFINE_KFIFO(aer_recover_ring, struct aer_recover_entry, static void aer_recover_work_func(struct work_struct *work) { + struct aer_capability_regs *regs; struct aer_recover_entry entry; struct pci_dev *pdev; + u32 err; while (kfifo_get(&aer_recover_ring, &entry)) { pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, @@ -1375,6 +1377,12 @@ static void aer_recover_work_func(struct work_struct *work) } pci_print_aer(pdev, entry.severity, entry.regs); + regs = entry.regs; + if (entry.severity == AER_CORRECTABLE) + err = regs->cor_status & ~regs->cor_mask; + else + err = regs->uncor_status & ~regs->uncor_mask; + /* * Memory for aer_capability_regs(entry.regs) is being * allocated from the ghes_estatus_pool to protect it from @@ -1385,12 +1393,14 @@ static void aer_recover_work_func(struct work_struct *work) ghes_estatus_pool_region_free((unsigned long)entry.regs, sizeof(struct aer_capability_regs)); - if (entry.severity == AER_NONFATAL) - pcie_do_recovery(pdev, pci_channel_io_normal, - aer_root_reset); - else if (entry.severity == AER_FATAL) - pcie_do_recovery(pdev, pci_channel_io_frozen, - aer_root_reset); + if (err) { + if (entry.severity == AER_NONFATAL) + pcie_do_recovery(pdev, pci_channel_io_normal, + aer_root_reset); + else if (entry.severity == AER_FATAL) + pcie_do_recovery(pdev, pci_channel_io_frozen, + aer_root_reset); + } pci_dev_put(pdev); } } diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 479789009c7f44..f8105e87ea3c01 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,12 @@ struct imx8m_blk_ctrl { struct regmap *regmap; struct imx8m_blk_ctrl_domain *domains; struct genpd_onecell_data onecell_data; + /* + * Serializes the blk-ctrl reset/clock sequence across sibling domains; + * their transitions interact through the shared VPUMIX bus domain, + * VPU_NOC and the not-ack-verified ADB400 handshake (ERR050531). + */ + struct mutex power_lock; }; struct imx8m_blk_ctrl_domain_data { @@ -98,6 +105,8 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) struct imx8m_blk_ctrl *bc = domain->bc; int ret; + guard(mutex)(&bc->power_lock); + /* make sure bus domain is awake */ ret = pm_runtime_get_sync(bc->bus_power_dev); if (ret < 0) { @@ -164,6 +173,8 @@ static int imx8m_blk_ctrl_power_off(struct generic_pm_domain *genpd) const struct imx8m_blk_ctrl_domain_data *data = domain->data; struct imx8m_blk_ctrl *bc = domain->bc; + guard(mutex)(&bc->power_lock); + /* put devices into reset and disable clocks */ if (data->mipi_phy_rst_mask) regmap_clear_bits(bc->regmap, BLK_MIPI_RESET_DIV, data->mipi_phy_rst_mask); @@ -202,6 +213,10 @@ static int imx8m_blk_ctrl_probe(struct platform_device *pdev) bc->dev = dev; + ret = devm_mutex_init(dev, &bc->power_lock); + if (ret) + return ret; + bc_data = of_device_get_match_data(dev); base = devm_platform_ioremap_resource(pdev, 0); diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index ba66ae71942892..e193f978d5f353 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -759,7 +759,19 @@ static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd, } i = 0; - while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) { + while (1) { + clk = of_clk_get(dev->of_node, i++); + if (IS_ERR(clk)) { + error = PTR_ERR(clk); + if (error == -ENOENT) + break; + + dev_err(dev, "failed to get clock %d: %d\n", i - 1, + error); + pm_clk_destroy(dev); + return error; + } + dev_dbg(dev, "adding clock '%pC' to list of PM clocks\n", clk); error = pm_clk_add_clk(dev, clk); if (error) { @@ -842,7 +854,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, dev_err(pmu->dev, "%pOFn: failed to get clk at index %d: %d\n", node, i, error); - return error; + goto err_put_clocks; } } @@ -1018,7 +1030,9 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, parent_domain->name, child_domain->name); } - rockchip_pm_add_subdomain(pmu, np); + error = rockchip_pm_add_subdomain(pmu, np); + if (error) + return error; } return 0; diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 471ffe914a4e21..e42e85f47629e8 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -197,6 +197,8 @@ static const struct pci_device_id pwrseq_m2_pci_ids[] = { .driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" }, { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_QCOM, 0x337c), .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_LENOVO, 0xe0e9), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, { } /* Sentinel */ }; @@ -570,6 +572,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); err_free_regulators: regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); return ret; } @@ -583,6 +586,7 @@ static void pwrseq_pcie_m2_remove(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); } static const struct of_device_id pwrseq_pcie_m2_of_match[] = { diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9ec2..a8cba2f64b98ae 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1825,7 +1825,7 @@ ptp_ocp_watchdog(struct timer_list *t) static void ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) { - ktime_t start, end, delay = U64_MAX; + ktime_t start, end, delay = KTIME_MAX; u32 ctrl; int i; diff --git a/drivers/regulator/ltc3676.c b/drivers/regulator/ltc3676.c index 307c6f7a155d95..d67cee4e0be06c 100644 --- a/drivers/regulator/ltc3676.c +++ b/drivers/regulator/ltc3676.c @@ -232,7 +232,7 @@ static const struct regulator_desc ltc3676_regulators[LTC3676_NUM_REGULATORS] = LTC3676_FIXED_REG(LDO4, ldo4, LDOB, 2), }; -static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) +static bool ltc3676_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case LTC3676_BUCK1 ... LTC3676_IRQSTAT: @@ -243,6 +243,15 @@ static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) return false; } +static bool ltc3676_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_BUCK1 ... LTC3676_IRQSTAT: + return true; + } + return false; +} + static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -255,8 +264,8 @@ static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) static const struct regmap_config ltc3676_regmap_config = { .reg_bits = 8, .val_bits = 8, - .writeable_reg = ltc3676_readable_writeable_reg, - .readable_reg = ltc3676_readable_writeable_reg, + .writeable_reg = ltc3676_writeable_reg, + .readable_reg = ltc3676_readable_reg, .volatile_reg = ltc3676_volatile_reg, .max_register = LTC3676_CLIRQ, .use_single_read = true, diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index c0cc6cc0afd8bf..09e26febfc834e 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -929,21 +929,26 @@ static int is_supply_name(const char *name) * before returning to the caller, and @consumers will not be * changed. * + * On success the array is allocated here and handed to the caller, which + * owns it from then on: release the regulators with regulator_bulk_free() + * and free the array itself with kfree(). The supply names live in the + * same allocation, so they are gone once the array is freed. + * * Return: Number of regulators on success, or a negative error number * on failure. */ int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, struct regulator_bulk_data **consumers) { - int num_consumers = 0; + int num_consumers = 0, names_len = 0; struct regulator *tmp; struct regulator_bulk_data *_consumers = NULL; struct property *prop; + char *names; int i, n = 0, ret; - char name[64]; /* - * first pass: get numbers of xxx-supply + * first pass: get numbers of xxx-supply and the room their names take * second pass: fill consumers */ restart: @@ -953,16 +958,19 @@ int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, continue; if (!_consumers) { num_consumers++; + names_len += i + 1; continue; } else { - memcpy(name, prop->name, i); - name[i] = '\0'; - tmp = regulator_get(dev, name); + memcpy(names, prop->name, i); + names[i] = '\0'; + tmp = regulator_get(dev, names); if (IS_ERR(tmp)) { ret = PTR_ERR(tmp); goto error; } + _consumers[n].supply = names; _consumers[n].consumer = tmp; + names += i + 1; n++; continue; } @@ -973,9 +981,16 @@ int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, } if (num_consumers == 0) return 0; - _consumers = kmalloc_objs(struct regulator_bulk_data, num_consumers); + /* + * The supply names are kept in the same allocation as the array, so + * that they share its lifetime and the caller has nothing extra to + * free. + */ + _consumers = kzalloc(size_add(size_mul(num_consumers, sizeof(*_consumers)), + names_len), GFP_KERNEL); if (!_consumers) return -ENOMEM; + names = (char *)(_consumers + num_consumers); goto restart; error: diff --git a/drivers/regulator/tps6594-regulator.c b/drivers/regulator/tps6594-regulator.c index 31a5218d551059..503b89a2f5f810 100644 --- a/drivers/regulator/tps6594-regulator.c +++ b/drivers/regulator/tps6594-regulator.c @@ -241,21 +241,21 @@ static const struct regulator_desc tps65224_buck_regs[] = { TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(1), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK3", "buck3", TPS6594_BUCK_3, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(2), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(2), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK4", "buck4", TPS6594_BUCK_4, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(3), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(3), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), }; static const struct tps6594_regulator_irq_type tps6594_buck1_irq_types[] = { diff --git a/drivers/rtc/dev.c b/drivers/rtc/dev.c index 8ba7c25d2565ef..be173c9511faca 100644 --- a/drivers/rtc/dev.c +++ b/drivers/rtc/dev.c @@ -216,7 +216,7 @@ static long rtc_dev_ioctl(struct file *file, struct rtc_device *rtc = file->private_data; const struct rtc_class_ops *ops = rtc->ops; struct rtc_time tm; - struct rtc_wkalrm alarm; + struct rtc_wkalrm alarm = { 0 }; struct rtc_param param; void __user *uarg = (void __user *)arg; diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c index bba7115ff3ad0f..c579c4bf5578d4 100644 --- a/drivers/rtc/rtc-ac100.c +++ b/drivers/rtc/rtc-ac100.c @@ -317,10 +317,12 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) if (!chip->clk_data) return -ENOMEM; - chip->rtc_32k_clk = clk_hw_register_fixed_rate(chip->dev, - AC100_RTC_32K_NAME, - NULL, 0, - AC100_RTC_32K_RATE); + chip->clk_data->num = AC100_CLKOUT_NUM; + + chip->rtc_32k_clk = devm_clk_hw_register_fixed_rate(chip->dev, + AC100_RTC_32K_NAME, + NULL, 0, + AC100_RTC_32K_RATE); if (IS_ERR(chip->rtc_32k_clk)) { ret = PTR_ERR(chip->rtc_32k_clk); dev_err(chip->dev, "Failed to register RTC-32k clock: %d\n", @@ -354,29 +356,14 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) if (ret) { dev_err(chip->dev, "Failed to register clk '%s': %d\n", init.name, ret); - goto err_unregister_rtc_32k; + return ret; } chip->clk_data->hws[i] = &clk->hw; } - chip->clk_data->num = i; - ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, chip->clk_data); - if (ret) - goto err_unregister_rtc_32k; - - return 0; - -err_unregister_rtc_32k: - clk_unregister_fixed_rate(chip->rtc_32k_clk->clk); - - return ret; -} - -static void ac100_rtc_unregister_clks(struct ac100_rtc_dev *chip) -{ - of_clk_del_provider(chip->dev->of_node); - clk_unregister_fixed_rate(chip->rtc_32k_clk->clk); + return devm_of_clk_add_hw_provider(chip->dev, of_clk_hw_onecell_get, + chip->clk_data); } /* @@ -614,13 +601,6 @@ static int ac100_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(chip->rtc); } -static void ac100_rtc_remove(struct platform_device *pdev) -{ - struct ac100_rtc_dev *chip = platform_get_drvdata(pdev); - - ac100_rtc_unregister_clks(chip); -} - static const struct of_device_id ac100_rtc_match[] = { { .compatible = "x-powers,ac100-rtc" }, { }, @@ -629,7 +609,6 @@ MODULE_DEVICE_TABLE(of, ac100_rtc_match); static struct platform_driver ac100_rtc_driver = { .probe = ac100_rtc_probe, - .remove = ac100_rtc_remove, .driver = { .name = "ac100-rtc", .of_match_table = of_match_ptr(ac100_rtc_match), diff --git a/drivers/rtc/rtc-efi.c b/drivers/rtc/rtc-efi.c index b4f44999ef0f4b..a8c18ebc559967 100644 --- a/drivers/rtc/rtc-efi.c +++ b/drivers/rtc/rtc-efi.c @@ -52,6 +52,7 @@ compute_wday(efi_time_t *eft, int yday) static void convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft) { + memset(eft, 0, sizeof(*eft)); eft->year = wtime->tm_year + 1900; eft->month = wtime->tm_mon + 1; eft->day = wtime->tm_mday; @@ -112,6 +113,35 @@ convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime) return true; } +static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + efi_time_t eft; + efi_status_t status; + + memset(&eft, 0, sizeof(eft)); + status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled, + (efi_bool_t *)&wkalrm->pending, &eft); + if (status != EFI_SUCCESS) + return -EINVAL; + + if (!convert_from_efi_time(&eft, &wkalrm->time)) + return -EIO; + + return rtc_valid_tm(&wkalrm->time); +} + +static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + efi_time_t eft; + efi_status_t status; + + convert_to_efi_time(&wkalrm->time, &eft); + + status = efi.set_wakeup_time((efi_bool_t)!!wkalrm->enabled, &eft); + + return status == EFI_SUCCESS ? 0 : -EINVAL; +} + static int efi_read_time(struct device *dev, struct rtc_time *tm) { efi_status_t status; @@ -146,13 +176,21 @@ static int efi_set_time(struct device *dev, struct rtc_time *tm) static int efi_procfs(struct device *dev, struct seq_file *seq) { - efi_time_t eft; + efi_time_t eft, alm; efi_time_cap_t cap; + efi_bool_t enabled, pending; + struct rtc_device *rtc = dev_get_drvdata(dev); memset(&eft, 0, sizeof(eft)); + memset(&alm, 0, sizeof(alm)); memset(&cap, 0, sizeof(cap)); efi.get_time(&eft, &cap); + if (test_bit(RTC_FEATURE_ALARM, rtc->features) && + efi.get_wakeup_time(&enabled, &pending, &alm) != EFI_SUCCESS) { + enabled = 0; + pending = 0; + } seq_printf(seq, "Time\t\t: %u:%u:%u.%09u\n" @@ -168,6 +206,25 @@ static int efi_procfs(struct device *dev, struct seq_file *seq) /* XXX fixme: convert to string? */ seq_printf(seq, "Timezone\t: %u\n", eft.timezone); + if (test_bit(RTC_FEATURE_ALARM, rtc->features)) { + seq_printf(seq, + "Alarm Time\t: %u:%u:%u.%09u\n" + "Alarm Date\t: %u-%u-%u\n" + "Alarm Daylight\t: %u\n" + "Enabled\t\t: %s\n" + "Pending\t\t: %s\n", + alm.hour, alm.minute, alm.second, alm.nanosecond, + alm.year, alm.month, alm.day, + alm.daylight, + enabled == 1 ? "yes" : "no", + pending == 1 ? "yes" : "no"); + + if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE) + seq_puts(seq, "Alarm Timezone\t: unspecified\n"); + else + seq_printf(seq, "Alarm Timezone\t: %d\n", alm.timezone); + } + /* * now prints the capabilities */ @@ -183,6 +240,8 @@ static int efi_procfs(struct device *dev, struct seq_file *seq) static const struct rtc_class_ops efi_rtc_ops = { .read_time = efi_read_time, .set_time = efi_set_time, + .read_alarm = efi_read_alarm, + .set_alarm = efi_set_alarm, .proc = efi_procfs, }; @@ -191,6 +250,7 @@ static int __init efi_rtc_probe(struct platform_device *dev) struct rtc_device *rtc; efi_time_t eft; efi_time_cap_t cap; + efi_bool_t enabled, pending; /* First check if the RTC is usable */ if (efi.get_time(&eft, &cap) != EFI_SUCCESS) @@ -203,7 +263,23 @@ static int __init efi_rtc_probe(struct platform_device *dev) platform_set_drvdata(dev, rtc); rtc->ops = &efi_rtc_ops; - clear_bit(RTC_FEATURE_ALARM, rtc->features); + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); + + /* + * The EFI_RT_SUPPORTED_WAKEUP_SERVICES bit defaults to enabled + * and only gets cleared when the RT_PROP table explicitly says + * wakeup is unsupported. Many platforms lack an RT_PROP table + * even though they don't implement the wakeup runtime service, + * so probe by actually calling GetWakeupTime() to avoid exposing + * a broken alarm to userspace. + */ + if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES) && + efi.get_wakeup_time(&enabled, &pending, &eft) == EFI_SUCCESS) { + set_bit(RTC_FEATURE_ALARM, rtc->features); + set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features); + } else { + clear_bit(RTC_FEATURE_ALARM, rtc->features); + } device_init_wakeup(&dev->dev, true); diff --git a/drivers/rtc/rtc-mpfs.c b/drivers/rtc/rtc-mpfs.c index ece6de4a6adbd1..60596b0ea9d809 100644 --- a/drivers/rtc/rtc-mpfs.c +++ b/drivers/rtc/rtc-mpfs.c @@ -256,8 +256,12 @@ static int mpfs_rtc_probe(struct platform_device *pdev) return ret; } + clk = devm_clk_get(&pdev->dev, "rtcref"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + /* prescaler hardware adds 1 to reg value */ - prescaler = clk_get_rate(devm_clk_get(&pdev->dev, "rtcref")) - 1; + prescaler = clk_get_rate(clk) - 1; if (prescaler > MAX_PRESCALER_COUNT) { dev_dbg(&pdev->dev, "invalid prescaler %lu\n", prescaler); return -EINVAL; diff --git a/drivers/rtc/rtc-spear.c b/drivers/rtc/rtc-spear.c index 959acff8faff0f..d12bccb3cf5ecb 100644 --- a/drivers/rtc/rtc-spear.c +++ b/drivers/rtc/rtc-spear.c @@ -363,6 +363,12 @@ static int spear_rtc_probe(struct platform_device *pdev) if (irq < 0) return irq; + config->ioaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(config->ioaddr)) + return PTR_ERR(config->ioaddr); + + spin_lock_init(&config->lock); + status = devm_request_irq(&pdev->dev, irq, spear_rtc_irq, 0, pdev->name, config); if (status) { @@ -371,10 +377,6 @@ static int spear_rtc_probe(struct platform_device *pdev) return status; } - config->ioaddr = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(config->ioaddr)) - return PTR_ERR(config->ioaddr); - config->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(config->clk)) return PTR_ERR(config->clk); @@ -383,7 +385,6 @@ static int spear_rtc_probe(struct platform_device *pdev) if (status < 0) return status; - spin_lock_init(&config->lock); platform_set_drvdata(pdev, config); config->rtc->ops = &spear_rtc_ops; diff --git a/drivers/scsi/ch.c b/drivers/scsi/ch.c index 4010fdbf813cc7..c36539c83ed1a4 100644 --- a/drivers/scsi/ch.c +++ b/drivers/scsi/ch.c @@ -112,7 +112,6 @@ typedef struct { int minor; char name[8]; struct scsi_device *device; - struct scsi_device **dt; /* ptrs to data transfer elements */ u_int firsts[CH_TYPES]; u_int counts[CH_TYPES]; u_int voltags; @@ -363,15 +362,10 @@ ch_readconfig(scsi_changer *ch) vendor_labels[i]); } - /* look up the devices of the data transfer elements */ - ch->dt = kzalloc_objs(*ch->dt, ch->counts[CHET_DT]); - - if (!ch->dt) { - kfree(buffer); - return -ENOMEM; - } - + /* report the devices of the data transfer elements */ for (elem = 0; elem < ch->counts[CHET_DT]; elem++) { + struct scsi_device *sdev; + id = -1; lun = 0; if (elem < CH_DT_MAX && -1 != dt_id[elem]) { @@ -387,10 +381,8 @@ ch_readconfig(scsi_changer *ch) VPRINTK(KERN_INFO, "dt 0x%x: ",elem+ch->firsts[CHET_DT]); if (data[6] & 0x80) { VPRINTK(KERN_CONT, "not this SCSI bus\n"); - ch->dt[elem] = NULL; } else if (0 == (data[6] & 0x30)) { VPRINTK(KERN_CONT, "ID/LUN unknown\n"); - ch->dt[elem] = NULL; } else { id = ch->device->id; lun = 0; @@ -400,18 +392,16 @@ ch_readconfig(scsi_changer *ch) } if (-1 != id) { VPRINTK(KERN_CONT, "ID %i, LUN %i, ",id,lun); - ch->dt[elem] = - scsi_device_lookup(ch->device->host, - ch->device->channel, - id,lun); - if (!ch->dt[elem]) { + sdev = scsi_device_lookup(ch->device->host, + ch->device->channel, + id, lun); + if (!sdev) { /* should not happen */ VPRINTK(KERN_CONT, "Huh? device not found!\n"); } else { VPRINTK(KERN_CONT, "name: %8.8s %16.16s %4.4s\n", - ch->dt[elem]->vendor, - ch->dt[elem]->model, - ch->dt[elem]->rev); + sdev->vendor, sdev->model, sdev->rev); + scsi_device_put(sdev); } } } @@ -575,7 +565,6 @@ static void ch_destroy(struct kref *ref) scsi_changer *ch = container_of(ref, scsi_changer, ref); ch->device = NULL; - kfree(ch->dt); kfree(ch); } diff --git a/drivers/scsi/qedi/qedi_main.c b/drivers/scsi/qedi/qedi_main.c index 227ff7bd1bdc9b..f394fcbb97e7ee 100644 --- a/drivers/scsi/qedi/qedi_main.c +++ b/drivers/scsi/qedi/qedi_main.c @@ -2652,8 +2652,6 @@ static int __qedi_probe(struct pci_dev *pdev, int mode) snprintf(host_buf, sizeof(host_buf), "host_%d", qedi->shost->host_no); qedi_ops->common->set_name(qedi->cdev, host_buf); - qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); - memset(¶ms, 0, sizeof(params)); params.mtu = DEF_PATH_MTU + IPV6_HDR_LEN + TCP_HDR_LEN; qedi->ll2_mtu = DEF_PATH_MTU; @@ -2670,6 +2668,7 @@ static int __qedi_probe(struct pci_dev *pdev, int mode) spin_lock_init(&qedi->task_idx_lock); mutex_init(&qedi->stats_lock); } + qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); qedi_ops->ll2->register_cb_ops(qedi->cdev, &qedi_ll2_cb_ops, qedi); qedi_ops->ll2->start(qedi->cdev, ¶ms); diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c index 197b7540689208..7156261743678a 100644 --- a/drivers/soundwire/dmi-quirks.c +++ b/drivers/soundwire/dmi-quirks.c @@ -111,6 +111,34 @@ static const struct adr_remap ghost_realtek[] = { {} }; +/* + * The Microsoft Surface Pro 11 (Intel) describes its single physical RT1320 + * amplifier twice on link 0, as two _ADR entries differing only in SDCA class + * id and sharing the same unique id. Only the class 1 entry enumerates; the + * class 0 entry is a ghost. + * + * Matched on the product SKU rather than the product name: a later batch of the + * same model could carry a different RT1320 version, which would change the + * _ADR. Keying on the SKU keeps this remap to the hardware it was verified on. + */ +static const struct adr_remap microsoft_sp11_intel[] = { + /* ghost rt1320 on link0 */ + { + 0x000030025d132000ull, + 0x0000000000000000ull + }, + {} +}; + +static const struct adr_remap ghost_rt711[] = { + /* rt711 on link0 */ + { + 0x000030025d071101ull, + 0x0000000000000000ull + }, + {} +}; + static const struct dmi_system_id adr_remap_quirk_table[] = { /* TGL devices */ { @@ -221,6 +249,13 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83SN"), + }, + .driver_data = (void *)ghost_realtek, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), @@ -249,6 +284,22 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + /* LNL devices */ + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, + "Surface_Pro_11th_Edition_With_Intel_For_Business_2103") + }, + .driver_data = (void *)microsoft_sp11_intel, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), + }, + .driver_data = (void *)ghost_rt711, + }, {} }; diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c index 901a71262094f9..5983fbc5725a26 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -71,8 +71,11 @@ static struct wake_capable_part wake_capable_list[] = { {0x025d, 0x715}, {0x025d, 0x716}, {0x025d, 0x717}, + {0x025d, 0x718}, {0x025d, 0x721}, {0x025d, 0x722}, + {0x025d, 0x766}, + {0x025d, 0x767}, {0x04b3, 0x9356}, {0x0102, 0x5572}, {0x0102, 0x5682}, diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 35ffffd541bd10..6e0c5fb10efad4 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -25,7 +25,9 @@ #define SWRM_COMP_SW_RESET 0x008 #define SWRM_COMP_STATUS 0x014 #define SWRM_LINK_MANAGER_EE 0x018 -#define SWRM_EE_CPU 1 +#define SWRM_EE_CPU0 0 +#define SWRM_EE_CPU1 1 +#define SWRM_EE_CPU SWRM_EE_CPU1 #define SWRM_FRM_GEN_ENABLED BIT(0) #define SWRM_VERSION_1_3_0 0x01030000 #define SWRM_VERSION_1_5_1 0x01050001 @@ -118,6 +120,7 @@ #define SWRM_V2_0_CLK_CTRL 0x5060 #define SWRM_V2_0_CLK_CTRL_CLK_START BIT(0) #define SWRM_V2_0_LINK_STATUS 0x5064 +#define SWRM_V2_REG_EE_STRIDE 0x1000 #define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18 #define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10 @@ -201,6 +204,7 @@ struct qcom_swrm_ctrl { struct mutex port_lock; struct clk *hclk; int irq; + u32 ee; unsigned int version; int wake_irq; int num_din_ports; @@ -224,6 +228,7 @@ struct qcom_swrm_ctrl { /* Per-Slave SCP_ADDRPAGE1/2 shadow; -1 = unknown. */ s16 page1_cache[SDW_MAX_DEVICES + 1]; s16 page2_cache[SDW_MAX_DEVICES + 1]; + unsigned int reg_layout_local[SWRM_OFFSET_DP_SAMPLECTRL2_BANK + 1]; }; struct qcom_swrm_data { @@ -232,6 +237,7 @@ struct qcom_swrm_data { bool sw_clk_gate_required; u32 max_reg; const unsigned int *reg_layout; + u32 ee; }; static const unsigned int swrm_v1_3_reg_layout[] = { @@ -258,6 +264,7 @@ static const struct qcom_swrm_data swrm_v1_3_data = { .default_cols = 16, .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR, .reg_layout = swrm_v1_3_reg_layout, + .ee = SWRM_EE_CPU, }; static const struct qcom_swrm_data swrm_v1_5_data = { @@ -265,6 +272,7 @@ static const struct qcom_swrm_data swrm_v1_5_data = { .default_cols = 16, .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR, .reg_layout = swrm_v1_3_reg_layout, + .ee = SWRM_EE_CPU, }; static const struct qcom_swrm_data swrm_v1_6_data = { @@ -273,6 +281,7 @@ static const struct qcom_swrm_data swrm_v1_6_data = { .sw_clk_gate_required = true, .max_reg = SWR_V1_3_MSTR_MAX_REG_ADDR, .reg_layout = swrm_v1_3_reg_layout, + .ee = SWRM_EE_CPU, }; static const unsigned int swrm_v2_0_reg_layout[] = { @@ -300,6 +309,7 @@ static const struct qcom_swrm_data swrm_v2_0_data = { .sw_clk_gate_required = true, .max_reg = SWR_V2_0_MSTR_MAX_REG_ADDR, .reg_layout = swrm_v2_0_reg_layout, + .ee = SWRM_EE_CPU, }; static const unsigned int swrm_v3_0_reg_layout[] = { @@ -327,9 +337,51 @@ static const struct qcom_swrm_data swrm_v3_0_data = { .sw_clk_gate_required = true, .max_reg = SWR_V2_0_MSTR_MAX_REG_ADDR, .reg_layout = swrm_v3_0_reg_layout, + .ee = SWRM_EE_CPU, +}; + +static const struct qcom_swrm_data swrm_shikra_data = { + .default_rows = 50, + .default_cols = 16, + .sw_clk_gate_required = true, + .max_reg = SWR_V2_0_MSTR_MAX_REG_ADDR, + .reg_layout = swrm_v3_0_reg_layout, + .ee = SWRM_EE_CPU0, }; #define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus) +static void qcom_swrm_set_ee_register_layout(struct qcom_swrm_ctrl *ctrl, + const struct qcom_swrm_data *data) +{ + int ee_offset; + + memcpy(ctrl->reg_layout_local, data->reg_layout, + sizeof(ctrl->reg_layout_local)); + ctrl->reg_layout = ctrl->reg_layout_local; + + if (ctrl->version < SWRM_VERSION_2_0_0) + return; + + /* + * The register layout constants are defined for EE1 (the default for + * most Qualcomm SoCs). For SoCs where the SoundWire master is assigned + * to EE0, the interrupt, FIFO and status register windows are shifted + * by one EE stride (0x1000) relative to the EE1 base addresses. + */ + ee_offset = ((int)ctrl->ee - SWRM_EE_CPU) * SWRM_V2_REG_EE_STRIDE; + if (!ee_offset) + return; + + ctrl->reg_layout_local[SWRM_REG_FRAME_GEN_ENABLED] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_STATUS] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_CLEAR] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_CPU_EN] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_WR_CMD] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_RD_CMD] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_STATUS] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_RD_FIFO_ADDR] += ee_offset; +} + static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val) { @@ -910,12 +962,13 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val); if (ctrl->version == SWRM_VERSION_1_7_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, - SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU); + SWRM_MCP_BUS_CLK_START << ctrl->ee); } else if (ctrl->version >= SWRM_VERSION_2_0_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); - ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL, + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); + ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL + + ((int)ctrl->ee - SWRM_EE_CPU) * SWRM_V2_REG_EE_STRIDE, SWRM_V2_0_CLK_CTRL_CLK_START); } else { ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START); @@ -941,11 +994,9 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR], 0xFFFFFFFF); - /* enable CPU IRQs */ - if (ctrl->mmio) { - ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN], - SWRM_INTERRUPT_STATUS_RMSK); - } + /* enable CPU IRQs for the selected EE window */ + ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN], + SWRM_INTERRUPT_STATUS_RMSK); /* Set IRQ to PULSE */ ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR, @@ -1580,6 +1631,7 @@ static int qcom_swrm_probe(struct platform_device *pdev) memset(ctrl->page2_cache, 0xff, sizeof(ctrl->page2_cache)); data = of_device_get_match_data(dev); + ctrl->ee = data->ee; ctrl->max_reg = data->max_reg; ctrl->reg_layout = data->reg_layout; ctrl->rows_index = sdw_find_row_index(data->default_rows); @@ -1659,6 +1711,7 @@ static int qcom_swrm_probe(struct platform_device *pdev) prop->default_row = data->default_rows; ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version); + qcom_swrm_set_ee_register_layout(ctrl, data); ret = devm_request_threaded_irq(dev, ctrl->irq, NULL, qcom_swrm_irq_handler, @@ -1769,16 +1822,19 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev) reset_control_reset(ctrl->audio_cgcr); if (ctrl->version == SWRM_VERSION_1_7_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, - SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU); + SWRM_MCP_BUS_CLK_START << ctrl->ee); } else if (ctrl->version >= SWRM_VERSION_2_0_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); - ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL, + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); + ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL + + ((int)ctrl->ee - SWRM_EE_CPU) * + SWRM_V2_REG_EE_STRIDE, SWRM_V2_0_CLK_CTRL_CLK_START); } else { ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START); } + ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR], SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET); @@ -1854,6 +1910,7 @@ static const struct of_device_id qcom_swrm_of_match[] = { { .compatible = "qcom,soundwire-v1.7.0", .data = &swrm_v1_5_data }, { .compatible = "qcom,soundwire-v2.0.0", .data = &swrm_v2_0_data }, { .compatible = "qcom,soundwire-v3.1.0", .data = &swrm_v3_0_data }, + { .compatible = "qcom,shikra-soundwire", .data = &swrm_shikra_data }, {/* sentinel */}, }; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a0f37e2df71658..eb042cf5268f80 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -63,7 +63,6 @@ comment "SPI Master Controller Drivers" config SPI_AIROHA_SNFI tristate "Airoha SPI NAND Flash Interface" depends on ARCH_AIROHA || COMPILE_TEST - depends on SPI_MASTER select REGMAP_MMIO help This enables support for SPI-NAND mode on the Airoha NAND @@ -92,6 +91,14 @@ config SPI_ALTERA_DFL Altera SPI master controller. The SPI master is connected to a SPI slave to Avalon bridge in a Intel MAX BMC. +config SPI_AMD + tristate "AMD SPI controller" + depends on PCI + depends on X86 || COMPILE_TEST + depends on SPI_MEM + help + Enables SPI controller driver for AMD SoC. + config SPI_AMLOGIC_SPIFC_A1 tristate "Amlogic A1 SPIFC controller" depends on ARCH_MESON || COMPILE_TEST @@ -136,6 +143,13 @@ config SPI_AR934X This enables support for the SPI controller present on the Qualcomm Atheros AR934X/QCA95XX SoCs. +config SPI_ARMADA_3700 + tristate "Marvell Armada 3700 SPI Controller" + depends on (ARCH_MVEBU && OF) || COMPILE_TEST + help + This enables support for the SPI controller present on the + Marvell Armada 3700 SoCs. + config SPI_ATCSPI200 tristate "Andes ATCSPI200 SPI controller" depends on ARCH_ANDES || COMPILE_TEST @@ -153,13 +167,6 @@ config SPI_ATH79 This enables support for the SPI controller present on the Atheros AR71XX/AR724X/AR913X SoCs. -config SPI_ARMADA_3700 - tristate "Marvell Armada 3700 SPI Controller" - depends on (ARCH_MVEBU && OF) || COMPILE_TEST - help - This enables support for the SPI controller present on the - Marvell Armada 3700 SoCs. - config SPI_ASPEED_SMC tristate "Aspeed flash controllers in SPI mode" depends on ARCH_ASPEED || COMPILE_TEST @@ -333,7 +340,7 @@ config SPI_CADENCE_XSPI config SPI_CH341 tristate "CH341 USB2SPI adapter" - depends on SPI_MASTER && USB + depends on USB help Enables the SPI controller on the CH341a USB to serial chip @@ -388,14 +395,14 @@ config SPI_DW_MMIO endif config SPI_DLN2 - tristate "Diolan DLN-2 USB SPI adapter" - depends on MFD_DLN2 - help - If you say yes to this option, support will be included for Diolan - DLN2, a USB to SPI interface. + tristate "Diolan DLN-2 USB SPI adapter" + depends on MFD_DLN2 + help + If you say yes to this option, support will be included for Diolan + DLN2, a USB to SPI interface. - This driver can also be built as a module. If so, the module - will be called spi-dln2. + This driver can also be built as a module. If so, the module + will be called spi-dln2. config SPI_EP93XX tristate "Cirrus Logic EP93xx SPI controller" @@ -420,6 +427,22 @@ config SPI_FSI This enables support for the driver for FSI bus attached SPI controllers. +config SPI_FSL_DSPI + tristate "Freescale DSPI controller" + select REGMAP_MMIO + depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST + help + This enables support for the Freescale DSPI controller in master + mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. + +config SPI_FSL_ESPI + tristate "Freescale eSPI controller" + depends on FSL_SOC + help + This enables using the Freescale eSPI controllers in master mode. + From MPC8536, 85xx platform uses the controller, and all P10xx, + P20xx, P30xx,P40xx, P50xx uses this controller. + config SPI_FSL_LPSPI tristate "Freescale i.MX LPSPI controller" depends on ARCH_MXC || COMPILE_TEST @@ -438,6 +461,26 @@ config SPI_FSL_QUADSPI This controller does not support generic SPI messages. It only supports the high-level SPI memory interface. +config SPI_FSL_LIB + tristate + depends on OF + +config SPI_FSL_CPM + tristate + depends on FSL_SOC + +config SPI_FSL_SPI + tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" + depends on OF + select SPI_FSL_LIB + select SPI_FSL_CPM if FSL_SOC + help + This enables using the Freescale SPI controllers in master mode. + MPC83xx platform uses the controller in cpu mode or CPM/QE mode. + MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. + This also enables using the Aeroflex Gaisler GRLIB SPI controller in + master mode. + config SPI_GXP tristate "GXP SPI driver" depends on ARCH_HPE || COMPILE_TEST @@ -463,27 +506,6 @@ config SPI_HISI_SFC_V3XX This enables support for HiSilicon v3xx SPI NOR flash controller found in hi16xx chipsets. -config SPI_NXP_FLEXSPI - tristate "NXP Flex SPI controller" - depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the Flex SPI controller in master mode. - Up to four slave devices can be connected on two buses with two - chipselects each. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - -config SPI_NXP_XSPI - tristate "NXP xSPI controller" - depends on ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the xSPI controller. Up to two devices - can be connected to one host. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - config SPI_GPIO tristate "GPIO-based bitbanging SPI Master" depends on GPIOLIB || COMPILE_TEST @@ -581,6 +603,25 @@ config SPI_KSPI2 This driver can also be built as a module. If so, the module will be called spi-kspi2. +config SPI_LANTIQ_SSC + tristate "Lantiq SSC SPI controller" + depends on LANTIQ || X86 || COMPILE_TEST + help + This driver supports the Lantiq SSC SPI controller in master + mode. This controller is found on Intel (former Lantiq) SoCs like + the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + +config SPI_LJCA + tristate "Intel La Jolla Cove Adapter SPI support" + depends on USB_LJCA + default USB_LJCA + help + Select this option to enable SPI driver for the Intel + La Jolla Cove Adapter (LJCA) board. + + This driver can also be built as a module. If so, the module + will be called spi-ljca. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT @@ -626,74 +667,6 @@ config SPI_LP8841_RTC Say N here unless you plan to run the kernel on an ICP DAS LP-8x4x industrial computer. -config SPI_MPC52xx - tristate "Freescale MPC52xx SPI (non-PSC) controller support" - depends on PPC_MPC52xx - help - This drivers supports the MPC52xx SPI controller in master SPI - mode. - -config SPI_MPC52xx_PSC - tristate "Freescale MPC52xx PSC SPI controller" - depends on PPC_MPC52xx - help - This enables using the Freescale MPC52xx Programmable Serial - Controller in master SPI mode. - -config SPI_MPC512x_PSC - tristate "Freescale MPC512x PSC SPI controller" - depends on PPC_MPC512x - help - This enables using the Freescale MPC5121 Programmable Serial - Controller in SPI master mode. - -config SPI_FSL_LIB - tristate - depends on OF - -config SPI_FSL_CPM - tristate - depends on FSL_SOC - -config SPI_FSL_SPI - tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" - depends on OF - select SPI_FSL_LIB - select SPI_FSL_CPM if FSL_SOC - help - This enables using the Freescale SPI controllers in master mode. - MPC83xx platform uses the controller in cpu mode or CPM/QE mode. - MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. - This also enables using the Aeroflex Gaisler GRLIB SPI controller in - master mode. - -config SPI_FSL_DSPI - tristate "Freescale DSPI controller" - select REGMAP_MMIO - depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST - help - This enables support for the Freescale DSPI controller in master - mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. - -config SPI_FSL_ESPI - tristate "Freescale eSPI controller" - depends on FSL_SOC - help - This enables using the Freescale eSPI controllers in master mode. - From MPC8536, 85xx platform uses the controller, and all P10xx, - P20xx, P30xx,P40xx, P50xx uses this controller. - -config SPI_LJCA - tristate "Intel La Jolla Cove Adapter SPI support" - depends on USB_LJCA - default USB_LJCA - help - Select this option to enable SPI driver for the Intel - La Jolla Cove Adapter (LJCA) board. - - This driver can also be built as a module. If so, the module - will be called spi-ljca. - config SPI_MA35D1_QSPI tristate "Nuvoton MA35D1 QSPI controller" depends on ARCH_MA35 || COMPILE_TEST @@ -722,7 +695,6 @@ config SPI_MESON_SPIFC config SPI_MICROCHIP_CORE_QSPI tristate "Microchip FPGA QSPI controllers" - depends on SPI_MASTER help This enables the QSPI driver for Microchip FPGA QSPI controllers. Say Y or M here if you want to use the QSPI controllers on @@ -731,13 +703,33 @@ config SPI_MICROCHIP_CORE_QSPI config SPI_MICROCHIP_CORE_SPI tristate "Microchip FPGA CoreSPI controller" - depends on SPI_MASTER help This enables the SPI driver for Microchip FPGA CoreSPI controller. Say Y or M here if you want to use the "soft" controllers on PolarFire SoC. If built as a module, it will be called spi-microchip-core-spi. +config SPI_MPC52xx + tristate "Freescale MPC52xx SPI (non-PSC) controller support" + depends on PPC_MPC52xx + help + This drivers supports the MPC52xx SPI controller in master SPI + mode. + +config SPI_MPC52xx_PSC + tristate "Freescale MPC52xx PSC SPI controller" + depends on PPC_MPC52xx + help + This enables using the Freescale MPC52xx Programmable Serial + Controller in master SPI mode. + +config SPI_MPC512x_PSC + tristate "Freescale MPC512x PSC SPI controller" + depends on PPC_MPC512x + help + This enables using the Freescale MPC5121 Programmable Serial + Controller in SPI master mode. + config SPI_MT65XX tristate "MediaTek SPI controller" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -773,6 +765,19 @@ config SPI_MTK_SNFI is implemented as a SPI-MEM controller with pipelined ECC capability. +config SPI_MXIC + tristate "Macronix MX25F0A SPI controller" + imply MTD_NAND_ECC_MXIC + help + This selects the Macronix MX25F0A SPI controller driver. + +config SPI_MXS + tristate "Freescale MXS SPI controller" + depends on ARCH_MXS + select STMP_DEVICE + help + SPI driver for Freescale MXS devices. + config SPI_WPCM_FIU tristate "Nuvoton WPCM450 Flash Interface Unit" depends on ARCH_NPCM || COMPILE_TEST @@ -801,13 +806,26 @@ config SPI_NPCM_PSPI This driver provides support for Nuvoton NPCM BMC Peripheral SPI controller in master mode. -config SPI_LANTIQ_SSC - tristate "Lantiq SSC SPI controller" - depends on LANTIQ || X86 || COMPILE_TEST +config SPI_NXP_FLEXSPI + tristate "NXP Flex SPI controller" + depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM help - This driver supports the Lantiq SSC SPI controller in master - mode. This controller is found on Intel (former Lantiq) SoCs like - the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + This enables support for the Flex SPI controller in master mode. + Up to four slave devices can be connected on two buses with two + chipselects each. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. + +config SPI_NXP_XSPI + tristate "NXP xSPI controller" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + This enables support for the xSPI controller. Up to two devices + can be connected to one host. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. config SPI_OC_TINY tristate "OpenCores tiny SPI" @@ -838,14 +856,6 @@ config SPI_OMAP24XX SPI master controller for OMAP24XX and later Multichannel SPI (McSPI) modules. -config SPI_TI_QSPI - tristate "DRA7xxx QSPI controller support" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - help - QSPI master controller for DRA7xxx used for flash devices. - This device supports single, dual and quad read support, while - it only supports single write mode. - config SPI_ORION tristate "Orion SPI master" depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST @@ -887,7 +897,6 @@ config SPI_PL022 config SPI_POLARFIRE_SOC tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER depends on ARCH_MICROCHIP || COMPILE_TEST help This enables the SPI driver for Microchip FPGA SPI controllers. @@ -950,7 +959,7 @@ config SPI_ROCKCHIP_SFC config SPI_RB4XX tristate "Mikrotik RB4XX SPI master" - depends on SPI_MASTER && (ATH79 || COMPILE_TEST) + depends on ATH79 || COMPILE_TEST depends on OF help SPI controller driver for the Mikrotik RB4xx series boards. @@ -967,6 +976,16 @@ config SPI_RSPI help SPI driver for Renesas RSPI and QSPI blocks. +config SPI_RTK_NOR + tristate "Realtek SPI-NOR flash controller" + depends on ARCH_REALTEK || COMPILE_TEST + depends on SPI_MEM + help + This enables support for the SPI NOR Flash Controller found on + Realtek DHC SoCs. The controller supports single and dual I/O + transfers and offloads the payload to an integrated DMA engine. + Say Y or M here if you are building a kernel for a Realtek DHC SoC + config SPI_RZV2H_RSPI tristate "Renesas RZ/V2H RSPI controller" depends on ARCH_RENESAS || COMPILE_TEST @@ -1035,12 +1054,14 @@ config SPI_SC18IS602 help SPI driver for NXP SC18IS602/602B/603 I2C to SPI bridge. -config SPI_SH_MSIOF - tristate "SuperH MSIOF SPI controller" - depends on HAVE_CLK - depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST +config SPI_SG2044_NOR + tristate "SG2044 SPI NOR Controller" + depends on ARCH_SOPHGO || COMPILE_TEST help - SPI driver for SuperH and SH Mobile MSIOF blocks. + This enables support for the SG2044 SPI NOR controller, + which supports Dual/Quad read and write operations while + also supporting 3Byte address devices and 4Byte address + devices. config SPI_SH tristate "SuperH SPI controller" @@ -1048,6 +1069,19 @@ config SPI_SH help SPI driver for SuperH SPI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH HSPI blocks. + +config SPI_SH_MSIOF + tristate "SuperH MSIOF SPI controller" + depends on HAVE_CLK + depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH and SH Mobile MSIOF blocks. + config SPI_SH_SCI tristate "SuperH SCI SPI controller" depends on SUPERH @@ -1055,28 +1089,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. -config SPI_SH_HSPI - tristate "SuperH HSPI controller" - depends on ARCH_RENESAS || COMPILE_TEST - help - SPI driver for SuperH HSPI blocks. - config SPI_SIFIVE tristate "SiFive SPI controller" depends on HAS_IOMEM help This exposes the SPI controller IP from SiFive. -config SPI_SLAVE_MT27XX - tristate "MediaTek SPI slave device" - depends on ARCH_MEDIATEK || COMPILE_TEST - depends on SPI_SLAVE - help - This selects the MediaTek(R) SPI slave device driver. - If you want to use MediaTek(R) SPI slave interface, - say Y or M here.If you are not sure, say N. - SPI slave drivers for Mediatek MT27XX series ARM SoCs. - config SPI_SN_F_OSPI tristate "Socionext F_OSPI SPI flash controller" depends on OF && HAS_IOMEM @@ -1086,15 +1104,6 @@ config SPI_SN_F_OSPI for connecting an SPI Flash memory over up to 8-bit wide bus. It supports indirect access mode only. -config SPI_SG2044_NOR - tristate "SG2044 SPI NOR Controller" - depends on ARCH_SOPHGO || COMPILE_TEST - help - This enables support for the SG2044 SPI NOR controller, - which supports Dual/Quad read and write operations while - also supporting 3Byte address devices and 4Byte address - devices. - config SPI_SPACEMIT_K1 tristate "K1 SPI Controller" depends on ARCH_SPACEMIT || COMPILE_TEST @@ -1189,20 +1198,6 @@ config SPI_SYNQUACER It also supports the new dual-bit and quad-bit SPI protocol. -config SPI_MXIC - tristate "Macronix MX25F0A SPI controller" - depends on SPI_MASTER - imply MTD_NAND_ECC_MXIC - help - This selects the Macronix MX25F0A SPI controller driver. - -config SPI_MXS - tristate "Freescale MXS SPI controller" - depends on ARCH_MXS - select STMP_DEVICE - help - SPI driver for Freescale MXS devices. - config SPI_TEGRA210_QUAD tristate "NVIDIA Tegra QSPI Controller" depends on ARCH_TEGRA || COMPILE_TEST @@ -1244,6 +1239,23 @@ config SPI_THUNDERX SPI host driver for the hardware found on Cavium ThunderX SOCs. +config SPI_TI_QSPI + tristate "DRA7xxx QSPI controller support" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + QSPI master controller for DRA7xxx used for flash devices. + This device supports single, dual and quad read support, while + it only supports single write mode. + +config SPI_TLE62X0 + tristate "Infineon TLE62X0 (for power switching)" + depends on SYSFS + help + SPI driver for Infineon TLE62X0 series line driver chips, + such as the TLE6220, TLE6230 and TLE6240. This provides a + sysfs interface, with each line presented as a kind of GPIO + exposing both switch control and diagnostic feedback. + config SPI_TOPCLIFF_PCH tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI" depends on PCI && (X86_32 || MIPS || COMPILE_TEST) @@ -1270,7 +1282,7 @@ config SPI_UNIPHIER config SPI_VIRTIO tristate "Virtio SPI Controller" - depends on SPI_MASTER && VIRTIO + depends on VIRTIO help If you say yes to this option, support will be included for the virtio SPI controller driver. The hardware can be emulated by any device model @@ -1336,14 +1348,6 @@ config SPI_ZYNQMP_GQSPI Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC. This controller only supports SPI memory interface. -config SPI_AMD - tristate "AMD SPI controller" - depends on PCI - depends on SPI_MASTER || X86 || COMPILE_TEST - depends on SPI_MEM - help - Enables SPI controller driver for AMD SoC. - # # Add new SPI master controllers in alphabetical order above this line # @@ -1392,15 +1396,6 @@ config SPI_LOOPBACK_TEST primarily used for development of spi_master drivers and to detect regressions -config SPI_TLE62X0 - tristate "Infineon TLE62X0 (for power switching)" - depends on SYSFS - help - SPI driver for Infineon TLE62X0 series line driver chips, - such as the TLE6220, TLE6230 and TLE6240. This provides a - sysfs interface, with each line presented as a kind of GPIO - exposing both switch control and diagnostic feedback. - # # Add new SPI protocol masters in alphabetical order above this line # @@ -1431,6 +1426,15 @@ config SPI_SLAVE_SYSTEM_CONTROL SPI slave handler to allow remote control of system reboot, power off, halt, and suspend. +config SPI_SLAVE_MT27XX + tristate "MediaTek SPI slave device" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + This selects the MediaTek(R) SPI slave device driver. + If you want to use MediaTek(R) SPI slave interface, + say Y or M here.If you are not sure, say N. + SPI slave drivers for Mediatek MT27XX series ARM SoCs. + endif # SPI_SLAVE config SPI_DYNAMIC diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index a18814fbde93f4..37c6eec9b54180 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -133,6 +133,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o +obj-$(CONFIG_SPI_RTK_NOR) += spi-rtk-nor.o obj-$(CONFIG_SPI_RZV2H_RSPI) += spi-rzv2h-rspi.o obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 9049a87e9d0f2d..1b00f34e9e931a 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -37,6 +37,12 @@ #define CFG_HW_POS BIT(6) /* start on vsync falling */ #define CFG_HW_NEG BIT(7) +#define CFG_WORD_GAP GENMASK(9, 8) +#define CFG_MO_IDLE_OUTPUT GENMASK(11, 10) +/* cs hold time in pclk */ +#define CFG_CS_HOLD GENMASK(26, 12) +/* high 4 bits of cs setup time in sclk */ +#define CFG_CS_SETUP_EXTEND GENMASK(30, 27) #define SPISG_REG_CFG_START 0x08 #define CFG_BLOCK_NUM GENMASK(19, 0) @@ -95,7 +101,7 @@ #define SPISG_MAX_REG 0x40 -#define SPISG_BLOCK_MAX 0x100000 +#define SPISG_BLOCK_MAX 0xFFFFF #define SPISG_OP_MODE_WRITE_CMD 0 #define SPISG_OP_MODE_READ_STS 1 @@ -143,6 +149,13 @@ struct spisg_descriptor_extra { int rx_ccsg_len; }; +struct aml_spisg_data { + bool mo_idle_output_ctrl; + bool word_gap_ctrl; + bool cs_hold_ctrl; + bool cs_setup_extend_ctrl; +}; + struct spisg_device { struct spi_controller *controller; struct platform_device *pdev; @@ -152,6 +165,7 @@ struct spisg_device { struct clk *sclk; struct clk_div_table *tbl; struct completion completion; + const struct aml_spisg_data *data; u32 status; u32 speed_hz; u32 effective_speed_hz; @@ -175,7 +189,7 @@ static int spi_delay_to_sclk(u32 slck_speed_hz, struct spi_delay *delay) if (ns < 0) return 0; - return DIV_ROUND_UP_ULL(slck_speed_hz * ns, NSEC_PER_SEC); + return DIV_ROUND_UP_ULL((u64)slck_speed_hz * ns, NSEC_PER_SEC); } static inline u32 aml_spisg_sem_down_read(struct spisg_device *spisg) @@ -424,7 +438,8 @@ static void aml_spisg_setup_null_desc(struct spisg_device *spisg, static void aml_spisg_pending(struct spisg_device *spisg, dma_addr_t desc_paddr, bool trig, - bool irq_en) + bool irq_en, + u32 delay) { u32 desc_l, desc_h, cfg_spi, irq_enable; @@ -437,6 +452,13 @@ static void aml_spisg_pending(struct spisg_device *spisg, #endif cfg_spi = spisg->cfg_spi; + + if (spisg->data && spisg->data->word_gap_ctrl) { + if (delay > 3) + delay = 3; + cfg_spi |= FIELD_PREP(CFG_WORD_GAP, delay); + } + if (trig) cfg_spi |= CFG_HW_POS; else @@ -483,13 +505,16 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, { struct spisg_device *spisg = spi_controller_get_devdata(ctlr); struct device *dev = &spisg->pdev->dev; + const struct aml_spisg_data *data = spisg->data; unsigned long long ms = 0; struct spi_transfer *xfer; struct spisg_descriptor *descs, *desc; struct spisg_descriptor_extra *exdescs, *exdesc; dma_addr_t descs_paddr; int desc_num = 1, descs_len; + bool last_xfer_keep_ss = false; u32 cs_hold_in_sclk = 0; + u32 val, delay = 0; int ret = -EIO; if (!aml_spisg_sem_down_read(spisg)) { @@ -523,15 +548,27 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, goto end; } - /* calculate cs-setup delay with the first xfer speed */ - if (list_is_first(&xfer->transfer_list, &msg->transfers)) - desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, - spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup)); + /* calculate cs-setup delay with the first xfer speed and word dealy*/ + if (list_is_first(&xfer->transfer_list, &msg->transfers)) { + val = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup); + if (data && data->cs_setup_extend_ctrl) { + val = min_t(u32, 0xFF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val & 0xF); + FIELD_MODIFY(CFG_CS_SETUP_EXTEND, &spisg->cfg_spi, val >> 4); + } else { + val = min_t(u32, 0xF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val); + } + + delay = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->word_delay); + } /* calculate cs-hold delay with the last xfer speed */ - if (list_is_last(&xfer->transfer_list, &msg->transfers)) + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { cs_hold_in_sclk = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_hold); + last_xfer_keep_ss = xfer->cs_change; + } desc++; exdesc++; @@ -539,13 +576,23 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, xfer->effective_speed_hz); } - if (cs_hold_in_sclk) + if (data && data->cs_hold_ctrl) { + cs_hold_in_sclk = cs_hold_in_sclk ? : 1; + val = cs_hold_in_sclk * (FIELD_GET(CFG_CLK_DIV, spisg->cfg_bus) + 1); + val = min_t(u32, 0x7FFF, val); + FIELD_MODIFY(CFG_CS_HOLD, &spisg->cfg_spi, val); + desc--; + } else if (cs_hold_in_sclk) { /* additional null-descriptor to achieve the cs-hold delay */ aml_spisg_setup_null_desc(spisg, desc, cs_hold_in_sclk); - else desc--; + desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 1); + desc++; + } else { + desc--; + } - desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 0); + FIELD_MODIFY(CFG_KEEP_SS, &desc->cfg_bus, last_xfer_keep_ss); desc->cfg_start |= FIELD_PREP(CFG_EOC, 1); /* some tolerances */ @@ -562,13 +609,16 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, } reinit_completion(&spisg->completion); - aml_spisg_pending(spisg, descs_paddr, false, true); + aml_spisg_pending(spisg, descs_paddr, false, true, delay); if (wait_for_completion_timeout(&spisg->completion, spi_controller_is_target(spisg->controller) ? - MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) + MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) { ret = spisg->status ? -EIO : 0; - else + } else { + /* stop transfer */ + regmap_write(spisg->map, SPISG_REG_DESC_LIST_H, 0); ret = -ETIMEDOUT; + } dma_unmap_single(dev, descs_paddr, descs_len, DMA_TO_DEVICE); end: @@ -704,7 +754,9 @@ static int aml_spisg_clk_init(struct spisg_device *spisg, void __iomem *base) return PTR_ERR(spisg->sclk); } - clk_prepare_enable(spisg->sclk); + ret = clk_prepare_enable(spisg->sclk); + if (ret) + return ret; return 0; } @@ -715,6 +767,7 @@ static int aml_spisg_probe(struct platform_device *pdev) struct spisg_device *spisg; struct device *dev = &pdev->dev; void __iomem *base; + u32 val = 0; int ret, irq; const struct regmap_config aml_regmap_config = { @@ -733,6 +786,7 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg = spi_controller_get_devdata(ctlr); spisg->controller = ctlr; + spisg->data = (struct aml_spisg_data *)of_device_get_match_data(dev); spisg->pdev = pdev; platform_set_drvdata(pdev, spisg); @@ -763,6 +817,14 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg->cfg_spi = FIELD_PREP(CFG_SFLASH_WP, 1) | FIELD_PREP(CFG_SFLASH_HD, 1); + + if (spisg->data && spisg->data->mo_idle_output_ctrl) { + if (!of_property_read_u32(dev->of_node, "amlogic,mo-idle-output", &val)) + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, val); + else + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, 0); + } + if (spi_controller_is_target(ctlr)) { spisg->cfg_spi |= FIELD_PREP(CFG_SLAVE_EN, 1); spisg->cfg_bus = FIELD_PREP(CFG_TX_TUNING, 0xf); @@ -841,14 +903,30 @@ static int spisg_suspend_runtime(struct device *dev) static int spisg_resume_runtime(struct device *dev) { struct spisg_device *spisg = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spisg->core); + if (ret) + return ret; + + ret = clk_prepare_enable(spisg->sclk); + if (ret) { + clk_disable_unprepare(spisg->core); + return ret; + } - clk_prepare_enable(spisg->core); - clk_prepare_enable(spisg->sclk); pinctrl_pm_select_default_state(&spisg->pdev->dev); return 0; } +static const struct aml_spisg_data a9_spisg_data = { + .mo_idle_output_ctrl = true, + .word_gap_ctrl = true, + .cs_hold_ctrl = true, + .cs_setup_extend_ctrl = true, +}; + static const struct dev_pm_ops amlogic_spisg_pm_ops = { .runtime_suspend = spisg_suspend_runtime, .runtime_resume = spisg_resume_runtime, @@ -858,6 +936,10 @@ static const struct of_device_id amlogic_spisg_of_match[] = { { .compatible = "amlogic,a4-spisg", }, + { + .compatible = "amlogic,a9-spisg", + .data = &a9_spisg_data, + }, { /* sentinel */ } }; diff --git a/drivers/spi/spi-ar934x.c b/drivers/spi/spi-ar934x.c index 2210186feab8f1..01e51a30591dec 100644 --- a/drivers/spi/spi-ar934x.c +++ b/drivers/spi/spi-ar934x.c @@ -197,23 +197,13 @@ static int ar934x_spi_probe(struct platform_device *pdev) SPI_BPW_MASK(16) | SPI_BPW_MASK(8); ctlr->num_chipselect = 3; - dev_set_drvdata(&pdev->dev, ctlr); - sp = spi_controller_get_devdata(ctlr); sp->base = base; sp->clk = clk; sp->clk_freq = clk_get_rate(clk); sp->ctlr = ctlr; - return spi_register_controller(ctlr); -} - -static void ar934x_spi_remove(struct platform_device *pdev) -{ - struct spi_controller *ctlr; - - ctlr = dev_get_drvdata(&pdev->dev); - spi_unregister_controller(ctlr); + return devm_spi_register_controller(&pdev->dev, ctlr); } static struct platform_driver ar934x_spi_driver = { @@ -222,7 +212,6 @@ static struct platform_driver ar934x_spi_driver = { .of_match_table = ar934x_spi_match, }, .probe = ar934x_spi_probe, - .remove = ar934x_spi_remove, }; module_platform_driver(ar934x_spi_driver); diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c index 804f6194c830f3..cdc771350db93f 100644 --- a/drivers/spi/spi-at91-usart.c +++ b/drivers/spi/spi-at91-usart.c @@ -239,8 +239,8 @@ static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr, if (dma_submit_error(cookie)) goto at91_usart_spi_err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index c8012c82c3a788..c04113ce17f702 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -877,8 +877,8 @@ static int atmel_spi_next_xfer_dma_submit(struct spi_controller *host, cookie = txdesc->tx_submit(txdesc); if (dma_submit_error(cookie)) goto err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 8f8715809c7ce4..cc574897328160 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -527,7 +527,7 @@ static void bcm2835_spi_transfer_prologue(struct spi_controller *ctlr, | BCM2835_SPI_CS_CLEAR_TX | BCM2835_SPI_CS_DONE); - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), sg_dma_address(&tfr->rx_sg.sgl[0]), bs->rx_prologue, DMA_FROM_DEVICE); @@ -871,7 +871,7 @@ static void bcm2835_dma_release(struct spi_controller *ctlr, dmaengine_desc_free(bs->fill_tx_desc); if (bs->fill_tx_addr) - dma_unmap_page_attrs(ctlr->dma_tx->device->dev, + dma_unmap_page_attrs(dmaengine_get_dma_device(ctlr->dma_tx), bs->fill_tx_addr, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); @@ -891,6 +891,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, struct bcm2835_spi *bs) { struct dma_slave_config slave_config; + struct device *tx_dma_dev; const __be32 *addr; dma_addr_t dma_reg_base; int ret; @@ -932,11 +933,13 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, if (ret) goto err_config; - bs->fill_tx_addr = dma_map_page_attrs(ctlr->dma_tx->device->dev, + tx_dma_dev = dmaengine_get_dma_device(ctlr->dma_tx); + + bs->fill_tx_addr = dma_map_page_attrs(tx_dma_dev, ZERO_PAGE(0), 0, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); - if (dma_mapping_error(ctlr->dma_tx->device->dev, bs->fill_tx_addr)) { + if (dma_mapping_error(tx_dma_dev, bs->fill_tx_addr)) { dev_err(dev, "cannot map zero page - not using DMA mode\n"); bs->fill_tx_addr = 0; ret = -ENOMEM; @@ -1157,7 +1160,7 @@ static void bcm2835_spi_cleanup(struct spi_device *spi) dmaengine_desc_free(target->clear_rx_desc); if (target->clear_rx_addr) - dma_unmap_single(ctlr->dma_rx->device->dev, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); @@ -1174,16 +1177,16 @@ static int bcm2835_spi_setup_dma(struct spi_controller *ctlr, struct bcm2835_spi *bs, struct bcm2835_spidev *target) { + struct device *rx_dev; int ret; if (!ctlr->dma_rx) return 0; - target->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev, - &target->clear_rx_cs, - sizeof(u32), - DMA_TO_DEVICE); - if (dma_mapping_error(ctlr->dma_rx->device->dev, target->clear_rx_addr)) { + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); + target->clear_rx_addr = dma_map_single(rx_dev, &target->clear_rx_cs, + sizeof(u32), DMA_TO_DEVICE); + if (dma_mapping_error(rx_dev, target->clear_rx_addr)) { dev_err(&spi->dev, "cannot map clear_rx_cs\n"); target->clear_rx_addr = 0; return -ENOMEM; @@ -1268,7 +1271,7 @@ static int bcm2835_spi_setup(struct spi_device *spi) target->clear_rx_cs = cs | BCM2835_SPI_CS_TA | BCM2835_SPI_CS_DMAEN | BCM2835_SPI_CS_CLEAR_RX; - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); @@ -1297,7 +1300,9 @@ static int bcm2835_spi_setup(struct spi_device *spi) } for (i = 0; i < ARRAY_SIZE(pinctrl_compats); i++) { - if (of_find_compatible_node(NULL, NULL, pinctrl_compats[i])) + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, pinctrl_compats[i]); + if (np) break; } diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 1d8e257bad3021..3f349c1db5815b 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -413,7 +413,7 @@ static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id) struct bcm63xx_spi *bs = spi_controller_get_devdata(host); u8 intr; - /* Read interupts and clear them immediately */ + /* Read interrupts and clear them immediately */ intr = bcm_spi_readb(bs, SPI_INT_STATUS); bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); bcm_spi_writeb(bs, 0, SPI_INT_MASK); diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ecb0be39469651..13e0170ce72f30 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1056,8 +1056,7 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata, * care of polling the status register. */ if (cqspi->wr_completion) { - reg = readl(reg_base + CQSPI_REG_WR_COMPLETION_CTRL); - reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL; + reg = CQSPI_REG_WR_DISABLE_AUTO_POLL; writel(reg, reg_base + CQSPI_REG_WR_COMPLETION_CTRL); /* * DAC mode require auto polling as flash needs to be polled @@ -1382,7 +1381,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata, return 0; } - ddev = cqspi->rx_chan->device->dev; + ddev = dmaengine_get_dma_device(cqspi->rx_chan); dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE); if (dma_mapping_error(ddev, dma_dst)) { dev_err(dev, "dma mapping failed\n"); diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d44..1f0e93800908ca 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -275,7 +275,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) { struct spi_controller *ctlr = dev_id; struct dw_spi *dws = spi_controller_get_devdata(ctlr); - u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK; + u16 irq_status = dw_readl(dws, DW_SPI_ISR); if (!irq_status) return IRQ_NONE; @@ -937,6 +937,15 @@ static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_o dw_spi_set_cs(mem->spi, false); } + + /* + * FIXME: The exact reason for this delay is not fully understood, + * but empirical testing shows it significantly improves the stability + * of read/write operations. Without this delay, occasional transfer + * errors or timeouts may occur under certain conditions. + * Keeping it as a safeguard based on practical validation. + */ + udelay(5); } static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) @@ -948,6 +957,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * unsigned long long ms; int ret; + dws->dma_mapped = false; + switch (op->data.buswidth) { case 0: case 1: @@ -971,10 +982,13 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * cfg.dfs = 8; cfg.freq = clamp(op->max_freq, 0U, dws->max_mem_freq); cfg.ndf = op->data.nbytes; - if (op->data.dir == SPI_MEM_DATA_IN) + if (op->data.dir == SPI_MEM_DATA_IN) { cfg.tmode = DW_SPI_CTRLR0_TMOD_RO; - else + dws->tx_dir = false; + } else { cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + dws->tx_dir = true; + } if (op->data.buswidth == op->addr.buswidth && op->data.buswidth == op->cmd.buswidth) @@ -1010,47 +1024,61 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * } } - dw_spi_enh_write_cmd_addr(dws, op, mem); + if (dws->dma_nr_chans && dws->dma_ops->dma_enh_mem_get_chan && + op->data.nbytes > dws->fifo_len) { + ret = dws->dma_ops->dma_enh_mem_get_chan(ctlr->dev.parent, dws); + if (ret) { + /* fall back to the PIO/IRQ path */ + dev_dbg(&ctlr->dev, "DMA enh mem channel unavailable (%d)\n", ret); + } else if (dws->dma_ops->can_dma_enh_mem(ctlr)) { + ret = dws->dma_ops->dma_enh_mem_setup(dws); + if (ret) + dev_err(&ctlr->dev, "DMA enh mem setup failed (%d)\n", ret); + else + dws->dma_mapped = true; + } + } - /* - * FIXME: The exact reason for this delay is not fully understood, - * but empirical testing shows it significantly improves the stability - * of read/write operations. Without this delay, occasional transfer - * errors or timeouts may occur under certain conditions. - * Keeping it as a safeguard based on practical validation. - */ - udelay(5); + if (dws->dma_mapped) { + dw_spi_enh_write_cmd_addr(dws, op, mem); - dw_spi_enh_irq_setup(dws); + ret = dws->dma_ops->dma_enh_mem_transfer(mem, op); - /* Use timeout calculation from spi_transfer_wait() */ - ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len); - do_div(ms, dws->current_freq); + dw_spi_stop_mem_op(dws, mem->spi); + } else { + dw_spi_enh_write_cmd_addr(dws, op, mem); - /* - * Increase it twice and add 200 ms tolerance, use - * predefined maximum in case of overflow. - */ - ms += ms + 200; - if (ms > UINT_MAX) - ms = UINT_MAX; + dw_spi_enh_irq_setup(dws); - ms = wait_for_completion_timeout(&ctlr->xfer_completion, - msecs_to_jiffies(ms)); - if (ms == 0) { - dw_spi_mask_intr(dws, 0xff); - synchronize_irq(dws->irq); - dws->rx = NULL; - dws->tx = NULL; - dws->rx_len = 0; - dws->tx_len = 0; - dw_spi_stop_mem_op(dws, mem->spi); - return -EIO; - } + /* Use timeout calculation from spi_transfer_wait() */ + ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len); + do_div(ms, dws->current_freq); - ret = dw_spi_wait_mem_op_done(dws); + /* + * Increase it twice and add 200 ms tolerance, use + * predefined maximum in case of overflow. + */ + ms += ms + 200; + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&ctlr->xfer_completion, + msecs_to_jiffies(ms)); + if (ms == 0) { + dw_spi_mask_intr(dws, 0xff); + synchronize_irq(dws->irq); + dws->rx = NULL; + dws->tx = NULL; + dws->rx_len = 0; + dws->tx_len = 0; + dw_spi_stop_mem_op(dws, mem->spi); + return -EIO; + } - dw_spi_stop_mem_op(dws, mem->spi); + ret = dw_spi_wait_mem_op_done(dws); + + dw_spi_stop_mem_op(dws, mem->spi); + } return ret; } @@ -1104,12 +1132,13 @@ static int dw_spi_setup(struct spi_device *spi) if (!chip) return -ENOMEM; spi_set_ctldata(spi, chip); - /* Get specific / default rx-sample-delay */ - if (device_property_read_u32(&spi->dev, - "rx-sample-delay-ns", - &rx_sample_dly_ns) != 0) - /* Use default controller value */ - rx_sample_dly_ns = dws->def_rx_sample_dly_ns; + /* + * Use the per-device value the core parsed from the peripheral + * node, and fall back to the controller-wide default when the + * device does not ask for a delay of its own. + */ + rx_sample_dly_ns = spi->rx_sample_delay_ns ?: + dws->def_rx_sample_dly_ns; chip->rx_sample_dly = DIV_ROUND_CLOSEST(rx_sample_dly_ns, NSEC_PER_SEC / dws->max_freq); @@ -1360,6 +1389,9 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) goto err_free_irq; } else if (ret) { dev_warn(dev, "DMA init failed\n"); + } else if (dws->dma_nr_chans == 1) { + /* Full-duplex DMA transfers need both channels */ + dev_info(dev, "Single DMA channel, full-duplex DMA disabled\n"); } else { ctlr->can_dma = dws->dma_ops->can_dma; ctlr->flags |= SPI_CONTROLLER_MUST_TX; diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index f7d848fec9ab6d..a8603905798820 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -42,14 +42,19 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) def_burst = dws->fifo_len / 2; - ret = dma_get_slave_caps(dws->rxchan, &caps); - if (!ret && caps.max_burst) - max_burst = caps.max_burst; - else - max_burst = DW_SPI_RX_BURST_LEVEL; + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = DW_SPI_RX_BURST_LEVEL; + + dws->rxburst = min(max_burst, def_burst); + dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + } - dws->rxburst = min(max_burst, def_burst); - dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + if (!dws->txchan) + return; ret = dma_get_slave_caps(dws->txchan, &caps); if (!ret && caps.max_burst) @@ -74,20 +79,39 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) static int dw_spi_dma_caps_init(struct dw_spi *dws) { - struct dma_slave_caps tx, rx; + struct dma_slave_caps tx = {}, rx = {}; int ret; - ret = dma_get_slave_caps(dws->txchan, &tx); - if (ret) - return ret; + if (dws->txchan) { + ret = dma_get_slave_caps(dws->txchan, &tx); + if (ret) + return ret; - ret = dma_get_slave_caps(dws->rxchan, &rx); - if (ret) - return ret; + if (!(tx.directions & BIT(DMA_MEM_TO_DEV))) + return -ENXIO; + } + + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &rx); + if (ret) + return ret; + + if (!(rx.directions & BIT(DMA_DEV_TO_MEM))) + return -ENXIO; + } - if (!(tx.directions & BIT(DMA_MEM_TO_DEV) && - rx.directions & BIT(DMA_DEV_TO_MEM))) - return -ENXIO; + /* With a single channel only one direction is available at a time */ + if (!dws->rxchan) { + dws->dma_sg_burst = tx.max_sg_burst; + dws->dma_addr_widths = tx.dst_addr_widths; + return 0; + } + + if (!dws->txchan) { + dws->dma_sg_burst = rx.max_sg_burst; + dws->dma_addr_widths = rx.src_addr_widths; + return 0; + } if (tx.max_sg_burst > 0 && rx.max_sg_burst > 0) dws->dma_sg_burst = min(tx.max_sg_burst, rx.max_sg_burst); @@ -169,40 +193,54 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) { int ret; + dws->dma_nr_chans = 0; + dws->rxchan = dma_request_chan(dev, "rx"); if (IS_ERR(dws->rxchan)) { ret = PTR_ERR(dws->rxchan); dws->rxchan = NULL; goto err_exit; } + dws->dma_nr_chans++; + /* + * Some platforms have only one DMA channel for the controller. Keep + * the Rx channel in that case, it can still serve half-duplex + * transfers. + */ dws->txchan = dma_request_chan(dev, "tx"); if (IS_ERR(dws->txchan)) { ret = PTR_ERR(dws->txchan); dws->txchan = NULL; - goto free_rxchan; + if (ret == -EPROBE_DEFER) + goto free_rxchan; + } else { + dws->dma_nr_chans++; } - dws->ctlr->dma_rx = dws->rxchan; - dws->ctlr->dma_tx = dws->txchan; - - init_completion(&dws->dma_completion); - ret = dw_spi_dma_caps_init(dws); if (ret) goto free_txchan; dw_spi_dma_maxburst_init(dws); + init_completion(&dws->dma_completion); + + dws->ctlr->dma_rx = dws->rxchan; + dws->ctlr->dma_tx = dws->txchan; + return 0; free_txchan: - dma_release_channel(dws->txchan); - dws->txchan = NULL; + if (dws->txchan) { + dma_release_channel(dws->txchan); + dws->txchan = NULL; + } free_rxchan: dma_release_channel(dws->rxchan); dws->rxchan = NULL; err_exit: + dws->dma_nr_chans = 0; return ret; } @@ -286,19 +324,27 @@ static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) (DW_SPI_SR_BUSY | DW_SPI_SR_TF_EMPT)) != DW_SPI_SR_TF_EMPT; } -static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, - struct spi_transfer *xfer) +static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, u32 speed_hz) { int retry = DW_SPI_WAIT_RETRIES; struct spi_delay delay; + unsigned long ns, us; u32 nents; nents = dw_readl(dws, DW_SPI_TXFLR); - delay.unit = SPI_DELAY_UNIT_SCK; - delay.value = nents * dws->n_bytes * BITS_PER_BYTE; + ns = DIV_ROUND_UP(NSEC_PER_SEC, speed_hz) * nents * + dws->n_bytes * BITS_PER_BYTE; + if (ns <= NSEC_PER_USEC) { + delay.unit = SPI_DELAY_UNIT_NSECS; + delay.value = ns; + } else { + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + delay.unit = SPI_DELAY_UNIT_USECS; + delay.value = clamp_val(us, 0, USHRT_MAX); + } while (dw_spi_dma_tx_busy(dws) && retry--) - spi_delay_exec(&delay, xfer); + spi_delay_exec(&delay, NULL); if (retry < 0) { dev_err(&dws->ctlr->dev, "Tx hanged up\n"); @@ -659,7 +705,7 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) return ret; if (dws->ctlr->cur_msg->status == -EINPROGRESS) { - ret = dw_spi_dma_wait_tx_done(dws, xfer); + ret = dw_spi_dma_wait_tx_done(dws, xfer->effective_speed_hz); if (ret) return ret; } @@ -682,6 +728,183 @@ static void dw_spi_dma_stop(struct dw_spi *dws) } } +/* + * The enhanced SPI memory operations are half-duplex, so a single DMA channel + * is enough for them. On a platform with only one channel, the channel is + * bound to a direction by its "tx"/"rx" DMA specifier, and whichever channel + * is currently held tells which direction it serves. Keep it across + * operations and swap it only when the transfer direction changes. + */ +static int dw_spi_enh_mem_dma_get_chan(struct device *dev, struct dw_spi *dws) +{ + struct dma_chan **chan, **other; + const char *name; + int ret; + + if (dws->dma_nr_chans == 2) + return 0; + + if (dws->tx_dir) { + chan = &dws->txchan; + other = &dws->rxchan; + name = "tx"; + } else { + chan = &dws->rxchan; + other = &dws->txchan; + name = "rx"; + } + + if (*chan) + return 0; + + dws->ctlr->dma_tx = NULL; + dws->ctlr->dma_rx = NULL; + + /* + * With a single physical channel, the held channel must be released + * before it can be requested again with the handshake of the other + * direction. On failure both stay NULL, and the next operation + * retries the request and falls back to PIO meanwhile. + */ + + if (*other) { + dma_release_channel(*other); + *other = NULL; + } + + *chan = dma_request_chan(dev, name); + if (IS_ERR(*chan)) { + ret = PTR_ERR(*chan); + *chan = NULL; + return ret; + } + + ret = dw_spi_dma_caps_init(dws); + if (ret) { + dma_release_channel(*chan); + *chan = NULL; + return ret; + } + + dw_spi_dma_maxburst_init(dws); + + dws->ctlr->dma_tx = dws->txchan; + dws->ctlr->dma_rx = dws->rxchan; + + return 0; +} + +static int dw_spi_enh_mem_dma_setup(struct dw_spi *dws) +{ + u16 dma_ctrl, level; + int ret; + + /* Setup DMA channels */ + if (dws->tx_dir) { + ret = dw_spi_dma_config_tx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_TDMAE; + } else { + ret = dw_spi_dma_config_rx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_RDMAE; + } + + dw_writel(dws, DW_SPI_DMACR, dma_ctrl); + + /* Clear stale error status, it is checked after the transfer */ + dw_readl(dws, DW_SPI_ICR); + + reinit_completion(&dws->dma_completion); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len); + dw_writel(dws, DW_SPI_TXFTLR, level); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->rx_len); + dw_writel(dws, DW_SPI_RXFTLR, level ? level - 1 : 0); + + return 0; +} + +static bool dw_spi_enh_mem_can_dma(struct spi_controller *ctlr) +{ + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + unsigned int len = dws->tx_dir ? dws->tx_len : dws->rx_len; + enum dma_slave_buswidth dma_bus_width; + + if (len <= dws->fifo_len) + return false; + + dma_bus_width = dw_spi_dma_convert_width(dws->n_bytes); + + return dws->dma_addr_widths & BIT(dma_bus_width); +} + +static void dw_spi_enh_mem_dma_stop(struct dw_spi *dws) +{ + if (dws->tx_dir) { + if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->txchan); + clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); + } + } else { + if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->rxchan); + clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); + } + } +} + +static int dw_spi_enh_mem_dma_transfer(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct spi_controller *ctlr = mem->spi->controller; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + struct sg_table sgt; + int ret; + + ret = spi_controller_dma_map_mem_op_data(ctlr, op, &sgt); + if (ret) + goto out_clear_dmac; + + if (dws->tx_dir) { + ret = dw_spi_dma_submit_tx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->txchan); + + ret = dw_spi_dma_wait(dws, dws->tx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_tx_done(dws, dws->current_freq); + } else { + ret = dw_spi_dma_submit_rx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->rxchan); + + ret = dw_spi_dma_wait(dws, dws->rx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_rx_done(dws); + } + + if (ret) + dw_spi_enh_mem_dma_stop(dws); + else + ret = dw_spi_check_status(dws, true); + +out_unmap: + spi_controller_dma_unmap_mem_op_data(ctlr, op, &sgt); +out_clear_dmac: + dw_writel(dws, DW_SPI_DMACR, 0); + + return ret; +} + static const struct dw_spi_dma_ops dw_spi_dma_mfld_ops = { .dma_init = dw_spi_dma_init_mfld, .dma_exit = dw_spi_dma_exit, @@ -704,6 +927,11 @@ static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = { .can_dma = dw_spi_can_dma, .dma_transfer = dw_spi_dma_transfer, .dma_stop = dw_spi_dma_stop, + + .dma_enh_mem_get_chan = dw_spi_enh_mem_dma_get_chan, + .dma_enh_mem_setup = dw_spi_enh_mem_dma_setup, + .can_dma_enh_mem = dw_spi_enh_mem_can_dma, + .dma_enh_mem_transfer = dw_spi_enh_mem_dma_transfer, }; void dw_spi_dma_setup_generic(struct dw_spi *dws) diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 8cdb0351605b2c..9c70947c969cc0 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -363,6 +363,8 @@ static int dw_spi_jhb100_init(struct platform_device *pdev, dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff); + dw_spi_dma_setup_generic(&dwsmmio->dws); + return 0; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index e62f919f1188a7..bfdf371bcacbca 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -183,6 +183,12 @@ struct dw_spi_dma_ops { struct spi_transfer *xfer); int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer); void (*dma_stop)(struct dw_spi *dws); + + /* enh mem dma ops */ + int (*dma_enh_mem_get_chan)(struct device *dev, struct dw_spi *dws); + int (*dma_enh_mem_setup)(struct dw_spi *dws); + bool (*can_dma_enh_mem)(struct spi_controller *ctlr); + int (*dma_enh_mem_transfer)(struct spi_mem *mem, const struct spi_mem_op *op); }; struct dw_spi { @@ -227,6 +233,8 @@ struct dw_spi { u32 txburst; struct dma_chan *rxchan; u32 rxburst; + unsigned int dma_nr_chans; + bool tx_dir; u32 dma_sg_burst; u32 dma_addr_widths; unsigned long dma_chan_busy; diff --git a/drivers/spi/spi-ep93xx.c b/drivers/spi/spi-ep93xx.c index bf389d7590d35e..d8f4aa38685f50 100644 --- a/drivers/spi/spi-ep93xx.c +++ b/drivers/spi/spi-ep93xx.c @@ -273,6 +273,7 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, struct dma_async_tx_descriptor *txd; enum dma_slave_buswidth buswidth; struct dma_slave_config conf; + struct device *dma_dev; struct scatterlist *sg; struct sg_table *sgt; struct dma_chan *chan; @@ -348,14 +349,15 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, return ERR_PTR(-EINVAL); } - nents = dma_map_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_dev = dmaengine_get_dma_device(chan); + nents = dma_map_sg(dma_dev, sgt->sgl, sgt->nents, dir); if (!nents) return ERR_PTR(-ENOMEM); txd = dmaengine_prep_slave_sg(chan, sgt->sgl, nents, conf.direction, DMA_CTRL_ACK); if (!txd) { - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dma_dev, sgt->sgl, sgt->nents, dir); return ERR_PTR(-ENOMEM); } return txd; @@ -384,7 +386,7 @@ static void ep93xx_spi_dma_finish(struct spi_controller *host, sgt = &espi->tx_sgt; } - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dmaengine_get_dma_device(chan), sgt->sgl, sgt->nents, dir); } static void ep93xx_spi_dma_callback(void *callback_param) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 23e6bc5f202e2f..f03488870d024b 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -670,6 +670,7 @@ static void dspi_dma_xfer(struct fsl_dspi *dspi) static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) { struct device *dev = &dspi->pdev->dev; + struct device *tx_dev, *rx_dev; struct dma_slave_config cfg; struct fsl_dspi_dma *dma; int ret; @@ -688,20 +689,23 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_channel; } + tx_dev = dmaengine_get_dma_device(dma->chan_tx); + rx_dev = dmaengine_get_dma_device(dma->chan_rx); + if (spi_controller_is_target(dspi->ctlr)) { /* * In target mode we have to be ready to receive the maximum * that can possibly be transferred at once by EDMA without any * FIFO underflows. */ - dma->bufsize = min(dma_get_max_seg_size(dma->chan_rx->device->dev), - dma_get_max_seg_size(dma->chan_tx->device->dev)) * + dma->bufsize = min(dma_get_max_seg_size(rx_dev), + dma_get_max_seg_size(tx_dev)) * DMA_SLAVE_BUSWIDTH_4_BYTES; } else { dma->bufsize = PAGE_SIZE; } - dma->tx_dma_buf = dma_alloc_noncoherent(dma->chan_tx->device->dev, + dma->tx_dma_buf = dma_alloc_noncoherent(tx_dev, dma->bufsize, &dma->tx_dma_phys, DMA_TO_DEVICE, GFP_KERNEL); if (!dma->tx_dma_buf) { @@ -709,7 +713,7 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_dma_buf; } - dma->rx_dma_buf = dma_alloc_noncoherent(dma->chan_rx->device->dev, + dma->rx_dma_buf = dma_alloc_noncoherent(rx_dev, dma->bufsize, &dma->rx_dma_phys, DMA_FROM_DEVICE, GFP_KERNEL); if (!dma->rx_dma_buf) { @@ -746,12 +750,11 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) return 0; err_slave_config: - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); err_rx_dma_buf: - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, DMA_TO_DEVICE); + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); err_tx_dma_buf: dma_release_channel(dma->chan_tx); err_tx_channel: @@ -771,16 +774,18 @@ static void dspi_release_dma(struct fsl_dspi *dspi) return; if (dma->chan_tx) { - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, - DMA_TO_DEVICE); + struct device *tx_dev = dmaengine_get_dma_device(dma->chan_tx); + + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); dma_release_channel(dma->chan_tx); } if (dma->chan_rx) { - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + struct device *rx_dev = dmaengine_get_dma_device(dma->chan_rx); + + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); dma_release_channel(dma->chan_rx); } } diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6c57f8309a3b2c..bced6279a06242 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -115,6 +117,7 @@ struct spi_geni_master { struct dma_chan *rx; int cur_xfer_mode; const struct geni_spi_desc *dev_data; + struct notifier_block panic_nb; }; static void spi_slv_setup(struct spi_geni_master *mas) @@ -363,8 +366,8 @@ static int geni_spi_set_clock_and_bw(struct geni_se *se, return 0; } -static int setup_fifo_params(struct spi_device *spi_slv, - struct spi_controller *spi) +static void setup_spi_params(struct spi_device *spi_slv, + struct spi_controller *spi) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); struct geni_se *se = &mas->se; @@ -390,8 +393,6 @@ static int setup_fifo_params(struct spi_device *spi_slv, trace_geni_spi_setup_params(mas->dev, chipselect, spi_slv->mode, mode_changed, cs_changed); - - return 0; } static void @@ -554,17 +555,14 @@ static int spi_geni_prepare_message(struct spi_controller *spi, struct spi_message *spi_msg) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); - int ret; switch (mas->cur_xfer_mode) { case GENI_SE_FIFO: case GENI_SE_DMA: if (spi_geni_is_abort_still_pending(mas)) return -EBUSY; - ret = setup_fifo_params(spi_msg->spi, spi); - if (ret) - dev_err(mas->dev, "Couldn't select mode %d\n", ret); - return ret; + setup_spi_params(spi_msg->spi, spi); + return 0; case GENI_GPI_DMA: /* nothing to do for GPI DMA */ @@ -700,7 +698,7 @@ static int spi_geni_init(struct spi_geni_master *mas) case 0: mas->cur_xfer_mode = GENI_SE_FIFO; geni_se_select_mode(se, GENI_SE_FIFO); - /* setup_fifo_params assumes that these registers start with a zero value */ + /* setup_spi_params assumes that these registers start with a zero value */ writel(0, se->base + SE_SPI_LOOPBACK); writel(0, se->base + SE_SPI_DEMUX_SEL); writel(0, se->base + SE_SPI_CPHA); @@ -1066,6 +1064,69 @@ static int spi_geni_target_abort(struct spi_controller *spi) return 0; } +static void spi_geni_shutdown(struct platform_device *pdev) +{ + struct spi_controller *spi = platform_get_drvdata(pdev); + + spi_controller_suspend(spi); +} + +static int spi_geni_panic_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct spi_geni_master *mas = container_of(nb, struct spi_geni_master, panic_nb); + struct spi_controller *spi = dev_get_drvdata(mas->dev); + struct geni_se *se = &mas->se; + u32 val; + + if (!pm_runtime_active(mas->dev)) + return NOTIFY_OK; + + if (mas->cur_xfer_mode == GENI_GPI_DMA) { + dmaengine_terminate_async(mas->tx); + dmaengine_terminate_async(mas->rx); + return NOTIFY_OK; + } + + if (!(readl_relaxed(se->base + SE_GENI_STATUS) & M_GENI_CMD_ACTIVE)) + return NOTIFY_OK; + + if (!spi->target) { + geni_se_cancel_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_CANCEL_EN, 10, 50000)) { + writel_relaxed(M_CMD_CANCEL_EN, se->base + SE_GENI_M_IRQ_CLEAR); + return NOTIFY_OK; + } + } + + geni_se_abort_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_ABORT_EN, 10, 50000)) + writel_relaxed(M_CMD_ABORT_EN, se->base + SE_GENI_M_IRQ_CLEAR); + + if (mas->cur_xfer_mode == GENI_SE_DMA) { + writel_relaxed(1, se->base + SE_DMA_TX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_TX_IRQ_STAT, val, + val & TX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_TX_IRQ_CLR); + + writel_relaxed(1, se->base + SE_DMA_RX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_RX_IRQ_STAT, val, + val & RX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_RX_IRQ_CLR); + } + + return NOTIFY_OK; +} + +static void spi_geni_unregister_notifiers(void *data) +{ + struct spi_geni_master *mas = data; + + atomic_notifier_chain_unregister(&panic_notifier_list, &mas->panic_nb); +} + static int spi_geni_probe(struct platform_device *pdev) { int ret, irq; @@ -1154,6 +1215,15 @@ static int spi_geni_probe(struct platform_device *pdev) if (ret) return ret; + mas->panic_nb.notifier_call = spi_geni_panic_notifier; + ret = atomic_notifier_chain_register(&panic_notifier_list, &mas->panic_nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, spi_geni_unregister_notifiers, mas); + if (ret) + return ret; + return devm_spi_register_controller(dev, spi); } @@ -1241,7 +1311,8 @@ static const struct of_device_id spi_geni_dt_match[] = { MODULE_DEVICE_TABLE(of, spi_geni_dt_match); static struct platform_driver spi_geni_driver = { - .probe = spi_geni_probe, + .probe = spi_geni_probe, + .shutdown = spi_geni_shutdown, .driver = { .name = "geni_spi", .pm = pm_ptr(&spi_geni_pm_ops), diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 79a6c1a60b0a05..868f963e61371f 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -673,7 +673,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, * propagate into the hardware. It takes exactly one tick of the * SCLK clock, but we will wait two SCLK clock just to be sure. The * effect of the delay it takes for the hardware to apply changes - * is noticable if the SCLK clock run very slow. In such a case, if + * is noticeable if the SCLK clock run very slow. In such a case, if * the polarity of SCLK should be inverted, the GPIO ChipSelect might * be asserted before the SCLK polarity changes, which would disrupt * the SPI communication as the device on the other end would consider @@ -1467,8 +1467,8 @@ static int spi_imx_calculate_timeout(struct spi_imx_data *spi_imx, int size) static void spi_imx_dma_unmap(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { - struct device *tx_dev = spi_imx->controller->dma_tx->device->dev; - struct device *rx_dev = spi_imx->controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_rx); dma_unmap_single(tx_dev, dma_data->dma_tx_addr, DMA_CACHE_ALIGNED_LEN(dma_data->dma_len), @@ -1526,8 +1526,8 @@ static int spi_imx_dma_map(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { struct spi_controller *controller = spi_imx->controller; - struct device *tx_dev = controller->dma_tx->device->dev; - struct device *rx_dev = controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(controller->dma_rx); int ret; dma_data->dma_tx_addr = dma_map_single(tx_dev, dma_data->dma_tx_buf, @@ -1919,7 +1919,7 @@ static int spi_imx_dma_package_transfer(struct spi_imx_data *spi_imx, return ret; /* Trim the DMA RX buffer and copy the actual data to rx_buf */ - dma_sync_single_for_cpu(controller->dma_rx->device->dev, dma_data->dma_rx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(controller->dma_rx), dma_data->dma_rx_addr, dma_data->dma_len, DMA_FROM_DEVICE); spi_imx_dma_rx_data_handle(spi_imx, dma_data, transfer->rx_buf + spi_imx->rx_offset, word_delay); diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index adcf85bccbcc01..a0a047510611e8 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -164,10 +164,8 @@ spi_ingenic_prepare_dma(struct spi_controller *ctlr, struct dma_chan *chan, cookie = dmaengine_submit(desc); ret = dma_submit_error(cookie); - if (ret) { - dmaengine_desc_free(desc); + if (ret) return ERR_PTR(ret); - } return desc; } @@ -186,7 +184,6 @@ static int spi_ingenic_dma_tx(struct spi_controller *ctlr, &xfer->tx_sg, DMA_MEM_TO_DEV, bits); if (IS_ERR(tx_desc)) { dmaengine_terminate_async(ctlr->dma_rx); - dmaengine_desc_free(rx_desc); return PTR_ERR(tx_desc); } @@ -354,8 +351,11 @@ static int spi_ingenic_request_dma(struct spi_controller *ctlr, ctlr->dma_tx = chan; chan = dma_request_chan(dev, "rx"); - if (IS_ERR(chan)) + if (IS_ERR(chan)) { + dma_release_channel(ctlr->dma_tx); + ctlr->dma_tx = NULL; return PTR_ERR(chan); + } ctlr->dma_rx = chan; ctlr->can_dma = spi_ingenic_can_dma; diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 7f938d0c6f1fc4..30e3239f777d6f 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -633,18 +633,23 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "failed to deassert reset\n"); ret = device_property_read_u32(dev, "num-cs", &num_cs); - if (ret && ret != -EINVAL) - return dev_err_probe(dev, ret, "failed to read num-cs\n"); + if (ret && ret != -EINVAL) { + ret = dev_err_probe(dev, ret, "failed to read num-cs\n"); + goto err_assert; + } - if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) - return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) { + ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", num_cs); + goto err_assert; + } ctlr->num_chipselect = num_cs; ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size; ctlr->max_message_size = nuvoton_qspi_max_message_size; ctlr->mem_ops = &nuvoton_qspi_mem_ops; ctlr->mem_caps = &nuvoton_qspi_mem_caps; + ctlr->dtr_caps = true; ctlr->set_cs = nuvoton_qspi_set_cs; ctlr->transfer_one = nuvoton_qspi_transfer_one; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); @@ -655,14 +660,20 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) ret = nuvoton_qspi_hw_init(qspi); if (ret) - return ret; + goto err_assert; ret = devm_spi_register_controller(dev, ctlr); - if (ret) - return dev_err_probe(dev, ret, - "failed to register spi controller\n"); + if (ret) { + ret = dev_err_probe(dev, ret, + "failed to register spi controller\n"); + goto err_assert; + } return 0; + +err_assert: + reset_control_assert(rst); + return ret; } static const struct of_device_id nuvoton_qspi_of_match[] = { diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 5f973ebfb8b6eb..b94680b60af958 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -46,9 +46,9 @@ int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, return -EINVAL; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; @@ -92,9 +92,9 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, return; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; @@ -172,7 +172,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr; if (op_is_dtr) { - if (!spi_mem_controller_is_capable(ctlr, dtr)) + if (!spi_mem_controller_is_capable(ctlr, dtr) && !ctlr->dtr_caps) return false; if (op->data.swap16 && !spi_mem_controller_is_capable(ctlr, swap16)) @@ -461,6 +461,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->cmd.nbytes; xfers[xferpos].tx_nbits = op->cmd.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->cmd.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen++; @@ -476,6 +477,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->addr.nbytes; xfers[xferpos].tx_nbits = op->addr.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->addr.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->addr.nbytes; @@ -488,6 +490,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_nbits = op->dummy.buswidth; xfers[xferpos].dummy_data = 1; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->dummy.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->dummy.nbytes; @@ -504,6 +507,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->data.nbytes; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->data.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->data.nbytes; diff --git a/drivers/spi/spi-mtk-nor.c b/drivers/spi/spi-mtk-nor.c index 63f5139176c8d0..6b6e55d68048b1 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -929,6 +929,7 @@ static int mtk_nor_probe(struct platform_device *pdev) return 0; err_probe: + pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index e3c85e5191e369..481edd4b35cbf9 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -744,7 +744,13 @@ static int mxic_spi_runtime_resume(struct device *dev) return ret; } - return mxic_spi_clk_enable(mxic); + ret = mxic_spi_clk_enable(mxic); + if (ret) { + clk_disable_unprepare(mxic->ps_clk); + return ret; + } + + return 0; } static const struct dev_pm_ops mxic_spi_dev_pm_ops = { diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 67d991dd249d28..66cb58f95fdb1c 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -898,10 +898,10 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, dev_dbg(f->dev, "Target device [CS:%x] selected\n", spi_get_chipselect(spi, 0)); /* - * Per the FlexSPI reference manual (initialization sequence), MCR0 and - * the DLL control registers should be configured while the module is in - * stop mode (MCR0[MDIS] = 1). Enter stop mode before reconfiguring the - * RX sample clock source and the DLL, then exit stop mode afterwards. + * Per the FlexSPI reference manual (initialization sequence), MCR0 + * should be configured while the module is in stop mode (MCR0[MDIS] = 1). + * Enter stop mode before reconfiguring the RX sample clock source, then + * exit stop mode afterwards. */ reg = fspi_readl(f, f->iobase + FSPI_MCR0); fspi_writel(f, reg | FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); @@ -936,6 +936,16 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, if (ret) return ret; + /* + * Exit stop mode before running the DLL calibration. The DLL + * reference clock is derived from the module clock domain, which is + * gated while MCR0[MDIS] = 1. Calibrating the DLL in stop mode leaves + * the STS2 lock bits stuck at 0 (the DLL never runs), so clear MDIS + * first and let the module and reference clock run. + */ + reg = fspi_readl(f, f->iobase + FSPI_MCR0); + fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); + /* * If clock rate > 100MHz, then switch from DLL override mode to * DLL calibration mode. @@ -945,10 +955,6 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, else nxp_fspi_dll_override(f); - /* Exit stop mode now that MCR0 and the DLL have been reconfigured. */ - reg = fspi_readl(f, f->iobase + FSPI_MCR0); - fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); - f->pre_op_rate = op->max_freq; f->selected = spi_get_chipselect(spi, 0); diff --git a/drivers/spi/spi-nxp-xspi.c b/drivers/spi/spi-nxp-xspi.c index 70cbc3ef658b6d..38c8d6269aadd6 100644 --- a/drivers/spi/spi-nxp-xspi.c +++ b/drivers/spi/spi-nxp-xspi.c @@ -841,23 +841,23 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, { struct dma_async_tx_descriptor *desc; struct dma_chan *chan = xspi->dma_rx; + struct device *dma_dev = dmaengine_get_dma_device(chan); unsigned long timeout; dma_addr_t src_addr, dst_addr; int ret = 0; - dst_addr = dma_map_single(chan->device->dev, - op->data.buf.in, op->data.nbytes, + dst_addr = dma_map_single(dma_dev, op->data.buf.in, op->data.nbytes, DMA_FROM_DEVICE); - if (dma_mapping_error(chan->device->dev, dst_addr)) { + if (dma_mapping_error(dma_dev, dst_addr)) { dev_err(xspi->dev, "failed to map DMA dst buffer\n"); return -ENOMEM; } - src_addr = dma_map_resource(chan->device->dev, + src_addr = dma_map_resource(dma_dev, (phys_addr_t)xspi->memmap_phy + op->addr.val, op->data.nbytes, DMA_TO_DEVICE, 0); - if (dma_mapping_error(chan->device->dev, src_addr)) { + if (dma_mapping_error(dma_dev, src_addr)) { dev_err(xspi->dev, "failed to map DMA src resource\n"); ret = -ENOMEM; goto err_unmap_dst; @@ -886,12 +886,10 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, } err_unmap_src: - dma_unmap_resource(chan->device->dev, src_addr, - op->data.nbytes, DMA_TO_DEVICE, 0); + dma_unmap_resource(dma_dev, src_addr, op->data.nbytes, DMA_TO_DEVICE, 0); err_unmap_dst: - dma_unmap_single(chan->device->dev, dst_addr, - op->data.nbytes, DMA_FROM_DEVICE); + dma_unmap_single(dma_dev, dst_addr, op->data.nbytes, DMA_FROM_DEVICE); return ret; } diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index a3355147f5de51..ace6886a75c3e9 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1244,7 +1244,7 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, omap2_mcspi_set_fifo(spi, t, 0); out: - /* Restore defaults if they were overriden */ + /* Restore defaults if they were overridden */ if (par_override) { par_override = 0; status = omap2_mcspi_setup_transfer(spi, NULL); @@ -1595,7 +1595,6 @@ static int omap2_mcspi_probe(struct platform_device *pdev) err_disable_rpm: pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); err_release_dma: omap2_mcspi_release_dma(ctlr); @@ -1613,7 +1612,6 @@ static void omap2_mcspi_remove(struct platform_device *pdev) omap2_mcspi_release_dma(ctlr); pm_runtime_dont_use_autosuspend(mcspi->dev); - pm_runtime_put_sync(mcspi->dev); pm_runtime_disable(&pdev->dev); } diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index 26a9f268b2d303..b8882229c05493 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -370,7 +370,16 @@ static inline int orion_spi_wait_till_ready(struct orion_spi *orion_spi) if (readl(spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG))) return 1; + /* + * This is a polled, byte-at-a-time transfer loop. Each + * iteration busy-waits in a tight udelay() loop, which can + * starve other time-sensitive peripherals (e.g. SATA) of CPU + * time and interfere with them if they run on this SoC. + * Yield to the scheduler between polls so pending IRQs can + * be serviced. + */ udelay(1); + cond_resched(); } return -1; diff --git a/drivers/spi/spi-pic32.c b/drivers/spi/spi-pic32.c index 972128271e4bfa..8363bc873b03a3 100644 --- a/drivers/spi/spi-pic32.c +++ b/drivers/spi/spi-pic32.c @@ -53,7 +53,7 @@ struct pic32_spi_regs { #define TX_FIFO_ALL_EMPTY 0 /* completely empty */ #define TX_FIFO_EMPTY 1 /* empty */ #define TX_FIFO_HALF_EMPTY 2 /* empty by half or more */ -#define TX_FIFO_NOT_FULL 3 /* atleast one empty */ +#define TX_FIFO_NOT_FULL 3 /* at least one empty */ #define CTRL_MSTEN BIT(5) /* enable master mode */ #define CTRL_CKP BIT(6) /* active low */ diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 4877ab909c51b7..3ff30486b2892f 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -684,9 +684,9 @@ static void readwriter(struct pl022 *pl022) static void unmap_free_dma_scatter(struct pl022 *pl022) { /* Unmap and free the SG tables */ - dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_tx_channel), pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); - dma_unmap_sg(pl022->dma_rx_channel->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_rx_channel), pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); sg_free_table(&pl022->sgt_rx); sg_free_table(&pl022->sgt_tx); @@ -811,6 +811,7 @@ static int configure_dma(struct pl022 *pl022) struct dma_chan *txchan = pl022->dma_tx_channel; struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + struct device *rx_dev, *tx_dev; /* Check that the channels are available */ if (!rxchan || !txchan) @@ -924,14 +925,16 @@ static int configure_dma(struct pl022 *pl022) setup_dma_scatter(pl022, pl022->tx, pl022->cur_transfer->len, &pl022->sgt_tx); + rx_dev = dmaengine_get_dma_device(rxchan); /* Map DMA buffers */ - rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl, - pl022->sgt_rx.nents, DMA_FROM_DEVICE); + rx_sglen = dma_map_sg(rx_dev, pl022->sgt_rx.sgl, + pl022->sgt_rx.nents, DMA_FROM_DEVICE); if (!rx_sglen) goto err_rx_sgmap; - tx_sglen = dma_map_sg(txchan->device->dev, pl022->sgt_tx.sgl, - pl022->sgt_tx.nents, DMA_TO_DEVICE); + tx_dev = dmaengine_get_dma_device(txchan); + tx_sglen = dma_map_sg(tx_dev, pl022->sgt_tx.sgl, + pl022->sgt_tx.nents, DMA_TO_DEVICE); if (!tx_sglen) goto err_tx_sgmap; @@ -969,10 +972,10 @@ static int configure_dma(struct pl022 *pl022) dmaengine_terminate_all(txchan); err_rxdesc: dmaengine_terminate_all(rxchan); - dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(tx_dev, pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); err_tx_sgmap: - dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(rx_dev, pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); err_rx_sgmap: sg_free_table(&pl022->sgt_tx); diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 05efe6313b7fed..001e4bd8bb4b09 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -63,7 +63,6 @@ #define ACC_FEATURE 0xe #define BAD_BLOCK_MARKER_SIZE 0x2 #define OOB_BUF_SIZE 128 -#define ecceng_to_qspi(eng) container_of(eng, struct qpic_spi_nand, ecc_eng) struct snandc_read_status { __le32 snandc_flash; @@ -98,7 +97,6 @@ struct qpic_ecc { u32 cfg1; u32 cfg0_raw; u32 cfg1_raw; - u32 ecc_buf_cfg; u32 ecc_bch_cfg; bool bch_enabled; }; @@ -168,8 +166,9 @@ static void qcom_spi_set_read_loc_last(struct qcom_nand_controller *snandc, static struct qcom_nand_controller *nand_to_qcom_snand(struct nand_device *nand) { struct nand_ecc_engine *eng = nand->ecc.engine; - struct qpic_spi_nand *qspi = ecceng_to_qspi(eng); + struct qpic_spi_nand *qspi; + qspi = container_of(eng, struct qpic_spi_nand, ecc_eng); return qspi->snandc; } @@ -343,6 +342,15 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes; bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1; + if ((ecc_cfg->cw_size * cwperpage) > (mtd->writesize + mtd->oobsize)) { + dev_err(snandc->dev, + "Current ECC settings require %d bytes, but the flash only has %d+%d bytes.\n", + (ecc_cfg->cw_size * cwperpage), mtd->writesize, + mtd->oobsize); + ret = -EINVAL; + goto err_free_ecc_cfg; + } + mtd_set_ooblayout(mtd, &qcom_spi_ooblayout); /* @@ -398,8 +406,6 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) FIELD_PREP(ECC_MODE_MASK, ecc_cfg->ecc_mode) | FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw); - ecc_cfg->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203); - conf->step_size = ecc_cfg->step_size; conf->strength = ecc_cfg->strength; @@ -411,6 +417,8 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) dev_dbg(snandc->dev, "ECC strength: %u bits per %u bytes\n", ecc_cfg->strength, ecc_cfg->step_size); + snandc->qspi->ecc = ecc_cfg; + return 0; err_free_ecc_cfg: @@ -427,6 +435,7 @@ static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand) kfree(snandc->qspi->oob_buf); snandc->qspi->oob_buf = NULL; + snandc->qspi->ecc = NULL; kfree(ecc_cfg); } @@ -434,9 +443,7 @@ static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand, struct nand_page_io_req *req) { struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand); - struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand); - snandc->qspi->ecc = ecc_cfg; snandc->qspi->raw_rw = false; snandc->qspi->oob_rw = false; snandc->qspi->page_rw = false; @@ -500,18 +507,28 @@ static void qcom_spi_set_read_loc(struct qcom_nand_controller *snandc, int cw, i read_size, is_last_read_loc); } +static void qcom_spi_config_page_read(struct qcom_nand_controller *snandc) +{ + qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, + NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} + static void qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw) { - __le32 *reg = &snandc->regs->read_location0; - int num_cw = snandc->qspi->num_cw; - - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - if (cw == (num_cw - 1)) { - reg = &snandc->regs->read_location_last0; - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_LAST_CW_0, 4, + if (cw == (snandc->qspi->num_cw - 1)) + qcom_write_reg_dma(snandc, &snandc->regs->read_location_last0, + NAND_READ_LOCATION_LAST_CW_0, 4, + NAND_BAM_NEXT_SGL); + else + qcom_write_reg_dma(snandc, &snandc->regs->read_location0, + NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - } qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); @@ -563,13 +580,7 @@ static void qcom_spi_config_single_cw_page_read(struct qcom_nand_controller *sna __le32 *reg = &snandc->regs->read_location0; int num_cw = snandc->qspi->num_cw; - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); if (cw == (num_cw - 1)) { reg = &snandc->regs->read_location_last0; @@ -762,14 +773,7 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu snandc->regs->exec = cpu_to_le32(1); qcom_spi_set_read_loc(snandc, raw_cw, 0, 0, ecc_cfg->cw_size, 1); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); data_size1 = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1); oob_size1 = ecc_cfg->bbm_size; @@ -874,14 +878,7 @@ static int qcom_spi_read_page_ecc(struct qcom_nand_controller *snandc, snandc->regs->exec = cpu_to_le32(1); qcom_clear_bam_transaction(snandc); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; @@ -962,13 +959,7 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc, snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); snandc->regs->exec = cpu_to_le32(1); - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; @@ -1204,13 +1195,12 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, u8 *data_buf = NULL, *oob_buf = NULL; int i, ret; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; if (snandc->qspi->data_buf) data_buf = snandc->qspi->data_buf; @@ -1228,7 +1218,6 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); qcom_spi_config_page_write(snandc); @@ -1281,13 +1270,12 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, u8 *oob_buf = NULL; int ret, col, data_size, oob_size; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, 0); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; col = ecc_cfg->cw_size * (num_cw - 1); @@ -1303,7 +1291,6 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); /* calculate the data and oob size for the last codeword/step */ @@ -1481,7 +1468,7 @@ static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_ if (copy_ftr) { qcom_nandc_dev_to_mem(snandc, true); - val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf); + val = le32_to_cpu(*snandc->reg_read_buf); val >>= 8; memcpy(op->data.buf.in, &val, snandc->buf_count); @@ -1579,15 +1566,9 @@ static int qcom_spi_probe(struct platform_device *pdev) struct spi_controller *ctlr; struct qcom_nand_controller *snandc; struct qpic_spi_nand *qspi; - struct qpic_ecc *ecc; struct resource *res; - const void *dev_data; int ret; - ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); - if (!ecc) - return -ENOMEM; - qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL); if (!qspi) return -ENOMEM; @@ -1607,16 +1588,13 @@ static int qcom_spi_probe(struct platform_device *pdev) snandc->dev = dev; snandc->qspi = qspi; snandc->qspi->ctlr = ctlr; - snandc->qspi->ecc = ecc; - dev_data = of_device_get_match_data(dev); - if (!dev_data) { + snandc->props = of_device_get_match_data(dev); + if (!snandc->props) { dev_err(&pdev->dev, "failed to get device data\n"); return -ENODEV; } - snandc->props = dev_data; - snandc->core_clk = devm_clk_get_enabled(dev, "core"); if (IS_ERR(snandc->core_clk)) return PTR_ERR(snandc->core_clk); @@ -1699,6 +1677,7 @@ static void qcom_spi_remove(struct platform_device *pdev) static const struct qcom_nandc_props ipq9574_snandc_props = { .dev_cmd_reg_start = 0x7000, .bam_offset = 0x30000, + .qpic_version2 = true, .supports_bam = true, }; diff --git a/drivers/spi/spi-realtek-rtl.c b/drivers/spi/spi-realtek-rtl.c index e1c40ff2e49d38..7b250b447abfdb 100644 --- a/drivers/spi/spi-realtek-rtl.c +++ b/drivers/spi/spi-realtek-rtl.c @@ -18,7 +18,6 @@ struct rtspi { #define RTL_SPI_SFCSR_CSB0 BIT(31) #define RTL_SPI_SFCSR_CSB1 BIT(30) #define RTL_SPI_SFCSR_RDY BIT(27) -#define RTL_SPI_SFCSR_CS BIT(24) #define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28) #define RTL_SPI_SFCSR_LEN1 (0x00 << 28) #define RTL_SPI_SFCSR_LEN4 (0x03 << 28) @@ -29,17 +28,31 @@ struct rtspi { #define REG(x) (rtspi->base + x) -static void rt_set_cs(struct spi_device *spi, bool active) +static void rt_set_cs(struct spi_device *spi, bool level) { struct rtspi *rtspi = spi_controller_get_devdata(spi->controller); - u32 value; + unsigned int cs = spi_get_chipselect(spi, 0); + u32 cs_mask, value; + + switch (cs) { + case 0: + cs_mask = RTL_SPI_SFCSR_CSB0; + break; + case 1: + cs_mask = RTL_SPI_SFCSR_CSB1; + break; + default: + return; + } - /* CS0 bit is active low */ value = __raw_readl(REG(RTL_SPI_SFCSR)); - if (active) - value |= RTL_SPI_SFCSR_CSB0; + + /* CSBx is active low */ + if (level) + value |= cs_mask; else - value &= ~RTL_SPI_SFCSR_CSB0; + value &= ~cs_mask; + __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -138,11 +151,9 @@ static void init_hw(struct rtspi *rtspi) value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO; __raw_writel(value, REG(RTL_SPI_SFCR)); - value = __raw_readl(REG(RTL_SPI_SFCSR)); - /* Permanently disable CS1, since it's never used */ - value |= RTL_SPI_SFCSR_CSB1; - /* Select CS0 for use */ - value &= RTL_SPI_SFCSR_CS; + /* CHIP_SEL is only used in MMIO mode; CSB0/CSB1 are active low. */ + value = 0; + value |= RTL_SPI_SFCSR_CSB0 | RTL_SPI_SFCSR_CSB1; __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -171,6 +182,7 @@ static int realtek_rtl_spi_probe(struct platform_device *pdev) ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX; ctrl->set_cs = rt_set_cs; ctrl->transfer_one = transfer_one; + ctrl->num_chipselect = 2; err = devm_spi_register_controller(&pdev->dev, ctrl); if (err) { diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 662a994da60bec..a85a260ba95f75 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -719,10 +719,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev) return 0; err_register: - dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); + if (sfc->use_dma) + dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); err_dma_map: - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + if (sfc->use_dma) + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); err_dma: pm_runtime_get_sync(dev); pm_runtime_put_noidle(dev); @@ -743,9 +745,16 @@ static void rockchip_sfc_remove(struct platform_device *pdev) struct spi_controller *host = sfc->host; spi_unregister_controller(host); - dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + if (sfc->use_dma) { + dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + } clk_disable_unprepare(sfc->clk); clk_disable_unprepare(sfc->hclk); diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c index 38df676774ee1a..c61edd1c1353b1 100644 --- a/drivers/spi/spi-rspi.c +++ b/drivers/spi/spi-rspi.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -1091,24 +1090,15 @@ static irqreturn_t rspi_irq_tx(int irq, void *_sr) static struct dma_chan *rspi_request_dma_chan(struct device *dev, enum dma_transfer_direction dir, - unsigned int id, dma_addr_t port_addr) { - dma_cap_mask_t mask; struct dma_chan *chan; struct dma_slave_config cfg; int ret; - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, - (void *)(unsigned long)id, dev, - dir == DMA_MEM_TO_DEV ? "tx" : "rx"); - if (!chan) { - dev_warn(dev, "dma_request_slave_channel_compat failed\n"); - return NULL; - } + chan = dma_request_chan(dev, dir == DMA_MEM_TO_DEV ? "tx" : "rx"); + if (IS_ERR(chan)) + return chan; memset(&cfg, 0, sizeof(cfg)); cfg.dst_addr = port_addr + RSPI_SPDR; @@ -1121,7 +1111,7 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, if (ret) { dev_warn(dev, "dmaengine_slave_config failed %d\n", ret); dma_release_channel(chan); - return NULL; + return ERR_PTR(ret); } return chan; @@ -1130,29 +1120,23 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, static int rspi_request_dma(struct device *dev, struct spi_controller *ctlr, const struct resource *res) { - unsigned int dma_tx_id, dma_rx_id; + struct dma_chan *chan; - if (dev->of_node) { - /* In the OF case we will get the slave IDs from the DT */ - dma_tx_id = 0; - dma_rx_id = 0; - } else { - /* The driver assumes no error. */ + if (!dev->of_node) return 0; - } - ctlr->dma_tx = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, dma_tx_id, - res->start); - if (!ctlr->dma_tx) - return -ENODEV; + chan = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, res->start); + if (IS_ERR(chan)) + return PTR_ERR(chan); + ctlr->dma_tx = chan; - ctlr->dma_rx = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, dma_rx_id, - res->start); - if (!ctlr->dma_rx) { + chan = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, res->start); + if (IS_ERR(chan)) { dma_release_channel(ctlr->dma_tx); ctlr->dma_tx = NULL; - return -ENODEV; + return PTR_ERR(chan); } + ctlr->dma_rx = chan; ctlr->can_dma = rspi_can_dma; dev_info(dev, "DMA available"); @@ -1355,6 +1339,11 @@ static int rspi_probe(struct platform_device *pdev) } ret = rspi_request_dma(&pdev->dev, ctlr, res); + if (ret == -EPROBE_DEFER) { + dev_err_probe(&pdev->dev, ret, + "failed to request DMA channels\n"); + goto error2; + } if (ret < 0) dev_warn(&pdev->dev, "DMA not available, using PIO\n"); diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c new file mode 100644 index 00000000000000..7f56346506b345 --- /dev/null +++ b/drivers/spi/spi-rtk-nor.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Realtek SPI Nor Flash Controller Driver (SFC) +// +// Copyright (c) 2024-2026 Realtek Semiconductor Corp. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SFC_OPCODE 0x00 +#define DUAL_MODE_EN BIT(9) + +#define SFC_CTL 0x04 +#define RW_DATAEN BIT(4) +#define ADDR_EN BIT(3) + +#define SFC_SCK 0x08 +#define FDIV_MASK GENMASK(7, 0) + +#define SFC_CE 0x0c +#define DESLT_TIME 0x1a +#define DESLT_TIME_SHIFT 16 +#define PH_CNT 0x13 +#define PH_CNT_SHIFT 8 +#define PL_CNT 0x7 + +#define WT_PROM_DONE BIT(8) + +#define SFC_POS_LATCH 0x14 +#define FALLING_EDGE_EN 0x0 + +#define SFC_WAIT_WR 0x18 +#define SFC_EN_WR 0x1c +#define WT_PROM_EN BIT(8) + +#define SFC_ADR_FOUR_BYTE_EN 0x28 + +#define MD_FDMA_DDR_SADDR 0x0c +#define MD_FDMA_FL_SADDR 0x10 + +#define MD_FDMA_CTRL2 0x14 +#define MAX_XFER_DMA_LEN (BIT(26) | BIT(27)) +#define MAX_XFER_256 BIT(26) +#define DMA_TO_FLASH BIT(25) + +#define MD_FDMA_CTRL1 0x18 +#define DMA_W_EN_START BIT(3) +#define DMA_END BIT(2) +#define DMA_W_EN BIT(1) +#define DMA_START BIT(0) + +#define MD_FDMA_DDR_SADDR1 0x20 + +#define SFC_DMA_TIMEOUT 20000 +#define SFC_DMA_MAX_LEN 0x100 + +#define SFC_CTL_DMYCNT_MASK GENMASK(31, 24) +#define SFC_CTL_DMYCNT_SHIFT 24 + +#define DMA_HIGH_BITS_MASK 0x7 + +#define RTK_SPI_OP_RDSR 0x05 +#define RTK_SPI_OP_WREN 0x06 +#define RTK_SPI_OP_EN4B 0xb7 +#define RTK_SPI_OP_EX4B 0xe9 + +#define SFC_AUTOSUSPEND_TIMEOUT 2000 + +struct rtk_spi_host { + struct device *dev; + struct clk *clk; + struct reset_control *rstc; + void __iomem *regbase; + void __iomem *iobase; + void __iomem *mdbase; + void *buffer; + bool is_4byte; + dma_addr_t dma_buffer; + resource_size_t flash_phys_base; + size_t dirmap_size; +}; + +static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op) +{ + if (!op->dummy.nbytes) + return 0; + + return (op->dummy.nbytes * 8) / op->dummy.buswidth; +} + +static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + if (op->data.buswidth == 2) + opcode |= DUAL_MODE_EN; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + + val &= ~SFC_CTL_DMYCNT_MASK; + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + readl(host->iobase); +} + +static void rtk_spi_write_mode(struct rtk_spi_host *host, + const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + writel(val, host->regbase + SFC_CTL); +} + +static void rtk_spi_enable_auto_write(struct rtk_spi_host *host) +{ + u32 val; + + val = WT_PROM_DONE | RTK_SPI_OP_RDSR; + writel(val, host->regbase + SFC_WAIT_WR); + + val = WT_PROM_EN | RTK_SPI_OP_WREN; + writel(val, host->regbase + SFC_EN_WR); +} + +static void rtk_spi_disable_auto_write(struct rtk_spi_host *host) +{ + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); +} + +static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, unsigned char *buf, bool is_read) +{ + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "PIO transfer out of bounds\n"); + return -EINVAL; + } + + if (is_read) + memcpy_fromio(buf, host->iobase + offset, len); + else + memcpy_toio(host->iobase + offset, buf, len); + + return 0; +} + +static void rtk_spi_init(struct rtk_spi_host *host) +{ + u32 val; + + val = readl(host->regbase + SFC_SCK); + val &= ~FDIV_MASK; + val |= (3 << 0) & FDIV_MASK; + writel(val, host->regbase + SFC_SCK); + + val = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT; + writel(val, host->regbase + SFC_CE); + + writel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH); + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); + + writel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); +} + +static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + size_t len = op->data.nbytes; + loff_t offset = op->addr.val; + u8 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN); + val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + val |= ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + return rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true); +} + +static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u8 opcode = op->cmd.opcode; + u32 ctl_val = 0; + + writel(opcode, host->regbase + SFC_OPCODE); + + if (op->data.nbytes > 0) + ctl_val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + ctl_val |= ADDR_EN; + + writel(ctl_val, host->regbase + SFC_CTL); + + if (op->data.nbytes > 0) { + const u8 *buf = op->data.buf.out; + + if (op->addr.val + op->data.nbytes > host->dirmap_size) + return -EINVAL; + + writeb(*buf, host->iobase + op->addr.val); + } else { + if (op->addr.val >= host->dirmap_size) + return -EINVAL; + + readb(host->iobase + op->addr.val); + } + + return 0; +} + +static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, bool is_read) +{ + u64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100; + u32 flash_phys_addr, val; + int ret; + + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "DMA transfer out of bounds\n"); + return -EINVAL; + } + + writel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1); + + dma_buffer = host->dma_buffer; + + /* Setup MD DDR address and flash address */ + writel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR); + writel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK, + host->mdbase + MD_FDMA_DDR_SADDR1); + + /* MD_FDMA_FL_SADDR is a 32-bit hardware register */ + flash_phys_addr = lower_32_bits(host->flash_phys_base + offset); + writel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR); + + if (is_read) + val = MAX_XFER_DMA_LEN | len; + else + val = DMA_TO_FLASH | MAX_XFER_256 | len; + + writel(val, host->mdbase + MD_FDMA_CTRL2); + + writel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1); + udelay(1); + + ret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_START), 100, timeout_us); + if (ret) { + dev_err(host->dev, "DMA transfer timed out\n"); + val = readl(host->mdbase + MD_FDMA_CTRL1); + val |= DMA_END | DMA_W_EN_START; + writel(val, host->mdbase + MD_FDMA_CTRL1); + + if (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_END), 10, 1000)) { + dev_err(host->dev, "Failed to stop DMA gracefully\n"); + } + + return ret; + } + + return 0; +} + +static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller); + u32 opcode = op->cmd.opcode; + + /* + * Hardware Workaround: + * The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be + * explicitly updated when the flash enters or exits 4-byte mode via control + * commands. + */ + if ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) { + host->is_4byte = true; + writel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) { + host->is_4byte = false; + writel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } + + if (op->data.dir == SPI_MEM_DATA_IN) + return rtk_spi_command_read(host, op); + + return rtk_spi_do_write_and_cmds(host, op); +} + +static bool rtk_spi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (op->cmd.buswidth != 1) + return false; + + if (op->cmd.dtr || op->addr.dtr || op->data.dtr) + return false; + + if (op->addr.nbytes != 0) { + if (op->addr.buswidth > 1) + return false; + if (op->addr.nbytes < 3 || op->addr.nbytes > 4) + return false; + } + + /* + * The data window is access-triggered: each CPU access to host->iobase + * generates one complete SPI transaction. Consecutive byte accesses + * therefore cannot be merged into a single transaction, so + * multi-byte stateless commands are not supported by this controller. + */ + if (!op->addr.nbytes && op->data.nbytes > 1) + return -EOPNOTSUPP; + + if (op->dummy.nbytes != 0) { + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) + return false; + } + + if (op->data.nbytes != 0 && op->data.buswidth > 2) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + const struct spi_mem_op *op = desc->info.op_tmpl; + + if (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4) + return -EOPNOTSUPP; + + if (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT) + return -EOPNOTSUPP; + + return 0; +} + +static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret; + + /* + * Handle unaligned address bytes at the beginning of the read operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true); + if (ret) + return ret; + + return chunk_len; + } + + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + ret = rtk_spi_dma_transfer(host, addr, chunk_len, true); + if (ret) { + dev_err(host->dev, "DMA read transfer failed: %d\n", ret); + return ret; + } + + memcpy(buf, host->buffer, chunk_len); + + return chunk_len; +} + +static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret = 0; + + rtk_spi_enable_auto_write(host); + rtk_spi_write_mode(host, op); + + /* + * Handle unaligned address bytes at the beginning of the write operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false); + + goto out; + } + + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + + memcpy(host->buffer, buf, chunk_len); + + ret = rtk_spi_dma_transfer(host, addr, chunk_len, false); + if (ret) + dev_err(host->dev, "DMA write transfer failed: %d\n", ret); + +out: + rtk_spi_disable_auto_write(host); + + return ret < 0 ? ret : chunk_len; +} + +static const struct spi_controller_mem_ops rtk_spi_mem_ops = { + .supports_op = rtk_spi_supports_op, + .exec_op = rtk_spi_exec_op, + .dirmap_create = rtk_spi_dirmap_create, + .dirmap_read = rtk_spi_dirmap_read, + .dirmap_write = rtk_spi_dirmap_write, +}; + +static int rtk_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct rtk_spi_host *host; + struct resource *res; + int ret; + + ctrl = devm_spi_alloc_host(dev, sizeof(*host)); + if (!ctrl) + return -ENOMEM; + + platform_set_drvdata(pdev, ctrl); + host = spi_controller_get_devdata(ctrl); + host->dev = dev; + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + host->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(host->rstc)) + return PTR_ERR(host->rstc); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35)); + if (ret) { + dev_err(dev, "Failed to set dma mask\n"); + return ret; + } + + host->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN, + &host->dma_buffer, GFP_KERNEL); + if (!host->buffer) + return -ENOMEM; + + host->regbase = devm_platform_ioremap_resource_byname(pdev, "ctrl"); + if (IS_ERR(host->regbase)) + return PTR_ERR(host->regbase); + + host->mdbase = devm_platform_ioremap_resource_byname(pdev, "dma"); + if (IS_ERR(host->mdbase)) + return PTR_ERR(host->mdbase); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap"); + host->iobase = devm_ioremap_resource(dev, res); + if (IS_ERR(host->iobase)) + return PTR_ERR(host->iobase); + + host->dirmap_size = resource_size(res); + host->flash_phys_base = res->start; + host->is_4byte = false; + + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + + ret = reset_control_assert(host->rstc); + usleep_range(10, 20); + ret = reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + pm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + + ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL; + ctrl->bus_num = -1; + ctrl->mem_ops = &rtk_spi_mem_ops; + ctrl->num_chipselect = 1; + ctrl->auto_runtime_pm = true; + + ret = spi_register_controller(ctrl); + if (ret < 0) { + dev_err(dev, "failed to register controller\n"); + goto err_pm_disable; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_pm_disable: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); + + return ret; +} + +static void rtk_spi_remove(struct platform_device *pdev) +{ + struct spi_controller *ctrl = platform_get_drvdata(pdev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctrl); + struct device *dev = &pdev->dev; + + spi_unregister_controller(ctrl); + + if (pm_runtime_resume_and_get(dev) < 0) + dev_warn(dev, "Failed to resume device during remove\n"); + + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); +} + +static const struct of_device_id rtk_spi_dt_ids[] = { + { .compatible = "realtek,rtd1625-nor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids); + +static int rtk_spi_runtime_suspend(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + + reset_control_assert(host->rstc); + + clk_disable_unprepare(host->clk); + + return 0; +} + +static int rtk_spi_runtime_resume(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + int ret; + + ret = clk_prepare_enable(host->clk); + if (ret < 0) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + + reset_control_assert(host->rstc); + usleep_range(10, 20); + reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend, + rtk_spi_runtime_resume, NULL); + +static struct platform_driver rtk_spi_driver = { + .driver = { + .name = "rtk-spi-nor", + .of_match_table = rtk_spi_dt_ids, + .pm = pm_ptr(&rtk_spi_pm_ops), + }, + .probe = rtk_spi_probe, + .remove = rtk_spi_remove, +}; +module_platform_driver(rtk_spi_driver); + +MODULE_DESCRIPTION("Realtek SPI Nor Controller Driver"); +MODULE_AUTHOR("Jyan Chou "); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8d4120cf9df11e..3546414dfc70f7 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -661,7 +661,7 @@ static int s3c64xx_wait_for_pio(struct s3c64xx_spi_driver_data *sdd, * loops = length / max fifo size (calculated by using the * fifo mask). * For any size less than the fifo size the below code is - * executed atleast once. + * executed at least once. */ loops = xfer->len / sdd->fifo_depth; buf = xfer->rx_buf; diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index f3bcb8a1b92bb1..bc9a8ee9bce448 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -307,15 +307,15 @@ static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc, return 0; } -static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, - const struct spi_mem_op *op) +static int sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, + const struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) - sg2044_spifmc_read(spifmc, op); + return sg2044_spifmc_read(spifmc, op); else if (op->data.dir == SPI_MEM_DATA_OUT) - sg2044_spifmc_write(spifmc, op); + return sg2044_spifmc_write(spifmc, op); else - sg2044_spifmc_tran_cmd(spifmc, op); + return sg2044_spifmc_tran_cmd(spifmc, op); } static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, @@ -385,29 +385,64 @@ static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, return 0; } +static int sg2044_spifmc_set_speed(struct sg2044_spifmc *spifmc, + unsigned int speed_hz) +{ + unsigned long rate = clk_get_rate(spifmc->clk); + u64 div; + u32 reg; + + if (!rate) + return -EINVAL; + + /* SCK = HCLK / (2 * (SckDiv + 1)); round down the resulting SCK. */ + div = speed_hz ? DIV_ROUND_UP_ULL(rate, 2ULL * speed_hz) : 1; + if (div > SPIFMC_CTRL_SCK_DIV_MASK + 1) + return -EINVAL; + + reg = readl(spifmc->io_base + SPIFMC_CTRL); + reg &= ~SPIFMC_CTRL_SCK_DIV_MASK; + reg |= div - 1; + writel(reg, spifmc->io_base + SPIFMC_CTRL); + + return 0; +} + static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct sg2044_spifmc *spifmc; + int ret; spifmc = spi_controller_get_devdata(mem->spi->controller); mutex_lock(&spifmc->lock); + ret = sg2044_spifmc_set_speed(spifmc, op->max_freq ? + min(op->max_freq, mem->spi->max_speed_hz) : + mem->spi->max_speed_hz); + if (ret) + goto out_unlock; + if (op->addr.nbytes == 0) - sg2044_spifmc_trans_reg(spifmc, op); + ret = sg2044_spifmc_trans_reg(spifmc, op); else - sg2044_spifmc_trans(spifmc, op); + ret = sg2044_spifmc_trans(spifmc, op); +out_unlock: mutex_unlock(&spifmc->lock); - return 0; + return ret; } static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { .exec_op = sg2044_spifmc_exec_op, }; +static const struct spi_controller_mem_caps sg2044_spifmc_mem_caps = { + .per_op_freq = true, +}; + static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc) { u32 tran_csr; @@ -447,6 +482,10 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) if (IS_ERR(spifmc->clk)) return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n"); + ctrl->max_speed_hz = clk_get_rate(spifmc->clk) / 2; + if (!ctrl->max_speed_hz) + return dev_err_probe(dev, -EINVAL, "Invalid AHB clock rate\n"); + spifmc->dev = &pdev->dev; spifmc->ctrl = ctrl; @@ -458,6 +497,7 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = false; ctrl->mem_ops = &sg2044_spifmc_mem_ops; + ctrl->mem_caps = &sg2044_spifmc_mem_caps; ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; ret = devm_mutex_init(dev, &spifmc->lock); diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 1aeab7ec0bc8d0..89fbdde46cfe9a 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -453,6 +453,7 @@ static int sh_msiof_spi_setup(struct spi_device *spi) struct sh_msiof_spi_priv *p = spi_controller_get_devdata(spi->controller); u32 clr, set, tmp; + int ret; if (spi_get_csgpiod(spi, 0) || spi_controller_is_target(p->ctlr)) return 0; @@ -468,7 +469,9 @@ static int sh_msiof_spi_setup(struct spi_device *spi) clr |= SIMDR1_SYNCAC; else set |= SIMDR1_SYNCAC; - pm_runtime_get_sync(&p->pdev->dev); + ret = pm_runtime_resume_and_get(&p->pdev->dev); + if (ret < 0) + return ret; tmp = sh_msiof_read(p, SITMDR1) & ~clr; sh_msiof_write(p, SITMDR1, tmp | set | SIMDR1_TRMD | SITMDR1_PCON); tmp = sh_msiof_read(p, SIRMDR1) & ~clr; @@ -671,7 +674,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, if (tx) { ier_bits |= SIIER_TDREQE | SIIER_TDMAE; - dma_sync_single_for_device(p->ctlr->dma_tx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(p->ctlr->dma_tx), p->tx_dma_addr, len, DMA_TO_DEVICE); desc_tx = dmaengine_prep_slave_single(p->ctlr->dma_tx, p->tx_dma_addr, len, DMA_MEM_TO_DEV, @@ -751,7 +754,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, } if (rx) - dma_sync_single_for_cpu(p->ctlr->dma_rx->device->dev, + dma_sync_single_for_cpu(dmaengine_get_dma_device(p->ctlr->dma_rx), p->rx_dma_addr, len, DMA_FROM_DEVICE); return 0; @@ -1013,6 +1016,7 @@ static const struct of_device_id sh_msiof_match[] __maybe_unused = { { .compatible = "renesas,msiof-r8a779a0", .data = &rcar_gen3_data }, { .compatible = "renesas,msiof-r8a779f0", .data = &rcar_gen3_data }, { .compatible = "renesas,rcar-gen4-msiof", .data = &rcar_gen4_data }, + { .compatible = "renesas,rcar-gen5-msiof", .data = &rcar_gen4_data }, { .compatible = "renesas,sh-msiof", .data = &sh_data }, /* Deprecated */ { /* sentinel */ } }; @@ -1138,13 +1142,13 @@ static int sh_msiof_request_dma(struct sh_msiof_spi_priv *p) if (!p->rx_dma_page) goto free_tx_page; - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); p->tx_dma_addr = dma_map_single(tx_dev, p->tx_dma_page, PAGE_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(tx_dev, p->tx_dma_addr)) goto free_rx_page; - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); p->rx_dma_addr = dma_map_single(rx_dev, p->rx_dma_page, PAGE_SIZE, DMA_FROM_DEVICE); if (dma_mapping_error(rx_dev, p->rx_dma_addr)) @@ -1174,9 +1178,9 @@ static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p) if (!ctlr->dma_tx) return; - dma_unmap_single(ctlr->dma_rx->device->dev, p->rx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), p->rx_dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); - dma_unmap_single(ctlr->dma_tx->device->dev, p->tx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_tx), p->tx_dma_addr, PAGE_SIZE, DMA_TO_DEVICE); free_page((unsigned long)p->rx_dma_page); free_page((unsigned long)p->tx_dma_page); diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index be88e62075af6c..5f5c43ae5096a4 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -515,7 +515,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi) /* * The most significant bit at DSIZE bit field is reserved when the - * maximum data size of periperal instances is limited to 16-bit + * maximum data size of peripheral instances is limited to 16-bit */ stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE); diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 4631e9c7ca1dd1..6b7a05dd0de8a5 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -386,7 +386,7 @@ static int sun6i_spi_transfer_one(struct spi_controller *host, * SPI_CLK = MOD_CLK / (2 ^ cdr) * Or we can use CDR2, which is calculated with the formula: * SPI_CLK = MOD_CLK / (2 * (cdr + 1)) - * Wether we use the former or the latter is set through the + * Whether we use the former or the latter is set through the * DRS bit. * * First try CDR2, and if we can't reach the expected diff --git a/drivers/spi/spi-sunplus-sp7021.c b/drivers/spi/spi-sunplus-sp7021.c index f16fcda187dd98..54431c363bbc9e 100644 --- a/drivers/spi/spi-sunplus-sp7021.c +++ b/drivers/spi/spi-sunplus-sp7021.c @@ -337,10 +337,15 @@ static int sp7021_spi_host_transfer_one(struct spi_controller *ctlr, struct spi_ SP7021_SPI_START_FD; writel(reg_temp, pspim->m_base + SP7021_SPI_STATUS_REG); - if (!wait_for_completion_interruptible_timeout(&pspim->isr_done, timeout)) { - dev_err(&spi->dev, "wait_for_completion err\n"); - mutex_unlock(&pspim->buf_lock); - return -ETIMEDOUT; + { + long ret = wait_for_completion_interruptible_timeout( + &pspim->isr_done, timeout); + if (ret <= 0) { + dev_err(&spi->dev, ret == 0 ? "SPI transfer timeout\n" + : "SPI transfer interrupted\n"); + mutex_unlock(&pspim->buf_lock); + return ret == 0 ? -ETIMEDOUT : -EINTR; + } } reg_temp = readl(pspim->m_base + SP7021_SPI_STATUS_REG); diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 8ede864c3d3c26..d8cca3aa276b29 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -191,6 +191,15 @@ struct tegra_qspi { void __iomem *base; phys_addr_t phys; unsigned int irq; + struct work_struct irq_work; + struct workqueue_struct *wq; + /* + * Set by tegra_qspi_handle_timeout() while it drains the bottom + * half so tegra_qspi_isr() suppresses new queue_work() calls + * that would otherwise race the recovery path or the caller's + * cleanup of curr_xfer. + */ + bool recovery_in_progress; u32 cur_speed; unsigned int cur_pos; @@ -205,6 +214,18 @@ struct tegra_qspi { unsigned int dma_buf_size; unsigned int max_buf_size; bool is_curr_dma_xfer; + /* + * Cached "this PIO chunk completes the whole transfer" decision, + * computed by tegra_qspi_start_cpu_based_transfer() before it + * unmasks the IRQ. Used by the hard IRQ small-PIO fastpath in + * place of dereferencing curr_xfer->len, so the ISR cannot touch + * the spi_transfer object even on a late IRQ that races with the + * synchronous teardown path. Multi-chunk PIO transfers always go + * through the workqueue (this flag is only set on the final + * chunk), so the fastpath cannot recurse into + * tegra_qspi_start_cpu_based_transfer() from hard IRQ context. + */ + bool is_last_pio_chunk; struct completion rx_dma_complete; struct completion tx_dma_complete; @@ -212,6 +233,7 @@ struct tegra_qspi { u32 tx_status; u32 rx_status; u32 status_reg; + u32 trans_status; bool is_packed; bool use_dma; @@ -622,6 +644,17 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct val = QSPI_DMA_BLK_SET(tqspi->curr_dma_words - 1); tegra_qspi_writel(tqspi, val, QSPI_DMA_BLK); + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk. The cache must represent only the IRQ for THIS + * chunk; a stale RDY from the previous chunk of a multi-chunk + * transfer would otherwise mislead tegra_qspi_handle_timeout() + * into a false-positive recovery while the new chunk is still in + * flight. Pairs with smp_load_acquire() in + * tegra_qspi_handle_timeout(). The new chunk's IRQ cannot fire + * until QSPI_DMA_CTL is written below. + */ + smp_store_release(&tqspi->trans_status, 0); tegra_qspi_unmask_irq(tqspi); if (tqspi->is_packed) @@ -713,7 +746,13 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); - tqspi->is_curr_dma_xfer = true; + /* + * WRITE_ONCE() pairs with READ_ONCE() in tegra_qspi_isr() and + * tegra_qspi_work_handler(); the flag is read lock-free across + * the hard-IRQ / process-context boundary so the annotation + * prevents compiler tearing and silences KCSAN. + */ + WRITE_ONCE(tqspi->is_curr_dma_xfer, true); tqspi->dma_control_reg = val; val |= QSPI_DMA_EN; tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); @@ -734,9 +773,33 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s val = QSPI_DMA_BLK_SET(cur_words - 1); tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + /* + * Snapshot whether this PIO chunk completes the whole transfer + * before unmasking the IRQ, so the hard IRQ small-PIO fastpath + * can decide whether to drain inline without dereferencing the + * spi_transfer object. cur_pos / curr_dma_words / bytes_per_word + * are stable here: they are written by + * tegra_qspi_calculate_curr_xfer_param() earlier in this code + * path. The IRQ cannot fire until the QSPI_COMMAND1 write below + * kicks the transfer off, so this store happens-before any ISR + * that observes the unmask. + */ + WRITE_ONCE(qspi->is_last_pio_chunk, + qspi->cur_pos + qspi->curr_dma_words * qspi->bytes_per_word >= t->len); + + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk so the cache represents only the IRQ for THIS chunk; + * a stale RDY from the previous chunk would otherwise mislead + * tegra_qspi_handle_timeout() into a false-positive recovery + * while the new chunk is still in flight. Pairs with + * smp_load_acquire() in tegra_qspi_handle_timeout(). The new + * chunk's IRQ cannot fire until QSPI_COMMAND1 is written below. + */ + smp_store_release(&qspi->trans_status, 0); tegra_qspi_unmask_irq(qspi); - qspi->is_curr_dma_xfer = false; + WRITE_ONCE(qspi->is_curr_dma_xfer, false); val = qspi->command1_reg; val |= QSPI_PIO; tegra_qspi_writel(qspi, val, QSPI_COMMAND1); @@ -859,6 +922,13 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran tqspi->cur_rx_pos = 0; tqspi->cur_tx_pos = 0; tqspi->curr_xfer = t; + /* + * Pairs with smp_load_acquire() in tegra_qspi_handle_timeout(). + * Clearing the cached trans_status before unmasking the IRQ for + * the new transfer prevents a stale RDY bit from the previous + * transfer fooling the timeout handler into a false recovery. + */ + smp_store_release(&tqspi->trans_status, 0); spin_unlock_irqrestore(&tqspi->lock, flags); if (is_first_of_msg) { @@ -1065,40 +1135,206 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check * @tqspi: QSPI controller instance * - * When a timeout occurs but hardware has completed the transfer (interrupt - * was lost or delayed), manually trigger transfer completion processing. - * This avoids failing transfers that actually succeeded. + * When wait_for_completion_timeout() expires the hardware may still have + * finished the current chunk. Drain the pending bottom half and, if the + * whole transfer really did complete during the drain, consume the + * completion and report success. + * + * When the bottom half advanced the transfer by only one chunk of a + * multi-chunk DMA/PIO transfer without signalling xfer_completion, a + * fallback that ran handle_{cpu,dma}_based_xfer() here would race with + * the DMA engine already moving the next chunk into the client buffer + * (spi_finalize_current_message() would then release the buffer while + * the controller is still writing memory). Fake completion is therefore + * only attempted when the current chunk is the last chunk of the + * transfer; multi-chunk continuation timeouts return -ETIMEDOUT and + * let the caller reset the controller. * - * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout + * Returns: 0 if the transfer completed, -ETIMEDOUT otherwise. */ static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) { + struct spi_transfer *t; + unsigned long flags; + bool is_last_chunk; + bool lost_irq_snapshot = false; irqreturn_t ret; - u32 status; + int retval; + u32 status, refreshed; + u32 lost_fifo_status = 0; + u32 lost_tx_status = 0; + u32 lost_rx_status = 0; - /* Check if hardware actually completed the transfer */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (!(status & QSPI_RDY)) - return -ETIMEDOUT; + /* + * Snapshot both the ISR cache and (if the cache is empty) the + * live status registers BEFORE entering recovery. The recovery + * path calls tegra_qspi_mask_clear_irq() below, which performs + * W1Cs on QSPI_TRANS_STATUS and on the QSPI_FIFO_STATUS error + * bits: a lost-IRQ recovery must capture the current FIFO error + * state before the mask erases it. + * + * Cache-live-cache retry: if the initial cache load returns zero + * we fall back to a live QSPI_TRANS_STATUS read, and if that also + * returns zero we retry the cache once more. That closes the + * interleaving where an ISR on another CPU publishes trans_status + * with release semantics and then W1Cs the hardware between our + * cache load and our live load: the second cache load observes + * the now-visible release and we correctly classify the transfer + * as complete rather than reporting a false timeout. + * + * The trans_status cache is reset to zero in + * tegra_qspi_start_{cpu,dma}_based_transfer() before unmasking + * the IRQ for every chunk, so a stale RDY from the previous + * chunk of a multi-chunk transfer cannot survive into this + * check. + */ + status = smp_load_acquire(&tqspi->trans_status); + if (!status) { + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + if (!status) { + /* Retry cache; pairs with release in ISR post-store. */ + status = smp_load_acquire(&tqspi->trans_status); + } else { + /* + * Live register shows RDY but the ISR cache is + * empty: either the ISR ran and cleared HW between + * our two loads (the cache retry above would have + * observed it, so we would not be here), or the IRQ + * was genuinely lost. Snapshot the live FIFO error + * status now so tegra_qspi_mask_clear_irq() below + * does not W1C it away before the manual handler + * downstream can see it. + */ + lost_fifo_status = tegra_qspi_readl(tqspi, + QSPI_FIFO_STATUS); + lost_tx_status = lost_fifo_status & + (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + lost_rx_status = lost_fifo_status & + (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + lost_irq_snapshot = true; + } + } /* - * Hardware completed but interrupt was lost/delayed. Manually - * process the completion by calling the appropriate handler. + * Enter recovery unconditionally. Every expired + * wait_for_completion_timeout() must serialise against a delayed + * ISR or worker before the caller runs dma_stop() + + * device_reset() + curr_xfer clear: publishing + * recovery_in_progress under tqspi->lock, masking the controller + * IRQ, calling synchronize_irq() to drain any in-flight ISR + * (including the small-PIO hard-IRQ fastpath), and finally + * cancel_work_sync() to drain the workqueue gives us that + * serialisation regardless of whether the hardware finished. A + * genuine hardware timeout still ends up as -ETIMEDOUT further + * down, but only after ISR and workqueue activity are quiesced. + * + * cancel_work_sync() cancels a pending worker without executing + * it and waits for a currently running one to finish; the + * recovery_in_progress guard checked inside tegra_qspi_isr() + * under tqspi->lock is atomic with its queue_work() and small-PIO + * fastpath dispatch decisions, so no new bottom-half work is + * enqueued once we publish the flag. + * + * tegra_qspi_mask_clear_irq() is idempotent: its read-modify-write + * of QSPI_INTR_MASK and W1C of QSPI_TRANS_STATUS / FIFO error + * status all tolerate a double-write, so it is safe whether or + * not the ISR has already run for this transfer. */ + spin_lock_irqsave(&tqspi->lock, flags); + WRITE_ONCE(tqspi->recovery_in_progress, true); + spin_unlock_irqrestore(&tqspi->lock, flags); + + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + cancel_work_sync(&tqspi->irq_work); + + if (try_wait_for_completion(&tqspi->xfer_completion)) { + retval = 0; + goto out; + } + + /* + * Re-check the cache after the drain: the worker we just drained + * may have published a completion status the entry snapshot did + * not observe (for example the ISR fired on another CPU after we + * loaded the cache but before we masked). + */ + refreshed = smp_load_acquire(&tqspi->trans_status); + if (refreshed) + status = refreshed; + + if (!(status & QSPI_RDY)) { + retval = -ETIMEDOUT; + goto out; + } + + /* + * If the ISR never ran (lost IRQ path) publish the FIFO error + * snapshot we captured before mask_clear_irq() so the manual + * handler downstream has fresh error state rather than stale + * fields from a previous chunk's ISR. + */ + if (lost_irq_snapshot) { + WRITE_ONCE(tqspi->status_reg, lost_fifo_status); + WRITE_ONCE(tqspi->tx_status, lost_tx_status); + WRITE_ONCE(tqspi->rx_status, lost_rx_status); + } + + /* + * The bottom half did not signal full completion. Either the work + * ran and advanced the transfer by one chunk (possibly arming the + * next chunk of a multi-chunk transfer), or it was cancelled + * before it could run, or the current chunk really did not + * complete. Only fake completion when the current chunk is the + * last chunk of the transfer; otherwise the DMA engine may still + * be moving the next chunk into memory, and returning 0 here would + * let seq_xfer clear curr_xfer and finalise the message while the + * hardware is still writing. + * + * The last-chunk arithmetic mirrors tegra_qspi_start_cpu_based_ + * transfer(), which uses cur_pos + curr_dma_words * bytes_per_word + * >= t->len to set is_last_pio_chunk before arming the IRQ. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + if (!t) { + /* CPU-path handler already cleared curr_xfer */ + spin_unlock_irqrestore(&tqspi->lock, flags); + retval = 0; + goto out; + } + is_last_chunk = (tqspi->cur_pos + + tqspi->curr_dma_words * tqspi->bytes_per_word) >= t->len; + spin_unlock_irqrestore(&tqspi->lock, flags); + + if (!is_last_chunk) { + retval = -ETIMEDOUT; + goto out; + } + dev_warn_ratelimited(tqspi->dev, "QSPI interrupt timeout, but transfer complete\n"); - /* Clear the transfer status */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); - - /* Manually trigger completion handler */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) ret = handle_cpu_based_xfer(tqspi); else ret = handle_dma_based_xfer(tqspi); - return (ret == IRQ_HANDLED) ? 0 : -EIO; + retval = (ret == IRQ_HANDLED) ? 0 : -EIO; + +out: + /* + * The drained bottom half may have unmasked the controller IRQ + * to arm the next chunk of a multi-chunk transfer. Re-mask and + * synchronize before clearing recovery_in_progress so that no + * lingering ISR can queue fresh work behind the caller's back + * (the caller's dma_stop() + device_reset() + curr_xfer clear + * runs immediately after we return on the error path). + */ + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + WRITE_ONCE(tqspi->recovery_in_progress, false); + return retval; } static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) @@ -1232,9 +1468,9 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* - * Check if hardware completed the transfer - * even though interrupt was lost or delayed. - * If so, process the completion and continue. + * Check if hardware completed the transfer even though + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1351,8 +1587,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* * Check if hardware completed the transfer even though - * interrupt was lost or delayed. If so, process the - * completion and continue. + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1506,6 +1742,19 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) long wait_status; int num_errors = 0; + /* + * Snapshot curr_xfer under the lock before the (potentially long) + * DMA waits below. The timeout path can clear tqspi->curr_xfer + * concurrently; using the local copy keeps the subsequent dma_unmap + * and FIFO-drain steps consistent with the transfer that actually + * started, and lets us bail safely if cleanup already happened. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + spin_unlock_irqrestore(&tqspi->lock, flags); + if (!t) + return IRQ_HANDLED; + if (tqspi->cur_direction & DATA_DIR_TX) { if (tqspi->tx_status) { if (tqspi->tx_dma_chan) @@ -1539,12 +1788,6 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) } spin_lock_irqsave(&tqspi->lock, flags); - t = tqspi->curr_xfer; - - if (!t) { - spin_unlock_irqrestore(&tqspi->lock, flags); - return IRQ_HANDLED; - } if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); @@ -1581,46 +1824,41 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) return IRQ_HANDLED; } -static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) +/** + * tegra_qspi_work_handler - Workqueue handler for interrupt bottom-half + * @work: work_struct embedded in tegra_qspi + * + * Runs in process context and can sleep (needed for DMA completion waits). + * Runs on any CPU in the WQ_UNBOUND pool, so the bottom half can migrate off + * the interrupt-taking CPU that the previous threaded IRQ pinned to + * (irq_thread() calls set_cpus_allowed_ptr() with the IRQ affinity mask). + * + * The hard IRQ handler has already: + * - Verified this is our interrupt (QSPI_RDY was set) + * - Cached FIFO status in tqspi->status_reg + * - Parsed tx_status / rx_status from FIFO status + * - Masked further interrupts + */ +static void tegra_qspi_work_handler(struct work_struct *work) { - struct tegra_qspi *tqspi = context_data; + struct tegra_qspi *tqspi = container_of(work, struct tegra_qspi, irq_work); unsigned long flags; - u32 status; - /* - * Read transfer status to check if interrupt was triggered by transfer - * completion - */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + spin_lock_irqsave(&tqspi->lock, flags); /* - * Occasionally the IRQ thread takes a long time to wake up (usually - * when the CPU that it's running on is excessively busy) and we have - * already reached the timeout before and cleaned up the timed out - * transfer. Avoid any processing in that case and bail out early. - * - * If no transfer is in progress, check if this was a real interrupt - * that the timeout handler already processed, or a spurious one. + * tegra_qspi_handle_timeout() sets recovery_in_progress under + * tqspi->lock and then calls cancel_work_sync(), so any running + * worker is drained and tegra_qspi_isr() cannot enqueue a new + * one while recovery runs. The curr_xfer NULL check catches the + * case where the timeout path already tore the transfer down + * before this work got a chance to run. */ - spin_lock_irqsave(&tqspi->lock, flags); if (!tqspi->curr_xfer) { spin_unlock_irqrestore(&tqspi->lock, flags); - /* Spurious interrupt - transfer not ready */ - if (!(status & QSPI_RDY)) - return IRQ_NONE; - /* Real interrupt, already handled by timeout path */ - return IRQ_HANDLED; + return; } - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); - - if (tqspi->cur_direction & DATA_DIR_TX) - tqspi->tx_status = tqspi->status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); - - if (tqspi->cur_direction & DATA_DIR_RX) - tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); - - tegra_qspi_mask_clear_irq(tqspi); spin_unlock_irqrestore(&tqspi->lock, flags); /* @@ -1629,10 +1867,127 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) * DMA handler also needs to sleep in wait_for_completion_*(), which * cannot be done while holding spinlock. */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) + handle_cpu_based_xfer(tqspi); + else + handle_dma_based_xfer(tqspi); +} + +/** + * tegra_qspi_isr - Hard IRQ handler + * @irq: IRQ number + * @context_data: QSPI controller instance + * + * Runs in hard IRQ context with minimal latency. Cannot sleep. + * + * Tegra QSPI uses a dedicated, non-shared GIC SPI line on every SoC that + * uses this driver. The handler always returns IRQ_HANDLED and always + * acknowledges/re-masks the controller IRQ, so the level-triggered line + * cannot stay asserted and trip the kernel spurious-IRQ detector into + * disabling the line. On a stray IRQ where curr_xfer is NULL (e.g. the + * timeout path has already torn the transfer down) the FIFO/status + * processing and bottom-half scheduling are skipped because there is no + * transfer to drive forward. + * + * Return: IRQ_HANDLED. + */ +static irqreturn_t tegra_qspi_isr(int irq, void *context_data) +{ + struct tegra_qspi *tqspi = context_data; + u32 status_reg, trans_status; + u32 tx_status = 0, rx_status = 0; + + if (!READ_ONCE(tqspi->curr_xfer)) { + tegra_qspi_mask_clear_irq(tqspi); + return IRQ_HANDLED; + } + + spin_lock(&tqspi->lock); + status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + + if (tqspi->cur_direction & DATA_DIR_TX) { + tx_status = status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + WRITE_ONCE(tqspi->tx_status, tx_status); + } + + if (tqspi->cur_direction & DATA_DIR_RX) { + rx_status = status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + WRITE_ONCE(tqspi->rx_status, rx_status); + } + + WRITE_ONCE(tqspi->status_reg, status_reg); + /* + * Publish trans_status with release semantics before we clear + * the hardware status in tegra_qspi_mask_clear_irq() below. That + * ordering matters for the lock-free cache read in + * tegra_qspi_handle_timeout(): if the timeout path sees the + * released trans_status it also observes the matching status_reg + * / tx_status / rx_status; if it does not yet see the released + * value it falls back to a live QSPI_TRANS_STATUS read, and that + * live read still returns QSPI_RDY because we have not cleared + * the register yet. Reversing this order would open a window + * where the cache is still zero but the hardware bit has already + * been cleared, making the fallback report a false timeout. + */ + smp_store_release(&tqspi->trans_status, trans_status); + + tegra_qspi_mask_clear_irq(tqspi); + + /* + * If tegra_qspi_handle_timeout() is draining the bottom half, + * skip queueing new work. The flag is set under tqspi->lock and + * queue_work() below happens while we still hold the lock, so + * the guard is atomic with the queue decision. Any ISR that had + * already passed this check is drained by the synchronize_irq() + * call that tegra_qspi_handle_timeout() issues after publishing + * the flag. + */ + if (READ_ONCE(tqspi->recovery_in_progress)) { + spin_unlock(&tqspi->lock); + return IRQ_HANDLED; + } + + /* + * Small-PIO fastpath: drain the FIFO inline only when this chunk + * completes the entire outstanding transfer and no error bit was + * latched, to avoid workqueue scheduling latency for TPM-style + * short reads. + * + * The "last chunk" decision is computed and cached as a scalar by + * tegra_qspi_start_cpu_based_transfer() before it unmasks the IRQ, + * so the hard-IRQ fastpath never dereferences the spi_transfer + * pointer here. That keeps the ISR safe against any teardown race + * where the synchronous path could clear curr_xfer concurrently. + * + * The fastpath dispatch decision is made while still holding + * tqspi->lock, so the recovery_in_progress guard above covers it + * atomically with queue_work() below: an ISR that reaches the + * fastpath cannot race a tegra_qspi_handle_timeout() that + * subsequently observes recovery_in_progress == true, because + * that path calls synchronize_irq() before proceeding. We drop + * the lock before calling handle_cpu_based_xfer() so it can take + * tqspi->lock internally without deadlocking. + * + * Multi-chunk PIO continuation stays on the workqueue so that + * tegra_qspi_start_cpu_based_transfer() can re-arm the IRQ from + * process context. DMA transfers also stay on the workqueue + * because their completion path sleeps on the DMA engine. + * tegra_qspi_handle_error() -> device_reset() can sleep, so the + * fastpath only runs when both status words are clean. + */ + if (!READ_ONCE(tqspi->is_curr_dma_xfer) && + READ_ONCE(tqspi->is_last_pio_chunk) && + !tx_status && !rx_status) { + spin_unlock(&tqspi->lock); return handle_cpu_based_xfer(tqspi); + } + + queue_work(tqspi->wq, &tqspi->irq_work); - return handle_dma_based_xfer(tqspi); + spin_unlock(&tqspi->lock); + + return IRQ_HANDLED; } static struct tegra_qspi_soc_data tegra210_qspi_soc_data = { @@ -1800,12 +2155,21 @@ static int tegra_qspi_probe(struct platform_device *pdev) pm_runtime_put_autosuspend(&pdev->dev); - ret = request_threaded_irq(tqspi->irq, NULL, - tegra_qspi_isr_thread, IRQF_ONESHOT, - dev_name(&pdev->dev), tqspi); + tqspi->wq = alloc_workqueue("%s", WQ_HIGHPRI | WQ_UNBOUND, 0, + dev_name(&pdev->dev)); + if (!tqspi->wq) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_pm_disable; + } + + INIT_WORK(&tqspi->irq_work, tegra_qspi_work_handler); + + ret = request_irq(tqspi->irq, tegra_qspi_isr, 0, + dev_name(&pdev->dev), tqspi); if (ret < 0) { dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", tqspi->irq, ret); - goto exit_pm_disable; + goto exit_destroy_wq; } ret = spi_register_controller(host); @@ -1817,7 +2181,9 @@ static int tegra_qspi_probe(struct platform_device *pdev) return 0; exit_free_irq: - free_irq(qspi_irq, tqspi); + free_irq(tqspi->irq, tqspi); +exit_destroy_wq: + destroy_workqueue(tqspi->wq); exit_pm_disable: pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); @@ -1830,8 +2196,15 @@ static void tegra_qspi_remove(struct platform_device *pdev) struct spi_controller *host = platform_get_drvdata(pdev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); + /* + * Tear down in reverse order of probe() so that the controller stops + * accepting transfers before the IRQ is released, no new work can be + * queued after the IRQ is freed, and any work already queued is + * drained while the clocks are still running. + */ spi_unregister_controller(host); free_irq(tqspi->irq, tqspi); + destroy_workqueue(tqspi->wq); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); tegra_qspi_deinit_dma(tqspi); diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 3e181bd8bc94b6..26f9711c99b39a 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -31,10 +31,6 @@ struct virtio_spi_priv { struct virtio_device *vdev; /* Pointer to the virtqueue */ struct virtqueue *vq; - /* Copy of config space mode_func_supported */ - u32 mode_func_supported; - /* Copy of config space max_freq_hz */ - u32 max_freq_hz; }; static void virtio_spi_msg_done(struct virtqueue *vq) @@ -162,8 +158,6 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, if (!spi_req) return -ENOMEM; - init_completion(&spi_req->completion); - th = &spi_req->transfer_head; /* Fill struct spi_transfer_head */ @@ -258,7 +252,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, static void virtio_spi_read_config(struct virtio_device *vdev) { struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev); - struct virtio_spi_priv *priv = vdev->priv; + u32 mode_func_supported; u8 cs_max_number; u8 tx_nbits_supported; u8 rx_nbits_supported; @@ -268,18 +262,18 @@ static void virtio_spi_read_config(struct virtio_device *vdev) ctrl->num_chipselect = cs_max_number; /* Set the mode bits which are understood by this driver */ - priv->mode_func_supported = + mode_func_supported = virtio_cread32(vdev, offsetof(struct virtio_spi_config, mode_func_supported)); - ctrl->mode_bits = priv->mode_func_supported & + ctrl->mode_bits = mode_func_supported & (VIRTIO_SPI_CS_HIGH | VIRTIO_SPI_MODE_LSB_FIRST); - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) ctrl->mode_bits |= VIRTIO_SPI_CPHA; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) ctrl->mode_bits |= VIRTIO_SPI_CPOL; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) ctrl->mode_bits |= SPI_LSB_FIRST; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) ctrl->mode_bits |= SPI_LOOP; tx_nbits_supported = virtio_cread8(vdev, offsetof(struct virtio_spi_config, @@ -304,7 +298,7 @@ static void virtio_spi_read_config(struct virtio_device *vdev) virtio_cread32(vdev, offsetof(struct virtio_spi_config, bits_per_word_mask)); - priv->max_freq_hz = + ctrl->max_speed_hz = virtio_cread32(vdev, offsetof(struct virtio_spi_config, max_freq_hz)); } diff --git a/drivers/spi/spi-xilinx.c b/drivers/spi/spi-xilinx.c index dc1e968659e1b4..df4ea99a5427d5 100644 --- a/drivers/spi/spi-xilinx.c +++ b/drivers/spi/spi-xilinx.c @@ -460,7 +460,7 @@ static int xilinx_spi_probe(struct platform_device *pdev) host->num_chipselect = num_cs; /* - * Detect endianess on the IP via loop bit in CR. Detection + * Detect endianness on the IP via loop bit in CR. Detection * must be done before reset is sent because incorrect reset * value generates error interrupt. * Setup little endian helper functions first and try to use them diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 5b5b3bc5f0d8cf..015d93680e5d0b 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -929,6 +929,15 @@ static int __spi_add_device(struct spi_device *spi, struct spi_device *parent) } } + /* + * Peripheral properties the core handles on behalf of controller + * drivers are parsed here, rather than in the firmware specific + * instantiation paths, so that device tree, ACPI and software nodes + * are covered alike, and early enough for ->setup() to act on them. + */ + device_property_read_u32(&spi->dev, "rx-sample-delay-ns", + &spi->rx_sample_delay_ns); + /* * Drivers may modify this initial i/o setup, but will * normally rely on the device being setup. Devices @@ -1456,14 +1465,14 @@ int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return 0; if (ctlr->dma_tx) - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); else if (ctlr->dma_map_dev) tx_dev = ctlr->dma_map_dev; else tx_dev = ctlr->dev.parent; if (ctlr->dma_rx) - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); else if (ctlr->dma_map_dev) rx_dev = ctlr->dma_map_dev; else @@ -2893,7 +2902,7 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { } * * This may only be called from main SPI device's probe routine. * - * Return: 0 on success; negative errno on failure + * Return: the new device on success; an ERR_PTR() on failure */ struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select) diff --git a/drivers/staging/greybus/uart.c b/drivers/staging/greybus/uart.c index 24b4dab069c38b..172cf900eaf0bb 100644 --- a/drivers/staging/greybus/uart.c +++ b/drivers/staging/greybus/uart.c @@ -948,7 +948,8 @@ static int gb_tty_init(void) int retval = 0; gb_tty_driver = tty_alloc_driver(GB_NUM_MINORS, TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(gb_tty_driver)) { pr_err("Can not allocate tty driver\n"); retval = PTR_ERR(gb_tty_driver); diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625cd..d027fe7ce86fd7 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -198,7 +198,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, if (shm) { u64 shm_offs = p->u.memref.shm_offs; - mp->u.fmem.internal_offs = shm->offset; + mp->u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1); mp->u.fmem.offs_low = shm_offs; mp->u.fmem.offs_high = shm_offs >> 32; @@ -284,14 +285,26 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt; + size_t ffa_offs; int rc; + if (!num_pages) + return -EINVAL; + rc = optee_check_mem_type(start, num_pages); if (rc) return rc; - rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, 0, - num_pages * PAGE_SIZE, GFP_KERNEL); + /* + * Start the FF-A descriptor at the 4 KiB page containing the shared + * buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is + * larger. Same approach as optee_fill_pages_list() in the SMC ABI. + * This leaves only the offset into that 4 KiB page for internal_offs. + */ + ffa_offs = round_down(tee_shm_get_page_offset(shm), FFA_PAGE_SIZE); + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs, + num_pages * PAGE_SIZE - ffa_offs, + GFP_KERNEL); if (rc) return rc; args.sg = sgt.sgl; @@ -458,7 +471,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .attr = OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT, .u.fmem.size = tee_shm_get_size(shm), .u.fmem.global_id = shm->sec_world_id, - .u.fmem.internal_offs = shm->offset, + .u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1), }; arg->ret = TEEC_SUCCESS; @@ -1123,14 +1137,6 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - rc = rhashtable_init(&optee->ffa.global_ids, &shm_rhash_params); if (rc) goto err_unreg_supp_teedev; @@ -1159,6 +1165,14 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) if (optee_ffa_protmem_pool_init(optee, sec_caps)) pr_info("Protected memory service not available\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_unregister_devices; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_unregister_devices; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_unregister_devices; diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c0354..6c3043f8da33c6 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem { * struct optee_msg_param_fmem - FF-A memory reference parameter * @offs_low: lower bits of offset into shared memory reference * @offs_high: higher bits of offset into shared memory reference - * @internal_offs: internal offset into the first page of shared memory - * reference + * @internal_offs: offset into the first 4 KiB page of the FF-A shared + * memory region * @size: size of the buffer * @global_id: global identifier of the shared memory */ diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index b8a2bdac3208f9..51624443359bfb 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1849,14 +1849,6 @@ static int optee_probe(struct platform_device *pdev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - optee_cq_init(&optee->call_queue, thread_count); optee_supp_init(&optee->supp); optee->smc.memremaped_shm = memremaped_shm; @@ -1916,6 +1908,14 @@ static int optee_probe(struct platform_device *pdev) if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) pr_info("dynamic shared memory is enabled\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_disable_shm_cache; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_disable_shm_cache; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_disable_shm_cache; @@ -1942,7 +1942,6 @@ static int optee_probe(struct platform_device *pdev) optee_shm_arg_cache_uninit(optee); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); -err_unreg_supp_teedev: tee_device_unregister(optee->supp_teedev); err_unreg_teedev: tee_device_unregister(optee->teedev); diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c index 31bff4309e6721..5849e51612033b 100644 --- a/drivers/tee/qcomtee/async.c +++ b/drivers/tee/qcomtee/async.c @@ -97,10 +97,10 @@ static void qcomtee_get_async_buffer(struct qcomtee_object_invoke_ctx *oic, /** * async_release() - Process QTEE async release requests. * @oic: context used for the current invocation. - * @msg: async message for object release. + * @async_msg: async message for object release. * @size: size of the async buffer available. * - * Return: Size of the outbound buffer used when processing @msg. + * Return: Size of the outbound buffer used when processing @async_msg. */ static size_t async_release(struct qcomtee_object_invoke_ctx *oic, struct qcomtee_async_msg_hdr *async_msg, diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h index 878f70178a5b6f..5d7b21fdd36880 100644 --- a/drivers/tee/qcomtee/qcomtee_msg.h +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -112,7 +112,7 @@ union qcomtee_msg_arg { /** * struct qcomtee_msg_object_invoke - Direct object invocation message. - * @ctx: object ID hosted in QTEE. + * @cxt: object ID hosted in QTEE. * @op: operation for the object. * @counts: number of different types of arguments in @args. * @args: array of arguments. diff --git a/drivers/tee/qcomtee/qcomtee_object.h b/drivers/tee/qcomtee/qcomtee_object.h index 8b4401ecad48c2..d5de02dcef3b96 100644 --- a/drivers/tee/qcomtee/qcomtee_object.h +++ b/drivers/tee/qcomtee/qcomtee_object.h @@ -74,6 +74,7 @@ enum qcomtee_object_type { * @QCOMTEE_ARG_TYPE_OO: output object (OO). * @QCOMTEE_ARG_TYPE_IB: input buffer (IB). * @QCOMTEE_ARG_TYPE_IO: input object (IO). + * @QCOMTEE_ARG_TYPE_NR: number of argument types. * * Use the invalid type to specify the end of the argument array. */ diff --git a/drivers/tee/tee_shm.c b/drivers/tee/tee_shm.c index 6742b3579c86d6..daa4af1e089e59 100644 --- a/drivers/tee/tee_shm.c +++ b/drivers/tee/tee_shm.c @@ -343,6 +343,10 @@ int tee_dyn_shm_alloc_helper(struct tee_shm *shm, size_t size, size_t align, unsigned int i; int rc = 0; + /* get_order(0) is undefined and exceeds MAX_PAGE_ORDER. */ + if (!nr_pages) + return -EINVAL; + /* * Ignore alignment since this is already going to be page aligned * and there's no need for any larger alignment. diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index cd47b627f97b27..965988b18608f4 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -73,7 +73,6 @@ struct tb_ctl { #define tb_ctl_dbg_once(ctl, format, arg...) \ dev_dbg_once((ctl)->nhi->dev, format, ## arg) -static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ static DEFINE_MUTEX(tb_cfg_request_lock); @@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) static int tb_cfg_request_enqueue(struct tb_ctl *ctl, struct tb_cfg_request *req) { + tb_cfg_request_get(req); + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); WARN_ON(req->ctl); - mutex_lock(&ctl->request_queue_lock); + guard(mutex)(&ctl->request_queue_lock); if (!ctl->running) { - mutex_unlock(&ctl->request_queue_lock); + tb_cfg_request_put(req); return -ENOTCONN; } req->ctl = ctl; list_add_tail(&req->list, &ctl->request_queue); set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - mutex_unlock(&ctl->request_queue_lock); return 0; } -static void tb_cfg_request_dequeue(struct tb_cfg_request *req) +static bool tb_cfg_request_is_active(struct tb_cfg_request *req) { - struct tb_ctl *ctl = req->ctl; - - mutex_lock(&ctl->request_queue_lock); - if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { - mutex_unlock(&ctl->request_queue_lock); - return; - } + return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +} - list_del(&req->list); - clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) - wake_up(&tb_cfg_request_cancel_queue); - mutex_unlock(&ctl->request_queue_lock); +static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) +{ + return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); } -static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +static void tb_cfg_request_dequeue(struct tb_cfg_request *req) { - return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + struct tb_ctl *ctl = req->ctl; + + guard(mutex)(&ctl->request_queue_lock); + if (tb_cfg_request_is_active(req)) { + list_del(&req->list); + clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + tb_cfg_request_put(req); + } } static struct tb_cfg_request * @@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) mutex_lock(&pkg->ctl->request_queue_lock); list_for_each_entry(iter, &pkg->ctl->request_queue, list) { tb_cfg_request_get(iter); - if (iter->match(iter, pkg)) { + if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { req = iter; break; } @@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); if (req) { - if (req->copy(req, pkg)) - schedule_work(&req->work); + scoped_guard(mutex, &pkg->ctl->request_queue_lock) { + if (!tb_cfg_request_is_canceled(req) && + req->copy(req, pkg)) + schedule_work(&req->work); + } tb_cfg_request_put(req); } @@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) { struct tb_cfg_request *req = container_of(work, typeof(*req), work); - if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) + if (!tb_cfg_request_is_canceled(req)) req->callback(req->callback_data); tb_cfg_request_dequeue(req); - tb_cfg_request_put(req); } /** @@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, INIT_WORK(&req->work, tb_cfg_request_work); INIT_LIST_HEAD(&req->list); - tb_cfg_request_get(req); ret = tb_cfg_request_enqueue(ctl, req); if (ret) - goto err_put; + return ret; ret = tb_ctl_tx(ctl, req->request, req->request_size, req->request_type); @@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, err_dequeue: tb_cfg_request_dequeue(req); -err_put: - tb_cfg_request_put(req); - return ret; } @@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, */ void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) { - set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); - schedule_work(&req->work); - wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); + scoped_guard(mutex, &req->ctl->request_queue_lock) + set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); + cancel_work_sync(&req->work); + tb_cfg_request_dequeue(req); req->result.err = err; } diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 12c88509a54fa8..24611f05b3cda9 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, transmit_ring, receive_path, receive_ring); } -static void tb_domain_reset_interface(struct tb *tb) -{ - struct tb_nhi *nhi = tb->nhi; - - if (!nhi->ops->reset_interface) - return; - - guard(mutex)(&tb->lock); - - /* The reset clears the ring state so stop the control channel */ - tb_ctl_stop(tb->ctl); - nhi->ops->reset_interface(nhi); - tb_ctl_start(tb->ctl); -} - /** * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain * @tb: Domain disabling the DMA paths @@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { - int ret; - if (!tb->cm_ops->disconnect_xdomain_paths) return -ENOTSUPP; - ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, transmit_ring, receive_path, receive_ring); - if (ret) - return ret; - - if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) - tb_domain_reset_interface(tb); - - return 0; } static int disconnect_xdomain(struct device *dev, void *data) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 5809809f64d42b..be18f7b6595a63 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, INIT_LIST_HEAD(&ring->in_flight); INIT_WORK(&ring->work, ring_work); init_waitqueue_head(&ring->wait); + lockdep_register_key(&ring->lock_key); + lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); ring->nhi = nhi; ring->hop = hop; @@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: + lockdep_unregister_key(&ring->lock_key); kfree(ring); return NULL; @@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) * to finish before freeing the ring. */ flush_work(&ring->work); + lockdep_unregister_key(&ring->lock_key); kfree(ring); } EXPORT_SYMBOL_GPL(tb_ring_free); @@ -1175,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } -/** - * nhi_reset_interface() - Reset the host interface - * @nhi: Host interface to reset - * - * Brings the registers in the memory BAR back to their default state and - * clears the End-to-End Flow Control state. The caller is responsible for - * stopping the control channel over the reset because it clears the ring - * state as well. - */ -void nhi_reset_interface(struct tb_nhi *nhi) -{ - u32 val; - - val = ioread32(nhi->iobase + REG_CAPS); - /* Only v1 host interfaces implement the reset */ - if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) - return; - - dev_dbg(nhi->dev, "issuing host interface reset\n"); - - iowrite32(REG_HOST_INTERFACE_RESET_RST, - nhi->iobase + REG_HOST_INTERFACE_RESET); - /* Wait for tHIReset (10 ms) to complete */ - usleep_range(10000, 20000); -} - static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index f72d6b27450189..d488eadadfceeb 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); irqreturn_t ring_msix(int irq, void *data); int nhi_probe(struct tb_nhi *nhi); void nhi_shutdown(struct tb_nhi *nhi); -void nhi_reset_interface(struct tb_nhi *nhi); - extern const struct dev_pm_ops nhi_pm_ops; /** @@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; * @release_ring_irq: NHI specific interrupt release hook * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook - * @reset_interface: Resets the host interface */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -69,7 +66,6 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); - void (*reset_interface)(struct tb_nhi *nhi); }; /* @@ -120,24 +116,11 @@ struct tb_nhi_ops { #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e - #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 /* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) -#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 99df60b6db36a7..d6a197fabc74a2 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -115,10 +115,6 @@ struct ring_desc { #define REG_CAPS_VERSION_MASK GENMASK(23, 16) #define REG_CAPS_VERSION_2 0x40 -/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ -#define REG_HOST_INTERFACE_RESET 0x39858 -#define REG_HOST_INTERFACE_RESET_RST BIT(0) - #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 99333729f3c2ac..8462ccb59b7e23 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) nhi->quirks |= QUIRK_E2E; break; } - } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { - switch (pdev->device) { - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: - /* - * These AMD hosts may hang the Tx ring when the - * DMA paths are torn down so they need the host - * interface reset after each teardown. - */ - nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; - break; - } } } @@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; /* Ice Lake specific NHI operations */ @@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { .release_ring_irq = nhi_pci_ring_release_msix, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 9f7914ac2f48cf..96b7fe0386e389 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { quirk_usb3_maximum_bandwidth }, { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, quirk_usb3_maximum_bandwidth }, + /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, + quirk_clx_disable }, /* * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host * controllers. diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 25c259dd07602d..988765c98c8c81 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -978,6 +978,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) } mutex_unlock(&sdev->lock); + + /* Stream handles IOCB_NOWAIT just fine */ + file->f_mode |= FMODE_NOWAIT; return 0; err_unlock: diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 404c0693df50e7..cf571a7c98d090 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, { int port_max_hopid; struct ida *ida; + int ret; if (in) { port_max_hopid = port->config.max_in_hop_id; @@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, if (max_hopid < 0 || max_hopid > port_max_hopid) max_hopid = port_max_hopid; - return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + if (ret >= 0) + tb_switch_get(port->sw); + + return ret; } /** @@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) void tb_port_release_in_hopid(struct tb_port *port, int hopid) { ida_free(&port->in_hopids, hopid); + tb_switch_put(port->sw); } /** @@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) void tb_port_release_out_hopid(struct tb_port *port, int hopid) { ida_free(&port->out_hopids, hopid); + tb_switch_put(port->sw); } static inline bool tb_switch_is_reachable(const struct tb_switch *parent, diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 47753a5c0f2ebd..4e5d0578fd4bc8 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, const char *reason); static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, int retry, unsigned long delay); +static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) { @@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, switch (port->config.type) { case TB_TYPE_DP_HDMI_IN: - tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); + tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, + tb_dp_tunnel_active, tb); tb_increase_tmu_accuracy(tunnel); break; @@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) struct tb *tb = data; mutex_lock(&tb->lock); + + /* + * If the DPRX read was canceled the tunnel is already being torn + * down by whoever canceled it. Do not touch the adapters here + * because the routers may be gone by now. + */ + if (tunnel->dprx_canceled) { + tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); + mutex_unlock(&tb->lock); + return; + } + if (tb_tunnel_is_active(tunnel)) { int consumed_up, consumed_down, ret; @@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); } mutex_unlock(&tb->lock); - - tb_domain_put(tb); } static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, @@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, available_up, available_down); tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, - available_down, tb_dp_tunnel_active, - tb_domain_get(tb)); + available_down, tb_dp_tunnel_active, tb); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim_usb; @@ -2048,7 +2059,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, tb_tunnel_put(tunnel); err_reclaim_usb: tb_reclaim_usb3_bandwidth(tb, in, out); - tb_domain_put(tb); err_detach_group: tb_detach_bandwidth_group(in); err_dealloc_dp: @@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb) /* tunnels are only present after everything has been initialized */ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { /* - * DMA tunnels require the driver to be functional so we - * tear them down. Other protocol tunnels can be left - * intact. + * DMA tunnels and DP tunnels which are not yet active require + * the driver to be functional so we tear them down. + * Other protocol tunnels can be left intact. */ - if (tb_tunnel_is_dma(tunnel)) + if (tb_tunnel_is_dma(tunnel) || + (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) tb_tunnel_deactivate(tunnel); tb_tunnel_put(tunnel); } @@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) struct tb *tb = tcm_to_tb(tcm); mutex_lock(&tb->lock); - if (tb->root_switch) + if (tb->root_switch) { tb_free_unplugged_children(tb->root_switch); + tb_free_unplugged_xdomains(tb->root_switch); + } mutex_unlock(&tb->lock); - - tb_free_unplugged_xdomains(tb->root_switch); } static int tb_runtime_resume(struct tb *tb) diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 05652ee82fbf88..c8c648f31107ff 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -33,6 +33,18 @@ static void kunit_ida_init(struct kunit *test, struct ida *ida) kunit_alloc_resource(test, __ida_init, __ida_destroy, GFP_KERNEL, ida); } +static void tb_test_switch_release(struct device *dev) +{ + /* The memory is owned by KUnit, nothing to do here */ +} + +static void tb_test_switch_put(void *data) +{ + struct tb_switch *sw = data; + + put_device(&sw->dev); +} + static struct tb_switch *alloc_switch(struct kunit *test, u64 route, u8 upstream_port, u8 max_port_number) { @@ -44,6 +56,15 @@ static struct tb_switch *alloc_switch(struct kunit *test, u64 route, if (!sw) return NULL; + /* + * HopID allocations take a reference to their routers and those devices + * have to be initialized for that to work. + */ + sw->dev.release = tb_test_switch_release; + device_initialize(&sw->dev); + if (kunit_add_action_or_reset(test, tb_test_switch_put, sw)) + return NULL; + sw->config.upstream_port_number = upstream_port; sw->config.depth = tb_route_length(route); sw->config.route_hi = upper_32_bits(route); @@ -1386,6 +1407,10 @@ static void tb_test_tunnel_pcie(struct kunit *test) tb_tunnel_put(tunnel1); } +static void tb_test_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) +{ +} + static void tb_test_tunnel_dp(struct kunit *test) { struct tb_switch *host, *dev; @@ -1406,7 +1431,8 @@ static void tb_test_tunnel_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1452,7 +1478,8 @@ static void tb_test_tunnel_dp_chain(struct kunit *test) in = &host->ports[5]; out = &dev4->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1502,7 +1529,8 @@ static void tb_test_tunnel_dp_tree(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1567,7 +1595,8 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test) in = &dev6->ports[13]; out = &dev12->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1637,7 +1666,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) out2 = &dev5->ports[13]; out3 = &dev4->ports[14]; - tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, NULL, NULL); + tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel1 != NULL); KUNIT_EXPECT_EQ(test, tunnel1->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel1->src_port, in1); @@ -1645,7 +1675,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel1->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel1->paths[0]->path_length, 3); - tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, NULL, NULL); + tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel2 != NULL); KUNIT_EXPECT_EQ(test, tunnel2->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel2->src_port, in2); @@ -1653,7 +1684,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel2->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel2->paths[0]->path_length, 4); - tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, NULL, NULL); + tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel3 != NULL); KUNIT_EXPECT_EQ(test, tunnel3->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel3->src_port, in3); @@ -1751,7 +1783,8 @@ static void tb_test_tunnel_port_on_path(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel); KUNIT_EXPECT_TRUE(test, tb_tunnel_port_on_path(dp_tunnel, in)); @@ -2183,7 +2216,8 @@ static void tb_test_credit_alloc_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3); @@ -2419,7 +2453,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL1(struct kunit *test, in = &host->ports[5]; out = &dev->ports[13]; - dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel1); KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3); @@ -2456,7 +2491,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL2(struct kunit *test, in = &host->ports[6]; out = &dev->ports[14]; - dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel2); KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 7e8284575dffb1..ffd2d04b3bfa32 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, goto err_deactivate; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work) struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); struct tb *tb = tunnel->tb; + /* + * The DPRX read can be canceled while this work is waiting for + * tb->lock. Check the flag only once it is held: while the lock is + * held the tunnel cannot be torn down under us and the adapters are + * safe to access. + */ + mutex_lock(&tb->lock); if (!tunnel->dprx_canceled) { - mutex_lock(&tb->lock); if (tb_dp_is_usb4(tunnel->src_port->sw) && tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { @@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work) } else { tb_tunnel_set_active(tunnel, true); } - mutex_unlock(&tb->lock); } + mutex_unlock(&tb->lock); - if (tunnel->callback) - tunnel->callback(tunnel, tunnel->callback_data); + tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); + tb_domain_put(tb); } static int tb_dp_dprx_start(struct tb_tunnel *tunnel) { /* - * Bump up the reference to keep the tunnel around. It will be - * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. + * Bump up the references to keep the tunnel and the domain around + * until the work has run or has been canceled. */ tb_tunnel_get(tunnel); + tb_domain_get(tunnel->tb); tunnel->dprx_started = true; + tunnel->dprx_canceled = false; + tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); + queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - if (tunnel->callback) { - tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); - queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - return -EINPROGRESS; - } - - return tb_dp_is_usb4(tunnel->src_port->sw) ? - tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; + return -EINPROGRESS; } static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) { + struct tb *tb = tunnel->tb; + if (tunnel->dprx_started) { tunnel->dprx_started = false; tunnel->dprx_canceled = true; - if (cancel_delayed_work(&tunnel->dprx_work)) + if (cancel_delayed_work(&tunnel->dprx_work)) { tb_tunnel_put(tunnel); + tb_domain_put(tb); + } } } @@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) * @tb: Pointer to the domain structure * @in: DP in adapter * @alloc_hopid: Allocate HopIDs from visited ports + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * If @in adapter is active, follows the tunnel to the DP out adapter * and back. Returns the discovered tunnel or %NULL if there was no - * tunnel. + * tunnel. See tb_tunnel_alloc_dp() for @callback. * * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. */ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid) + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data) { struct tb_tunnel *tunnel; struct tb_port *port; struct tb_path *path; + if (WARN_ON(!callback)) + return NULL; + if (!tb_dp_port_is_enabled(in)) return NULL; @@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; tunnel->src_port = in; + tunnel->callback = callback; + tunnel->callback_data = callback_data; + INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, &tunnel->dst_port, "Video", alloc_hopid); @@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tb_dp_dump(tunnel); + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -1677,16 +1697,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, * %0 if no available bandwidth. * @max_down: Maximum available downstream bandwidth for the DP tunnel. * %0 if no available bandwidth. - * @callback: Optional callback that is called when the DP tunnel is - * fully activated (or there is an error) - * @callback_data: Optional data for @callback + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * Allocates a tunnel between @in and @out that is capable of tunneling - * Display Port traffic. If @callback is not %NULL it will be called - * after tb_tunnel_activate() once the tunnel has been fully activated. - * It can call tb_tunnel_is_active() to check if activation was - * successful (or if it returns %false there was some sort of issue). - * The @callback is called without @tb->lock held. + * Display Port traffic. The @callback is called after tb_tunnel_activate() + * once the tunnel has been fully activated. It can call + * tb_tunnel_is_active() to check if activation was successful (or if it + * returns %false there was some sort of issue). The @callback is called + * without @tb->lock held. * * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ @@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_path *path; bool pm_support; - if (WARN_ON(!in->cap_adap || !out->cap_adap)) + if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) return NULL; tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); @@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, tb_usb3_reclaim_available_bandwidth; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 4878763a82b318..7d1d255ab5a7de 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -66,8 +66,8 @@ enum tb_tunnel_state { * @dprx_canceled: Was DPRX capabilities read poll canceled * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read - * @callback: Optional callback called when DP tunnel is fully activated - * @callback_data: Optional data for @callback + * @callback: Callback called when DP tunnel is fully activated + * @callback_data: Data for @callback * @paths: All paths required by the tunnel */ struct tb_tunnel { @@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down); struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid); + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data); struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, int max_up, int max_down, diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index c179bd751fe423..b187a60663f513 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, len += sizeof(res->hdr.xd_hdr) / 4; len -= sizeof(*res) / 4; + if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { + ret = -EINVAL; + goto err; + } if (res->offset != req.offset) { ret = -EINVAL; @@ -1814,7 +1818,6 @@ static void tb_xdomain_state_work(struct work_struct *work) tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; - tb_xdomain_queue_properties_changed(xd); } break; diff --git a/drivers/tty/amiserial.c b/drivers/tty/amiserial.c index 28af0fd981818b..60f6060b893dcf 100644 --- a/drivers/tty/amiserial.c +++ b/drivers/tty/amiserial.c @@ -1138,11 +1138,6 @@ static int rs_ioctl(struct tty_struct *tty, local_irq_save(flags); cnow = info->icount; /* atomic copy */ local_irq_restore(flags); - if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && - cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) { - ret = -EIO; /* no change => error */ - break; - } if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || @@ -1150,6 +1145,10 @@ static int rs_ioctl(struct tty_struct *tty, ret = 0; break; } + if (tty_io_error(tty)) { + ret = -EIO; + break; + } schedule(); /* see if a signal did it */ if (signal_pending(current)) { diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index c13e050de83b12..e55779ff0af2bb 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -454,14 +454,13 @@ static const u8 gsm_fcs8[256] = { static void gsm_dlci_close(struct gsm_dlci *dlci); static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len); -static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk); +static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk, bool wait); static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, u8 ctrl); static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg); static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr); static void gsmld_write_trigger(struct gsm_mux *gsm); static void gsmld_write_task(struct work_struct *work); -static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci); /** * gsm_fcs_add - update FCS @@ -2174,7 +2173,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci) pr_debug("DLCI %d goes open.\n", dlci->addr); /* Send current modem state */ if (dlci->addr) { - gsm_modem_send_initial_msc(dlci); + gsm_modem_update(dlci, 0, false); } else { /* Start keep-alive control */ gsm->ka_num = 0; @@ -4136,9 +4135,10 @@ static void gsm_modem_upd_via_data(struct gsm_dlci *dlci, u8 brk) * gsm_modem_upd_via_msc - send modem bits via control frame * @dlci: channel * @brk: break signal + * @wait: wait for the MSC command */ -static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) +static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk, bool wait) { u8 modembits[3]; struct gsm_control *ctrl; @@ -4155,41 +4155,22 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) modembits[2] = (brk << 4) | 2 | EA; /* Length, Break, EA */ len++; } + if (!wait) + return gsm_control_command(dlci->gsm, CMD_MSC, modembits, len); ctrl = gsm_control_send(dlci->gsm, CMD_MSC, modembits, len); if (ctrl == NULL) return -ENOMEM; return gsm_control_wait(dlci->gsm, ctrl); } -/** - * gsm_modem_send_initial_msc - Send initial modem status message - * - * @dlci: channel - * - * Send an initial MSC message after DLCI open to set the initial - * modem status lines. This is only done for basic mode. - * Does not wait for a response as we cannot block the input queue - * processing. - */ -static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci) -{ - u8 modembits[2]; - - if (dlci->adaption != 1 || dlci->gsm->encoding != GSM_BASIC_OPT) - return 0; - - modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */ - modembits[1] = (gsm_encode_modem(dlci) << 1) | EA; - return gsm_control_command(dlci->gsm, CMD_MSC, (const u8 *)&modembits, 2); -} - /** * gsm_modem_update - send modem status line state * @dlci: channel * @brk: break signal + * @wait: wait for the MSC command */ -static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk) +static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk, bool wait) { if (dlci->gsm->dead) return -EL2HLT; @@ -4199,7 +4180,7 @@ static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk) return 0; } else if (dlci->gsm->encoding == GSM_BASIC_OPT) { /* Send as MSC control message. */ - return gsm_modem_upd_via_msc(dlci, brk); + return gsm_modem_upd_via_msc(dlci, brk, wait); } /* Modem status lines are not supported. */ @@ -4265,7 +4246,7 @@ static void gsm_dtr_rts(struct tty_port *port, bool active) modem_tx &= ~(TIOCM_DTR | TIOCM_RTS); if (modem_tx != dlci->modem_tx) { dlci->modem_tx = modem_tx; - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } } @@ -4471,7 +4452,7 @@ static int gsmtty_tiocmset(struct tty_struct *tty, if (modem_tx != dlci->modem_tx) { dlci->modem_tx = modem_tx; - return gsm_modem_update(dlci, 0); + return gsm_modem_update(dlci, 0, true); } return 0; } @@ -4553,7 +4534,7 @@ static void gsmtty_throttle(struct tty_struct *tty) dlci->modem_tx &= ~TIOCM_RTS; dlci->throttled = true; /* Send an MSC with RTS cleared */ - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } static void gsmtty_unthrottle(struct tty_struct *tty) @@ -4565,7 +4546,7 @@ static void gsmtty_unthrottle(struct tty_struct *tty) dlci->modem_tx |= TIOCM_RTS; dlci->throttled = false; /* Send an MSC with RTS set */ - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } static int gsmtty_break_ctl(struct tty_struct *tty, int state) @@ -4583,7 +4564,7 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) if (encode > 0x0F) encode = 0x0F; /* Best effort */ } - return gsm_modem_update(dlci, encode); + return gsm_modem_update(dlci, encode, true); } static void gsmtty_cleanup(struct tty_struct *tty) diff --git a/drivers/tty/serial/8250/8250_bcm7271.c b/drivers/tty/serial/8250/8250_bcm7271.c index cb7d594d263e95..5f1140f0515044 100644 --- a/drivers/tty/serial/8250/8250_bcm7271.c +++ b/drivers/tty/serial/8250/8250_bcm7271.c @@ -1121,8 +1121,8 @@ static void brcmuart_remove(struct platform_device *pdev) struct brcmuart_priv *priv = platform_get_drvdata(pdev); debugfs_remove_recursive(priv->debugfs_dir); - hrtimer_cancel(&priv->hrt); serial8250_unregister_port(priv->line); + hrtimer_cancel(&priv->hrt); brcmuart_free_bufs(&pdev->dev, priv); if (priv->dma_enabled) brcmuart_arbitration(priv, 0); diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 3c7775df27effd..ceecb39fb82da2 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -711,12 +711,6 @@ static int omap_8250_startup(struct uart_port *port) struct uart_8250_dma *dma = &priv->omap8250_dma; int ret; - if (priv->wakeirq) { - ret = dev_pm_set_dedicated_wake_irq(port->dev, priv->wakeirq); - if (ret) - return ret; - } - #ifdef CONFIG_PM up->capabilities |= UART_CAP_RPM; #endif @@ -787,7 +781,6 @@ static void omap_8250_shutdown(struct uart_port *port) } disable_irq_nosync(port->irq); - dev_pm_clear_wake_irq(port->dev); serial8250_release_dma(up); up->dma = NULL; @@ -1588,6 +1581,12 @@ static int omap8250_probe(struct platform_device *pdev) priv->wakeirq = irq_of_parse_and_map(np, 1); + if (priv->wakeirq) { + ret = dev_pm_set_dedicated_wake_irq(&pdev->dev, priv->wakeirq); + if (ret) + goto err; + } + ret = serial8250_register_8250_port(&up); if (ret < 0) { dev_err(&pdev->dev, "unable to register 8250 port\n"); @@ -1603,6 +1602,7 @@ static int omap8250_probe(struct platform_device *pdev) return 0; err: + dev_pm_clear_wake_irq(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); flush_work(&priv->qos_work); @@ -1625,6 +1625,7 @@ static void omap8250_remove(struct platform_device *pdev) omap_8250_shutdown(&up->port); serial8250_unregister_port(priv->line); priv->line = -ENODEV; + dev_pm_clear_wake_irq(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); flush_work(&priv->qos_work); diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 38fa45e74a37a2..b01baff71f60e2 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -3417,8 +3417,11 @@ static void __serial8250_console_write(struct uart_8250_port *up, * If the console printer did not fully output the previous line, it * must have been handed or taken over. Insert a newline in order to * maintain clean output. + * + * Braille consoles are an exception. The serial port is not used + * for printk(). The driver is supposed to write exactly what it gets. */ - if (!up->console_line_ended) { + if (unlikely(!up->console_line_ended && !nbcon_write_context_is_braille(wctxt))) { if (use_fifo) __serial8250_console_fifo_write(up, wctxt, "\n", 1); else @@ -3597,7 +3600,7 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe) up->console_line_ended = true; up->console_msr_work_allow = true; - init_irq_work(&up->console_msr_work, console_msr_handler); + up->console_msr_work = IRQ_WORK_INIT_LAZY(console_msr_handler); if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index c4824c201e1c35..e67f2ac454b63a 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2656,7 +2656,7 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt uap, REG_CR); } - if (!uap->console_line_ended) + if (unlikely(!uap->console_line_ended && !nbcon_is_braille(co))) uart_console_write(&uap->port, "\n", 1, pl011_console_putchar); uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar); diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 513dbe95f7e9c9..986fefb45c6322 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2136,7 +2136,7 @@ static void imx_uart_console_write_atomic(struct console *co, imx_uart_writel(sport, ucr1, UCR1); imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2); - if (!sport->last_putchar_was_newline) + if (unlikely(!sport->last_putchar_was_newline && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, imx_uart_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, imx_uart_console_putchar); diff --git a/drivers/tty/serial/kgdboc.c b/drivers/tty/serial/kgdboc.c index 09648d64389796..64e3f8cf2f55db 100644 --- a/drivers/tty/serial/kgdboc.c +++ b/drivers/tty/serial/kgdboc.c @@ -188,6 +188,11 @@ static void cleanup_kgdboc(void) kgdboc_unregister_kbd(); kgdb_unregister_io_module(&kgdboc_io_ops); + + if (kgdb_tty_driver) { + tty_driver_kref_put(kgdb_tty_driver); + kgdb_tty_driver = NULL; + } } static int configure_kgdboc(void) @@ -254,6 +259,10 @@ static int configure_kgdboc(void) noconfig: kgdboc_unregister_kbd(); + if (kgdb_tty_driver) { + tty_driver_kref_put(kgdb_tty_driver); + kgdb_tty_driver = NULL; + } configured = 0; return err; diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c index 920fe7ff5083b7..0809655f439596 100644 --- a/drivers/tty/serial/ma35d1_serial.c +++ b/drivers/tty/serial/ma35d1_serial.c @@ -728,7 +728,7 @@ static int ma35d1serial_probe(struct platform_device *pdev) ret = clk_prepare_enable(up->clk); if (ret) - goto err_iounmap; + goto err_put_clk; if (up->port.line != 0) up->port.uartclk = clk_get_rate(up->clk); @@ -755,6 +755,9 @@ static int ma35d1serial_probe(struct platform_device *pdev) err_clk_disable: clk_disable_unprepare(up->clk); +err_put_clk: + clk_put(up->clk); + err_iounmap: iounmap(up->port.membase); return ret; @@ -770,6 +773,7 @@ static void ma35d1serial_remove(struct platform_device *dev) uart_remove_one_port(&ma35d1serial_reg, port); clk_disable_unprepare(up->clk); + clk_put(up->clk); } static int ma35d1serial_suspend(struct platform_device *dev, pm_message_t state) diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c index a5a5cd68f6962e..f858a6669011a5 100644 --- a/drivers/tty/serial/max3100.c +++ b/drivers/tty/serial/max3100.c @@ -535,11 +535,11 @@ static void max3100_shutdown(struct uart_port *port) timer_delete_sync(&s->timer); if (s->workqueue) { + free_irq(port->irq, s); + cancel_work_sync(&s->work); destroy_workqueue(s->workqueue); s->workqueue = NULL; } - if (port->irq) - free_irq(port->irq, s); /* set shutdown mode to save power */ max3100_sr(s, MAX3100_WC | MAX3100_SHDN, &rx); @@ -753,6 +753,15 @@ static void max3100_remove(struct spi_device *spi) if (max3100s[i] == s) { dev_dbg(&spi->dev, "%s: removing port %d\n", __func__, i); uart_remove_one_port(&max3100_uart_driver, &max3100s[i]->port); + + s->force_end_work = 1; + timer_shutdown_sync(&s->timer); + if (s->workqueue) { + free_irq(s->port.irq, s); + cancel_work_sync(&s->work); + destroy_workqueue(s->workqueue); + s->workqueue = NULL; + } kfree(max3100s[i]); max3100s[i] = NULL; break; diff --git a/drivers/tty/serial/mpc52xx_uart.c b/drivers/tty/serial/mpc52xx_uart.c index b566206f42a2b9..472ec39caf279c 100644 --- a/drivers/tty/serial/mpc52xx_uart.c +++ b/drivers/tty/serial/mpc52xx_uart.c @@ -634,6 +634,9 @@ static void __exit mpc512x_psc_fifoc_uninit(void) } } +static struct clk *psc_mclk_clk[MPC52xx_PSC_MAXNUM]; +static struct clk *psc_ipg_clk[MPC52xx_PSC_MAXNUM]; + /* 512x specific interrupt handler. The caller holds the port lock */ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port) { @@ -654,9 +657,6 @@ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port) return IRQ_NONE; } -static struct clk *psc_mclk_clk[MPC52xx_PSC_MAXNUM]; -static struct clk *psc_ipg_clk[MPC52xx_PSC_MAXNUM]; - /* called from within the .request_port() callback (allocation) */ static int mpc512x_psc_alloc_clock(struct uart_port *port) { diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 3633723acef8d7..a851b8f0ef0476 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -171,7 +171,6 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport); static void qcom_geni_serial_resume_tx(struct uart_port *uport); -static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport); static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) { @@ -467,6 +466,102 @@ static int qcom_geni_serial_poll_init(struct uart_port *uport) #endif #ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE +static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) +{ + u32 i; + unsigned char buf[sizeof(u32)]; + struct tty_port *tport; + struct qcom_geni_serial_port *port = to_dev_port(uport); + + tport = &uport->state->port; + for (i = 0; i < bytes; ) { + int c; + int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD); + + ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1); + i += chunk; + if (drop) + continue; + + for (c = 0; c < chunk; c++) { + int sysrq; + + uport->icount.rx++; + if (port->brk && buf[c] == 0) { + port->brk = false; + if (uart_handle_break(uport)) + continue; + } + + sysrq = uart_prepare_sysrq_char(uport, buf[c]); + + if (!sysrq) + tty_insert_flip_char(tport, buf[c], TTY_NORMAL); + } + } + if (!drop) + tty_flip_buffer_push(tport); +} +#else +static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) +{ + +} +#endif + +static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop) +{ + u32 status; + u32 word_cnt; + u32 last_word_byte_cnt; + u32 last_word_partial; + u32 total_bytes; + + status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS); + word_cnt = status & RX_FIFO_WC_MSK; + last_word_partial = status & RX_LAST; + last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >> + RX_LAST_BYTE_VALID_SHFT; + + if (!word_cnt) + return; + total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1); + if (last_word_partial && last_word_byte_cnt) + total_bytes += last_word_byte_cnt; + else + total_bytes += BYTES_PER_FIFO_WORD; + handle_rx_console(uport, total_bytes, drop); +} + +#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE +/* Caller holds the UART port lock. */ +static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + struct tty_port *tport = &uport->state->port; + u32 s_irq_status; + bool drop_rx = false; + + s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); + writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); + + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { + uport->icount.overrun++; + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + } + + if (s_irq_status & (S_GP_IRQ_0_EN | S_GP_IRQ_1_EN)) { + if (s_irq_status & S_GP_IRQ_0_EN) + uport->icount.parity++; + drop_rx = true; + } else if (s_irq_status & (S_GP_IRQ_2_EN | S_GP_IRQ_3_EN)) { + uport->icount.brk++; + port->brk = true; + } + + qcom_geni_serial_handle_rx_fifo(uport, drop_rx); +} + static void qcom_geni_serial_drain_fifo(struct uart_port *uport) { struct qcom_geni_serial_port *port = to_dev_port(uport); @@ -676,47 +771,6 @@ static void qcom_geni_serial_console_device_unlock(struct console *co, #endif } -static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) -{ - u32 i; - unsigned char buf[sizeof(u32)]; - struct tty_port *tport; - struct qcom_geni_serial_port *port = to_dev_port(uport); - - tport = &uport->state->port; - for (i = 0; i < bytes; ) { - int c; - int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD); - - ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1); - i += chunk; - if (drop) - continue; - - for (c = 0; c < chunk; c++) { - int sysrq; - - uport->icount.rx++; - if (port->brk && buf[c] == 0) { - port->brk = false; - if (uart_handle_break(uport)) - continue; - } - - sysrq = uart_prepare_sysrq_char(uport, buf[c]); - - if (!sysrq) - tty_insert_flip_char(tport, buf[c], TTY_NORMAL); - } - } - if (!drop) - tty_flip_buffer_push(tport); -} -#else -static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) -{ - -} #endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */ static void handle_rx_uart(struct uart_port *uport, u32 bytes) @@ -890,58 +944,6 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport) port->tx_queued = 0; } -static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop) -{ - u32 status; - u32 word_cnt; - u32 last_word_byte_cnt; - u32 last_word_partial; - u32 total_bytes; - - status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS); - word_cnt = status & RX_FIFO_WC_MSK; - last_word_partial = status & RX_LAST; - last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >> - RX_LAST_BYTE_VALID_SHFT; - - if (!word_cnt) - return; - total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1); - if (last_word_partial && last_word_byte_cnt) - total_bytes += last_word_byte_cnt; - else - total_bytes += BYTES_PER_FIFO_WORD; - handle_rx_console(uport, total_bytes, drop); -} - -/* Caller holds the UART port lock. */ -static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - struct tty_port *tport = &uport->state->port; - u32 s_irq_status; - bool drop_rx = false; - - s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); - writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); - - if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { - uport->icount.overrun++; - tty_insert_flip_char(tport, 0, TTY_OVERRUN); - } - - if (s_irq_status & (S_GP_IRQ_0_EN | S_GP_IRQ_1_EN)) { - if (s_irq_status & S_GP_IRQ_0_EN) - uport->icount.parity++; - drop_rx = true; - } else if (s_irq_status & (S_GP_IRQ_2_EN | S_GP_IRQ_3_EN)) { - uport->icount.brk++; - port->brk = true; - } - - qcom_geni_serial_handle_rx_fifo(uport, drop_rx); -} - static void qcom_geni_serial_stop_rx_fifo(struct uart_port *uport) { u32 irq_en; @@ -1482,9 +1484,8 @@ static int geni_serial_set_rate(struct geni_se *se, unsigned long baud) * Bump up BW vote on CPU and CORE path as driver supports FIFO mode * only. */ - avg_bw_core = (baud > 115200) ? Bps_to_icc(CORE_2X_50_MHZ) - : GENI_DEFAULT_BW; - port->se.icc_paths[GENI_TO_CORE].avg_bw = avg_bw_core; + avg_bw_core = baud > 115200 ? CORE_2X_50_MHZ : CORE_2X_19_2_MHZ; + port->se.icc_paths[GENI_TO_CORE].avg_bw = Bps_to_icc(avg_bw_core); port->se.icc_paths[CPU_TO_GENI].avg_bw = Bps_to_icc(baud); geni_icc_set_bw(&port->se); @@ -1625,16 +1626,37 @@ static int qcom_geni_console_setup(struct console *co, char *options) if (unlikely(!uport->membase)) return -ENXIO; + ret = pm_runtime_resume_and_get(uport->dev); + if (ret < 0) + return ret; + if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); - if (ret) + if (ret) { + pm_runtime_put_sync(uport->dev); return ret; + } } if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); - return uart_set_options(uport, co, baud, parity, bits, flow); + ret = uart_set_options(uport, co, baud, parity, bits, flow); + if (ret) + pm_runtime_put_sync(uport->dev); + + return ret; +} + +static int qcom_geni_console_exit(struct console *co) +{ + struct qcom_geni_serial_port *port; + + port = get_port_from_line(co->index, true, NULL); + if (IS_ERR(port)) + return PTR_ERR(port); + + return pm_runtime_put_sync(port->uport.dev); } static void qcom_geni_serial_earlycon_write(struct console *con, @@ -1751,6 +1773,7 @@ static struct console cons_ops = { .device_unlock = qcom_geni_serial_console_device_unlock, .device = uart_console_device, .setup = qcom_geni_console_setup, + .exit = qcom_geni_console_exit, .flags = CON_PRINTBUFFER | CON_NBCON, .index = -1, .data = &qcom_geni_console_driver, @@ -2099,9 +2122,11 @@ static int qcom_geni_serial_resume(struct device *dev) struct uart_port *uport = &port->uport; struct qcom_geni_private_data *private_data = uport->private_data; - ret = pm_runtime_force_resume(dev); - if (ret) - return ret; + if (console_suspend_enabled || !uart_console(uport)) { + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + } ret = uart_resume_port(private_data->drv, uport); if (uart_console(uport)) { diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 7107a0fb1e7bbb..296079c16fdb97 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -216,6 +216,8 @@ #define SC16IS7XX_TLR_TX_TRIGGER(words) ((((words) / 4) & 0x0f) << 0) #define SC16IS7XX_TLR_RX_TRIGGER(words) ((((words) / 4) & 0x0f) << 4) +#define SC16IS7XX_TX_TRIGGER_LEVEL 32 + /* IOControl register bits (Only 75x/76x) */ #define SC16IS7XX_IOCONTROL_LATCH_BIT BIT(0) /* Enable input latching */ #define SC16IS7XX_IOCONTROL_MODEM_A_BIT BIT(1) /* Enable GPIO[7:4] as modem A pins */ @@ -647,12 +649,26 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, tty_flip_buffer_push(&port->state->port); } +static unsigned int sc16is7xx_txlvl(struct uart_port *port) +{ + unsigned int txlvl; + + txlvl = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); + if (txlvl > SC16IS7XX_FIFO_SIZE) { + dev_err_ratelimited(port->dev, + "chip reports %u free bytes in TX FIFO, but it only has %u\n", + txlvl, SC16IS7XX_FIFO_SIZE); + return 0; + } + + return txlvl; +} + static void sc16is7xx_handle_tx(struct uart_port *port) { struct tty_port *tport = &port->state->port; unsigned long flags; unsigned int txlen; - unsigned char *tail; if (unlikely(port->x_char)) { sc16is7xx_port_write(port, SC16IS7XX_THR_REG, port->x_char); @@ -669,17 +685,28 @@ static void sc16is7xx_handle_tx(struct uart_port *port) } /* Limit to space available in TX FIFO */ - txlen = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); - if (txlen > SC16IS7XX_FIFO_SIZE) { - dev_err_ratelimited(port->dev, - "chip reports %d free bytes in TX fifo, but it only has %d", - txlen, SC16IS7XX_FIFO_SIZE); - txlen = 0; - } + txlen = sc16is7xx_txlvl(port); + + /* Handle circular buffer wrap-around by sending multiple segments */ + while (txlen > 0 && !kfifo_is_empty(&tport->xmit_fifo)) { + unsigned char *tail; + unsigned int to_send; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + if (!to_send) + break; - txlen = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); - sc16is7xx_fifo_write(port, tail, txlen); - uart_xmit_advance(port, txlen); + sc16is7xx_fifo_write(port, tail, to_send); + uart_xmit_advance(port, to_send); + + if (kfifo_is_empty(&tport->xmit_fifo)) + break; + + /* Refill below the trigger to enable the next THRI crossing. */ + txlen = sc16is7xx_txlvl(port); + if (txlen < SC16IS7XX_TX_TRIGGER_LEVEL) + break; + } uart_port_lock_irqsave(port, &flags); if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) @@ -1130,6 +1157,10 @@ static int sc16is7xx_startup(struct uart_port *port) SC16IS7XX_TCR_RX_RESUME(24) | SC16IS7XX_TCR_RX_HALT(48)); + /* Sync hardware and software TX trigger levels */ + sc16is7xx_port_write(port, SC16IS7XX_TLR_REG, + SC16IS7XX_TLR_TX_TRIGGER(SC16IS7XX_TX_TRIGGER_LEVEL)); + /* Disable TCR/TLR access */ sc16is7xx_port_update(port, SC16IS7XX_MCR_REG, SC16IS7XX_MCR_TCRTLR_BIT, 0); diff --git a/drivers/tty/serial/serial-tegra.c b/drivers/tty/serial/serial-tegra.c index 8004fc00fb9cd5..b0fc4ae3686f8d 100644 --- a/drivers/tty/serial/serial-tegra.c +++ b/drivers/tty/serial/serial-tegra.c @@ -306,11 +306,19 @@ static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits) { unsigned long fcr = tup->fcr_shadow; unsigned int lsr, tmout = 10000; + bool clear_tx = !!(fcr_bits & UART_FCR_CLEAR_XMIT); if (tup->rts_active) set_rts(tup, false); - if (tup->cdata->allow_txfifo_reset_fifo_mode) { + /* + * Leaving FIFO mode below is a workaround for a Tegra30 + * restriction on clearing the Tx FIFO while FIFO mode is + * enabled. It empties both FIFOs, so applying it to an + * Rx-only reset would destroy an in-flight transmission. + * Only take that path when the caller asked for CLEAR_XMIT. + */ + if (tup->cdata->allow_txfifo_reset_fifo_mode || !clear_tx) { fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); tegra_uart_write(tup, fcr, UART_FCR); } else { @@ -335,9 +343,15 @@ static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits) */ tegra_uart_wait_cycle_time(tup, 32); + /* + * Only wait for the transmitter to drain when the Tx FIFO was + * part of the reset. For an Rx-only reset it is left intact, + * and waiting for TEMT here would spin for a full frame time. + */ do { lsr = tegra_uart_read(tup, UART_LSR); - if ((lsr & UART_LSR_TEMT) && !(lsr & UART_LSR_DR)) + if ((!clear_tx || (lsr & UART_LSR_TEMT)) && + !(lsr & UART_LSR_DR)) break; udelay(1); } while (--tmout); diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 95774b0f1484b2..6332ed545c8995 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -896,7 +896,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, upf_t old_flags, new_flags; int retval; - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; new_port = new_info->port; @@ -1119,7 +1119,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state) guard(mutex)(&port->mutex); uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; if (uport->type != PORT_UNKNOWN && uport->ops->break_ctl) @@ -1144,7 +1144,7 @@ static int uart_do_autoconfig(struct tty_struct *tty, struct uart_state *state) */ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->mutex) { uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; if (tty_port_users(port) != 1) @@ -1199,7 +1199,7 @@ static void uart_enable_ms(struct uart_port *uport) * FIXME: This wants extracting into a common all driver implementation * of TIOCMWAIT using tty_port. */ -static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) +static int uart_wait_modem_status(struct tty_struct *tty, struct uart_state *state, unsigned long arg) { struct uart_port *uport; struct tty_port *port = &state->port; @@ -1213,18 +1213,29 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) uport = uart_port_ref(state); if (!uport) return -EIO; - scoped_guard(uart_port_lock_irq, uport) { - memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); - uart_enable_ms(uport); + + mutex_lock(&port->mutex); + if (tty_io_error(tty)) { + mutex_unlock(&port->mutex); + ret = -EIO; + goto out_deref; } + uart_port_lock_irq(uport); + memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); + uart_enable_ms(uport); + uart_port_unlock_irq(uport); + + mutex_unlock(&port->mutex); + add_wait_queue(&port->delta_msr_wait, &wait); for (;;) { - scoped_guard(uart_port_lock_irq, uport) - memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); - set_current_state(TASK_INTERRUPTIBLE); + uart_port_lock_irq(uport); + memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); + uart_port_unlock_irq(uport); + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || @@ -1233,6 +1244,11 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) break; } + if (tty_io_error(tty)) { + ret = -EIO; + break; + } + schedule(); /* see if a signal did it */ @@ -1245,6 +1261,7 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) } __set_current_state(TASK_RUNNING); remove_wait_queue(&port->delta_msr_wait, &wait); +out_deref: uart_port_deref(uport); return ret; @@ -1567,7 +1584,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) /* This should only be used when the hardware is present. */ if (cmd == TIOCMIWAIT) - return uart_wait_modem_status(state, arg); + return uart_wait_modem_status(tty, state, arg); /* rs485_config requires more locking than others */ if (cmd == TIOCSRS485) @@ -1623,14 +1640,13 @@ static void uart_set_ldisc(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; struct uart_port *uport; - struct tty_port *port = &state->port; - - if (!tty_port_initialized(port)) - return; guard(mutex)(&state->port.mutex); uport = uart_port_check(state); - if (uport && uport->ops->set_ldisc) + if (!uport || tty_io_error(tty)) + return; + + if (uport->ops->set_ldisc) uport->ops->set_ldisc(uport, &tty->termios); } @@ -1646,7 +1662,7 @@ static void uart_set_termios(struct tty_struct *tty, guard(mutex)(&state->port.mutex); uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return; /* @@ -1798,7 +1814,14 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout) * 'timeout' / 'expire' give us the maximum amount of time * we wait. */ - while (!port->ops->tx_empty(port)) { + for (;;) { + mutex_lock(&state->port.mutex); + if (tty_io_error(tty) || port->ops->tx_empty(port)) { + mutex_unlock(&state->port.mutex); + break; + } + mutex_unlock(&state->port.mutex); + msleep_interruptible(jiffies_to_msecs(char_time)); if (signal_pending(current)) break; @@ -3358,6 +3381,7 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) err_unregister_port_dev: serial_base_port_device_remove(port->port_dev); + port->port_dev = NULL; err_unregister_ctrl_dev: serial_base_ctrl_device_remove(new_ctrl_dev); @@ -3372,12 +3396,24 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port) { struct device *phys_dev = port->dev; - struct serial_port_device *port_dev = port->port_dev; - struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); + struct serial_port_device *port_dev; + struct serial_ctrl_device *ctrl_dev; int ctrl_id = port->ctrl_id; guard(mutex)(&port_mutex); + /* + * A NULL port device means there is no registered port device to + * remove: serial_core_remove_one_port() clears port_dev on + * teardown, and it is never set if registration failed before + * serial_core_port_device_add(). + */ + port_dev = port->port_dev; + if (!port_dev) + return; + + ctrl_dev = serial_core_get_ctrl_dev(port_dev); + port->flags |= UPF_DEAD; serial_core_remove_one_port(drv, port); diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index 110d676131922a..f615701fceb7d9 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -821,7 +821,7 @@ static void sifive_serial_console_write_atomic(struct console *co, ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS); __ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp); - if (!ssp->console_line_ended) + if (unlikely(!ssp->console_line_ended && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, sifive_serial_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, sifive_serial_console_putchar); diff --git a/drivers/tty/serial/vt8500_serial.c b/drivers/tty/serial/vt8500_serial.c index 78a1c1eea11bbf..fc6fdc751b1675 100644 --- a/drivers/tty/serial/vt8500_serial.c +++ b/drivers/tty/serial/vt8500_serial.c @@ -656,6 +656,7 @@ static int vt8500_serial_probe(struct platform_device *pdev) ret = clk_prepare_enable(vt8500_port->clk); if (ret) { dev_err(&pdev->dev, "failed to enable clock\n"); + clk_put(vt8500_port->clk); return ret; } diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 48569035da56e5..1a6c8a1bc764a3 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -620,6 +620,8 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session) tty_ldisc_hangup(tty, cons_filp != NULL); + wake_up_interruptible(&tty->break_wait); + spin_lock_irq(&tty->ctrl.lock); clear_bit(TTY_THROTTLED, &tty->flags); clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); @@ -2431,6 +2433,7 @@ static int tiocgetd(struct tty_struct *tty, int __user *p) * send_break - performed time break * @tty: device to break on * @duration: timeout in mS + * @file: file object * * Perform a timed break on hardware that lacks its own driver level timed * break functionality. @@ -2438,8 +2441,9 @@ static int tiocgetd(struct tty_struct *tty, int __user *p) * Locking: * @tty->atomic_write_lock serializes */ -static int send_break(struct tty_struct *tty, unsigned int duration) +static int send_break(struct file *file, struct tty_struct *tty, unsigned int duration) { + long timeout; int retval; if (tty->ops->break_ctl == NULL) @@ -2453,13 +2457,26 @@ static int send_break(struct tty_struct *tty, unsigned int duration) return -EINTR; retval = tty->ops->break_ctl(tty, -1); - if (!retval) { - msleep_interruptible(duration); - retval = tty->ops->break_ctl(tty, 0); - } else if (retval == -EOPNOTSUPP) { - /* some drivers can tell only dynamically */ - retval = 0; + if (retval) { + if (retval == -EOPNOTSUPP) { + /* some drivers can tell only dynamically */ + retval = 0; + } + goto out_unlock; + } + + timeout = msecs_to_jiffies(duration); + timeout = wait_event_interruptible_timeout(tty->break_wait, + tty_hung_up_p(file), + timeout); + /* return early on hangup only */ + if (timeout > 0) { + retval = -EIO; + goto out_unlock; } + + retval = tty->ops->break_ctl(tty, 0); +out_unlock: tty_write_unlock(tty); if (signal_pending(current)) @@ -2727,10 +2744,10 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) * This is used by the tcdrain() termios function. */ if (!arg) - return send_break(tty, 250); + return send_break(file, tty, 250); return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ - return send_break(tty, arg ? arg*100 : 250); + return send_break(file, tty, arg ? arg * 100 : 250); case TIOCMGET: return tty_tiocmget(tty, p); @@ -3090,6 +3107,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); + init_waitqueue_head(&tty->break_wait); INIT_WORK(&tty->hangup_work, do_tty_hangup); mutex_init(&tty->atomic_write_lock); spin_lock_init(&tty->ctrl.lock); @@ -3238,14 +3256,16 @@ struct device *tty_register_device_attr(struct tty_driver *driver, goto err_put; if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { - /* - * Free any saved termios data so that the termios state is - * reset when reusing a minor number. - */ - tp = driver->termios[index]; - if (tp) { - driver->termios[index] = NULL; - kfree(tp); + if (driver->flags & TTY_DRIVER_RESET_SAVED_TERMIOS) { + /* + * Free any saved termios data so that the termios + * state is reset when reusing a minor number. + */ + tp = driver->termios[index]; + if (tp) { + driver->termios[index] = NULL; + kfree(tp); + } } retval = tty_cdev_add(driver, devt, index, 1); diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index 54359310e293cd..b22ff9eb77784c 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -340,19 +340,9 @@ void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty) } EXPORT_SYMBOL(tty_port_tty_set); -/** - * tty_port_shutdown - internal helper to shutdown the device - * @port: tty port to be shut down - * @tty: the associated tty - * - * It is used by tty_port_hangup() and tty_port_close(). Its task is to - * shutdown the device if it was initialized (note consoles remain - * functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes - * @port->ops->shutdown(). - */ -static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) +static void tty_port_shutdown_locked(struct tty_port *port, struct tty_struct *tty) { - guard(mutex)(&port->mutex); + lockdep_assert_held(&port->mutex); if (port->console) return; @@ -372,6 +362,23 @@ static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) port->ops->shutdown(port); } +/** + * tty_port_shutdown - internal helper to shutdown the device + * @port: tty port to be shut down + * @tty: the associated tty + * + * It is used by tty_port_hangup() and tty_port_close(). Its task is to + * shutdown the device if it was initialized (note consoles remain + * functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes + * @port->ops->shutdown(). + */ +static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) +{ + guard(mutex)(&port->mutex); + + tty_port_shutdown_locked(port, tty); +} + /** * tty_port_hangup - hangup helper * @port: tty port @@ -385,36 +392,58 @@ void tty_port_hangup(struct tty_port *port) { struct tty_struct *tty; - scoped_guard(spinlock_irqsave, &port->lock) { - port->count = 0; - tty = port->tty; - if (tty) - set_bit(TTY_IO_ERROR, &tty->flags); - port->tty = NULL; - } + scoped_guard(mutex, &port->mutex) { + scoped_guard(spinlock_irqsave, &port->lock) { + port->count = 0; + tty = port->tty; + if (tty) + set_bit(TTY_IO_ERROR, &tty->flags); + port->tty = NULL; + } - tty_port_set_active(port, false); - tty_port_shutdown(port, tty); + tty_port_set_active(port, false); + tty_port_shutdown_locked(port, tty); + } tty_kref_put(tty); wake_up_interruptible(&port->open_wait); wake_up_interruptible(&port->delta_msr_wait); } EXPORT_SYMBOL(tty_port_hangup); -void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async) +/** + * tty_port_tty_hangup - helper to hang up a tty asynchronously + * @port: tty port + * @check_clocal: hang only ttys with %CLOCAL unset? + */ +void tty_port_tty_hangup(struct tty_port *port, bool check_clocal) { scoped_guard(tty_port_tty, port) { struct tty_struct *tty = scoped_tty(); - if (!check_clocal || !C_CLOCAL(tty)) { - if (async) - tty_hangup(tty); - else - tty_vhangup(tty); - } + if (!check_clocal || !C_CLOCAL(tty)) + tty_hangup(tty); + } +} +EXPORT_SYMBOL_GPL(tty_port_tty_hangup); + +/** + * tty_port_tty_vhangup - helper to hang up a tty synchronously + * @port: tty port + */ +void tty_port_tty_vhangup(struct tty_port *port) +{ + struct tty_struct *tty; + + mutex_lock(&port->mutex); + tty = tty_port_tty_get(port); + mutex_unlock(&port->mutex); + + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); } } -EXPORT_SYMBOL_GPL(__tty_port_tty_hangup); +EXPORT_SYMBOL_GPL(tty_port_tty_vhangup); /** * tty_port_tty_wakeup - helper to wake up a tty @@ -767,8 +796,10 @@ int tty_port_open(struct tty_port *port, struct tty_struct *tty, clear_bit(TTY_IO_ERROR, &tty->flags); if (port->ops->activate) { int retval = port->ops->activate(port, tty); - if (retval) + if (retval) { + set_bit(TTY_IO_ERROR, &tty->flags); return retval; + } } tty_port_set_initialized(port, true); } diff --git a/drivers/tty/vcc.c b/drivers/tty/vcc.c index 27a55465bf5ec5..9adb533f7034b9 100644 --- a/drivers/tty/vcc.c +++ b/drivers/tty/vcc.c @@ -492,7 +492,7 @@ static ssize_t domain_show(struct device *dev, static int vcc_send_ctl(struct vcc_port *port, int ctl) { - struct vio_vcc pkt; + struct vio_vcc pkt = {}; int rv; pkt.tag.type = VIO_TYPE_CTRL; @@ -986,7 +986,8 @@ static void vcc_cleanup(struct tty_struct *tty) port = vcc_get(tty->index, true); if (port) { - port->tty = NULL; + scoped_guard(spinlock_irqsave, &port->lock) + port->tty = NULL; if (port->removed) { vcc_table_remove(tty->index); diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 13f4e48b414272..f1a3bc5b5fadc1 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -434,8 +434,8 @@ int paste_selection(struct tty_struct *tty) bps = NULL; } - count = vc_sel.buf_len - pasted; - if (count) { + if (vc_sel.buf_len > pasted) { + count = vc_sel.buf_len - pasted; pasted += tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted, NULL, count); if (vc_sel.buf_len > pasted) diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c index bf1502fd5bd4b0..79453abcf4d9a6 100644 --- a/drivers/tty/vt/vc_screen.c +++ b/drivers/tty/vt/vc_screen.c @@ -631,6 +631,18 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) ret = copy_from_user(con_buf, buf, this_round); console_lock(); + /* The vc might have been freed or vcs_size might have changed + * while we slept to grab the user buffer; recheck here, before + * if (ret), so every break path below passes a fresh vc to the + * post-loop vcs_scr_updated(). Return data written so far. + */ + vc = vcs_vc(inode, &viewed); + if (!vc) { + if (written) + break; + return -ENXIO; + } + if (ret) { this_round -= ret; if (!this_round) { @@ -642,17 +654,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) return -EFAULT; } } - - /* The vc might have been freed or vcs_size might have changed - * while we slept to grab the user buffer, so recheck. - * Return data written up to now on failure. - */ - vc = vcs_vc(inode, &viewed); - if (!vc) { - if (written) - break; - return -ENXIO; - } size = vcs_size(vc, attr, false); if (size < 0) { if (written) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 57edf37495a81f..49a140b49eff0a 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -1134,6 +1134,8 @@ int vc_allocate(unsigned int currcons) /* return 0 on success */ return 0; err_free: visual_deinit(vc); + if (*vc->uni_pagedict_loc) + con_free_unimap(vc); kfree(vc); vc_cons[currcons].d = NULL; return err; @@ -2860,7 +2862,8 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c) csi_J(vc, CSI_J_VISIBLE); vc->vc_video_erase_char = (vc->vc_video_erase_char & 0xff00) | ' '; - do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2); + if (con_should_update(vc)) + do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2); } return; case ESsetG0: /* ESC ( */ diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c index 8106d55f4041e6..df293b66350dac 100644 --- a/drivers/ufs/core/ufs-mcq.c +++ b/drivers/ufs/core/ufs-mcq.c @@ -312,20 +312,24 @@ static int ufshcd_mcq_get_tag(struct ufs_hba *hba, struct cq_entry *cqe) UFSHCD_NUM_RESERVED; } +static void ufshcd_mcq_compl_cqe(struct ufs_hba *hba, struct cq_entry *cqe) +{ + int tag = ufshcd_mcq_get_tag(hba, cqe); + + ufshcd_compl_one_cqe(hba, tag, cqe); + /* After processing the CQE, mark it as an empty (invalid) entry. */ + cqe->command_desc_base_addr = 0; +} + static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, struct ufs_hw_queue *hwq) { struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); - if (cqe->command_desc_base_addr) { - int tag = ufshcd_mcq_get_tag(hba, cqe); - - ufshcd_compl_one_cqe(hba, tag, cqe); - /* After processed the cqe, mark it empty (invalid) entry */ - cqe->command_desc_base_addr = 0; - } else { + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); + else dev_err(hba->dev, "Abnormal CQ entry!\n"); - } } /* @@ -333,7 +337,7 @@ static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, * controller disabled (HCE = 0). Reading host controller registers, e.g. the * CQ tail pointer (CQTPy), may not be safe with the host controller disabled. * Hence, iterate over all completion queue entries. This won't result in - * double completions because ufshcd_mcq_process_cqe() clears a CQE after it + * double completions because ufshcd_mcq_compl_cqe() clears a CQE after it * has been processed. */ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, @@ -344,13 +348,13 @@ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, spin_lock_irqsave(&hwq->cq_lock, flags); while (entries > 0) { - ufshcd_mcq_process_cqe(hba, hwq); + struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); + + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); ufshcd_mcq_inc_cq_head_slot(hwq); entries--; } - - ufshcd_mcq_update_cq_tail_slot(hwq); - hwq->cq_head_slot = hwq->cq_tail_slot; spin_unlock_irqrestore(&hwq->cq_lock, flags); } diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 54f4e7d7de02e3..ca8329052cbc5c 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6044,8 +6044,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) if (blk_mq_is_reserved_rq(rq) || !hwq) return true; - ufshcd_mcq_compl_all_cqes_lock(hba, hwq); - /* * For those cmds of which the cqes are not present in the cq, complete * them explicitly. @@ -6061,19 +6059,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) return true; } -static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) -{ - struct scsi_device *sdev = rq->q->queuedata; - struct Scsi_Host *shost = sdev->host; - struct ufs_hba *hba = shost_priv(shost); - struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq); - - if (!blk_mq_is_reserved_rq(rq) && hwq) - ufshcd_mcq_poll_cqe_lock(hba, hwq); - - return true; -} - /** * ufshcd_mcq_compl_pending_transfer - MCQ mode function. It is * invoked from the error handler context or ufshcd_host_reset_and_restore() @@ -6088,10 +6073,18 @@ static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) static void ufshcd_mcq_compl_pending_transfer(struct ufs_hba *hba, bool force_compl) { - blk_mq_tagset_busy_iter(&hba->host->tag_set, - force_compl ? ufshcd_mcq_force_compl_one : - ufshcd_mcq_compl_one, - NULL); + int i; + + for (i = 0; i < hba->nr_hw_queues; i++) { + if (force_compl) + ufshcd_mcq_compl_all_cqes_lock(hba, &hba->uhq[i]); + else + ufshcd_mcq_poll_cqe_lock(hba, &hba->uhq[i]); + } + + if (force_compl) + blk_mq_tagset_busy_iter(&hba->host->tag_set, + ufshcd_mcq_force_compl_one, NULL); } /** @@ -9556,6 +9549,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) ret = ufshcd_add_lus(hba); out: + ufshcd_release(hba); pm_runtime_put_sync(hba->dev); if (ret) @@ -11278,6 +11272,9 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_init_clk_gating(hba); + /* Released by ufshcd_async_scan(), or by out_release on failure. */ + ufshcd_hold(hba); + ufshcd_init_clk_scaling(hba); /* @@ -11298,7 +11295,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba); if (err) { dev_err(hba->dev, "request irq failed\n"); - goto out_disable; + goto out_release; } else { hba->is_irq_enabled = true; } @@ -11314,7 +11311,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) dev_err(hba->dev, "Host controller enable failed\n"); ufshcd_print_evt_hist(hba); ufshcd_print_host_state(hba); - goto out_disable; + goto out_release; } INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work, ufshcd_rpm_dev_flush_recheck_work); @@ -11328,7 +11325,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_add_scsi_host(hba); if (err) - goto out_disable; + goto out_release; /* Hold auto suspend until async scan completes */ pm_runtime_get_sync(dev); @@ -11348,7 +11345,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_link_startup(hba); if (err) - goto out_disable; + goto out_release; if (hba->mcq_enabled) ufshcd_config_mcq(hba); @@ -11365,23 +11362,23 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) /* Verify device initialization by sending NOP OUT UPIU */ err = ufshcd_verify_dev_init(hba); if (err) - goto out_disable; + goto out_release; /* Initiate UFS initialization, and waiting until completion */ err = ufshcd_complete_dev_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_device_params_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_post_device_init(hba); initialized: ufshcd_process_probe_result(hba, probe_start, err); if (err) - goto out_disable; + goto out_release; ufs_sysfs_add_nodes(hba->dev); hba->dme_qos_sysfs_handle = sysfs_get_dirent(hba->dev->kobj.sd, @@ -11392,6 +11389,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_pm_qos_init(hba); return 0; +out_release: + ufshcd_release(hba); out_disable: hba->is_irq_enabled = false; ufshcd_hba_exit(hba); diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c index 814c1b7343b9e3..12a6d4138e4453 100644 --- a/drivers/ufs/host/ufs-mediatek.c +++ b/drivers/ufs/host/ufs-mediatek.c @@ -509,6 +509,7 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) struct ufs_mtk_host *host = ufshcd_get_variant(hba); struct phy *mphy = host->mphy; struct arm_smccc_res res; + int err; int ret = 0; if (!mphy || !(on ^ host->mphy_powered_on)) @@ -523,7 +524,18 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) usleep_range(200, 210); ufs_mtk_va09_pwr_ctrl(res, 1); } - phy_power_on(mphy); + ret = phy_power_on(mphy); + if (ret) { + if (ufs_mtk_is_va09_supported(hba)) { + ufs_mtk_va09_pwr_ctrl(res, 0); + err = regulator_disable(host->reg_va09); + if (err) + dev_err(hba->dev, + "failed to unwind va09: %d\n", err); + } + dev_err(hba->dev, "failed to enable mphy: %d\n", ret); + return ret; + } } else { phy_power_off(mphy); if (ufs_mtk_is_va09_supported(hba)) { @@ -1295,7 +1307,9 @@ static int ufs_mtk_init(struct ufs_hba *hba) * * Enable phy clocks specifically here. */ - ufs_mtk_mphy_power_on(hba, true); + err = ufs_mtk_mphy_power_on(hba, true); + if (err) + goto out_variant_clear; if (ufs_mtk_is_rtff_mtcmos(hba)) { /* First Restore here, to avoid backup unexpected value */ diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 42311c1bfada1d..0d272e9c556ca4 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) usb_del_gadget(&priv_dev->gadget); devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + /* The works can still be queued until the IRQ is freed. */ + cancel_work_sync(&priv_dev->pending_status_wq); + cancel_work_sync(&priv_dev->aligned_buf_wq); + cdns3_free_all_eps(priv_dev); while (!list_empty(&priv_dev->aligned_buf_list)) { diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c index eb5760f75b9d28..fd06a3d8e638fb 100644 --- a/drivers/usb/cdns3/cdns3-pci-wrap.c +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, if (pci_is_enabled(func)) { wrap = pci_get_drvdata(func); + if (!wrap) { + dev_err(&pdev->dev, + "second function not initialized, retrying\n"); + return -EPROBE_DEFER; + } } else { wrap = kzalloc_obj(*wrap); if (!wrap) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 372788f0f97014..26225b034d3b21 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); err = pm_runtime_resume_and_get(&pdev->dev); - if (err) + if (err) { + pm_runtime_disable(&pdev->dev); return err; + } if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) usb->needs_double_reset = true; diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7bc5329fa3ed39..0d3f815b2f1e87 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) spin_lock_irqsave(&acm->read_lock, flags); acm->ctrlin = newctrl; - acm->oldcount = acm->iocount; if (difference & USB_CDC_SERIAL_STATE_DSR) acm->iocount.dsr++; @@ -788,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) usb_autopm_put_interface_async(acm->control); } + if (acm->disconnected) + return; + acm_unpoison_urbs(acm); if (acm->quirks & ALWAYS_POLL_CTRL) { @@ -1027,11 +1029,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) DECLARE_WAITQUEUE(wait, current); struct async_icount old, new; - do { + spin_lock_irq(&acm->read_lock); + old = acm->iocount; + spin_unlock_irq(&acm->read_lock); + + add_wait_queue(&acm->wioctl, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&acm->read_lock); - old = acm->oldcount; new = acm->iocount; - acm->oldcount = new; spin_unlock_irq(&acm->read_lock); if ((arg & TIOCM_DSR) && @@ -1044,22 +1051,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) old.rng != new.rng) break; - add_wait_queue(&acm->wioctl, &wait); - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - remove_wait_queue(&acm->wioctl, &wait); if (acm->disconnected) { - if (arg & TIOCM_CD) - break; - else - rv = -ENODEV; - } else { - if (signal_pending(current)) - rv = -ERESTARTSYS; + rv = -ENODEV; + break; } - } while (!rv); - + schedule(); + + if (signal_pending(current)) { + rv = -ERESTARTSYS; + break; + } + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&acm->wioctl, &wait); return rv; } @@ -2140,7 +2145,7 @@ static int __init acm_init(void) { int retval; acm_tty_driver = tty_alloc_driver(ACM_TTY_MINORS, TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(acm_tty_driver)) return PTR_ERR(acm_tty_driver); acm_tty_driver->driver_name = "acm", diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 01f448a783c031..d245e60eb9f50f 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -90,7 +90,6 @@ struct acm { unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ unsigned int ctrlout; /* output control lines (DTR, RTS) */ struct async_icount iocount; /* counters for control line changes */ - struct async_icount oldcount; /* for comparison of counter */ wait_queue_head_t wioctl; /* for ioctl */ unsigned int writesize; /* max packet size for the output bulk endpoint */ unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3345b3298daf35..b446905b48be78 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -73,8 +73,11 @@ /* * Give SS hubs 200ms time after wake to train downstream links before * assuming no port activity and allowing hub to runtime suspend back. + * Root hubs have no upstream hub whose wake propagation needs to be + * accounted for, so they need less time, use 120ms for them. */ #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ +#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ /* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, except that ->state can @@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) queue_delayed_work(system_power_efficient_wq, &hub->post_resume_work, - msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); + msecs_to_jiffies(hdev->parent ? + USB_SS_PORT_U0_WAKE_TIME : + USB_SS_RH_PORT_U0_WAKE_TIME)); return; } diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 75e2bfd744a984..0cd6dd2b6334bc 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) * * Disables the endpoint for URB submission and nukes all pending URBs. * If @reset_hardware is set then also deallocates hcd/hardware state - * for the endpoint. + * for the endpoint (clearing both ep_in and ep_out pointers for + * bidirectional non-ep0 control endpoints). */ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, bool reset_hardware) @@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, dev->ep_in[epnum] = NULL; } if (ep) { + if (reset_hardware && epnum != 0 && + usb_endpoint_xfer_control(&ep->desc)) { + dev->ep_out[epnum] = NULL; + dev->ep_in[epnum] = NULL; + } ep->enabled = 0; usb_hcd_flush_endpoint(dev, ep); if (reset_hardware) diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2414291aa9087d..cd0dc876b421e4 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) } cancel_work_sync(&hsotg->phy_reset_work); - - timer_delete(&hsotg->wkp_timer); } static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) { /* Turn off all host-specific interrupts */ dwc2_disable_host_interrupts(hsotg); + timer_shutdown_sync(&hsotg->wkp_timer); dwc2_hcd_free(hsotg); } diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 608daeb7ef1041..f3dabb37f806b6 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,6 +49,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 +#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -841,6 +842,12 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; +enum dwc3_err_state { + DWC3_ERR_NONE = 0, + DWC3_ERR_RECOVERY, + DWC3_ERR_UNRECOVERABLE, +}; + /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping + * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching + * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state + * @err_state: current error recovery state. + * @err_recovery_count: number of consecutive error recovery attempts until + * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; + struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1203,6 +1216,9 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; + /* serializes pull-up run/stop vs error recovery */ + struct mutex connect_mutex; + struct device *dev; struct device *sysdev; @@ -1332,6 +1348,9 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; + enum dwc3_err_state err_state; + + u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index 632634d6e81e10..d03b3950ce046a 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) err_pm_disable: clk_disable_unprepare(am62->usb2_refclk); + pm_runtime_put_noidle(dev); pm_runtime_disable(dev); pm_runtime_set_suspended(dev); return ret; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236aa2..46a34c4a046b40 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + ret = -ESHUTDOWN; + goto out; + } + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f245e66cd13d00..ee837235630ad9 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); + /* Serialize against dwc3_err_recovery_work() */ + mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); + mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; + /* The controller is recovered. */ + if (dwc->err_state == DWC3_ERR_NONE) + dwc->err_recovery_count = 0; + /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } +static void dwc3_schedule_err_recovery(struct dwc3 *dwc) +{ + dwc->err_state = DWC3_ERR_RECOVERY; + schedule_work(&dwc->err_recovery_work); +} + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - return IRQ_NONE; + /* + * This is a fatal error - the driver and controller are out of + * sync on which event has been consumed. Reinitializing the + * controller is required to recover. Write the bogus count back + * to GEVNTCOUNT to clear the IRQ source, consistent with the + * stale event clearing in dwc3_event_buffers_setup(), then + * schedule error recovery. + */ + spin_lock(&dwc->lock); + dwc3_schedule_err_recovery(dwc); + spin_unlock(&dwc->lock); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + return IRQ_HANDLED; } evt->count = count; @@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } +static void dwc3_err_recovery_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); + unsigned long flags; + int ret; + + /* serializes against dwc3_gadget_pullup() */ + mutex_lock(&dwc->connect_mutex); + + ret = dwc3_gadget_soft_disconnect(dwc); + + dwc3_disconnect_gadget_sleepable(dwc); + + if (ret) + goto err_unrecoverable; + + if (dwc->softconnect) { + u32 count; + /* + * Wait irq to finish before soft_connect resets evt->lpos and + * the event buffer registers to avoid racing with + * dwc3_process_event_buf(). + */ + synchronize_irq(dwc->irq_gadget); + + spin_lock_irqsave(&dwc->lock, flags); + count = ++dwc->err_recovery_count; + spin_unlock_irqrestore(&dwc->lock, flags); + + if (count > DWC3_ERR_RECOVERY_MAX) + goto err_unrecoverable; + + ret = dwc3_gadget_soft_connect(dwc); + if (ret) + goto err_unrecoverable; + } + mutex_unlock(&dwc->connect_mutex); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); + return; + +err_unrecoverable: + dev_err(dwc->dev, "Unable to recover the controller\n"); + mutex_unlock(&dwc->connect_mutex); + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_UNRECOVERABLE; + spin_unlock_irqrestore(&dwc->lock, flags); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); + mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; + cancel_work_sync(&dwc->err_recovery_work); + mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 43962e05eacf8b..c64a268e98a433 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, break; case FUNCTIONFS_ENDPOINT_DESC: { + const struct usb_endpoint_descriptor *desc; + struct usb_endpoint_descriptor desc1; int desc_idx; - struct usb_endpoint_descriptor desc1, *desc; switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: @@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, desc_idx = 0; } - desc = epfile->ep->descs[desc_idx]; + do { + desc = epfile->ep->descs[desc_idx]; + } while (!desc && --desc_idx >= 0); + + if (!desc) + desc = epfile->ep->ep->desc; + if (!desc) { + ret = -EINVAL; + break; + } + memcpy(&desc1, desc, desc->bLength); spin_unlock_irq(&epfile->ffs->eps_lock); diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 5b8b18281989c0..d693de427641dd 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { /* MIDI 1.0 jacks */ unsigned char jack_in, jack_out, jack_id; - struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; - struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; + struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; + struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; }; static int append_config(struct f_midi2_usb_config *config, void *d) diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 3f52099a4fdd5b..7c8d60c7e87d12 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + con->data[cmd] = value; return 0; @@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + return con->data[cmd]; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 4a8b9ff8368fcf..069673f0d35966 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) for (i = 0; i < vhub->max_ports; i++) ast_vhub_del_dev(&vhub->ports[i].dev); + /* Final drain; the worker takes vhub->lock, so stay outside of it */ + cancel_work_sync(&vhub->wake_work); + spin_lock_irqsave(&vhub->lock, flags); /* Mask & ack all interrupts */ @@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) return -ENOMEM; spin_lock_init(&vhub->lock); + INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); vhub->pdev = pdev; vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, VHUB_IRQ_DEV1_BIT); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 8b9449d16324e3..4b389de0d9350d 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) int rc = -EINVAL; spin_lock_irqsave(&d->vhub->lock, flags); - if (!d->wakeup_en) + if (!d->wakeup_en || !d->registered) goto err; DDBG(d, "Device initiated wakeup\n"); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 02fe1a08d5757e..d0345f31037aa8 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, ast_vhub_send_host_wakeup(vhub); } -static void ast_vhub_wake_work(struct work_struct *work) +void ast_vhub_wake_work(struct work_struct *work) { struct ast_vhub *vhub = container_of(work, struct ast_vhub, @@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) void ast_vhub_hub_wake_all(struct ast_vhub *vhub) { + lockdep_assert_held(&vhub->lock); + /* * A device is trying to wake the world, because this * can recurse into the device, we break the call chain @@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) int ast_vhub_init_hub(struct ast_vhub *vhub) { vhub->speed = USB_SPEED_UNKNOWN; - INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); return ast_vhub_init_desc(vhub); } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index aca2050e2db0bc..96f1c37090428f 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); void ast_vhub_hub_resume(struct ast_vhub *vhub); void ast_vhub_hub_reset(struct ast_vhub *vhub); void ast_vhub_hub_wake_all(struct ast_vhub *vhub); +void ast_vhub_wake_work(struct work_struct *work); /* dev.c */ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index c0e40fa6dde586..0d30dd67b9cad1 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, { bool fifo = req == &dum->fifo_req; + ++dum->callback_usage; spin_unlock(&dum->lock); usb_gadget_giveback_request(_ep, &req->req); spin_lock(&dum->lock); + --dum->callback_usage; if (fifo) dum->fifo_req_busy = 0; } @@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, req->req.complete = fifo_complete; list_add_tail(&req->queue, &ep->queue); + ++dum->callback_usage; spin_unlock(&dum->lock); _req->actual = _req->length; _req->status = 0; usb_gadget_giveback_request(_ep, _req); spin_lock(&dum->lock); + --dum->callback_usage; } else list_add_tail(&req->queue, &ep->queue); spin_unlock_irqrestore(&dum->lock, flags); diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 34ce771fa0f52b..256e459ff6d8dc 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -378,8 +378,22 @@ struct octeon_hcd { struct cvmx_usb_transaction *active_split; struct cvmx_usb_tx_fifo periodic; struct cvmx_usb_tx_fifo nonperiodic; + struct hrtimer sof_timer; }; +static int cvmx_usb_poll(struct octeon_hcd *usb); + +static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) +{ + struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_poll(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return HRTIMER_NORESTART; +} + /* * This macro logically sets a single field in a CSR. It does the sequence * read, modify, and write @@ -578,7 +592,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) int result; u64 address = CVMX_USBCX_GRSTCTL(usb->index); u64 done = cvmx_get_cycle() + 100 * - (u64)octeon_get_clock_rate / 1000000; + (u64)octeon_get_clock_rate() / 1000000; union cvmx_usbcx_grstctl c; while (1) { @@ -853,6 +867,14 @@ static int cvmx_usb_initialize(struct device *dev, * USBC_GAHBCFG[PTXFEMPLVL] * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 */ + usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GHWCFG3(usb->index)); + if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { + dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", + usbcx_gahbcfg.u32); + return -ENODEV; + } + usbcx_gahbcfg.u32 = 0; usbcx_gahbcfg.s.dmaen = !(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA); @@ -1900,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) int channel; struct cvmx_usb_pipe *pipe; int need_sof; + u64 min_due; enum cvmx_usb_transfer ttype; if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { @@ -1940,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) * future that might need to be scheduled */ need_sof = 0; + min_due = ~0ull; for (ttype = CVMX_USB_TRANSFER_CONTROL; ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { - if (pipe->next_tx_frame > usb->frame_number) { - need_sof = 1; - break; - } + if (pipe->next_tx_frame > usb->frame_number && + pipe->next_tx_frame < min_due) + min_due = pipe->next_tx_frame; } } + if (min_due != ~0ull) { + u64 delta = min_due - usb->frame_number; + + /* + * frame_number is resynced from HFNUM on every poll, so a + * deadline that is many frames away does not need an + * interrupt on every SOF to count them down - sleep on the + * timer instead and keep SOF interrupts for deadlines within + * a few frames. Stay well below the 16383-frame wrap of + * HFNUM. One (micro)frame is 125us in high-speed mode. + */ + if (delta <= 4 || delta > 8000) + need_sof = 1; + else + hrtimer_start(&usb->sof_timer, + ns_to_ktime((delta - 2) * 125000), + HRTIMER_MODE_REL); + } USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk, sofmsk, need_sof); } @@ -3638,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) usb = (struct octeon_hcd *)hcd->hcd_priv; spin_lock_init(&usb->lock); + hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); usb->init_flags = initialize_flags; @@ -3688,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) unsigned long flags; usb_remove_hcd(hcd); + hrtimer_cancel(&usb->sof_timer); spin_lock_irqsave(&usb->lock, flags); status = cvmx_usb_shutdown(usb); spin_unlock_irqrestore(&usb->lock, flags); diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 0938c0e7a8b6d5..a7cd5fb4f11946 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) err_remove_hcd: usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); err: usb_put_hcd(hcd); return error; @@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index e623e24d3f8edf..7c0e35be58f1cc 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); s3c2410_stop_hc(dev); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 6843d7cb3f9f37..5aeabb0512659f 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) struct spear_ohci *sohci_p = to_spear_ohci(hcd); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); clk_disable_unprepare(sohci_p->clk); usb_put_hcd(hcd); diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c index d1656fce540047..53010a277b67fc 100644 --- a/drivers/usb/host/ohci-st.c +++ b/drivers/usb/host/ohci-st.c @@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) int clk; usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); if (pdata->power_off) pdata->power_off(dev); diff --git a/drivers/usb/mtu3/mtu3_trace.h b/drivers/usb/mtu3/mtu3_trace.h index 89870175d63561..9aaa167d69c130 100644 --- a/drivers/usb/mtu3/mtu3_trace.h +++ b/drivers/usb/mtu3/mtu3_trace.h @@ -224,6 +224,7 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __field(unsigned int, flags) __field(unsigned int, direction) __field(struct mtu3_gpd_ring *, gpd_ring) + __field(dma_addr_t, gpd_ring_dma) ), TP_fast_assign( __assign_str(name); @@ -235,12 +236,13 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __entry->flags = mep->flags; __entry->direction = mep->is_in; __entry->gpd_ring = &mep->gpd_ring; + __entry->gpd_ring_dma = mep->gpd_ring.dma ), TP_printk("%s: type %s maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", __get_str(name), usb_ep_type_string(__entry->type), __entry->maxp, __entry->slot, __entry->mult, __entry->maxburst, - __entry->gpd_ring, &__entry->gpd_ring->dma, + __entry->gpd_ring, &__entry->gpd_ring_dma, __entry->flags & MTU3_EP_ENABLED ? 'E' : 'e', __entry->flags & MTU3_EP_STALL ? 'S' : 's', __entry->flags & MTU3_EP_WEDGE ? 'W' : 'w', diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 9e2a18c0b21839..ea1fe5d1e44908 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -165,7 +165,11 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) void usb_serial_bus_deregister(struct usb_serial_driver *driver) { - free_dynids(driver); driver_unregister(&driver->driver); + free_dynids(driver); } +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) +{ + driver_remove_file(&driver->driver, &driver_attr_new_id); +} diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 333b8b46370483..c3f9add9833341 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 6eaf74930aa3f7..17272701fab04c 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; unsigned int bps; unsigned long period; unsigned long expire; @@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) __func__, jiffies_to_msecs(timeout), jiffies_to_msecs(period)); expire = jiffies + timeout; - while (!port->serial->type->tx_empty(port)) { + for (;;) { + mutex_lock(&tport->mutex); + if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { + mutex_unlock(&tport->mutex); + break; + } + mutex_unlock(&tport->mutex); + schedule_timeout_interruptible(period); if (signal_pending(current)) break; diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e4ad14375d6a73..828b1be3fee2d1 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EM060K_12a 0x012a #define QUECTEL_PRODUCT_EM060K_12b 0x012b #define QUECTEL_PRODUCT_EM060K_12c 0x012c +#define QUECTEL_PRODUCT_RG660QB 0x013d #define QUECTEL_PRODUCT_EG91 0x0191 #define QUECTEL_PRODUCT_EG95 0x0195 #define QUECTEL_PRODUCT_BG96 0x0296 @@ -280,6 +281,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EC200U 0x0901 #define QUECTEL_PRODUCT_EG912Y 0x6001 #define QUECTEL_PRODUCT_EC200S_CN 0x6002 +#define QUECTEL_PRODUCT_EG060W 0x6004 #define QUECTEL_PRODUCT_EC200A 0x6005 #define QUECTEL_PRODUCT_EG916Q 0x6007 #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 @@ -1268,12 +1270,15 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, @@ -2168,6 +2173,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), + .driver_info = RSVD(4) }, { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ .driver_info = RSVD(3) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ @@ -2429,6 +2436,13 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index dc2b39810a26c2..3b4df3ac7c7399 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -52,6 +52,7 @@ #define QT2_XMIT_FLUSH 0x05 /* no following info */ #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 921600 #define DEFAULT_BAUD_RATE 9600 @@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) static inline int qt2_set_port_config(struct usb_device *dev, unsigned char port_number, - u16 baudrate, u16 lcr) + speed_t baudrate, u16 lcr) { int divisor = calc_baud_divisor(baudrate); u16 index = ((u16) (lcr << 8) | (u16) (port_number)); @@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, struct usb_device *dev = port->serial->dev; struct qt2_port_private *port_priv; struct ktermios *termios = &tty->termios; - u16 baud; unsigned int cflag = termios->c_cflag; u16 new_lcr = 0; + speed_t baud; int status; port_priv = usb_get_serial_port_data(port); @@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + status = qt2_set_port_config(dev, port_priv->device_port, baud, new_lcr); if (status < 0) @@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) port_priv->device_port = (u8) device_port; if (tty) - qt2_set_termios(tty, port, &tty->termios); + qt2_set_termios(tty, port, NULL); return 0; diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c index b0d51558b73cea..8c5b9ef708b359 100644 --- a/drivers/usb/serial/ssu100.c +++ b/drivers/usb/serial/ssu100.c @@ -32,6 +32,7 @@ #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 460800 #define ATC_DISABLED 0x00 @@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, { struct usb_device *dev = port->serial->dev; struct ktermios *termios = &tty->termios; - u16 baud, divisor, remainder; + speed_t baud, remainder; unsigned int cflag = termios->c_cflag; u16 urb_value = 0; /* will hold the new flags */ + u16 divisor; int result; if (cflag & PARENB) { @@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); @@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) dev_dbg(&port->dev, "%s - set uart failed\n", __func__); if (tty) - ssu100_set_termios(tty, port, &tty->termios); + ssu100_set_termios(tty, port, NULL); return usb_serial_generic_open(tty, port); } diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 17edc057a31101..4a19621f08ba25 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) static void serial_wait_until_sent(struct tty_struct *tty, int timeout) { struct usb_serial_port *port = tty->driver_data; - struct usb_serial *serial = port->serial; dev_dbg(&port->dev, "%s\n", __func__); if (!port->serial->type->wait_until_sent) return; - mutex_lock(&serial->disc_mutex); - if (!serial->disconnected) - port->serial->type->wait_until_sent(tty, timeout); - mutex_unlock(&serial->disc_mutex); + port->serial->type->wait_until_sent(tty, timeout); } static void serial_throttle(struct tty_struct *tty) @@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) struct usb_serial_port *port = tty->driver_data; struct tty_port *tport = &port->port; unsigned int close_delay, closing_wait; + int ret = 0; mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } close_delay = jiffies_to_msecs(tport->close_delay) / 10; closing_wait = tport->closing_wait; @@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) if (port->serial->type->get_serial) port->serial->type->get_serial(tty, ss); - +out_unlock: mutex_unlock(&tport->mutex); - return 0; + return ret; } static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) @@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) closing_wait = msecs_to_jiffies(closing_wait * 10); mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } if (!capable(CAP_SYS_ADMIN)) { if (close_delay != tport->close_delay || @@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; int retval = -ENOIOCTLCMD; dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); @@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, retval = port->serial->type->tiocmiwait(tty, arg); break; default: - if (port->serial->type->ioctl) + if (!port->serial->type->ioctl) + break; + + if (cmd == TIOCSRS485) + down_write(&tty->termios_rwsem); + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + retval = -EIO; + else retval = port->serial->type->ioctl(tty, cmd, arg); + mutex_unlock(&tport->mutex); + + if (cmd == TIOCSRS485) + up_write(&tty->termios_rwsem); } return retval; @@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, const struct ktermios *old) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->set_termios) - port->serial->type->set_termios(tty, port, old); - else + if (!port->serial->type->set_termios) { tty_termios_copy_hw(&tty->termios, old); + return; + } + + mutex_lock(&tport->mutex); + if (!tty_io_error(tty)) + port->serial->type->set_termios(tty, port, old); + mutex_unlock(&tport->mutex); } static int serial_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->break_ctl) - return port->serial->type->break_ctl(tty, break_state); + if (!port->serial->type->break_ctl) + return -ENOTTY; - return -ENOTTY; + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->break_ctl(tty, break_state); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_proc_show(struct seq_file *m, void *v) @@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) static int serial_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmget) - return port->serial->type->tiocmget(tty); - return -ENOTTY; + if (!port->serial->type->tiocmget) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmget(tty); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmset) - return port->serial->type->tiocmset(tty, set, clear); - return -ENOTTY; + if (!port->serial->type->tiocmset) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmset(tty, set, clear); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_get_icount(struct tty_struct *tty, @@ -1191,12 +1245,17 @@ static void usb_serial_disconnect(struct usb_interface *interface) usb_serial_port_poison_urbs(port); wake_up_interruptible(&port->port.delta_msr_wait); cancel_work_sync(&port->work); - if (device_is_registered(&port->dev)) - device_del(&port->dev); } + if (serial->type->disconnect) serial->type->disconnect(serial); + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } + release_sibling(serial, interface); /* let the last holder of this object cause it to be cleaned up */ @@ -1313,7 +1372,8 @@ static int __init usb_serial_init(void) int result; usb_serial_tty_driver = tty_alloc_driver(USB_SERIAL_TTY_MINORS, - TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(usb_serial_tty_driver)) return PTR_ERR(usb_serial_tty_driver); @@ -1464,7 +1524,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers { int rc; struct usb_driver *udriver; - struct usb_serial_driver * const *sd; + struct usb_serial_driver * const *sd, * const *s; /* * udriver must be registered before any of the serial drivers, @@ -1517,9 +1577,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers return 0; err_deregister_drivers: + for (s = serial_drivers; s < sd; ++s) + usb_serial_bus_remove_new_id(*s); + usb_deregister(udriver); while (sd-- > serial_drivers) usb_serial_deregister(*sd); - usb_deregister(udriver); err_free_driver: kfree(udriver); return rc; @@ -1537,10 +1599,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) { struct usb_driver *udriver = (*serial_drivers)->usb_driver; + struct usb_serial_driver * const *sd; + + /* + * udriver must be deregistered before the serial drivers so that + * I/O is stopped before unbinding the ports. + * + * Remove the new_id attributes to prevent ids from being added and + * triggering a probe of udriver after it has been deregistered. + */ + for (sd = serial_drivers; *sd; ++sd) + usb_serial_bus_remove_new_id(*sd); - for (; *serial_drivers; ++serial_drivers) - usb_serial_deregister(*serial_drivers); usb_deregister(udriver); + + for (sd = serial_drivers; *sd; ++sd) + usb_serial_deregister(*sd); + kfree(udriver); } EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 352c765d88034e..c08f4aa14a3d90 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; struct xr_data *data = usb_get_serial_port_data(port); - down_read(&tty->termios_rwsem); - if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { - up_read(&tty->termios_rwsem); + /* core holds port mutex */ + if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) return -EFAULT; - } - up_read(&tty->termios_rwsem); return 0; } @@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, return -EFAULT; xr_sanitize_serial_rs485(&rs485); - down_write(&tty->termios_rwsem); + /* core holds termios rwsem and port mutex */ data->rs485 = rs485; xr_set_flow_mode(tty, port, NULL); - up_write(&tty->termios_rwsem); if (copy_to_user(argp, &rs485, sizeof(rs485))) return -EFAULT; diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c index c5e3eeeb2144d0..992685ca6f1afb 100644 --- a/drivers/usb/storage/sierra_ms.c +++ b/drivers/usb/storage/sierra_ms.c @@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, sizeof(struct swoc_info), /* __u16 size */ USB_CTRL_SET_TIMEOUT); /* int timeout */ + /* + * A short IN transfer leaves the tail of swocInfo uninitialized; + * only a full transfer is valid. + */ + if (result != sizeof(struct swoc_info)) + return -EIO; + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); return result; diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 41df115912b93f..c3d36da692e4a9 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) anx7411_partner_unregister_altmode(plat); anx7411_unregister_partner(plat); - if (plat->workqueue) + if (plat->workqueue) { + disable_irq(plat->intp_irq); + cancel_work_sync(&plat->work); destroy_workqueue(plat->workqueue); + } i2c_unregister_device(plat->spi_client); diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index cdbb7c11d71422..3a97b084c3bd6d 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) return usb4_usb3_port_match(dev, fwnode); } +static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) +{ + if (!IS_REACHABLE(CONFIG_USB4)) + return false; + + struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = + fwnode_find_reference(fwnode, "usb4-host-interface", 0); + + /* + * The USB4 port can only appear if the host interface is enabled in + * the firmware and has been enumerated as a device. The latter is + * the same check as in usb_acpi_add_usb4_devlink(). + */ + return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && + nhi_fwnode->dev; +} + static int typec_port_compare(struct device *dev, void *fwnode) { return device_match_fwnode(dev, fwnode); @@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) adev_fwnode); /* - * If dev is USB 3.x port, it may have reference to the + * If dev is USB 3.x port, it may have reference to an available * USB4 host interface in which case we can also link the * Type-C port with the USB4 port. */ - if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) + if (typec_has_usb4_host_interface(adev_fwnode)) component_match_add(&arg->port->dev, &arg->match, usb4_port_compare, adev_fwnode); } diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 2d6b14aa208569..06692feb6d4653 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) { hrtimer_cancel(&port->vdm_discovery_timer); - kthread_cancel_work_sync(&port->vdm_discovery_work); + if (port->wq) + kthread_cancel_work_sync(&port->vdm_discovery_work); } static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) @@ -7113,7 +7114,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) port->upcoming_state = FR_SWAP_SEND; ret = tcpm_ams_start(port, FAST_ROLE_SWAP); if (ret == -EAGAIN) - port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, ERROR_RECOVERY, 0); } else { tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); } @@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) port->registered = false; kthread_destroy_worker(port->wq); + port->wq = NULL; hrtimer_cancel(&port->vdm_discovery_timer); hrtimer_cancel(&port->enable_frs_timer); diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f76f563dc42b8f..48353918517781 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, static int tps6598x_connect(struct tps6598x *tps, u32 status) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; enum typec_pwr_opmode mode; int ret; @@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) desc.usb_pd = mode == TYPEC_PWR_MODE_PD; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) { ret = tps6598x_read_partner_identity(tps); @@ -850,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) /* Set up partner if we were previously disconnected (or changed). */ if (!tps->partner) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; desc.usb_pd = is_pd; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) desc.identity = &st.partner_identity; @@ -1792,6 +1790,7 @@ static int tps6598x_probe(struct i2c_client *client) const struct tipd_data *data; struct tps6598x *tps; struct fwnode_handle *fwnode; + bool patch_loaded = false; u32 status; u32 vid = 0; int ret; @@ -1847,6 +1846,7 @@ static int tps6598x_probe(struct i2c_client *client) return ret; if (ret == TPS_MODE_PTCH) { + patch_loaded = true; ret = tps->data->init(tps); if (ret) return ret; @@ -1937,7 +1937,8 @@ static int tps6598x_probe(struct i2c_client *client) tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); err_reset_controller: /* Reset PD controller to remove any applied patch */ - tps->data->reset(tps); + if (patch_loaded) + tps->data->reset(tps); return ret; } diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 8d2032d0762ca8..572da7bbd29c73 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) if (ret < 0) { if (ucsi->version > 0x0100) goto err_unlock; - cur = 0xff; } - if (cur < UCSI_MAX_ALTMODES) { - ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + if (cur != 0xff) { + if (cur < UCSI_MAX_ALTMODES) + ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + else + ret = -EINVAL; + goto err_unlock; } diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index bef3f9b71d718d..c1450639c79565 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) { struct fwnode_handle *fwnode; int i = 1; + int ret; + u32 port; - device_for_each_child_node(con->ucsi->dev, fwnode) - if (i++ == con->num) - return fwnode; + device_for_each_child_node(con->ucsi->dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + if (i == con->num) + return fwnode; + } else { + if (port == con->num - 1) + return fwnode; + } + + i++; + } return NULL; } diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 18286d3e9cc59a..5697ccc24ec3fc 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { .async_control = ucsi_acpi_async_control }; +static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_acpi_read_version(ucsi, version); + if (ret) + return ret; + + if (!*version) { + dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); + *version = UCSI_VERSION_1_2; + } + + return 0; +} + +static const struct ucsi_operations ucsi_acer_ops = { + .read_version = ucsi_acer_read_version, + .read_cci = ucsi_acpi_read_cci, + .poll_cci = ucsi_acpi_poll_cci, + .read_message_in = ucsi_acpi_read_message_in, + .write_message_out = ucsi_acpi_write_message_out, + .sync_control = ucsi_sync_control_common, + .async_control = ucsi_acpi_async_control +}; + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, void *val, size_t len, void *msg_out, size_t msg_out_size) @@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { }; static const struct dmi_system_id ucsi_acpi_quirks[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + }, + .driver_data = (void *)&ucsi_acer_ops, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 12e07b9fe62280..1db0c88d817e6d 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) { struct pmic_glink_ucsi *ucsi = data; + /* Drain the work items before ucsi_destroy() frees the ucsi instance */ + cancel_work_sync(&ucsi->notify_work); + cancel_work_sync(&ucsi->register_work); + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ mutex_lock(&ucsi->lock); ucsi_destroy(ucsi->ucsi); @@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) { struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); - /* Unregister first to stop having read & writes */ - ucsi_unregister(ucsi->ucsi); + /* Callbacks can queue work until devres releases the client */ + disable_work_sync(&ucsi->notify_work); + disable_work_sync(&ucsi->register_work); + + /* register_work may have unregistered the instance already */ + if (ucsi->ucsi_registered) { + ucsi->ucsi_registered = false; + ucsi_unregister(ucsi->ucsi); + } } static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c index c91300a73f5023..0d7bdad924b73c 100644 --- a/drivers/virt/nitro_enclaves/ne_misc_dev.c +++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c @@ -1706,6 +1706,7 @@ static int ne_create_vm_ioctl(struct ne_pci_dev *ne_pci_dev, u64 __user *slot_ui return enclave_fd; put_file: + enclave_file->private_data = NULL; fput(enclave_file); put_fd: put_unused_fd(enclave_fd); diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c index 66135eac3abffa..ce957f5b1c317d 100644 --- a/drivers/virt/vmgenid.c +++ b/drivers/virt/vmgenid.c @@ -25,7 +25,7 @@ struct vmgenid_state { static void vmgenid_notify(struct device *device) { - struct vmgenid_state *state = device->driver_data; + struct vmgenid_state *state = dev_get_drvdata(device); u8 old_id[VMGENID_SIZE]; memcpy(old_id, state->this_id, sizeof(old_id)); @@ -75,13 +75,16 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) phys_addr = (obj->package.elements[0].integer.value << 0) | (obj->package.elements[1].integer.value << 32); - virt_addr = devm_memremap(&device->dev, phys_addr, VMGENID_SIZE, MEMREMAP_WB); + virt_addr = devm_memremap(&device->dev, phys_addr, VMGENID_SIZE, + MEMREMAP_WB | MEMREMAP_DEC); if (IS_ERR(virt_addr)) { ret = PTR_ERR(virt_addr); goto out; } setup_vmgenid_state(state, virt_addr); + dev_set_drvdata(dev, state); + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, vmgenid_acpi_handler, dev); if (ACPI_FAILURE(status)) { @@ -89,7 +92,6 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) goto out; } - dev->driver_data = state; out: ACPI_FREE(parsed.pointer); return ret; @@ -123,12 +125,13 @@ static int vmgenid_add_of(struct platform_device *pdev, if (ret < 0) return ret; + dev_set_drvdata(&pdev->dev, state); + ret = devm_request_irq(&pdev->dev, ret, vmgenid_of_irq_handler, IRQF_SHARED, "vmgenid", &pdev->dev); if (ret < 0) return ret; - pdev->dev.driver_data = state; return 0; } diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 558b4a3f963364..faf6ed7deb18bd 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9623,7 +9623,6 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, struct completion sync_reads; unsigned long i = 0; struct btrfs_bio *bbio; - int ret; /* * Fast path for synchronous reads which completes in this call, io_uring @@ -9670,10 +9669,10 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, if (uring_ctx) { if (refcount_dec_and_test(&priv->pending_refs)) { - ret = blk_status_to_errno(READ_ONCE(priv->status)); - btrfs_uring_read_extent_endio(uring_ctx, ret); + int error = blk_status_to_errno(READ_ONCE(priv->status)); + + btrfs_uring_read_extent_endio(uring_ctx, error); kfree(priv); - return ret; } return -EIOCBQUEUED; diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index e4b2da31a0d5de..576cd76ca0bf30 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4601,7 +4601,7 @@ static void btrfs_uring_read_finished(struct io_tw_req tw_req, io_tw_token_t tw) size_t page_offset; ssize_t ret; - /* The inode lock has already been acquired in btrfs_uring_read_extent. */ + /* The inode lock has already been acquired in btrfs_encoded_read(). */ btrfs_lockdep_inode_acquire(inode, i_rwsem); if (priv->err) { @@ -4667,7 +4667,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, struct iovec *iov, struct io_uring_cmd *cmd) { struct btrfs_inode *inode = BTRFS_I(file_inode(iocb->ki_filp)); - struct extent_io_tree *io_tree = &inode->io_tree; struct page **pages = NULL; struct btrfs_uring_priv *priv = NULL; unsigned long nr_pages; @@ -4723,8 +4722,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, return -EIOCBQUEUED; out_fail: - btrfs_unlock_extent(io_tree, start, lockend, &cached_state); - btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); kfree(priv); for (int i = 0; i < nr_pages; i++) { if (pages[i]) @@ -4837,7 +4834,7 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue ret = btrfs_encoded_read(&kiocb, &data->iter, &data->args, &cached_state, &disk_bytenr, &disk_io_size); if (ret == -EAGAIN) - goto out_acct; + goto out_free; if (ret < 0 && ret != -EIOCBQUEUED) goto out_free; @@ -4865,8 +4862,10 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue cached_state, disk_bytenr, disk_io_size, count, data->args.compression, data->iov, cmd); - - goto out_acct; + if (ret == -EIOCBQUEUED) + goto out_acct; + btrfs_unlock_extent(io_tree, start, lockend, &cached_state); + btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); } out_free: @@ -4877,8 +4876,10 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue add_rchar(current, ret); inc_syscr(current); - if (ret != -EIOCBQUEUED && ret != -EAGAIN) + if (ret != -EIOCBQUEUED) { kfree(data); + bc->data = NULL; + } return ret; } @@ -4907,6 +4908,11 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu goto out_acct; } + if (issue_flags & IO_URING_F_NONBLOCK) { + ret = -EAGAIN; + goto out_acct; + } + if (!data) { data = kzalloc_obj(*data, GFP_NOFS); if (!data) { @@ -4975,11 +4981,6 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu } } - if (issue_flags & IO_URING_F_NONBLOCK) { - ret = -EAGAIN; - goto out_acct; - } - pos = data->args.offset; ret = rw_verify_area(WRITE, file, &pos, data->args.len); if (ret < 0) @@ -5005,8 +5006,8 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu add_wchar(current, ret); inc_syscw(current); - if (ret != -EAGAIN) - kfree(data); + kfree(data); + bc->data = NULL; return ret; } diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index c09d4213ad8915..479c0e037015ab 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -536,8 +536,10 @@ static int scrub_print_warning_inode(u64 inum, u64 offset, u64 num_bytes, } ret = paths_from_inode(inum, ipath); - if (ret < 0) + if (ret < 0) { + btrfs_put_root(local_root); goto err; + } /* * we deliberately ignore the bit ipath might have been too small to diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 6802b94ed76ff4..11b38133f737e6 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, spin_lock(&fs_info->fs_roots_radix_lock); if (btrfs_get_root_last_trans(root) == trans->transid && !force) { spin_unlock(&fs_info->fs_roots_radix_lock); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); return 0; } radix_tree_tag_set(&fs_info->fs_roots_radix, diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index ab55d10bd71fdb..cb001a211f5575 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -161,6 +161,7 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, const u16 old_data_len = btrfs_dir_data_len(leaf, di); const u32 item_size = btrfs_item_size(leaf, slot); const u32 data_size = sizeof(*di) + name_len + size; + unsigned long name_ptr; unsigned long data_ptr; char *ptr; @@ -189,8 +190,16 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, ptr = btrfs_item_ptr(leaf, slot, char); ptr += btrfs_item_size(leaf, slot) - data_size; di = (struct btrfs_dir_item *)ptr; + memzero_extent_buffer(leaf, (unsigned long)ptr + + offsetof(struct btrfs_dir_item, location), + sizeof(struct btrfs_disk_key)); + btrfs_set_dir_flags(leaf, di, BTRFS_FT_XATTR); + btrfs_set_dir_transid(leaf, di, trans->transid); + btrfs_set_dir_name_len(leaf, di, name_len); btrfs_set_dir_data_len(leaf, di, size); + name_ptr = (unsigned long)(di + 1); data_ptr = ((unsigned long)(di + 1)) + name_len; + write_extent_buffer(leaf, name, name_ptr, name_len); write_extent_buffer(leaf, value, data_ptr, size); } else { /* @@ -320,10 +329,8 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size) if (!size) goto next; - if (!buffer || (name_len + 1) > size_left) { - iter_ret = -ERANGE; - break; - } + if (!buffer || (name_len + 1) > size_left) + return -ERANGE; read_extent_buffer(leaf, buffer, name_ptr, name_len); buffer[name_len] = '\0'; diff --git a/fs/dcache.c b/fs/dcache.c index a66be85f9d014e..f44c7b39c3b384 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1583,6 +1583,8 @@ EXPORT_SYMBOL(path_has_submounts); * * Only one of d_invalidate() and d_set_mounted() must succeed. For * this reason take rename_lock and d_lock on dentry and ancestors. + * Likewise for dont_mount() which marks a dentry that is being removed + * under d_lock. */ int d_set_mounted(struct dentry *dentry) { @@ -1599,7 +1601,7 @@ int d_set_mounted(struct dentry *dentry) spin_unlock(&p->d_lock); } spin_lock(&dentry->d_lock); - if (!d_unlinked(dentry)) { + if (!d_unlinked(dentry) && !cant_mount(dentry)) { ret = -EBUSY; if (!d_mountpoint(dentry)) { dentry->d_flags |= DCACHE_MOUNTED; diff --git a/fs/ext4/super.c b/fs/ext4/super.c index bca0dc87d0b7c5..d636b89090a249 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5057,6 +5057,7 @@ static int ext4_load_and_init_journal(struct super_block *sb, static int ext4_check_journal_data_mode(struct super_block *sb) { if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA) { + printk(KERN_WARNING "EXT4-fs: The data=journal mount option is deprecated and support will be removed in January 2028\n"); printk_once(KERN_WARNING "EXT4-fs: Warning: mounting with " "data=journal disables delayed allocation, " "dioread_nolock, O_DIRECT and fast_commit support!\n"); diff --git a/fs/fcntl.c b/fs/fcntl.c index c158f082f1da4f..9b71bc9d71d159 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -372,6 +372,9 @@ static long fcntl_set_rw_hint(struct file *file, unsigned long arg) u64 __user *argp = (u64 __user *)arg; u64 hint; + /* nothing is ever written to it */ + if (IS_IMMUTABLE(inode)) + return -EPERM; if (!inode_owner_or_capable(file_mnt_idmap(file), inode)) return -EPERM; diff --git a/fs/fhandle.c b/fs/fhandle.c index f8829231e3d758..d22f2e065677a1 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -298,6 +298,22 @@ static bool capable_wrt_mount(struct mount *mount) return mnt_ns && ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN); } +/* + * Does the caller have an unobstructed way to everything below @root? Only + * if the mount is mounted, the caller is privileged over its mount namespace + * and no locked child covers something below @root->dentry. One answer from + * under mount_lock: an umount in between clears ->mnt_ns and takes the + * children off the mount, the locked ones too. + */ +static bool subtree_unobstructed(const struct path *root) +{ + struct mount *mnt = real_mount(root->mnt); + + guard(mount_locked_reader)(); + return is_mounted(root->mnt) && capable_wrt_mount(mnt) && + !has_locked_children(mnt, root->dentry); +} + static inline int may_decode_fh(struct handle_to_path_ctx *ctx, unsigned int o_flags) { @@ -332,9 +348,7 @@ static inline int may_decode_fh(struct handle_to_path_ctx *ctx, if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN)) ctx->flags = HANDLE_CHECK_PERMS; - else if (is_mounted(root->mnt) && - capable_wrt_mount(real_mount(root->mnt)) && - !has_locked_children(real_mount(root->mnt), root->dentry)) + else if (subtree_unobstructed(root)) ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE; else return -EPERM; diff --git a/fs/file_table.c b/fs/file_table.c index c68b8c0a4097ae..e2665eaa6f8c2a 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -520,7 +520,8 @@ static void __fput(struct file *file) dput(dentry); if (unlikely(mode & FMODE_NEED_UNMOUNT)) dissolve_on_fput(mnt); - mntput(mnt); + else + mntput(mnt); out: file_free(file); } diff --git a/fs/fs_context.c b/fs/fs_context.c index 23ad66cd94e1f9..a50584df97bea7 100644 --- a/fs/fs_context.c +++ b/fs/fs_context.c @@ -315,6 +315,10 @@ struct fs_context *fs_context_for_reconfigure(struct dentry *dentry, unsigned int sb_flags, unsigned int sb_flags_mask) { + /* kernel-internal superblocks are nobody's to reconfigure */ + if (dentry->d_sb->s_flags & SB_NOUSER) + return ERR_PTR(-EINVAL); + return alloc_fs_context(dentry->d_sb->s_type, dentry, sb_flags, sb_flags_mask, FS_CONTEXT_FOR_RECONFIGURE); } diff --git a/fs/mount.h b/fs/mount.h index 94fcc306d21e7d..4d23cade9f6c1f 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -6,6 +6,8 @@ #include extern struct file_system_type nullfs_fs_type; +extern struct dentry *nullfs_new_file(struct super_block *sb); +extern struct vfsmount *knullfs; extern struct list_head notify_list; struct mnt_namespace { @@ -33,16 +35,20 @@ struct mnt_namespace { } __randomize_layout; struct mnt_pcp { - int mnt_count; + unsigned int mnt_gets; + unsigned int mnt_puts; int mnt_writers; }; struct mountpoint { struct hlist_node m_hash; struct dentry *m_dentry; - struct hlist_head m_list; + struct hlist_head m_list; /* mounts on it and pins */ + struct hlist_head m_covers; /* covers of unmounted parents */ }; +struct mnt_cover; + struct mount { struct hlist_node mnt_hash; struct mount *mnt_parent; @@ -84,7 +90,7 @@ struct mount { struct mountpoint *mnt_mp; /* where is it mounted */ union { struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ - struct hlist_node mnt_umount; + struct hlist_node mnt_umount; /* on the unmounted list */ }; #ifdef CONFIG_FSNOTIFY struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks; @@ -98,7 +104,8 @@ struct mount { int mnt_group_id; /* peer group identifier */ int mnt_expiry_mark; /* true if marked for expiry */ struct hlist_head mnt_pins; - struct hlist_head mnt_stuck_children; + struct mnt_cover *mnt_cover; /* the one it may leave behind */ + struct hlist_head mnt_covers; /* left behind by its unmounted children */ struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */ struct mount *overmount; /* mounted on ->mnt_root */ } __randomize_layout; @@ -233,6 +240,9 @@ static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns) #ifdef CONFIG_FSNOTIFY static inline void mnt_notify_add(struct mount *m) { + /* queued already under this namespace_sem hold */ + if (!list_empty(&m->to_notify)) + return; /* Optimize the case where there are no watches */ if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) || (m->prev_ns && m->prev_ns->n_fsnotify_marks)) diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3efff..c87c14ee1a2521 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2086,6 +2086,33 @@ static noinline const char *pick_link(struct nameidata *nd, struct path *link, return NULL; } +/* + * Be careful in case the dentry is unlinked or renamed. Any mounts + * stacked on top of it are going away. We need to make sure that we + * don't reveal the underyling dentry during refwalk. In rcuwalk we + * catch this via d_seq and another lookup for the name. Give the same + * guarantee in refwalk. + */ +static bool unlink_may_reveal(struct nameidata *nd, int flags, + struct path *path, struct dentry *dentry) +{ + /* ".." and LOOKUP_DOWN may land on an unhashed directory */ + if (flags & WALK_NOFOLLOW) + return false; + if (nd->flags & LOOKUP_REVAL) + return false; + /* We crossed onto a mount and the name led us here while it still existed */ + if (path->mnt != nd->path.mnt) + return false; + /* only a name on its way out is flagged */ + if (likely(!cant_mount(dentry))) + return false; + if (!d_unlinked(dentry)) + return false; + dput(no_free_ptr(path->dentry)); + return true; +} + /* * Do we need to follow links? We _really_ want to be able * to do this check without having to look at inode->i_op, @@ -2115,6 +2142,8 @@ static noinline const char *step_into_slowpath(struct nameidata *nd, int flags, if (unlikely(!inode)) return ERR_PTR(-ENOENT); } else { + if (unlikely(unlink_may_reveal(nd, flags, &path, dentry))) + return ERR_PTR(-ESTALE); dput(nd->path.dentry); if (nd->path.mnt != path.mnt) mntput(nd->path.mnt); diff --git a/fs/namespace.c b/fs/namespace.c index 580877e46b1acc..3ed3766f3a1837 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -80,6 +80,8 @@ static u64 mnt_id_ctr = MNT_UNIQUE_ID_OFFSET; static struct hlist_head *mount_hashtable __ro_after_init; static struct hlist_head *mountpoint_hashtable __ro_after_init; static struct kmem_cache *mnt_cache __ro_after_init; +struct vfsmount *knullfs __ro_after_init; /* private nullfs instance */ +static struct vfsmount *knullfs_file __ro_after_init; /* its regular file */ static DECLARE_RWSEM(namespace_sem); static HLIST_HEAD(unmounted); /* protected by namespace_sem */ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */ @@ -130,7 +132,6 @@ static void mnt_ns_release(struct mnt_namespace *ns) { /* keep alive for {list,stat}mount() */ if (ns && refcount_dec_and_test(&ns->passive)) { - fsnotify_mntns_delete(ns); put_user_ns(ns->user_ns); kfree(ns); } @@ -210,6 +211,19 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry) return &mountpoint_hashtable[tmp & mp_hash_mask]; } +/* + * What an unmounted mount leaves behind at its unmounted parent instead of + * staying attached to it. A lookup on the parent at the mountpoint finds + * a stand-in for as long as the cover is there. The parent frees it. + */ +struct mnt_cover { + struct hlist_node node; /* parent->mnt_covers, RCU */ + struct hlist_node pin; /* mp->m_covers, keeps the mountpoint */ + struct dentry *dentry; + struct mountpoint *mp; + struct rcu_head rcu; +}; + static int mnt_alloc_id(struct mount *mnt) { int res; @@ -249,16 +263,24 @@ void mnt_release_group_id(struct mount *mnt) mnt->mnt_group_id = 0; } -/* - * vfsmount lock must be held for read - */ -static inline void mnt_add_count(struct mount *mnt, int n) +static inline void mnt_inc_count(struct mount *mnt) +{ +#ifdef CONFIG_SMP + this_cpu_inc(mnt->mnt_pcp->mnt_gets); +#else + preempt_disable(); + mnt->mnt_count++; + preempt_enable(); +#endif +} + +static inline void mnt_dec_count(struct mount *mnt) { #ifdef CONFIG_SMP - this_cpu_add(mnt->mnt_pcp->mnt_count, n); + this_cpu_inc(mnt->mnt_pcp->mnt_puts); #else preempt_disable(); - mnt->mnt_count += n; + mnt->mnt_count--; preempt_enable(); #endif } @@ -269,14 +291,17 @@ static inline void mnt_add_count(struct mount *mnt, int n) int mnt_get_count(struct mount *mnt) { #ifdef CONFIG_SMP - int count = 0; + unsigned int gets = 0, puts = 0; int cpu; - for_each_possible_cpu(cpu) { - count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count; - } + /* puts first, so a put counted here has its get counted below */ + for_each_possible_cpu(cpu) + puts += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_puts; + smp_mb(); /* pairs with the smp_wmb() in mntput_no_expire() */ + for_each_possible_cpu(cpu) + gets += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_gets; - return count; + return gets - puts; #else return mnt->mnt_count; #endif @@ -288,6 +313,10 @@ static struct mount *alloc_vfsmnt(const char *name) if (mnt) { int err; + mnt->mnt_cover = kzalloc_obj(struct mnt_cover, GFP_KERNEL_ACCOUNT); + if (!mnt->mnt_cover) + goto out_free_cache; + err = mnt_alloc_id(mnt); if (err) goto out_free_cache; @@ -305,7 +334,7 @@ static struct mount *alloc_vfsmnt(const char *name) if (!mnt->mnt_pcp) goto out_free_devname; - this_cpu_add(mnt->mnt_pcp->mnt_count, 1); + this_cpu_inc(mnt->mnt_pcp->mnt_gets); #else mnt->mnt_count = 1; mnt->mnt_writers = 0; @@ -320,8 +349,11 @@ static struct mount *alloc_vfsmnt(const char *name) INIT_HLIST_HEAD(&mnt->mnt_slave_list); INIT_HLIST_NODE(&mnt->mnt_slave); INIT_HLIST_NODE(&mnt->mnt_mp_list); - INIT_HLIST_HEAD(&mnt->mnt_stuck_children); + INIT_HLIST_HEAD(&mnt->mnt_covers); INIT_HLIST_NODE(&mnt->mnt_ns_visible); +#ifdef CONFIG_FSNOTIFY + INIT_LIST_HEAD(&mnt->to_notify); +#endif RB_CLEAR_NODE(&mnt->mnt_node); mnt->mnt.mnt_idmap = &nop_mnt_idmap; } @@ -334,6 +366,7 @@ static struct mount *alloc_vfsmnt(const char *name) out_free_id: mnt_free_id(mnt); out_free_cache: + kfree(mnt->mnt_cover); kmem_cache_free(mnt_cache, mnt); return NULL; } @@ -725,6 +758,8 @@ int sb_prepare_remount_readonly(struct super_block *sb) static void free_vfsmnt(struct mount *mnt) { mnt_idmap_put(mnt_idmap(&mnt->mnt)); + /* NULL if it left it behind */ + kfree(mnt->mnt_cover); kfree_const(mnt->mnt_devname); #ifdef CONFIG_SMP free_percpu(mnt->mnt_pcp); @@ -746,13 +781,13 @@ int __legitimize_mnt(struct vfsmount *bastard, unsigned seq) if (bastard == NULL) return 0; mnt = real_mount(bastard); - mnt_add_count(mnt, 1); - smp_mb(); // see mntput_no_expire() and do_umount() + mnt_inc_count(mnt); + smp_mb(); /* see mntput_no_expire_slowpath() and do_umount() */ if (likely(!read_seqretry(&mount_lock, seq))) return 0; lock_mount_hash(); if (unlikely(bastard->mnt_flags & (MNT_SYNC_UMOUNT | MNT_DOOMED))) { - mnt_add_count(mnt, -1); + mnt_dec_count(mnt); unlock_mount_hash(); return 1; } @@ -781,20 +816,31 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq) * @dentry: dentry of mountpoint * * If @mnt has a child mount @c mounted on @dentry find and return it. + * If @mnt is unmounted and a child that was unmounted with it left its + * cover behind at @dentry, return the stand-in for it instead: knullfs + * for a directory, its regular file for anything else. * Caller must either hold the spinlock component of @mount_lock or * hold rcu_read_lock(), sample the seqcount component before the call * and recheck it afterwards. * - * Return: The child of @mnt mounted on @dentry or %NULL. + * Return: The child of @mnt mounted on @dentry, a stand-in or %NULL. */ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry) { struct hlist_head *head = m_hash(mnt, dentry); + struct mnt_cover *cover; struct mount *p; hlist_for_each_entry_rcu(p, head, mnt_hash) if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry) return p; + /* an unmounted mount keeps the covers its unmounted children left */ + /* a lockless caller rechecks mount_lock after a miss, a stale flag is harmless */ + if (unlikely(data_race(mnt->mnt_flags) & MNT_UMOUNT)) { + hlist_for_each_entry_rcu(cover, &real_mount(mnt)->mnt_covers, node) + if (cover->dentry == dentry) + return real_mount(d_is_dir(dentry) ? knullfs : knullfs_file); + } return NULL; } @@ -910,6 +956,7 @@ static int get_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m) mp->m_dentry = dget(dentry); hlist_add_head(&mp->m_hash, mp_hash(dentry)); INIT_HLIST_HEAD(&mp->m_list); + INIT_HLIST_HEAD(&mp->m_covers); hlist_add_head(&m->node, &mp->m_list); m->mp = no_free_ptr(mp); read_sequnlock_excl(&mount_lock); @@ -922,7 +969,7 @@ static int get_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m) */ static void maybe_free_mountpoint(struct mountpoint *mp, struct list_head *list) { - if (hlist_empty(&mp->m_list)) { + if (hlist_empty(&mp->m_list) && hlist_empty(&mp->m_covers)) { struct dentry *dentry = mp->m_dentry; spin_lock(&dentry->d_lock); dentry->d_flags &= ~DCACHE_MOUNTED; @@ -1009,6 +1056,36 @@ static void umount_mnt(struct mount *mnt) __umount_mnt(mnt, &ex_mountpoints); } +/* + * @mnt is unmounted together with its parent and would have stayed attached + * to it. Leave its cover behind before it is detached so that a lookup on the + * parent at the mountpoint keeps finding a mount instead of what @mnt covered. + * + * locks: mount_lock[write_seqlock] + */ +static void leave_cover(struct mount *mnt) +{ + struct mnt_cover *cover = mnt->mnt_cover; + + mnt->mnt_cover = NULL; + cover->dentry = mnt->mnt_mountpoint; + cover->mp = mnt->mnt_mp; + /* keeps the mountpoint once @mnt has let go of it */ + hlist_add_head(&cover->pin, &cover->mp->m_covers); + hlist_add_head_rcu(&cover->node, &mnt->mnt_parent->mnt_covers); +} + +/* + * locks: mount_lock[write_seqlock] + */ +static void drop_cover(struct mnt_cover *cover, struct list_head *shrink_list) +{ + hlist_del_rcu(&cover->node); + hlist_del(&cover->pin); + maybe_free_mountpoint(cover->mp, shrink_list); + kfree_rcu(cover, rcu); +} + /* * vfsmount lock must be held for write */ @@ -1058,7 +1135,10 @@ static void attach_mnt(struct mount *mnt, struct mount *parent, void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt) { struct mountpoint *old_mp = mnt->mnt_mp; + struct mount *old_parent = mnt->mnt_parent; + if (old_parent->overmount == mnt) + old_parent->overmount = NULL; list_del_init(&mnt->mnt_child); hlist_del_init(&mnt->mnt_mp_list); hlist_del_init_rcu(&mnt->mnt_hash); @@ -1254,6 +1334,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, mnt->mnt.mnt_flags = READ_ONCE(old->mnt.mnt_flags) & ~MNT_INTERNAL_FLAGS; + mnt->mnt_t_flags = old->mnt_t_flags & T_UNBINDABLE; if (flag & (CL_SLAVE | CL_PRIVATE)) mnt->mnt_group_id = 0; /* not a peer of original */ @@ -1296,8 +1377,6 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, static void cleanup_mnt(struct mount *mnt) { - struct hlist_node *p; - struct mount *m; /* * The warning here probably indicates that somebody messed * up a mnt_want/drop_write() pair. If this happens, the @@ -1308,10 +1387,6 @@ static void cleanup_mnt(struct mount *mnt) WARN_ON(mnt_get_writers(mnt)); if (unlikely(mnt->mnt_pins.first)) mnt_pin_kill(mnt); - hlist_for_each_entry_safe(m, p, &mnt->mnt_stuck_children, mnt_umount) { - hlist_del(&m->mnt_umount); - mntput(&m->mnt); - } fsnotify_vfsmount_delete(&mnt->mnt); dput(mnt->mnt.mnt_root); deactivate_super(mnt->mnt.mnt_sb); @@ -1337,6 +1412,8 @@ static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput); static void noinline mntput_no_expire_slowpath(struct mount *mnt) { + struct mnt_cover *cover; + struct hlist_node *n; LIST_HEAD(list); int count; @@ -1347,7 +1424,7 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt) * mount_lock, we'll see their refcount increment here. */ smp_mb(); - mnt_add_count(mnt, -1); + mnt_dec_count(mnt); count = mnt_get_count(mnt); if (count != 0) { WARN_ON(count < 0); @@ -1367,13 +1444,10 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt) if (unlikely(!list_empty(&mnt->mnt_expire))) list_del(&mnt->mnt_expire); - if (unlikely(!list_empty(&mnt->mnt_mounts))) { - struct mount *p, *tmp; - list_for_each_entry_safe(p, tmp, &mnt->mnt_mounts, mnt_child) { - __umount_mnt(p, &list); - hlist_add_head(&p->mnt_umount, &mnt->mnt_stuck_children); - } - } + /* nothing stays attached to an unmounted mount */ + VFS_WARN_ON_ONCE(!list_empty(&mnt->mnt_mounts)); + hlist_for_each_entry_safe(cover, n, &mnt->mnt_covers, node) + drop_cover(cover, &list); unlock_mount_hash(); shrink_dentry_list(&list); @@ -1404,7 +1478,8 @@ static void mntput_no_expire(struct mount *mnt) * non-NULL under rcu_read_lock(), the reference * we are dropping is not the final one. */ - mnt_add_count(mnt, -1); + smp_wmb(); /* pairs with the smp_mb() in mnt_get_count() */ + mnt_dec_count(mnt); rcu_read_unlock(); return; } @@ -1426,7 +1501,7 @@ EXPORT_SYMBOL(mntput); struct vfsmount *mntget(struct vfsmount *mnt) { if (mnt) - mnt_add_count(real_mount(mnt), 1); + mnt_inc_count(real_mount(mnt)); return mnt; } EXPORT_SYMBOL(mntget); @@ -1737,36 +1812,17 @@ static inline void namespace_lock(void) enum umount_tree_flags { UMOUNT_SYNC = 1, UMOUNT_PROPAGATE = 2, - UMOUNT_CONNECTED = 4, + UMOUNT_COVER = 4, }; -static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how) +/* Do we need to leave the mountpoint on the parent covered? */ +static bool needs_cover(struct mount *mnt, enum umount_tree_flags how) { - /* Leaving mounts connected is only valid for lazy umounts */ if (how & UMOUNT_SYNC) - return true; - - /* A mount without a parent has nothing to be connected to */ - if (!mnt_has_parent(mnt)) - return true; - - /* Because the reference counting rules change when mounts are - * unmounted and connected, umounted mounts may not be - * connected to mounted mounts. - */ - if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT)) - return true; - - /* Has it been requested that the mount remain connected? */ - if (how & UMOUNT_CONNECTED) return false; - - /* Is the mount locked such that it needs to remain connected? */ - if (IS_MNT_LOCKED(mnt)) + if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT)) return false; - - /* By default disconnect the mount */ - return true; + return (how & UMOUNT_COVER) || IS_MNT_LOCKED(mnt); } /* @@ -1783,6 +1839,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) /* Gather the mounts to umount */ for (p = mnt; p; p = next_mnt(p, mnt)) { + /* A mount is unmounted once. */ + VFS_WARN_ON_ONCE(p->mnt.mnt_flags & MNT_UMOUNT); p->mnt.mnt_flags |= MNT_UMOUNT; if (mnt_ns_attached(p)) move_from_ns(p); @@ -1802,7 +1860,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) while (!list_empty(&tmp_list)) { struct mnt_namespace *ns; - bool disconnect; p = list_first_entry(&tmp_list, struct mount, mnt_list); list_del_init(&p->mnt_expire); list_del_init(&p->mnt_list); @@ -1815,17 +1872,12 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) if (how & UMOUNT_SYNC) p->mnt.mnt_flags |= MNT_SYNC_UMOUNT; - disconnect = disconnect_mount(p, how); if (mnt_has_parent(p)) { - if (!disconnect) { - /* Don't forget about p */ - list_add_tail(&p->mnt_child, &p->mnt_parent->mnt_mounts); - } else { - umount_mnt(p); - } + if (needs_cover(p, how)) + leave_cover(p); + umount_mnt(p); } - if (disconnect) - hlist_add_head(&p->mnt_umount, &unmounted); + hlist_add_head(&p->mnt_umount, &unmounted); /* * At this point p->mnt_ns is NULL, notification will be queued @@ -1976,11 +2028,16 @@ static int do_umount(struct mount *mnt, int flags) * detach_mounts allows lazily unmounting those mounts instead of * leaking them. * + * The dentry is unhashed before the mounts go so that no lookup finds + * what they covered. The caller removes it for good afterwards. + * * The caller may hold dentry->d_inode->i_rwsem. */ void __detach_mounts(struct dentry *dentry) { struct pinned_mountpoint mp = {}; + struct mnt_cover *cover; + struct hlist_node *n; struct mount *mnt; guard(namespace_excl)(); @@ -1989,15 +2046,16 @@ void __detach_mounts(struct dentry *dentry) if (!lookup_mountpoint(dentry, &mp)) return; + /* the name goes first, what covered it goes second */ + d_drop(dentry); event++; while (mp.node.next) { mnt = hlist_entry(mp.node.next, struct mount, mnt_mp_list); - if (mnt->mnt.mnt_flags & MNT_UMOUNT) { - umount_mnt(mnt); - hlist_add_head(&mnt->mnt_umount, &unmounted); - } - else umount_tree(mnt, UMOUNT_CONNECTED); + umount_tree(mnt, UMOUNT_COVER); } + /* the dentry goes away, so do the covers left behind on it */ + hlist_for_each_entry_safe(cover, n, &mp.mp->m_covers, pin) + drop_cover(cover, &ex_mountpoints); unpin_mountpoint(&mp); } @@ -2294,9 +2352,11 @@ void drop_collected_paths(const struct path *paths, const struct path *prealloc) static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool); +/* Consumes the caller's reference to @mnt. */ void dissolve_on_fput(struct vfsmount *mnt) { - struct mount *m = real_mount(mnt); + struct vfsmount *p __free(mntput) = mnt; + struct mount *m = real_mount(p); /* * m used to be the root of anon namespace; if it still is one, @@ -2319,13 +2379,14 @@ void dissolve_on_fput(struct vfsmount *mnt) emptied_ns = m->mnt_ns; lock_mount_hash(); - umount_tree(m, UMOUNT_CONNECTED); + umount_tree(m, UMOUNT_COVER); unlock_mount_hash(); + mntput(no_free_ptr(p)); } } /* locks: namespace_shared && pinned(mnt) || mount_locked_reader */ -static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) +bool has_locked_children(struct mount *mnt, struct dentry *dentry) { struct mount *child; @@ -2339,10 +2400,16 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) return false; } -bool has_locked_children(struct mount *mnt, struct dentry *dentry) +/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */ +static bool __has_children(struct mount *mnt, struct dentry *dentry) { - guard(mount_locked_reader)(); - return __has_locked_children(mnt, dentry); + struct mount *child; + + list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { + if (is_subdir(child->mnt_mountpoint, dentry)) + return true; + } + return false; } /* @@ -2401,7 +2468,7 @@ struct vfsmount *clone_private_mount(const struct path *path) if (!ns_capable(old_mnt->mnt_ns->user_ns, CAP_SYS_ADMIN)) return ERR_PTR(-EPERM); - if (__has_locked_children(old_mnt, path->dentry)) + if (has_locked_children(old_mnt, path->dentry)) return ERR_PTR(-EINVAL); new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE); @@ -2564,11 +2631,11 @@ enum mnt_tree_flags_t { static int attach_recursive_mnt(struct mount *source_mnt, const struct pinned_mountpoint *dest) { - struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns; struct mount *dest_mnt = dest->parent; struct mountpoint *dest_mp = dest->mp; HLIST_HEAD(tree_list); struct mnt_namespace *ns = dest_mnt->mnt_ns; + struct user_namespace *user_ns = ns->user_ns; struct pinned_mountpoint root = {}; struct mountpoint *shorter = NULL; struct mount *child, *p; @@ -2577,13 +2644,31 @@ static int attach_recursive_mnt(struct mount *source_mnt, int err = 0; bool moving = mnt_has_parent(source_mnt); + /* + * A caller in an unprivileged mount namespaces may trigger an + * automount and propagate locked mounts into privileged mount + * namespaces. Take ownership from the target mount namespace. + * It's equivalent for everything but the automount case. + * + * Detached trees in anonymous mount namespaces by be handed + * over via SCM_RIGHTS or inherited in other ways on purpose + * the attaching task's mount namespace is authoritative, not + * the creator of the detached tree. + */ + if (is_anon_ns(ns)) + user_ns = current->nsproxy->mnt_ns->user_ns; + else + user_ns = ns->user_ns; + /* * Preallocate a mountpoint in case the new mounts need to be * mounted beneath mounts on the same mountpoint. */ - for (top = source_mnt; unlikely(top->overmount); top = top->overmount) { + for (top = source_mnt; ; top = top->overmount) { if (!shorter && is_mnt_ns_file(top->mnt.mnt_root)) shorter = top->mnt_mp; + if (likely(!top->overmount)) + break; } err = get_mountpoint(top->mnt.mnt_root, &root); if (err) @@ -2654,15 +2739,17 @@ static int attach_recursive_mnt(struct mount *source_mnt, /* * If @q was locked it was meant to hide * whatever was under it. Let @child take over - * that job and lock it, then we can unlock @q. - * That'll allow another namespace to shed @q - * and reveal @child. Clearly, that mounter - * consented to this by not severing the mount - * relationship. Otherwise, what's the point. + * that job and lock it. If @child is the mount + * the caller placed we can then unlock @q: + * nothing another namespace does removes it + * again. A propagated copy goes away when the + * mounter of the original unmounts it, so @q + * keeps its lock. */ if (IS_MNT_LOCKED(q)) { child->mnt.mnt_flags |= MNT_LOCKED; - q->mnt.mnt_flags &= ~MNT_LOCKED; + if (child == source_mnt) + q->mnt.mnt_flags &= ~MNT_LOCKED; } mnt_change_mountpoint(r, mp, q); } @@ -2759,6 +2846,11 @@ static void do_lock_mount(const struct path *path, scoped_guard(mount_locked_reader) { m = where_to_mount(path, &dentry, beneath); + /* sticky, so it takes no locks to refuse it */ + if (unlikely(cant_mount(dentry))) { + res->parent = ERR_PTR(-ENOENT); + return; + } if (&m->mnt != path->mnt) { mntget(&m->mnt); dget(dentry); @@ -2989,7 +3081,14 @@ static struct mount *__do_loopback(const struct path *old_path, if (!may_copy_tree(old_path)) return ERR_PTR(-EINVAL); - if (!recurse && __has_locked_children(old, old_path->dentry)) + /* a pseudo dentry is freed without an RCU delay, no walk may find it */ + if (old_path->dentry->d_flags & DCACHE_NORCU) + return ERR_PTR(-EINVAL); + + if (recurse && !old->mnt_ns) + return ERR_PTR(-EINVAL); + + if (!recurse && has_locked_children(old, old_path->dentry)) return ERR_PTR(-EINVAL); if (recurse) @@ -3028,7 +3127,11 @@ static int do_loopback(const struct path *path, const char *old_name, if (IS_ERR(mnt)) return PTR_ERR(mnt); - err = graft_tree(mnt, &mp); + /* the copy may carry mount namespace files from below the source */ + if (recurse && !check_for_nsfs_mounts(mnt)) + err = -EINVAL; + else + err = graft_tree(mnt, &mp); if (err) { lock_mount_hash(); umount_tree(mnt, UMOUNT_SYNC); @@ -3088,7 +3191,7 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags) path->mnt = mntget(&ns->root->mnt); file = dentry_open(path, O_PATH, current_cred()); if (IS_ERR(file)) - dissolve_on_fput(path->mnt); + dissolve_on_fput(no_free_ptr(path->mnt)); else file->f_mode |= FMODE_NEED_UNMOUNT; return file; @@ -3110,12 +3213,18 @@ static struct mnt_namespace *create_new_namespace(struct path *path, struct mount *mnt; unsigned int copy_flags = 0; bool locked = false, recurse = flags & MOUNT_COPY_RECURSIVE; + bool foreign = user_ns != ns->user_ns; if (unlikely(!d_can_lookup(path->dentry))) return ERR_PTR(-ENOTDIR); - if (user_ns != ns->user_ns) - copy_flags |= CL_SLAVE; + /* + * Without privileges over the mount namespace the copy is made from + * nothing mounted below @path may be left out. It would reveal what + * it covers. That's what unshare() gives such a caller as well. + */ + if (foreign) + copy_flags |= CL_SLAVE | CL_COPY_UNBINDABLE; new_ns = alloc_mnt_ns(user_ns, false); if (IS_ERR(new_ns)) @@ -3149,10 +3258,14 @@ static struct mnt_namespace *create_new_namespace(struct path *path, /* * We don't emulate unshare()ing a mount namespace. We stick to * the restrictions of creating detached bind-mounts. It has a - * lot saner and simpler semantics. + * lot saner and simpler semantics. A caller without privileges + * over the mount namespace can't leave out any child though. */ if (flags & MOUNT_COPY_NEW) mnt = clone_mnt(real_mount(path->mnt), path->dentry, copy_flags); + else if (foreign && !recurse && + __has_children(real_mount(path->mnt), path->dentry)) + mnt = ERR_PTR(-EINVAL); else mnt = __do_loopback(path, recurse, copy_flags); scoped_guard(mount_writer) { @@ -3169,7 +3282,7 @@ static struct mnt_namespace *create_new_namespace(struct path *path, * of the real rootfs we created. */ attach_mnt(mnt, new_ns_root, mp.mp); - if (user_ns != ns->user_ns) + if (foreign) lock_mnt_tree(new_ns_root); } @@ -3463,11 +3576,11 @@ static int do_set_group(const struct path *from_path, const struct path *to_path return -EINVAL; /* From mount should not have locked children in place of To's root */ - if (__has_locked_children(from, to->mnt.mnt_root)) + if (has_locked_children(from, to->mnt.mnt_root)) return -EINVAL; /* Setting sharing groups is only allowed on private mounts */ - if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to)) + if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to) || IS_MNT_UNBINDABLE(to)) return -EINVAL; /* From should not be private */ @@ -3743,7 +3856,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp if (!(mnt_flags & MNT_SHRINKABLE)) return -EINVAL; /* ... and for those we'd better have mountpoint still alive */ - if (!parent->mnt_ns) + if (!is_mounted(&parent->mnt)) return -EINVAL; } @@ -3953,6 +4066,8 @@ void mark_mounts_for_expiry(struct list_head *mounts) list_for_each_entry_safe(mnt, next, mounts, mnt_expire) { if (!is_mounted(&mnt->mnt)) continue; + /* lock_mnt_tree() leaves expirable mounts alone */ + VFS_WARN_ON_ONCE(IS_MNT_LOCKED(mnt)); if (!xchg(&mnt->mnt_expiry_mark, 1) || propagate_mount_busy(mnt, 1)) continue; @@ -3960,6 +4075,11 @@ void mark_mounts_for_expiry(struct list_head *mounts) } while (!list_empty(&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire); + /* an earlier umount_tree() may have moved a busy mount here */ + if (propagate_mount_busy(mnt, 1)) { + list_move(&mnt->mnt_expire, mounts); + continue; + } touch_mnt_namespace(mnt->mnt_ns); umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); } @@ -3967,17 +4087,38 @@ void mark_mounts_for_expiry(struct list_head *mounts) EXPORT_SYMBOL_GPL(mark_mounts_for_expiry); +/* + * Unmount @mnt if it's a shrinkable mount without children that nobody uses. + * + * mount_lock must be held for write + */ +static bool shrink_submount(struct mount *mnt) +{ + /* not the kernel's to remove either */ + if (IS_MNT_LOCKED(mnt) || propagate_mount_busy(mnt, 1)) + return false; + touch_mnt_namespace(mnt->mnt_ns); + umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); + return true; +} + /* * Ripoff of 'select_parent()' * - * search the list of submounts for a given mountpoint, and move any - * shrinkable submounts to the 'graveyard' list. + * unmount the shrinkable submounts of @parent that aren't busy, children + * before their parent, and say whether anything went + * + * The cursor into the children of @this_parent survives the umount of a + * child mount without child mounts. The mounts that get umounted together with + * it are located under receiving mounts of @this_parent and never under + * @this_parent itself. The one exception is @this_parent getting unmounted + * then the walk starts over. */ -static int select_submounts(struct mount *parent, struct list_head *graveyard) +static bool __shrink_submounts(struct mount *parent) { struct mount *this_parent = parent; struct list_head *next; - int found = 0; + bool shrunk = false; repeat: next = this_parent->mnt_mounts.next; @@ -3996,42 +4137,45 @@ static int select_submounts(struct mount *parent, struct list_head *graveyard) this_parent = mnt; goto repeat; } - - if (!propagate_mount_busy(mnt, 1)) { - list_move_tail(&mnt->mnt_expire, graveyard); - found++; - } + if (!shrink_submount(mnt)) + continue; + shrunk = true; + if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT)) + return true; } /* * All done at this level ... ascend and resume the search */ if (this_parent != parent) { - next = this_parent->mnt_child.next; - this_parent = this_parent->mnt_parent; + struct mount *mnt = this_parent; + + next = mnt->mnt_child.next; + this_parent = mnt->mnt_parent; + /* its children are gone, maybe it can go as well */ + if (shrink_submount(mnt)) { + shrunk = true; + if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT)) + return true; + } goto resume; } - return found; + return shrunk; } /* - * process a list of expirable mountpoints with the intent of discarding any - * submounts of a specific parent mountpoint + * unmount the shrinkable submounts of @mnt that aren't busy + * + * The busy check and the umount of a mount are adjacent. An umount can + * still empty or move a mount in a part of the tree that was walked + * already, so walk again until nothing goes. * * mount_lock must be held for write */ static void shrink_submounts(struct mount *mnt) { - LIST_HEAD(graveyard); - struct mount *m; - - /* extract submounts of 'mountpoint' from the expiration list */ - while (select_submounts(mnt, &graveyard)) { - while (!list_empty(&graveyard)) { - m = list_first_entry(&graveyard, struct mount, - mnt_expire); - touch_mnt_namespace(m->mnt_ns); - umount_tree(m, UMOUNT_PROPAGATE|UMOUNT_SYNC); - } + for (;;) { + if (!__shrink_submounts(mnt)) + break; } } @@ -4196,6 +4340,8 @@ static void free_mnt_ns(struct mnt_namespace *ns) if (!is_anon_ns(ns)) ns_common_free(ns); dec_mnt_namespaces(ns->ucounts); + /* the last active reference is gone, no mark can show up anymore */ + fsnotify_mntns_delete(ns); mnt_ns_tree_remove(ns); } @@ -4545,7 +4691,7 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, FD_PREPARE(fdf, (flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0, dentry_open(&new_path, O_PATH, fc->cred)); if (fdf.err) { - dissolve_on_fput(new_path.mnt); + dissolve_on_fput(no_free_ptr(new_path.mnt)); return fdf.err; } @@ -4697,14 +4843,15 @@ int path_pivot_root(struct path *new, struct path *old) new_mnt = real_mount(new->mnt); root_mnt = real_mount(root.mnt); + /* only a mounted mount has a parent that namespace_sem pins */ + if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) + return -EINVAL; ex_parent = new_mnt->mnt_parent; root_parent = root_mnt->mnt_parent; if (IS_MNT_SHARED(old_mnt) || IS_MNT_SHARED(ex_parent) || IS_MNT_SHARED(root_parent)) return -EINVAL; - if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) - return -EINVAL; if (new_mnt->mnt.mnt_flags & MNT_LOCKED) return -EINVAL; if (d_unlinked(new->dentry)) @@ -4955,6 +5102,11 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr) * propagation. */ namespace_lock(); + /* invent_group_ids() walks the tree, only walk a mounted one */ + if (!anon_ns_root(mnt) && !check_mnt(mnt)) { + namespace_unlock(); + return -EINVAL; + } if (kattr->propagation == MS_SHARED) { err = invent_group_ids(mnt, kattr->kflags & MOUNT_KATTR_RECURSE); if (err) { @@ -4967,6 +5119,7 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr) err = -EINVAL; lock_mount_hash(); + /* Checked under namespace_sem already if the propagation changes. */ if (!anon_ns_root(mnt) && !check_mnt(mnt)) goto out; @@ -5319,15 +5472,25 @@ static void statmount_sb_basic(struct kstatmount *s) s->sm.sb_flags = sb->s_flags & (SB_RDONLY|SB_SYNCHRONOUS|SB_DIRSYNC|SB_LAZYTIME); } +static void statmount_mnt_parent(struct kstatmount *s, const struct mount *m) +{ + s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique; + s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id; +} + static void statmount_mnt_basic(struct kstatmount *s) { struct mount *m = real_mount(s->mnt); s->sm.mask |= STATMOUNT_MNT_BASIC; s->sm.mnt_id = m->mnt_id_unique; - s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique; s->sm.mnt_id_old = m->mnt_id; - s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id; + /* An unmounted mount is cut loose from its parent under mount_lock alone. */ + if (likely(is_mounted(s->mnt))) + statmount_mnt_parent(s, m); + else + scoped_guard(mount_locked_reader) + statmount_mnt_parent(s, m); s->sm.mnt_attr = mnt_to_attr_flags(&m->mnt); s->sm.mnt_propagation = mnt_to_propagation_flags(m); s->sm.mnt_peer_group = m->mnt_group_id; @@ -6199,6 +6362,25 @@ static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt, attach_mnt(real_mount(mnt), mp.parent, mp.mp); } +static struct vfsmount *__init knullfs_file_mount(void) +{ + struct dentry *file; + struct mount *mnt; + + file = nullfs_new_file(knullfs->mnt_sb); + if (IS_ERR(file)) + return ERR_CAST(file); + mnt = clone_mnt(real_mount(knullfs), file, CL_PRIVATE); + dput(file); + if (IS_ERR(mnt)) + return ERR_CAST(mnt); + mnt->mnt_ns = MNT_NS_INTERNAL; + mnt->mnt.mnt_flags |= MNT_INTERNAL; + mnt->mnt.mnt_flags &= ~MNT_READONLY; + dont_mount(mnt->mnt.mnt_root); + return &mnt->mnt; +} + static void __init init_mount_tree(void) { struct vfsmount *mnt, *nullfs_mnt; @@ -6210,10 +6392,14 @@ static void __init init_mount_tree(void) * * (1) nullfs with mount id 1 * (2) mutable rootfs with mount id 2 - * (3) private nullfs for kthreads (SB_KERNMOUNT) + * (3) private nullfs for kthreads (SB_KERNMOUNT), kept in knullfs + * (4) a second mount of (3) rooted on a regular file, kept in + * knullfs_file * * with (2) mounted on top of (1). The init_task's root and pwd * are pointed at (3) so all kthreads start isolated in nullfs. + * A lookup at the cover an unmounted mount left behind finds (3) + * or (4), see __lookup_mnt(). */ nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL); if (IS_ERR(nullfs_mnt)) @@ -6245,11 +6431,18 @@ static void __init init_mount_tree(void) init_mnt_ns.nr_mounts++; } - nullfs_mnt = kern_mount(&nullfs_fs_type); - if (IS_ERR(nullfs_mnt)) + knullfs = kern_mount(&nullfs_fs_type); + if (IS_ERR(knullfs)) panic("VFS: Failed to create private nullfs instance"); - root.mnt = nullfs_mnt; - root.dentry = nullfs_mnt->mnt_root; + /* nothing is ever mounted on the root of a kernel thread */ + dont_mount(knullfs->mnt_root); + /* and nothing is ever written through it */ + knullfs->mnt_flags |= MNT_READONLY; + knullfs_file = knullfs_file_mount(); + if (IS_ERR(knullfs_file)) + panic("VFS: Failed to create the nullfs file stand-in"); + root.mnt = knullfs; + root.dentry = knullfs->mnt_root; init_task.nsproxy->mnt_ns = &init_mnt_ns; get_mnt_ns(&init_mnt_ns); diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 105194de6e13e0..e6506942aeda19 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -541,6 +541,13 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) ret = netfs_wait_for_read(rreq); if (ret >= 0) { + if (ret < flen) { + struct iov_iter iter; + + iov_iter_bvec(&iter, ITER_DEST, bvec, i, flen); + iov_iter_advance(&iter, ret); + iov_iter_zero(flen - ret, &iter); + } if (group) folio_change_private(folio, group); else diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index 2cdb68e6b16fff..ecf119b4f91660 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -469,6 +469,7 @@ ssize_t netfs_buffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *fr struct netfs_group *netfs_group) { struct file *file = iocb->ki_filp; + struct inode *inode = file_inode(file); ssize_t ret; trace_netfs_write_iter(iocb, from); @@ -481,6 +482,14 @@ ssize_t netfs_buffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *fr if (ret) return ret; + if (iocb->ki_pos > i_size_read(inode)) { + ret = netfs_clear_stale_pre_isize(inode, i_size_read(inode), + iocb->ki_pos, + iocb->ki_flags & IOCB_NOWAIT); + if (ret) + return ret; + } + return netfs_perform_write(iocb, from, netfs_group); } EXPORT_SYMBOL(netfs_buffered_write_iter_locked); diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 2361277416c730..8be74706d85e2a 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -359,6 +359,15 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from) ret = file_update_time(file); if (ret < 0) goto out; + + if (iocb->ki_pos > i_size_read(inode)) { + ret = netfs_clear_stale_pre_isize(inode, i_size_read(inode), + iocb->ki_pos, + iocb->ki_flags & IOCB_NOWAIT); + if (ret < 0) + goto out; + } + if (iocb->ki_flags & IOCB_NOWAIT) { /* We could block if there are any pages in the range. */ ret = -EAGAIN; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index c79c8e69d60ca7..786af76da98c25 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -80,6 +80,8 @@ ssize_t netfs_wait_for_write(struct netfs_io_request *rreq); void netfs_wait_for_paused_read(struct netfs_io_request *rreq); void netfs_wait_for_paused_write(struct netfs_io_request *rreq); void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq); +int netfs_clear_stale_pre_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait); /* * objects.c diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index f5c1c463f4ff7a..523057390d6f4b 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -6,6 +6,7 @@ */ #include +#include #include "internal.h" /** @@ -582,3 +583,101 @@ void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq) trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs); finish_wait(&rreq->waitq, &myself); } + +/** + * netfs_clear_stale_isize - Clear stale pagecache in a to-be-created hole + * @inode: The inode to act upon. + * @from: The base of the hole to be made. + * @to: The top of the hole to be made. + * @nowait: True to return -EAGAIN rather than block. + * @exclusive: True if the caller holds i_rwsem exclusively for the resize. + * + * Zero any data left in the pagecache within the [@from, @to) hole by a + * write through an mmap so that it isn't exposed as file content once the + * file is extended. Only the uptodate folio straddling @from can hold such + * data as pages wholly beyond the EOF can't be faulted in, so the zeroing + * is limited to that folio. The folio is zeroed rather than dropped so + * that a concurrent extending write can't lose data. + * + * If @exclusive is false, @from is re-read from i_size and used to clamp + * the zeroed range, for callers that may race with another writer also + * extending the file (eg. multiple buffered writes extending the same file + * under a shared i_rwsem). If @exclusive is true, for callers that hold + * i_rwsem exclusively across the whole resize and have already updated + * i_size to @to, staleness is decided from the folio's dirty state instead: + * since no genuine concurrent buffered writer can be racing, a lockless + * stat() adopting a server-confirmed size mid-resize has no data behind it + * and never dirties the folio, so it can't fool this check into skipping + * the zeroing the way it could fool the @exclusive false clamp. + * + * pagecache_isize_extended() can't be reused here: it is keyed on a + * sub-page block size (a no-op when the block size is >= PAGE_SIZE, as on + * network filesystems), runs after i_size is updated, can't honour + * @nowait, and doesn't wait for writeback. Keep the two in sync if either + * is changed. + * + * Return: 0 on success, or -EAGAIN if @nowait is set and the folio is + * mapped or under writeback and so can't be cleaned without blocking. + */ +static int netfs_clear_stale_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait, bool exclusive) +{ + struct address_space *mapping = inode->i_mapping; + fgf_t fgp = FGP_LOCK; + struct folio *folio; + int ret; + + if (from >= to) + return 0; + + if (nowait) + fgp |= FGP_NOWAIT; + + folio = __filemap_get_folio(mapping, from >> PAGE_SHIFT, fgp, 0); + if (IS_ERR(folio)) + return PTR_ERR(folio) == -EAGAIN ? -EAGAIN : 0; + + ret = 0; + if (nowait && (folio_mapped(folio) || folio_test_writeback(folio))) { + ret = -EAGAIN; + goto out; + } + + folio_wait_writeback(folio); + + if (folio_mkclean(folio)) + folio_mark_dirty(folio); + + if (folio_test_uptodate(folio) && + (!exclusive || folio_test_dirty(folio))) { + uoff_t fpos = folio_pos(folio); + + if (!exclusive) + from = umax(from, i_size_read(inode)); + if (from < to && from < fpos + folio_size(folio)) { + size_t end = umin(to - fpos, folio_size(folio)); + size_t offset = from - fpos; + + folio_zero_segment(folio, offset, end); + } + } +out: + folio_unlock(folio); + folio_put(folio); + return ret; +} + +/* Clear stale pagecache before an extending buffered/DIO write. */ +int netfs_clear_stale_pre_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait) +{ + return netfs_clear_stale_isize(inode, from, to, nowait, false); +} + +/* Clear stale pagecache when extending a file under an exclusive resize. */ +void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to) +{ + netfs_clear_stale_isize(inode, from, to, false, true); +} +EXPORT_SYMBOL(netfs_clear_stale_post_isize); diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index a94197ef018113..c5bfaf2c6f2592 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -33,6 +33,22 @@ static void netfs_clear_unread(struct netfs_io_subrequest *subreq) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); } +static void netfs_clear_unread_dio(struct netfs_io_subrequest *subreq) +{ + uoff_t pos = subreq->start + subreq->transferred; + struct netfs_io_request *rreq = subreq->rreq; + size_t fill; + + if (pos >= rreq->i_size) + return; + + fill = min_t(uoff_t, rreq->i_size - pos, + subreq->len - subreq->transferred); + + netfs_reset_iter(subreq); + subreq->transferred += iov_iter_zero(fill, &subreq->io_iter); +} + /* * Cancel the copy-to-cache mark on a folio. */ @@ -311,6 +327,14 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) netfs_read_unlock_folios(rreq, ¬es); } else { + if (!(notes & HIT_PENDING) && + front->error == 0 && + transferred < front->len && + test_bit(NETFS_SREQ_CLEAR_TAIL, &front->flags)) { + netfs_clear_unread_dio(front); + transferred = front->transferred; + trace_netfs_sreq(front, netfs_sreq_trace_clear); + } stream->collected_to = front->start + transferred; rreq->collected_to = stream->collected_to; } diff --git a/fs/notify/mark.c b/fs/notify/mark.c index b2640d836a712f..d17628580a5703 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -903,6 +903,10 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, void *obj, if (WARN_ON(!fsnotify_valid_obj_type(obj_type))) return -EINVAL; + /* the filesystem doesn't want its objects watched */ + if (sb && (sb->s_type->fs_flags & FS_DISALLOW_NOTIFY)) + return -EINVAL; + /* * Attach the sb info before attaching a connector to any object on sb. * The sb info will remain attached as long as sb lives. diff --git a/fs/nullfs.c b/fs/nullfs.c index e06352c7b2ccff..e7221bc8aedb62 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "mount.h" @@ -10,6 +12,121 @@ static const struct super_operations nullfs_super_operations = { .statfs = simple_statfs, }; +static loff_t nullfs_dir_llseek(struct file *file, loff_t offset, int whence) +{ + /* an empty directory has two entries . and .. at offsets 0 and 1 */ + return generic_file_llseek_size(file, offset, whence, 2, 2); +} + +static int nullfs_dir_readdir(struct file *file, struct dir_context *ctx) +{ + dir_emit_dots(file, ctx); + return 0; +} + +/* the one inode of nullfs is shared by every holder, so no locks on it */ +static int nullfs_nolock(struct file *file, int cmd, struct file_lock *fl) +{ + return -ENOLCK; +} + +/* what libfs gives an empty directory, plus the refusal of file locks */ +static const struct file_operations nullfs_dir_operations = { + .llseek = nullfs_dir_llseek, + .read = generic_read_dir, + .iterate_shared = nullfs_dir_readdir, + .fsync = noop_fsync, + .lock = nullfs_nolock, + .flock = nullfs_nolock, + .fop_flags = FOP_IMMUTABLE, +}; + +static ssize_t nullfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + return 0; +} + +static ssize_t nullfs_file_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, + size_t len, unsigned int flags) +{ + return 0; +} + +static ssize_t nullfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + size_t count = iov_iter_count(from); + + iov_iter_advance(from, count); + return count; +} + +static int nullfs_pipe_to_null(struct pipe_inode_info *pipe, + struct pipe_buffer *buf, struct splice_desc *sd) +{ + return sd->len; +} + +static ssize_t nullfs_file_splice_write(struct pipe_inode_info *pipe, + struct file *out, loff_t *ppos, + size_t len, unsigned int flags) +{ + return splice_from_pipe(pipe, out, ppos, len, flags, nullfs_pipe_to_null); +} + +static int nullfs_file_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) +{ + if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) + return -EPERM; + return 0; +} + +static const struct inode_operations nullfs_file_inode_operations = { + .setattr = nullfs_file_setattr, +}; + +static const struct file_operations nullfs_file_operations = { + .llseek = generic_file_llseek, + .read_iter = nullfs_file_read_iter, + .write_iter = nullfs_file_write_iter, + .splice_read = nullfs_file_splice_read, + .splice_write = nullfs_file_splice_write, + .fsync = noop_fsync, + .lock = nullfs_nolock, + .flock = nullfs_nolock, +}; + +/* + * An empty regular file on @sb as a dentry of its own, never hashed under + * the root so no lookup finds it. + */ +struct dentry *nullfs_new_file(struct super_block *sb) +{ + struct dentry *dentry; + struct inode *inode; + + inode = new_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + /* the root directory is 1 */ + inode->i_ino = 2; + inode->i_mode = S_IFREG | 0666; + inode->i_op = &nullfs_file_inode_operations; + inode->i_fop = &nullfs_file_operations; + simple_inode_init_ts(inode); + inode->i_flags |= S_NOATIME; + + dentry = d_alloc_anon(sb); + if (!dentry) { + iput(inode); + return ERR_PTR(-ENOMEM); + } + d_instantiate(dentry, inode); + return dentry; +} + static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) { struct inode *inode; @@ -30,10 +147,11 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) /* nullfs is permanently empty... */ make_empty_dir_inode(inode); + inode->i_fop = &nullfs_dir_operations; simple_inode_init_ts(inode); inode->i_ino = 1; - /* ... and immutable. */ - inode->i_flags |= S_IMMUTABLE; + /* ... and immutable, reading it leaves no trace either. */ + inode->i_flags |= S_IMMUTABLE | S_NOATIME; s->s_root = d_make_root(inode); if (!s->s_root) @@ -61,6 +179,7 @@ static int nullfs_init_fs_context(struct fs_context *fc) struct file_system_type nullfs_fs_type = { .name = "nullfs", + .fs_flags = FS_DISALLOW_NOTIFY, .init_fs_context = nullfs_init_fs_context, .kill_sb = kill_anon_super, }; diff --git a/fs/pnode.c b/fs/pnode.c index 5d91c3e58d2a35..5f07fa14099bff 100644 --- a/fs/pnode.c +++ b/fs/pnode.c @@ -410,19 +410,99 @@ bool propagation_would_overmount(const struct mount *from, return false; } +/* Does @m receive propagation from @parent? */ +static bool receives_from(struct mount *m, struct mount *parent) +{ + if (m == parent) + return false; + for (; m; m = m->mnt_master) + if (m == parent || peers(m, parent)) + return true; + return false; +} + +/* + * Does @m receive propagation from the victim's parent as well? If so, then + * the mount at the victim's mountpoint inside of @m is a umount candidate as + * well. So it's the next candidate in the chain. Otherwise the chain ends at + * @m. + */ +static struct mount *next_candidate(struct mount *m, struct mount *victim) +{ + if (!receives_from(m, victim->mnt_parent)) + return NULL; + return __lookup_mnt(&m->mnt, victim->mnt_mountpoint); +} + +/* + * Would propagate_umount() pull out a mount of the chain of candidates that + * starts at @c, and does that mount have references beyond its own? + * + * This mirrors how trim_one(), trim_ancestors() and handle_locked() handle a + * synchronous umount: + * + * - single victim + * - without children + * - with MNT_LOCKED already cleared on every candidate by propagate_mount_unlock() + * + * A copy of the victim gets unmounted when each of its children is + * the next candidate in the chain or its overmount, unless the next + * unmount candidate is not its overmount and some unmount candidate further + * down has a child outside the chain. Keep this in sync with + * Documentation/filesystems/propagate_umount.txt. + */ +static bool chain_busy(struct mount *c, struct mount *victim) +{ + struct mount *m, *n, *next, *deepest = NULL; + bool above; + + /* the deepest candidate with a child outside the chain */ + for (m = c; m; m = next) { + next = next_candidate(m, victim); + list_for_each_entry(n, &m->mnt_mounts, mnt_child) { + if (n != next && n != victim) { + deepest = m; + break; + } + } + } + + above = deepest != NULL; /* @deepest is at or below @m */ + for (m = c; m; m = next) { + bool goes = true; + + next = next_candidate(m, victim); + list_for_each_entry(n, &m->mnt_mounts, mnt_child) { + if (n != next && n != m->overmount && n != victim) { + goes = false; + break; + } + } + if (goes && next && next != m->overmount && above && m != deepest) + goes = false; + if (m == deepest) + above = false; + if (goes && do_refcount_check(m, 1)) + return true; + } + return false; +} + /* * check if the mount 'mnt' can be unmounted successfully. * @mnt: the mount to be checked for unmount * NOTE: unmounting 'mnt' would naturally propagate to all * other mounts its parent propagates to. - * Check if any of these mounts that **do not have submounts** - * have more references than 'refcnt'. If so return busy. + * Check if any of the mounts that propagate_umount() would pull out + * along with it have more references than their own. If so return busy. * * vfsmount lock must be held for write */ int propagate_mount_busy(struct mount *mnt, int refcnt) { struct mount *parent = mnt->mnt_parent; + struct dentry *mp = mnt->mnt_mountpoint; + struct mount *m; /* * quickly check if the current mount can be unmounted. @@ -435,24 +515,16 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) if (mnt == parent) return 0; - for (struct mount *m = propagation_next(parent, parent); m; - m = propagation_next(m, parent)) { - struct list_head *head; - struct mount *child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint); + /* the candidates are the mounts at @mp below the receivers */ + for (m = propagation_next(parent, parent); m; + m = propagation_next(m, parent)) { + struct mount *c = __lookup_mnt(&m->mnt, mp); - if (!child) + /* each chain once, from its top: skip receivers that are candidates */ + if (!c || (mnt_has_parent(m) && m->mnt_mountpoint == mp && + receives_from(m->mnt_parent, parent))) continue; - - head = &child->mnt_mounts; - if (!list_empty(head)) { - /* - * a mount that covers child completely wouldn't prevent - * it being pulled out; any other would. - */ - if (!list_is_singular(head) || !child->overmount) - continue; - } - if (do_refcount_check(child, 1)) + if (chain_busy(c, mnt)) return 1; } return 0; @@ -625,8 +697,8 @@ static void handle_locked(struct mount *m, struct list_head *to_umount) * the same parent and mountpoint; that will be remedied as soon as we * return from propagate_umount() - its caller (umount_tree()) will detach * the stack from the parent it (and now @m) is attached to. umount_tree() - * might choose to keep unmounted pieces stuck to each other, but it always - * detaches them from the mounts that remain in the tree. + * detaches every unmounted mount from its parent, whether that parent + * remains in the tree or not. */ static void reparent(struct mount *m) { diff --git a/fs/readdir.c b/fs/readdir.c index 76bb1ae3a45041..f2288841337ad1 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -87,6 +87,8 @@ EXPORT_SYMBOL(wrap_directory_iterator); int iterate_dir(struct file *file, struct dir_context *ctx) { struct inode *inode = file_inode(file); + /* never an entry, never removed: nothing for the lock to keep still */ + bool locked = !(file->f_op->fop_flags & FOP_IMMUTABLE); int res = -ENOTDIR; if (!file->f_op->iterate_shared) @@ -100,9 +102,11 @@ int iterate_dir(struct file *file, struct dir_context *ctx) if (res) goto out; - res = down_read_killable(&inode->i_rwsem); - if (res) - goto out; + if (locked) { + res = down_read_killable(&inode->i_rwsem); + if (res) + goto out; + } res = -ENOENT; if (!IS_DEADDIR(inode)) { @@ -112,7 +116,8 @@ int iterate_dir(struct file *file, struct dir_context *ctx) fsnotify_access(file); file_accessed(file); } - inode_unlock_shared(inode); + if (locked) + inode_unlock_shared(inode); out: return res; } diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 7ecd70efdfea89..98b610b2a11428 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1412,6 +1412,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, * server could even support copy of range where source = target */ lock_two_nondirectories(target_inode, src_inode); + filemap_invalidate_lock(target_inode->i_mapping); if (len == 0) { loff_t src_size = i_size_read(src_inode); @@ -1467,9 +1468,15 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, i_size = target_inode->i_size; spin_unlock(&target_inode->i_lock); - /* Discard all the folios that overlap the destination region. */ + /* + * Discard all the folios that overlap the destination region. Start at + * the old EOF when extending so the folio straddling it, which may hold + * data written past EOF through an mmap, is dropped too. + */ cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend); - truncate_inode_pages_range(&target_inode->i_data, fstart, fend); + truncate_inode_pages_range(&target_inode->i_data, + min(fstart, i_size), fend); + netfs_wait_for_outstanding_io(target_inode); fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size, 0); @@ -1508,6 +1515,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, if (rc) CIFS_I(target_inode)->time = 0; unlock: + filemap_invalidate_unlock(target_inode->i_mapping); /* although unlocking in the reverse order from locking is not strictly necessary here it is a little cleaner to be consistent */ unlock_two_nondirectories(src_inode, target_inode); @@ -1531,6 +1539,9 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct cifs_tcon *target_tcon; ssize_t rc; + if (len == 0) + return 0; + cifs_dbg(FYI, "copychunk range\n"); if (!src_file->private_data || !dst_file->private_data) { @@ -1560,6 +1571,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, * server could even support copy of range where source = target */ lock_two_nondirectories(target_inode, src_inode); + filemap_invalidate_lock(target_inode->i_mapping); cifs_dbg(FYI, "about to flush pages\n"); @@ -1581,10 +1593,28 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, /* Flush and invalidate all the folios in the destination region. If * the copy was successful, then some of the flush is extra overhead, * but we need to allow for the copy failing in some way (eg. ENOSPC). + * + * Start at the old EOF when extending so the folio straddling it, which + * may hold data written past EOF through an mmap, is dropped too. */ - rc = filemap_invalidate_inode(target_inode, true, destoff, destoff + len - 1); - if (rc) - goto unlock; + if (target_inode->i_mapping->nrpages) { + loff_t fstart = min(destoff, i_size_read(target_inode)); + loff_t fend = destoff + len - 1; + + unmap_mapping_pages(target_inode->i_mapping, + fstart >> PAGE_SHIFT, + (fend >> PAGE_SHIFT) - + (fstart >> PAGE_SHIFT) + 1, + false); + rc = filemap_write_and_wait_range(target_inode->i_mapping, + fstart, fend); + if (rc) + goto unlock; + invalidate_inode_pages2_range(target_inode->i_mapping, + fstart >> PAGE_SHIFT, + fend >> PAGE_SHIFT); + } + netfs_wait_for_outstanding_io(target_inode); fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size_read(target_inode), 0); @@ -1616,6 +1646,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, CIFS_I(target_inode)->time = 0; unlock: + filemap_invalidate_unlock(target_inode->i_mapping); /* although unlocking in the reverse order from locking is not * strictly necessary here it is a little cleaner to be consistent */ diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 0c85daa8386ef8..e3d820c9778bc8 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -146,8 +146,9 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file, unsigned int flags); long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg); -void cifs_setsize(struct inode *inode, loff_t offset); -void cifs_resize_file_locked(struct inode *inode, loff_t offset); +void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset); +void cifs_resize_file_locked(struct inode *inode, loff_t old_size, + loff_t offset); struct fs_context; struct smb3_fs_context; diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 79e4e84f8985c9..5c5b76a9e9fefb 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1450,11 +1450,11 @@ struct cifsFileInfo { struct dentry *dentry; struct tcon_link *tlink; unsigned int f_flags; - bool invalidHandle:1; /* file closed via session abend */ - bool swapfile:1; - bool oplock_break_cancelled:1; - bool status_file_deleted:1; /* file has been deleted */ - bool offload:1; /* offload final part of _put to a wq */ + bool invalidHandle; /* file closed via session abend */ + bool swapfile; + bool oplock_break_cancelled; + bool status_file_deleted; /* file has been deleted */ + bool offload; /* offload final part of _put to a wq */ __u16 oplock_epoch; /* epoch from the lease break */ __u32 oplock_level; /* oplock/lease level from the lease break */ int count; diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 0d428517f4542f..11355d6bf89d6f 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1012,11 +1012,14 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) } mapping_set_error(inode->i_mapping, rc); + netfs_wait_for_outstanding_io(inode); + cfile = find_writable_file(cinode, FIND_FSUID_ONLY); rc = cifs_file_flush(xid, inode, cfile); if (!rc) { if (cfile) { struct netfs_inode *ictx = netfs_inode(inode); + loff_t old_size = i_size_read(inode); tcon = tlink_tcon(cfile->tlink); server = tcon->ses->server; @@ -1025,7 +1028,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) cfile, 0, false); if (!rc) { netfs_resize_file(&cinode->netfs, 0, true); - cifs_setsize(inode, 0); + cifs_setsize(inode, old_size, 0); cifs_invalidate_cache(inode, 0); } netfs_wb_end(ictx); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 1fe0ef0a95dbde..f29555f3c7d015 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -88,6 +88,8 @@ static void cifs_set_ops(struct inode *inode) else inode->i_data.a_ops = &cifs_addr_ops; mapping_set_large_folios(inode->i_mapping); + if (tcon->ses->server->sign) + mapping_set_stable_writes(inode->i_mapping); break; case S_IFDIR: if (IS_AUTOMOUNT(inode)) { @@ -3053,15 +3055,12 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start, return -EOPNOTSUPP; } -void cifs_setsize(struct inode *inode, loff_t offset) +void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset) { - loff_t old_size; u64 blocks = CIFS_INO_BLOCKS(offset); spin_lock(&inode->i_lock); - old_size = i_size_read(inode); i_size_write(inode, offset); - /* * Extending EOF does not allocate the intervening range. Only clamp * i_blocks on shrink; allocation growth comes from writes or from the @@ -3071,20 +3070,28 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode->i_blocks = blocks; spin_unlock(&inode->i_lock); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + + /* + * Zero the tail of the folio straddling the old EOF so data dirtied + * past EOF through an mmap isn't exposed. truncate_pagecache() then + * drops any pagecache beyond the new EOF, as in truncate_setsize(). + */ if (offset > old_size) - pagecache_isize_extended(inode, old_size, offset); + netfs_clear_stale_post_isize(inode, old_size, offset); + truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); } -void cifs_resize_file_locked(struct inode *inode, loff_t offset) +void cifs_resize_file_locked(struct inode *inode, loff_t old_size, + loff_t offset) { struct fscache_cookie *cookie = cifs_inode_cookie(inode); lockdep_assert_held_write(&inode->i_rwsem); netfs_resize_file(netfs_inode(inode), offset, true); - cifs_setsize(inode, offset); + cifs_setsize(inode, old_size, offset); if (!cookie) return; @@ -3101,6 +3108,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, struct inode *inode = d_inode(dentry); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsInodeInfo *cifsInode = CIFS_I(inode); + loff_t old_size = i_size_read(inode); struct tcon_link *tlink = NULL; struct cifs_tcon *tcon = NULL; struct TCP_Server_Info *server; @@ -3159,7 +3167,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, set_size_out: if (rc == 0) - cifs_resize_file_locked(inode, size); + cifs_resize_file_locked(inode, old_size, size); return rc; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index aa142420dae2e6..583ab4c4d77383 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2287,6 +2287,7 @@ smb2_duplicate_extents(const unsigned int xid, struct duplicate_extents_to_file dup_ext_buf; struct timespec64 ts; struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); + loff_t old_size; u64 asize; /* server fileays advertise duplicate extent support with this flag */ @@ -2305,11 +2306,12 @@ smb2_duplicate_extents(const unsigned int xid, trgtfile->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dest_off, len); inode = d_inode(trgtfile->dentry); - if (i_size_read(inode) < dest_off + len) { + old_size = i_size_read(inode); + if (old_size < dest_off + len) { rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; - cifs_resize_file_locked(inode, dest_off + len); + cifs_resize_file_locked(inode, old_size, dest_off + len); } rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, @@ -3520,6 +3522,21 @@ static long smb3_zero_data(struct file *file, struct cifs_tcon *tcon, 0, NULL, NULL); } +static long query_server_eof(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, + unsigned long long *eof) +{ + struct smb2_file_all_info file_inf = {}; + long rc; + + rc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + if (!rc) + *eof = le64_to_cpu(file_inf.EndOfFile); + return rc; +} + static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, unsigned long long offset, unsigned long long len, bool keep_size) @@ -3547,25 +3564,32 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, filemap_invalidate_lock(inode->i_mapping); netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); - if (offset + len >= remote_i_size && offset < i_size) { - unsigned long long top = umin(offset + len, i_size); - rc = filemap_write_and_wait_range(inode->i_mapping, offset, top - 1); - if (rc < 0) - goto zero_range_exit; - } + rc = filemap_write_and_wait_range(inode->i_mapping, offset, + offset + len - 1); + if (rc < 0) + goto zero_range_exit; /* * We zero the range through ioctl, so we need remove the page caches * first, otherwise the data may be inconsistent with the server. + * + * Start at the old EOF when extending so the folio straddling it, which + * may hold data written past EOF through an mmap, is dropped too. */ - truncate_pagecache_range(inode, offset, offset + len - 1); + truncate_pagecache_range(inode, min(offset, i_size), offset + len - 1); netfs_wait_for_outstanding_io(inode); - /* if file not oplocked can't be sure whether asking to extend size */ - rc = -EOPNOTSUPP; - if (keep_size == false && !CIFS_CACHE_READ(cifsi)) - goto zero_range_exit; + if (!keep_size && !CIFS_CACHE_READ(cifsi)) { + rc = query_server_eof(xid, tcon, cfile, &remote_i_size); + if (rc) + goto zero_range_exit; + i_size = max(i_size, remote_i_size); + if (i_size < new_size) { + rc = -EOPNOTSUPP; + goto zero_range_exit; + } + } fscache_invalidate(cifs_inode_cookie(inode), NULL, i_size_read(inode), 0); @@ -3577,7 +3601,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, /* * do we also need to change the size of the file? */ - if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) { + if (!keep_size && umax(i_size, i_size_read(inode)) < new_size) { rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_size); if (rc >= 0) { @@ -3723,7 +3747,8 @@ static int smb3_simple_fallocate_write_range(unsigned int xid, static int smb3_simple_fallocate_range(unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile, - loff_t off, loff_t len) + loff_t off, loff_t len, + loff_t old_eof) { struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data; struct inode *inode = d_inode(cfile->dentry); @@ -3739,12 +3764,30 @@ static int smb3_simple_fallocate_range(unsigned int xid, goto out; } - if (off >= i_size_read(inode)) { + if (off >= old_eof) { rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, len, buf); goto out; } + filemap_invalidate_lock(inode->i_mapping); + + /* + * Flush and commit the data to the server, otherwise + * FSCTL_QUERY_ALLOCATED_RANGES might report recently written data as + * unallocated holes on Windows Servers, and the loop below would + * then zero-fill them and corrupt the file. + */ + rc = filemap_write_and_wait_range(inode->i_mapping, off, + off + len - 1); + if (rc) + goto out_unlock; + netfs_wait_for_outstanding_io(inode); + rc = SMB2_flush(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid); + if (rc) + goto out_unlock; + in_data.file_offset = cpu_to_le64(off); in_data.length = cpu_to_le64(len); rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, @@ -3754,7 +3797,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, 1024 * sizeof(struct file_allocated_range_buffer), (char **)&out_data, &out_data_len); if (rc) - goto out; + goto out_unlock; tmp_data = out_data; while (len) { @@ -3764,12 +3807,12 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (out_data_len == 0) { rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, len, buf); - goto out; + goto out_unlock; } if (out_data_len < sizeof(struct file_allocated_range_buffer)) { rc = -EINVAL; - goto out; + goto out_unlock; } range_start = le64_to_cpu(tmp_data->file_offset); @@ -3777,7 +3820,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (check_add_overflow(range_start, range_len, &range_end) || range_end > S64_MAX) { rc = -EINVAL; - goto out; + goto out_unlock; } if (off < range_start) { @@ -3792,11 +3835,11 @@ static int smb3_simple_fallocate_range(unsigned int xid, rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, l, buf); if (rc) - goto out; + goto out_unlock; off = off + l; len = len - l; if (len == 0) - goto out; + goto out_unlock; } /* * We are at a section of allocated data, just skip forward @@ -3815,6 +3858,8 @@ static int smb3_simple_fallocate_range(unsigned int xid, out_data_len -= sizeof(struct file_allocated_range_buffer); } + out_unlock: + filemap_invalidate_unlock(inode->i_mapping); out: kfree(out_data); kvfree(buf); @@ -3830,7 +3875,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; long rc = -EOPNOTSUPP; unsigned int xid; - loff_t old_eof, new_eof; + loff_t old_eof, new_eof, local_eof; struct smb2_file_all_info file_inf; u64 asize; int qrc; @@ -3839,18 +3884,37 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, inode = d_inode(cfile->dentry); cifsi = CIFS_I(inode); - old_eof = i_size_read(inode); + old_eof = local_eof = i_size_read(inode); trace_smb3_falloc_enter(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len); - /* if file not oplocked can't be sure whether asking to extend size */ - if (!CIFS_CACHE_READ(cifsi)) - if (!keep_size) { + + if (!keep_size && !CIFS_CACHE_READ(cifsi)) { + unsigned long long server_eof; + + rc = filemap_write_and_wait(inode->i_mapping); + if (rc) { trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len, rc); free_xid(xid); return rc; } + netfs_wait_for_outstanding_io(inode); + + rc = query_server_eof(xid, tcon, cfile, &server_eof); + if (rc) { + trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, + tcon->tid, tcon->ses->Suid, off, len, rc); + free_xid(xid); + return rc; + } + /* + * Only use the larger EOF to decide whether we're extending. + * The pagecache zeroing below must still key off the local + * i_size, so keep local_eof for that. + */ + old_eof = max_t(loff_t, old_eof, server_eof); + } /* * Extending the file @@ -3874,7 +3938,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } rc = smb3_simple_fallocate_range(xid, tcon, cfile, - off, len); + off, len, old_eof); if (rc) { spin_lock(&inode->i_lock); cifsi->time = 0; @@ -3883,7 +3947,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } new_eof = off + len; - cifs_resize_file_locked(inode, new_eof); + cifs_resize_file_locked(inode, local_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3931,7 +3995,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - cifs_resize_file_locked(inode, new_eof); + cifs_resize_file_locked(inode, local_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3976,7 +4040,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } } - if ((keep_size == true) || (i_size_read(inode) >= off + len)) { + if (keep_size || old_eof >= off + len) { /* * At this point, we are trying to fallocate an internal * regions of a sparse file. Since smb2 does not have a @@ -3993,7 +4057,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, */ if (len <= 1024 * 1024) { rc = smb3_simple_fallocate_range(xid, tcon, cfile, - off, len); + off, len, old_eof); goto out; } @@ -4005,7 +4069,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, * ie potentially making a few extra pages at the beginning * or end of the file non-sparse via set_sparse is harmless. */ - if ((off > 8192) || (off + len + 8192 < i_size_read(inode))) { + if (off > 8192 || off + len + 8192 < old_eof) { rc = -EOPNOTSUPP; goto out; } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 3d7ead36d1a0e6..cfe3c4b4abe748 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4796,14 +4796,20 @@ smb2_readv_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid) rdata->got_bytes); if (rdata->result == -ENODATA) { - __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); rdata->result = 0; + if (rdata->subreq.start + rdata->subreq.transferred >= i_size_read(inode)) + __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); + else + __set_bit(NETFS_SREQ_CLEAR_TAIL, &rdata->subreq.flags); } else { size_t trans = rdata->subreq.transferred + rdata->got_bytes; if (trans < rdata->subreq.len && rdata->subreq.start + trans >= netfs_read_remote_i_size(inode)) { - __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); rdata->result = 0; + if (rdata->subreq.start + trans >= i_size_read(inode)) + __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); + else + __set_bit(NETFS_SREQ_CLEAR_TAIL, &rdata->subreq.flags); } if (rdata->got_bytes) __set_bit(NETFS_SREQ_MADE_PROGRESS, &rdata->subreq.flags); diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index 881d46f25e087c..6f85f63861aaa1 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -11,6 +11,7 @@ #define pr_fmt(fmt) "fs-verity: " fmt #include +#include #include /* @@ -76,7 +77,19 @@ struct merkle_tree_params { struct fsverity_info { struct rhash_head rhash_head; struct merkle_tree_params tree_params; - u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; + union { + u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; + + /* + * Used for final freeing of the struct, in which case root_hash + * is no longer needed. Specifically, the only fields needed + * besides the rcu_head are those needed by fsverity_free_info() + * itself, and those that can be accessed by fsverity_get_info() + * (rhash_head and inode) during the RCU-protected rhashtable + * chain search trying to find another inode's fsverity_info. + */ + struct rcu_head rcu_head; + }; u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE]; struct inode *inode; unsigned long *hash_block_verified; diff --git a/fs/verity/open.c b/fs/verity/open.c index d0c56a7faa3bb1..52c8cd0149216e 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -399,11 +399,22 @@ void fsverity_free_info(struct fsverity_info *vi) kmem_cache_free(fsverity_info_cachep, vi); } +static void fsverity_free_info_rcu(struct rcu_head *head) +{ + fsverity_free_info(container_of(head, struct fsverity_info, rcu_head)); +} + void fsverity_remove_info(struct fsverity_info *vi) { rhashtable_remove_fast(&fsverity_info_hash, &vi->rhash_head, fsverity_info_hash_params); - fsverity_free_info(vi); + /* + * The freeing must be RCU-delayed because __fsverity_get_info() for a + * *different* inode's fsverity_info in the same rhashtable hash chain + * can still be accessing *this* fsverity_info's rhash_head and inode + * fields as part of the RCU-protected hash chain search. + */ + call_rcu(&vi->rcu_head, fsverity_free_info_rcu); } void fsverity_cleanup_inode(struct inode *inode) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 6a66b6075e0af4..f527fce46defec 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -49,7 +49,7 @@ struct xfs_exchmaps_adjacent { .right2 = { .br_startblock = HOLESTARTBLOCK }, \ } -/* Information to reset reflink flag / CoW fork state after an exchange. */ +/* Information to reset CoW fork state after an exchange. */ /* * If the reflink flag is set on either inode, make sure it has an incore CoW @@ -127,9 +127,7 @@ xmi_has_more_exchange_work(const struct xfs_exchmaps_intent *xmi) static inline bool xmi_has_postop_work(const struct xfs_exchmaps_intent *xmi) { - return xmi->xmi_flags & (XFS_EXCHMAPS_CLEAR_INO1_REFLINK | - XFS_EXCHMAPS_CLEAR_INO2_REFLINK | - __XFS_EXCHMAPS_INO2_SHORTFORM); + return xmi->xmi_flags & __XFS_EXCHMAPS_INO2_SHORTFORM; } /* Check all mappings to make sure we can actually exchange them. */ @@ -525,18 +523,6 @@ xfs_exchmaps_link_to_sf( return error; } -/* Clear the reflink flag after an exchange. */ -static inline void -xfs_exchmaps_clear_reflink( - struct xfs_trans *tp, - struct xfs_inode *ip) -{ - trace_xfs_reflink_unset_inode_flag(ip); - - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); -} - /* Finish whatever work might come after an exchange operation. */ static int xfs_exchmaps_do_postop_work( @@ -557,16 +543,6 @@ xfs_exchmaps_do_postop_work( return error; } - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO1_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip1); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - } - - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO2_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip2); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - return 0; } @@ -948,46 +924,14 @@ xfs_exchmaps_intent_destroy_cache(void) } /* - * Decide if we will exchange the reflink flags between the two files after the - * exchange. The only time we want to do this is if we're exchanging all - * mappings under EOF and the inode reflink flags have different states. + * Allocate and initialize a new incore intent item from a request. */ -static inline bool -xmi_can_exchange_reflink_flags( - const struct xfs_exchmaps_req *req, - unsigned int reflink_state) -{ - struct xfs_mount *mp = req->ip1->i_mount; - - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - - if (hweight32(reflink_state) != 1) - return false; - if (req->startoff1 != 0 || req->startoff2 != 0) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip1->i_disk_size)) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip2->i_disk_size)) - return false; - return true; -} - - -/* Allocate and initialize a new incore intent item from a request. */ struct xfs_exchmaps_intent * xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; - unsigned int rs = 0; + struct xfs_mount *mp = req->ip1->i_mount; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -1005,27 +949,18 @@ xfs_exchmaps_init_intent( return xmi; } - if (req->flags & XFS_EXCHMAPS_SET_SIZES) { - xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; - xmi->xmi_isize1 = req->ip2->i_disk_size; - xmi->xmi_isize2 = req->ip1->i_disk_size; - } - - /* Record the state of each inode's reflink flag before the op. */ - if (xfs_is_reflink_inode(req->ip1)) - rs |= 1; - if (xfs_is_reflink_inode(req->ip2)) - rs |= 2; - /* - * Figure out if we're clearing the reflink flags (which effectively - * exchanges them) after the operation. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. */ - if (xmi_can_exchange_reflink_flags(req, rs)) { - if (rs & 1) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - if (rs & 2) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO2_REFLINK; + if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + loff_t off1 = XFS_FSB_TO_B(mp, xmi->xmi_startoff1); + loff_t off2 = XFS_FSB_TO_B(mp, xmi->xmi_startoff2); + + xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; + xmi->xmi_isize1 = off1 + (req->ip2->i_disk_size - off2); + xmi->xmi_isize2 = off2 + (req->ip1->i_disk_size - off1); } if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h index a4e1b3eb425cda..6e620380d5dc86 100644 --- a/fs/xfs/libxfs/xfs_log_format.h +++ b/fs/xfs/libxfs/xfs_log_format.h @@ -927,10 +927,8 @@ struct xfs_xmi_log_format { */ #define XFS_EXCHMAPS_INO1_WRITTEN (1ULL << 2) -/* Clear the reflink flag from inode1 after the operation. */ +/* No longer set, but older intents may carry them; keep them valid. */ #define XFS_EXCHMAPS_CLEAR_INO1_REFLINK (1ULL << 3) - -/* Clear the reflink flag from inode2 after the operation. */ #define XFS_EXCHMAPS_CLEAR_INO2_REFLINK (1ULL << 4) #define XFS_EXCHMAPS_LOGGED_FLAGS (XFS_EXCHMAPS_ATTR_FORK | \ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 268d159339d06f..865aec3ae5ab32 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1544,7 +1544,6 @@ xfs_swap_extents( struct xfs_bstat *sbp = &sxp->sx_stat; int src_log_flags, target_log_flags; int error = 0; - uint64_t f; int resblks = 0; unsigned int flags = 0; struct timespec64 ctime, mtime; @@ -1700,14 +1699,12 @@ xfs_swap_extents( if (error) goto out_trans_cancel; - /* Do we have to swap reflink flags? */ - if ((ip->i_diflags2 & XFS_DIFLAG2_REFLINK) ^ - (tip->i_diflags2 & XFS_DIFLAG2_REFLINK)) { - f = ip->i_diflags2 & XFS_DIFLAG2_REFLINK; - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - ip->i_diflags2 |= tip->i_diflags2 & XFS_DIFLAG2_REFLINK; - tip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - tip->i_diflags2 |= f & XFS_DIFLAG2_REFLINK; + /* Either inode may now own shared blocks; flag both, with CoW forks. */ + if ((ip->i_diflags2 | tip->i_diflags2) & XFS_DIFLAG2_REFLINK) { + ip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + tip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + xfs_ifork_init_cow(ip); + xfs_ifork_init_cow(tip); } /* Swap the cow forks. */ diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index fafb4e3f065c75..935b561dfd3c5c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -290,17 +290,20 @@ xfs_exchrange_mappings( goto out_unlock; /* - * If the caller wanted us to exchange the contents of two complete - * files of unequal length, exchange the incore sizes now. This should - * be safe because we flushed both files' page caches, exchanged all - * the mappings, and updated the ondisk sizes. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. This should be safe because + * we flushed both files' page caches, exchanged all the mappings, and + * updated the ondisk sizes. */ if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) { - loff_t temp; + loff_t old_ip1_size = i_size_read(VFS_I(ip1)); + loff_t old_ip2_size = i_size_read(VFS_I(ip2)); - temp = i_size_read(VFS_I(ip2)); - i_size_write(VFS_I(ip2), i_size_read(VFS_I(ip1))); - i_size_write(VFS_I(ip1), temp); + i_size_write(VFS_I(ip2), fxr->file2_offset + + (old_ip1_size - fxr->file1_offset)); + i_size_write(VFS_I(ip1), fxr->file1_offset + + (old_ip2_size - fxr->file2_offset)); } out_unlock: @@ -445,6 +448,30 @@ xfs_exchange_range_checks( return blen == fxr->length ? 0 : -EINVAL; } +/* Flush all relevant dirty pagecache near the range to be exchanged. */ +static inline int +xfs_exchange_range_flush( + struct xfs_inode *ip, + const struct xfs_exchrange *fxr, + loff_t offset) +{ + /* + * If TO_EOF is set, the ondisk file size update logic depends on the + * ondisk file size matching the incore file size. Flush everything. + */ + if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) + return filemap_write_and_wait(VFS_I(ip)->i_mapping); + + /* + * Because we're updating the ondisk mappings, flush all dirty data + * between the ondisk size and the exchange offset to reduce the chance + * of zeroed file contents in that gap after a crash. + */ + return filemap_write_and_wait_range(VFS_I(ip)->i_mapping, + min(offset, ip->i_disk_size), + offset + fxr->length - 1); +} + /* * Check that the two inodes are eligible for range exchanges, the ranges make * sense, and then flush all dirty data. Caller must ensure that the inodes @@ -470,15 +497,11 @@ xfs_exchange_range_prep( if (!same_inode) inode_dio_wait(inode2); - error = filemap_write_and_wait_range(inode1->i_mapping, - fxr->file1_offset, - fxr->file1_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode1), fxr, fxr->file1_offset); if (error) return error; - error = filemap_write_and_wait_range(inode2->i_mapping, - fxr->file2_offset, - fxr->file2_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode2), fxr, fxr->file2_offset); if (error) return error; diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h index 25386af184451b..5cb77d48b0b8cc 100644 --- a/include/dt-bindings/sound/cs35l45.h +++ b/include/dt-bindings/sound/cs35l45.h @@ -17,6 +17,33 @@ #define CS35L45_ASP_TX_HIZ_UNUSED 0x1 #define CS35L45_ASP_TX_HIZ_DISABLED 0x2 +/* + * cirrus,boost-low-power-mode + * + * BST_LPMODE_NO_REACTION: No reaction + * BST_LPMODE_DCM_MODE: Force boost DCM low power mode + * BST_LPMODE_STATIC_BYPASS: Force boost static bypass + */ +#define CS35L45_BST_LPMODE_NO_REACTION 0x0 +#define CS35L45_BST_LPMODE_DCM_MODE 0x1 +#define CS35L45_BST_LPMODE_STATIC_BYPASS 0x2 + +/* + * cirrus,amplifier-low-power-mode + * + * AMP_LPMODE_NG_MODE: Noise gate mode + * AMP_LPMODE_NFR_MODE: Noise floor reduction mode + */ + #define CS35L45_AMP_LPMODE_NG_MODE 0x0 + #define CS35L45_AMP_LPMODE_NFR_MODE 0x1 + +/* + * cirrus,ldpm-groupX-pcm-threshold-db + * + * LDPM_PCM_THLD_TRUE_ZERO: + */ +#define CS35L45_LDPM_PCM_THLD_TRUE_ZERO -2147483648 + /* * Optional GPIOX Sub-nodes: * The cs35l45 node can have up to three "cirrus,gpio-ctrlX" ('X' = [1,2,3]) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 1d2676782d707c..0ecb9418dfbd66 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1258,11 +1258,15 @@ struct btf_func_model { #define BPF_TRAMP_F_INDIRECT BIT(8) /* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50 - * bytes on x86. + * bytes on x86. The trampoline image has to fit in PAGE_SIZE. */ enum { -#if defined(__s390x__) +#if defined(__s390x__) || defined(__powerpc64__) BPF_MAX_TRAMP_LINKS = 27, +#elif defined(__x86_64__) + BPF_MAX_TRAMP_LINKS = 36, +#elif defined(__aarch64__) + BPF_MAX_TRAMP_LINKS = 37, #else BPF_MAX_TRAMP_LINKS = 38, #endif @@ -1315,6 +1319,7 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i void *arch_alloc_bpf_trampoline(unsigned int size); void arch_free_bpf_trampoline(void *image, unsigned int size); int __must_check arch_protect_bpf_trampoline(void *image, unsigned int size); +int arch_bpf_trampoline_skip(void *nop, void *target); int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr); @@ -1363,19 +1368,48 @@ enum bpf_tramp_prog_type { BPF_TRAMP_FSESSION, }; +/* + * Each prog call in a trampoline image is preceded by a nop. When the prog is + * detached, the nop is patched to a jump to target, right after the call, so + * that tasks still running in the image skip the prog. + */ +struct bpf_tramp_skip { + struct bpf_prog *prog; + void *nop; + void *target; +}; + struct bpf_tramp_image { void *image; int size; struct bpf_ksym ksym; struct percpu_ref pcref; - void *ip_after_call; - void *ip_epilogue; + bool call_orig; + /* entry in tr->images, the image holds a reference on tr */ + struct bpf_trampoline *tr; + struct list_head list; + int nr_skips; + struct bpf_tramp_skip *skips; union { struct rcu_head rcu; struct work_struct work; }; }; +static inline void bpf_tramp_image_add_skip(struct bpf_tramp_image *im, struct bpf_prog *prog, + void *nop, void *target) +{ + struct bpf_tramp_skip *skip; + + /* struct_ops trampolines and arch_bpf_trampoline_size() have no image */ + if (!im || !im->skips) + return; + skip = &im->skips[im->nr_skips++]; + skip->prog = prog; + skip->nop = nop; + skip->target = target; +} + struct bpf_trampoline { /* hlist for trampoline_key_table */ struct hlist_node hlist_key; @@ -1402,6 +1436,8 @@ struct bpf_trampoline { int progs_cnt[BPF_TRAMP_MAX]; /* Executable image of trampoline */ struct bpf_tramp_image *cur_image; + /* Images not freed yet, cur_image and older ones still in use */ + struct list_head images; /* Used as temporary old image storage for multi_attach */ struct { struct bpf_tramp_image *old_image; @@ -1770,6 +1806,7 @@ struct bpf_prog_aux { bool offload_requested; /* Program is bound and offloaded to the netdev. */ bool attach_btf_trace; /* true if attaching to BTF-enabled raw tp */ bool attach_tracing_prog; /* true if tracing another tracing program */ + bool tramp_linked; /* true if it was ever linked to a trampoline */ bool func_proto_unreliable; bool tail_call_reachable; bool xdp_has_frags; diff --git a/include/linux/can/core.h b/include/linux/can/core.h index 3287232e3cad92..2de74c2b78b6c1 100644 --- a/include/linux/can/core.h +++ b/include/linux/can/core.h @@ -17,6 +17,7 @@ #include #include #include +#include #define DNAME(dev) ((dev) ? (dev)->name : "any") @@ -58,7 +59,7 @@ extern void can_rx_unregister(struct net *net, struct net_device *dev, void *data); extern int can_send(struct sk_buff *skb, int loop); -void can_set_skb_uid(struct sk_buff *skb); +void can_set_skb_uid(struct can_skb_ext *csx); void can_sock_destruct(struct sock *sk); #endif /* !_CAN_CORE_H */ diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h index a70a0296707115..5d27843862fca4 100644 --- a/include/linux/can/skb.h +++ b/include/linux/can/skb.h @@ -43,8 +43,10 @@ static inline struct can_skb_ext *can_skb_ext_add(struct sk_buff *skb) struct can_skb_ext *csx = skb_ext_add(skb, SKB_EXT_CAN); /* skb_ext_add() returns uninitialized space */ - if (csx) + if (csx) { csx->can_gw_hops = 0; + csx->can_skb_uid = 0; + } return csx; } diff --git a/include/linux/console.h b/include/linux/console.h index 502d1abe3f50bd..df23148987eeb5 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -615,6 +615,15 @@ extern bool nbcon_allow_unsafe_takeover(void); extern bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt); extern void nbcon_kdb_release(struct nbcon_write_context *wctxt); +extern bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt); +extern bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt); +extern void nbcon_braille_release(struct nbcon_write_context *wctxt); + +static inline bool nbcon_is_braille(struct console *con) +{ + return con && (con->flags & CON_BRL); +} /* * Check if the given console is currently capable and allowed to print @@ -678,8 +687,14 @@ static inline void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt) { } static inline bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt) { return false; } static inline void nbcon_kdb_release(struct nbcon_write_context *wctxt) { } +static inline +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) { return false; } static inline bool console_is_usable(struct console *con, short flags, bool use_atomic) { return false; } +static inline bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) { return false; } +static inline void nbcon_braille_release(struct nbcon_write_context *wctxt) { } +static inline bool nbcon_is_braille(struct console *con) { return false; } #endif extern int console_set_on_cmdline; diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 35ce665edfd824..d3d0d9d02aa4a6 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -420,6 +420,9 @@ struct cpufreq_driver { /* Will be called after the driver is fully initialized */ void (*ready)(struct cpufreq_policy *policy); + /* Return the capacity reference frequency for policy. */ + unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); + struct freq_attr **attr; /* platform specific boost support code */ diff --git a/include/linux/fs.h b/include/linux/fs.h index f9d1e05e8ae68e..deb411e866617d 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1978,6 +1978,8 @@ struct file_operations { #define FOP_ASYNC_LOCK ((__force fop_flags_t)(1 << 6)) /* File system supports uncached read/write buffered IO */ #define FOP_DONTCACHE ((__force fop_flags_t)(1 << 7)) +/* Never changes and is never removed, readdir of a directory takes no lock */ +#define FOP_IMMUTABLE ((__force fop_flags_t)(1 << 8)) /* Wrap a directory iterator that needs exclusive inode access */ int wrap_directory_iterator(struct file *, struct dir_context *, @@ -2296,6 +2298,7 @@ struct file_system_type { #define FS_POWER_FREEZE 256 /* Always freeze on suspend/hibernate */ #define FS_USERNS_MOUNT_RESTRICTED 512 /* Restrict mount in userns if not already visible */ #define FS_USERNS_DELEGATABLE 1024 /* Can be mounted inside userns from outside */ +#define FS_DISALLOW_NOTIFY 2048 /* No fsnotify marks on its objects */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); const struct fs_parameter_spec *parameters; diff --git a/include/linux/hrtimer_rearm.h b/include/linux/hrtimer_rearm.h index 17a81826bd9a9b..12e42721c8e019 100644 --- a/include/linux/hrtimer_rearm.h +++ b/include/linux/hrtimer_rearm.h @@ -45,7 +45,7 @@ hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif clear_thread_flag(TIF_HRTIMER_REARM); __hrtimer_rearm_deferred(); /* Don't go into the loop if HRTIMER_REARM was the only flag */ - *tif_work &= ~TIF_HRTIMER_REARM; + *tif_work &= ~_TIF_HRTIMER_REARM; return !*tif_work; } return false; diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 39629ee77b911c..8126af652845bb 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -322,6 +322,8 @@ enum { IO_RING_F_DRAIN_DISABLED = BIT(10), IO_RING_F_COMPAT = BIT(11), IO_RING_F_IOWQ_LIMITS_SET = BIT(12), + /* restrictions were inherited from the task that created the ring */ + IO_RING_F_RESTRICT_INHERITED = BIT(13), }; struct iou_ctx {}; diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index cf7fe835c4ad29..c844def6dd9519 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -791,7 +791,6 @@ struct kvm { /* The current active memslot set for each address space */ struct kvm_memslots __rcu *memslots[KVM_MAX_NR_ADDRESS_SPACES]; struct xarray vcpu_array; - DECLARE_BITMAP(vcpu_ids, KVM_MAX_VCPU_IDS); /* * Protected by slots_lock, but can be read outside if an * incorrect answer is acceptable. diff --git a/include/linux/mount.h b/include/linux/mount.h index acfe7ef86a1b3f..b16ba5d456057a 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -52,7 +52,7 @@ enum mount_flags { MNT_ATIME_MASK = MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME, MNT_INTERNAL_FLAGS = MNT_INTERNAL | MNT_DOOMED | - MNT_SYNC_UMOUNT | MNT_LOCKED + MNT_SYNC_UMOUNT | MNT_LOCKED | MNT_UMOUNT }; struct vfsmount { diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 09c8c93e4dbabd..6936180b6ea584 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -305,6 +305,8 @@ int nand_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req); int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req); +bool nand_ecc_is_pipelined(const struct nand_device *nand); + bool nand_ecc_is_strong_enough(struct nand_device *nand); #if IS_REACHABLE(CONFIG_MTD_NAND_CORE) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 87cafc932e9e65..3cff2174dc03a3 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -887,6 +887,7 @@ enum net_device_path_type { DEV_PATH_DSA, DEV_PATH_MTK_WDMA, DEV_PATH_TUN, + DEV_PATH_IEEE80211, }; struct net_device_path { @@ -953,6 +954,8 @@ struct net_device_path_ctx { u16 id; __be16 proto; } vlan[NET_DEVICE_PATH_VLAN_MAX]; + + bool ieee80211; }; enum tc_setup_type { diff --git a/include/linux/netfilter_netdev.h b/include/linux/netfilter_netdev.h index 3175073a66ba6b..2a854a0bbd4ba7 100644 --- a/include/linux/netfilter_netdev.h +++ b/include/linux/netfilter_netdev.h @@ -133,7 +133,7 @@ static inline struct sk_buff *nf_hook_egress(struct sk_buff *skb, int *rc, static inline void nf_skip_egress(struct sk_buff *skb, bool skip) { -#ifdef CONFIG_NETFILTER_SKIP_EGRESS +#ifdef CONFIG_NET_EGRESS skb->nf_skip_egress = skip; #endif } diff --git a/include/linux/netfs.h b/include/linux/netfs.h index b4dd32863dd45f..fe6275e55fc279 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -397,6 +397,8 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from); ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *iter, struct netfs_group *netfs_group); ssize_t netfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from); +void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to); /* Single, monolithic object read/write API. */ void netfs_single_mark_inode_dirty(struct inode *inode); diff --git a/include/linux/nsproxy.h b/include/linux/nsproxy.h index 5a67648721c7d9..dc2447f5f09240 100644 --- a/include/linux/nsproxy.h +++ b/include/linux/nsproxy.h @@ -100,6 +100,7 @@ void exit_cred_namespaces(struct task_struct *tsk); void switch_task_namespaces(struct task_struct *tsk, struct nsproxy *new); int exec_task_namespaces(void); void deactivate_nsproxy(struct nsproxy *ns); +void nsproxy_free(struct nsproxy *ns); int unshare_nsproxy_namespaces(unsigned long, struct nsproxy **, struct cred *, struct fs_struct *); int __init nsproxy_cache_init(void); diff --git a/include/linux/nvme.h b/include/linux/nvme.h index 91ce434a7e8d95..6248711784b370 100644 --- a/include/linux/nvme.h +++ b/include/linux/nvme.h @@ -1266,8 +1266,8 @@ enum { NVME_ZRASF_ZONE_STATE_IMP_OPEN = 0x02, NVME_ZRASF_ZONE_STATE_EXP_OPEN = 0x03, NVME_ZRASF_ZONE_STATE_CLOSED = 0x04, - NVME_ZRASF_ZONE_STATE_READONLY = 0x05, - NVME_ZRASF_ZONE_STATE_FULL = 0x06, + NVME_ZRASF_ZONE_STATE_FULL = 0x05, + NVME_ZRASF_ZONE_STATE_READONLY = 0x06, NVME_ZRASF_ZONE_STATE_OFFLINE = 0x07, NVME_REPORT_ZONE_PARTIAL = 1, }; diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 915c6fd3f08458..2d5e7687875b0c 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1506,9 +1506,10 @@ is_default_overflow_handler(struct perf_event *event) } extern void -perf_event_header__init_id(struct perf_event_header *header, - struct perf_sample_data *data, - struct perf_event *event); +perf_event_header__init(struct perf_event_header *header, + struct perf_sample_data *data, + u32 type, u16 misc, u16 size, + struct perf_event *event); extern void perf_event__output_id_sample(struct perf_event *event, struct perf_output_handle *handle, diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h index 95729339e7a548..a54ec40d095c04 100644 --- a/include/linux/rtnetlink.h +++ b/include/linux/rtnetlink.h @@ -186,7 +186,12 @@ void net_dec_ingress_queue(void); #ifdef CONFIG_NET_EGRESS void net_inc_egress_queue(void); void net_dec_egress_queue(void); -void netdev_xmit_skip_txqueue(bool skip); +bool netdev_xmit_skip_txqueue(bool skip); +#else +static inline bool netdev_xmit_skip_txqueue(bool skip) +{ + return false; +} #endif void rtnetlink_init(void); diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h index 582d7cd4a98395..36c04797436c75 100644 --- a/include/linux/sched/ext.h +++ b/include/linux/sched/ext.h @@ -207,6 +207,7 @@ struct sched_ext_entity { s32 holding_cpu; s32 selected_cpu; s32 runnable_cpu; /* cpu @p is runnable on, -1 if not */ + u32 ops_qseq; /* protected by rq lock */ struct task_struct *kf_tasks[2]; /* see SCX_CALL_OP_TASK() */ struct list_head runnable_node; /* rq->scx.runnable_list */ diff --git a/include/linux/siphash.h b/include/linux/siphash.h index 9153e77382e109..00c58bbe549a94 100644 --- a/include/linux/siphash.h +++ b/include/linux/siphash.h @@ -75,8 +75,10 @@ static inline u64 ___siphash_aligned(const __le64 *data, size_t len, /** * siphash - compute 64-bit siphash PRF value * @data: buffer to hash - * @size: size of @data + * @len: size of @data * @key: the siphash key + * + * Returns: siphash PRF value */ static inline u64 siphash(const void *data, size_t len, const siphash_key_t *key) @@ -125,8 +127,10 @@ static inline u32 ___hsiphash_aligned(const __le32 *data, size_t len, /** * hsiphash - compute 32-bit hsiphash PRF value * @data: buffer to hash - * @size: size of @data + * @len: size of @data * @key: the hsiphash key + * + * Returns: hsiphash PRF value */ static inline u32 hsiphash(const void *data, size_t len, const hsiphash_key_t *key) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 84308498a3a8cd..a3ff380d43f01d 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1020,7 +1020,7 @@ struct sk_buff { #ifdef CONFIG_NET_REDIRECT __u8 from_ingress:1; #endif -#ifdef CONFIG_NETFILTER_SKIP_EGRESS +#ifdef CONFIG_NET_EGRESS __u8 nf_skip_egress:1; #endif #ifdef CONFIG_SKB_DECRYPTED diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 29a53bbc0dd495..5f18d281e6a426 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -347,17 +347,12 @@ struct geni_se { #define QUP_SE_VERSION_2_5 0x20050000 /* - * Define bandwidth thresholds that cause the underlying Core 2X interconnect - * clock to run at the named frequency. These baseline values are recommended - * by the hardware team, and are not dynamically scaled with GENI bandwidth - * beyond basic on/off. + * QUP Core 2X clock votes used by GENI clients through the "qup-core" ICC + * path. Values are in Bps and must be converted with Bps_to_icc() before + * setting avg_bw. */ -#define CORE_2X_19_2_MHZ 960 -#define CORE_2X_50_MHZ 2500 -#define CORE_2X_100_MHZ 5000 -#define CORE_2X_150_MHZ 7500 -#define CORE_2X_200_MHZ 10000 -#define CORE_2X_236_MHZ 16383 +#define CORE_2X_19_2_MHZ 9600000 +#define CORE_2X_50_MHZ 25000000 #define GENI_DEFAULT_BW Bps_to_icc(1000) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 88d17fce02dcbf..fb4baa0d339821 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -169,6 +169,12 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @cs_inactive: delay to be introduced by the controller after CS is * deasserted. If @cs_change_delay is used from @spi_transfer, then the * two delays will be added up. + * @rx_sample_delay_ns: Delay in nanoseconds by which the controller should + * postpone sampling the incoming data, relative to the sampling point it + * uses by default. Describes the board rather than the device, namely the + * flight time of the clock and data signals between controller and + * device, and comes from the "rx-sample-delay-ns" property. Zero when the + * property is absent. * @chip_select: Array of physical chipselect, spi->chipselect[i] gives * the corresponding physical CS for logical CS i. * @num_chipselect: Number of physical chipselects used. @@ -235,6 +241,9 @@ struct spi_device { struct spi_delay cs_hold; struct spi_delay cs_inactive; + /* Additional delay before the incoming data is sampled, in ns */ + u32 rx_sample_delay_ns; + u8 chip_select[SPI_DEVICE_CS_CNT_MAX]; u8 num_chipselect; diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index d48623fda79ba4..b62dfa52b1492c 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -22,6 +22,7 @@ struct device; #include #include #include +#include #include #include #include @@ -565,6 +566,7 @@ struct tb_nhi { * @interval_nsec: Interval counter if interrupt throttling is to be * used with this ring (in ns) * @wait: Used to signal that the ring may be empty now + * @lock_key: Lock validator class key per-ring */ struct tb_ring { spinlock_t lock; @@ -590,6 +592,7 @@ struct tb_ring { void *poll_data; unsigned int interval_nsec; wait_queue_head_t wait; + struct lock_class_key lock_key; }; /* Leave ring interrupt enabled on suspend */ diff --git a/include/linux/tty.h b/include/linux/tty.h index 0a46e4054dec78..833bb7b97cebbb 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -168,6 +168,7 @@ struct tty_operations; * @write_wait: concurrent writers are waiting in this queue until they are * allowed to write * @read_wait: readers wait for data in this queue + * @break_wait: wait queue for timed breaks * @hangup_work: normally a work to perform a hangup (do_tty_hangup()); while * freeing the tty, (re)used to release_one_tty() * @disc_data: pointer to @ldisc's private data (e.g. to &struct n_tty_data) @@ -230,6 +231,7 @@ struct tty_struct { struct fasync_struct *fasync; wait_queue_head_t write_wait; wait_queue_head_t read_wait; + wait_queue_head_t break_wait; struct work_struct hangup_work; void *disc_data; void *driver_data; diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h index 1f2896e56e7794..40ae3bb13e11a2 100644 --- a/include/linux/tty_driver.h +++ b/include/linux/tty_driver.h @@ -73,6 +73,11 @@ struct serial_struct; * @TTY_DRIVER_NO_WORKQUEUE: * Do not create workqueue when tty_register_driver(). Whenever set, flip * buffer workqueue can be set by tty_port_link_wq() for every port. + * + * @TTY_DRIVER_RESET_SAVED_TERMIOS: + * Reset any saved termios settings on device registration when reusing a + * minor number. Must only be set by drivers that guarantee that the minor + * number is no longer in use. */ enum tty_driver_flag { TTY_DRIVER_INSTALLED = BIT(0), @@ -84,6 +89,7 @@ enum tty_driver_flag { TTY_DRIVER_DYNAMIC_ALLOC = BIT(6), TTY_DRIVER_UNNUMBERED_NODE = BIT(7), TTY_DRIVER_NO_WORKQUEUE = BIT(8), + TTY_DRIVER_RESET_SAVED_TERMIOS = BIT(9), }; enum tty_driver_type { diff --git a/include/linux/tty_port.h b/include/linux/tty_port.h index 23cad403bb8f5e..8d22c59c6f15b4 100644 --- a/include/linux/tty_port.h +++ b/include/linux/tty_port.h @@ -245,7 +245,8 @@ bool tty_port_carrier_raised(struct tty_port *port); void tty_port_raise_dtr_rts(struct tty_port *port); void tty_port_lower_dtr_rts(struct tty_port *port); void tty_port_hangup(struct tty_port *port); -void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async); +void tty_port_tty_hangup(struct tty_port *port, bool check_clocal); +void tty_port_tty_vhangup(struct tty_port *port); void tty_port_tty_wakeup(struct tty_port *port); int tty_port_block_til_ready(struct tty_port *port, struct tty_struct *tty, struct file *filp); @@ -264,25 +265,6 @@ static inline int tty_port_users(struct tty_port *port) return port->count + port->blocked_open; } -/** - * tty_port_tty_hangup - helper to hang up a tty asynchronously - * @port: tty port - * @check_clocal: hang only ttys with %CLOCAL unset? - */ -static inline void tty_port_tty_hangup(struct tty_port *port, bool check_clocal) -{ - __tty_port_tty_hangup(port, check_clocal, true); -} - -/** - * tty_port_tty_vhangup - helper to hang up a tty synchronously - * @port: tty port - */ -static inline void tty_port_tty_vhangup(struct tty_port *port) -{ - __tty_port_tty_hangup(port, false, false); -} - #ifdef CONFIG_TTY void tty_kref_put(struct tty_struct *tty); __DEFINE_CLASS_IS_CONDITIONAL(tty_port_tty, true); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 534e6650e2aadf..ab0a32a90629d4 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, int usb_serial_bus_register(struct usb_serial_driver *device); void usb_serial_bus_deregister(struct usb_serial_driver *device); +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); extern const struct bus_type usb_serial_bus_type; extern struct tty_driver *usb_serial_tty_driver; diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c381b916c1b54a..d6466f96cdd00c 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, p_off = nh_min_len + thlen; if (!pskb_may_pull(skb, p_off)) return -EINVAL; - } else { + } else if (gso_type) { /* gso packets without NEEDS_CSUM do not set transport_offset. * probe and drop if does not match one of the above types. */ - if (gso_type && skb->network_header) { - struct flow_keys_basic keys; - - if (!skb->protocol) { - __be16 protocol = dev_parse_header_protocol(skb); - - if (!protocol) - virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, - hdr_gso_type)) - return -EINVAL; - else - skb->protocol = protocol; - } + struct flow_keys_basic keys; + + if (!skb->protocol) { + skb->protocol = dev_parse_header_protocol(skb); + if (!skb->protocol) + virtio_net_hdr_set_proto(skb, hdr); + } retry: - if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, - NULL, 0, 0, 0, - 0)) { - /* UFO does not specify ipv4 or 6: try both */ - if (gso_type & SKB_GSO_UDP && - skb->protocol == htons(ETH_P_IP)) { - skb->protocol = htons(ETH_P_IPV6); - goto retry; - } - return -EINVAL; + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, + NULL, 0, 0, 0, + 0)) { + /* UFO does not specify ipv4 or 6: try both */ + if (gso_type & SKB_GSO_UDP && + skb->protocol == htons(ETH_P_IP)) { + skb->protocol = htons(ETH_P_IPV6); + goto retry; } + return -EINVAL; + } - p_off = keys.control.thoff + thlen; - if (!pskb_may_pull(skb, p_off) || - keys.basic.ip_proto != ip_proto) - return -EINVAL; + p_off = keys.control.thoff + thlen; + if (!pskb_may_pull(skb, p_off) || + keys.basic.ip_proto != ip_proto || + !virtio_net_hdr_match_proto(keys.basic.n_proto, + hdr_gso_type)) + return -EINVAL; - skb_set_transport_header(skb, keys.control.thoff); - } else if (gso_type) { - p_off = nh_min_len + thlen; - if (!pskb_may_pull(skb, p_off)) - return -EINVAL; - } + skb_set_transport_header(skb, keys.control.thoff); } if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { diff --git a/include/net/amt.h b/include/net/amt.h index a0255491f5b05e..2846dde0cadce3 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -231,10 +231,6 @@ struct amt_relay_headers { }; } __packed; -struct amt_skb_cb { - struct amt_tunnel_list *tunnel; -}; - struct amt_tunnel_list { struct list_head list; /* Protect All resources under an amt_tunne_list */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 4105c446ca983b..fa2a367731b5e2 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -62,7 +62,7 @@ struct inquiry_entry { NAME_PENDING, NAME_KNOWN, } name_state; - __u32 timestamp; + unsigned long timestamp; struct inquiry_data data; }; @@ -78,7 +78,7 @@ struct discovery_state { struct list_head all; /* All devices found during inquiry */ struct list_head unknown; /* Name state not known */ struct list_head resolve; /* Name needs to be resolved */ - __u32 timestamp; + unsigned long timestamp; bdaddr_t last_adv_addr; u8 last_adv_addr_type; s8 last_adv_rssi; @@ -562,6 +562,7 @@ struct hci_dev { struct list_head link_keys; struct list_head long_term_keys; struct list_head identity_resolving_keys; + struct mutex remote_oob_lock; struct list_head remote_oob_data; struct list_head le_accept_list; struct list_head le_resolv_list; diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 102c278e35844c..9dc5fd4cb8c815 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -207,6 +207,7 @@ struct rfcomm_dlc { #define RFCOMM_AUTH_REJECT 7 #define RFCOMM_DEFER_SETUP 8 #define RFCOMM_ENC_DROP 9 +#define RFCOMM_CLOSED 10 /* Scheduling flags and events */ #define RFCOMM_SCHED_WAKEUP 31 diff --git a/include/net/can.h b/include/net/can.h index 6db9e826f0e05f..2b8af2598732c0 100644 --- a/include/net/can.h +++ b/include/net/can.h @@ -17,12 +17,14 @@ * @can_framelen: cached echo CAN frame length for bql * @can_gw_hops: can-gw CAN frame time-to-live counter * @can_ext_flags: CAN skb extensions flags + * @can_skb_uid: CAN skb UID for raw_rcv and isotp echo handling */ struct can_skb_ext { int can_iif; u16 can_framelen; u8 can_gw_hops; u8 can_ext_flags; + u32 can_skb_uid; }; #endif /* _NET_CAN_H */ diff --git a/include/net/gso.h b/include/net/gso.h index 29975440cad51e..0749230d414ecb 100644 --- a/include/net/gso.h +++ b/include/net/gso.h @@ -19,10 +19,18 @@ struct skb_gso_cb { int encap_level; __wsum csum; __u16 csum_start; + /* Number of IPv4/IPv6 GSO handler entries for this packet. */ + u8 recursion_counter; }; #define SKB_GSO_CB_OFFSET 32 #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) +static inline bool gso_recursion_inc_test(struct sk_buff *skb, + unsigned int limit) +{ + return ++SKB_GSO_CB(skb)->recursion_counter > limit; +} + static inline int skb_tnl_header_len(const struct sk_buff *inner_skb) { return (skb_mac_header(inner_skb) - inner_skb->head) - diff --git a/include/net/ip.h b/include/net/ip.h index 6f602df72ee621..d07c2c5730240b 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 83b7eff4646ead..8c3dc9d0b29934 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -377,7 +377,18 @@ struct mana_recv_buf_oob { #define MANA_RXBUF_PAD (SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) \ + ETH_HLEN) -#define MANA_XDP_MTU_MAX (PAGE_SIZE - MANA_RXBUF_PAD - XDP_PACKET_HEADROOM) +/* Headroom an RX buffer has to reserve while XDP is attached: the XDP program + * needs XDP_PACKET_HEADROOM, and the TX path needs LL_RESERVED_SPACE() (see + * mana_get_rxbuf_headroom()). LL_RESERVED_SPACE() grows with MAX_SKB_FRAGS and + * exceeds XDP_PACKET_HEADROOM once CONFIG_MAX_SKB_FRAGS is 19 or more. + */ +static inline u32 mana_xdp_headroom(struct net_device *ndev) +{ + return max_t(u32, LL_RESERVED_SPACE(ndev), XDP_PACKET_HEADROOM); +} + +#define MANA_XDP_MTU_MAX(ndev) \ + (PAGE_SIZE - MANA_RXBUF_PAD - mana_xdp_headroom(ndev)) struct mana_rxq { struct gdma_queue *gdma_rq; @@ -691,6 +702,7 @@ int mana_query_link_cfg(struct mana_port_context *apc); int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, int enable_clamping); void mana_query_phy_stats(struct mana_port_context *apc); +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu); int mana_pre_alloc_rxbufs(struct mana_port_context *apc, int mtu, int num_queues); void mana_pre_dealloc_rxbufs(struct mana_port_context *apc); void mana_unmap_skb(struct sk_buff *skb, struct mana_port_context *apc); diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index f2e2771f188f4f..5b611efaa3cd17 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -183,10 +183,10 @@ enum nf_flow_flags { NF_FLOW_DNAT, NF_FLOW_CLOSING, NF_FLOW_TEARDOWN, + NF_FLOW_PENDING, NF_FLOW_HW, NF_FLOW_HW_DYING, NF_FLOW_HW_DEAD, - NF_FLOW_HW_PENDING, NF_FLOW_HW_BIDIRECTIONAL, NF_FLOW_HW_ESTABLISHED, }; diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index f35bd06a6bad68..1acaadb3e2cf08 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -810,6 +810,15 @@ static inline bool skb_at_tc_ingress(const struct sk_buff *skb) #endif } +static inline bool skb_at_tc_egress(const struct sk_buff *skb) +{ +#ifdef CONFIG_NET_EGRESS + return skb->nf_skip_egress && !skb_at_tc_ingress(skb); +#else + return false; +#endif +} + static inline bool skb_skip_tc_classify(struct sk_buff *skb) { #ifdef CONFIG_NET_CLS_ACT diff --git a/include/net/strparser.h b/include/net/strparser.h index 0ed73e364faa29..881b7ae3449864 100644 --- a/include/net/strparser.h +++ b/include/net/strparser.h @@ -91,11 +91,11 @@ static inline struct strp_msg *strp_msg(struct sk_buff *skb) struct strparser { struct sock *sk; - u32 stopped : 1; - u32 paused : 1; - u32 aborted : 1; - u32 interrupted : 1; - u32 unrecov_intr : 1; + u8 stopped; + u8 paused; + u8 aborted; + u8 interrupted; + u8 unrecov_intr; struct sk_buff **skb_nextp; struct sk_buff *skb_head; diff --git a/include/net/xdp.h b/include/net/xdp.h index aa742f413c3585..07231adfb5f829 100644 --- a/include/net/xdp.h +++ b/include/net/xdp.h @@ -106,6 +106,11 @@ struct xdp_buff { }; }; +static __always_inline void xdp_reinit_buff(struct xdp_buff *xdp) +{ + xdp->flags = 0; +} + static __always_inline bool xdp_buff_has_frags(const struct xdp_buff *xdp) { return !!(xdp->flags & XDP_FLAGS_HAS_FRAGS); diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index a1e0de6be8eac1..38d9bd6abd8b6d 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -449,6 +449,11 @@ #define AW88399_NOISE_GATE_EN_MASK \ (~(((1<dev = (devptr)) /* device.c */ diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 45a5df574aa6d2..747e10ec1a2748 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -312,6 +312,7 @@ struct snd_ctl_elem_value; #define CS35L56_SDW1_PLAYBACK_PORT 1 #define CS35L56_SDW1_CAPTURE_PORT 3 +#define CS35L56_OT25_CAPTURE_PORT 4 #define CS35L56_NUM_BULK_SUPPLIES 3 #define CS35L56_NUM_DSP_REGIONS 5 diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 1d05f991f2ce7e..c7f29ad64e02c8 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -28,6 +28,7 @@ struct hda_codec; struct hda_pcm; struct hda_pcm_stream; struct hda_codec_ops; +struct hda_jack_tbl; /* * codec bus @@ -274,7 +275,8 @@ struct hda_codec { struct hda_codec *codec, hda_nid_t nid); /* jack detection */ - struct snd_array jacktbl; + struct hda_jack_tbl *jacktbl, *jacktbl_last; /* linked list head/tail */ + int jacktbl_used; /* number of jacktbl elements */ unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */ struct delayed_work jackpoll_work; @@ -519,7 +521,7 @@ void snd_hda_update_power_acct(struct hda_codec *codec); static inline bool hda_codec_need_resume(struct hda_codec *codec) { - return !codec->relaxed_resume && codec->jacktbl.used; + return !codec->relaxed_resume && codec->jacktbl_used; } /* diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index aa994d6e6d35f2..04fd4794bceee5 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -96,7 +96,8 @@ struct hdac_device { bool lazy_cache:1; /* don't wake up for writes */ bool caps_overwriting:1; /* caps overwrite being in process */ bool cache_coef:1; /* cache COEF read/write too */ - unsigned int registered:1; /* codec was registered */ + bool registered; /* codec was registered */ + bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */ }; /* device/driver type used for matching */ @@ -123,6 +124,7 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus, const char *name, unsigned int addr); void snd_hdac_device_exit(struct hdac_device *dev); int snd_hdac_device_register(struct hdac_device *codec); +void snd_hdac_device_disable_unsol(struct hdac_device *codec); void snd_hdac_device_unregister(struct hdac_device *codec); int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name); int snd_hdac_codec_modalias(const struct hdac_device *hdac, char *buf, size_t size); @@ -179,9 +181,9 @@ static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, #ifdef CONFIG_PM int snd_hdac_power_up(struct hdac_device *codec); -int snd_hdac_power_down(struct hdac_device *codec); +void snd_hdac_power_down(struct hdac_device *codec); int snd_hdac_power_up_pm(struct hdac_device *codec); -int snd_hdac_power_down_pm(struct hdac_device *codec); +void snd_hdac_power_down_pm(struct hdac_device *codec); int snd_hdac_keep_power_up(struct hdac_device *codec); /* call this at entering into suspend/resume callbacks in codec driver */ @@ -207,9 +209,9 @@ static inline bool snd_hdac_is_power_on(struct hdac_device *codec) } #else static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down(struct hdac_device *codec) {} static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {} static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; } static inline void snd_hdac_enter_pm(struct hdac_device *codec) {} static inline void snd_hdac_leave_pm(struct hdac_device *codec) {} diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 6c1acc984ecb06..ac5e9412582e7c 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -292,9 +292,12 @@ static inline int snd_soc_component_cache_sync( return regcache_sync(component->regmap); } -struct snd_soc_component *snd_soc_component_alloc(struct device *dev); +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver); +int snd_soc_component_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + int num_dai); -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); const char *snd_soc_component_name(struct snd_soc_component *component); void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); diff --git a/include/sound/soc.h b/include/sound/soc.h index 553bb7e2be9840..08fdee15dba7a7 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -447,15 +447,9 @@ static inline int snd_soc_resume(struct device *dev) } #endif int snd_soc_poweroff(struct device *dev); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, int num_dai); -int snd_soc_register_component_d(struct device *dev, +int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, struct snd_soc_dai_driver *dai_drv, int num_dai); -#define snd_soc_register_component(x, ...) _Generic((x), \ -struct device * : snd_soc_register_component_d, \ -struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) int devm_snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index e3ad94444864d5..7d4618bea04349 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -38,6 +38,7 @@ struct allocinfo_counter { __u64 bytes; __u64 calls; __u8 accurate; + __u8 pad[7]; } __attribute__((aligned(8))); struct allocinfo_tag_data { diff --git a/include/uapi/linux/random.h b/include/uapi/linux/random.h index 1dd047ec98a121..ef6614d9c70497 100644 --- a/include/uapi/linux/random.h +++ b/include/uapi/linux/random.h @@ -58,7 +58,7 @@ struct rand_pool_info { /** * struct vgetrandom_opaque_params - arguments for allocating memory for vgetrandom * - * @size_per_opaque_state: Size of each state that is to be passed to vgetrandom(). + * @size_of_opaque_state: Size of each state passed to vgetrandom(). * @mmap_prot: Value of the prot argument in mmap(2). * @mmap_flags: Value of the flags argument in mmap(2). * @reserved: Reserved for future use. diff --git a/include/vdso/getrandom.h b/include/vdso/getrandom.h index 6ca4d6de9e4688..1d9dbd4d76c121 100644 --- a/include/vdso/getrandom.h +++ b/include/vdso/getrandom.h @@ -64,7 +64,7 @@ extern void __arch_chacha20_blocks_nostack(u8 *dst_bytes, const u32 *key, u32 *c * @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 + * This function is implemented by making a single call to __cvdso_getrandom(), whose * documentation may be consulted for more information. * * Returns: The return value of __cvdso_getrandom(). diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c522e..ad592fdf29af4f 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -173,6 +173,11 @@ config CC_HAS_ALLOC_TOKEN config CC_HAS_MULTIDIMENSIONAL_NONSTRING def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) +config CC_OPT_INLINE_MEMSET + string + default "-finline-stringops=memset" if $(cc-option,-finline-stringops=memset) + default "-mllvm -max-store-memset=4294967294" if $(cc-option,-mllvm -max-store-memset=4294967294) + config LD_CAN_USE_KEEP_IN_OVERLAY # ld.lld prior to 21.0.0 did not support KEEP within an overlay description # https://github.com/llvm/llvm-project/pull/130661 diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index c0037632b7af60..4a21511c481163 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -253,6 +253,7 @@ void io_bpf_filter_clone(struct io_restriction *dst, struct io_restriction *src) * If the src filter is going away, just ignore it. */ if (refcount_inc_not_zero(&src->bpf_filters->refs)) { + src->bpf_filters_cow = true; dst->bpf_filters = src->bpf_filters; dst->bpf_filters_cow = true; } diff --git a/io_uring/cancel.c b/io_uring/cancel.c index 7d7820eab878be..4f706aff14fc2e 100644 --- a/io_uring/cancel.c +++ b/io_uring/cancel.c @@ -634,12 +634,11 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd) prepare_to_wait(&tctx->wait, &wait, TASK_INTERRUPTIBLE); io_run_task_work(); io_uring_drop_tctx_refs(current); + /* only skip the sleep for local work this task may run */ xa_for_each(&tctx->xa, index, node) { - if (io_local_work_pending(node->ctx)) { - WARN_ON_ONCE(node->ctx->submitter_task && - node->ctx->submitter_task != current); + if (io_local_work_pending(node->ctx) && + io_allowed_defer_tw_run(node->ctx)) goto end_wait; - } } /* * If we've seen completions, retry without waiting. This diff --git a/io_uring/cmd_net.c b/io_uring/cmd_net.c index 7cd411fc4f33eb..90d4ec7cc7615c 100644 --- a/io_uring/cmd_net.c +++ b/io_uring/cmd_net.c @@ -69,8 +69,8 @@ static inline int io_uring_cmd_setsockopt(struct socket *sock, optlen); } -static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, - struct sk_buff *skb, unsigned issue_flags) +static int io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, + struct sk_buff *skb, unsigned int issue_flags) { struct sock_exterr_skb *serr = SKB_EXT_ERR(skb); struct io_uring_cqe cqe[2]; @@ -83,7 +83,7 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, ret = skb_get_tx_timestamp(skb, sk, &ts); if (ret < 0) - return false; + return ret; tskey = serr->ee.ee_data; tstype = serr->ee.ee_info; @@ -98,7 +98,9 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, iots = (struct io_timespec *)&cqe[1]; iots->tv_sec = ts.tv_sec; iots->tv_nsec = ts.tv_nsec; - return io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe); + if (!io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe)) + return -ENOBUFS; + return 0; } static int io_uring_cmd_timestamp(struct socket *sock, @@ -135,7 +137,8 @@ static int io_uring_cmd_timestamp(struct socket *sock, skb = skb_peek(&list); if (!skb) break; - if (!io_process_timestamp_skb(cmd, sk, skb, issue_flags)) + ret = io_process_timestamp_skb(cmd, sk, skb, issue_flags); + if (ret) break; __skb_dequeue(&list); consume_skb(skb); @@ -145,6 +148,12 @@ static int io_uring_cmd_timestamp(struct socket *sock, scoped_guard(spinlock_irqsave, &q->lock) skb_queue_splice(&list, q); } + /* + * Aux CQEs cannot overflow and the poll is edge triggered, so nothing + * re-runs the command once the CQ drains. End the multishot instead. + */ + if (ret == -ENOBUFS) + return -ENOBUFS; return -EAGAIN; } diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 61053421d809a7..cf5218d100d902 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -733,7 +733,8 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) * Post dummy CQE if a 32b CQE is needed and there's only room for a * 16b CQE before the ring wraps. */ - if (cqe32 && off + 1 == ctx->cq_entries) { + if (cqe32 && (ctx->flags & IORING_SETUP_CQE_MIXED) && + off + 1 == ctx->cq_entries) { if (!io_fill_nop_cqe(ctx, off)) return false; off = 0; @@ -742,12 +743,13 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) free = ctx->cq_entries - io_cqring_queued(ctx); /* we need a contiguous range, limit based on the current array offset */ len = min(free, ctx->cq_entries - off); - if (len < (cqe32 + 1)) - return false; - if (ctx->flags & IORING_SETUP_CQE32) { + if (!len) + return false; off <<= 1; len <<= 1; + } else if (len < (cqe32 + 1)) { + return false; } ctx->cqe_cached = &rings->cqes[off]; @@ -781,7 +783,7 @@ static bool io_fill_cqe_aux(struct io_ring_ctx *ctx, u64 user_data, s32 res, WRITE_ONCE(cqe->res, res); WRITE_ONCE(cqe->flags, cflags); - if (cqe32) { + if (cqe32 || (ctx->flags & IORING_SETUP_CQE32)) { WRITE_ONCE(cqe->big_cqe[0], 0); WRITE_ONCE(cqe->big_cqe[1], 0); } @@ -2068,8 +2070,9 @@ int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) static void io_rings_free(struct io_ring_ctx *ctx) { - io_free_region(ctx->user, &ctx->sq_region); - io_free_region(ctx->user, &ctx->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &ctx->sq_region); + io_free_region(NULL, &ctx->ring_region); ctx->rings = NULL; RCU_INIT_POINTER(ctx->rings_rcu, NULL); ctx->sq_sqes = NULL; @@ -2403,8 +2406,8 @@ static __cold void io_ring_exit_work(struct work_struct *work) spin_lock(&ctx->completion_lock); spin_unlock(&ctx->completion_lock); - /* pairs with RCU read section in io_req_local_work_add() */ - if (ctx->flags & IORING_SETUP_DEFER_TASKRUN) + /* pairs with the RCU read sections in the task_work add paths */ + if (ctx->flags & (IORING_SETUP_DEFER_TASKRUN | IORING_SETUP_SQPOLL)) synchronize_rcu(); io_ring_ctx_free(ctx); @@ -2733,7 +2736,8 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + ret = io_create_region(NULL, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; ctx->rings = rings = io_region_get_ptr(&ctx->ring_region); @@ -2747,7 +2751,7 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &ctx->sq_region, &rd, IORING_OFF_SQES); if (ret) { io_rings_free(ctx); return ret; @@ -2958,7 +2962,8 @@ static void io_ctx_restriction_clone(struct io_ring_ctx *ctx, if (dst->op_registered) ctx->int_flags |= IO_RING_F_OP_RESTRICTED; if (dst->reg_registered) - ctx->int_flags |= IO_RING_F_REG_RESTRICTED; + ctx->int_flags |= IO_RING_F_REG_RESTRICTED | + IO_RING_F_RESTRICT_INHERITED; } static __cold int io_uring_create(struct io_ctx_config *config) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c309173dd194b..6e2facf34bfa6b 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -459,7 +459,7 @@ static int io_remove_buffers_legacy(struct io_ring_ctx *ctx, static void io_put_bl(struct io_ring_ctx *ctx, struct io_buffer_list *bl) { if (bl->flags & IOBL_BUF_RING) - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); else io_remove_buffers_legacy(ctx, bl, -1U); @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &bl->region, &rd, mmap_offset); + ret = io_create_region(NULL, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); @@ -709,7 +709,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) if (!ret) return 0; fail: - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); kfree(bl); return ret; } diff --git a/io_uring/loop.c b/io_uring/loop.c index bbbb6ef14e6a8e..3d472b7827fac8 100644 --- a/io_uring/loop.c +++ b/io_uring/loop.c @@ -2,6 +2,7 @@ #include "io_uring.h" #include "wait.h" #include "loop.h" +#include "tctx.h" static inline int io_loop_nr_cqes(const struct io_ring_ctx *ctx, const struct iou_loop_params *lp) @@ -81,6 +82,10 @@ int io_run_loop(struct io_ring_ctx *ctx) { int ret; + ret = io_uring_add_tctx_node(ctx); + if (unlikely(ret)) + return ret; + if (!io_allowed_run_tw(ctx)) return -EEXIST; diff --git a/io_uring/memmap.c b/io_uring/memmap.c index 48c0eb01241239..d4d71694697149 100644 --- a/io_uring/memmap.c +++ b/io_uring/memmap.c @@ -204,7 +204,7 @@ static int io_region_allocate_pages(struct io_mapped_region *mr, return 0; } -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset) { @@ -230,8 +230,8 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, return -EOVERFLOW; nr_pages = reg->size >> PAGE_SHIFT; - if (ctx->user) { - ret = __io_account_mem(ctx->user, nr_pages); + if (user) { + ret = __io_account_mem(user, nr_pages); if (ret) return ret; } @@ -240,7 +240,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, if (reg->flags & IORING_MEM_REGION_TYPE_USER) ret = io_region_pin_pages(mr, reg); else - ret = io_region_allocate_pages(mr, reg, mmap_offset, ctx->user); + ret = io_region_allocate_pages(mr, reg, mmap_offset, user); if (ret) goto out_free; @@ -249,7 +249,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, goto out_free; return 0; out_free: - io_free_region(ctx->user, mr); + io_free_region(user, mr); return ret; } diff --git a/io_uring/memmap.h b/io_uring/memmap.h index f4cfbb6b9a1f46..0714bb5a9616ef 100644 --- a/io_uring/memmap.h +++ b/io_uring/memmap.h @@ -18,7 +18,7 @@ unsigned long io_uring_get_unmapped_area(struct file *file, unsigned long addr, int io_uring_mmap(struct file *file, struct vm_area_struct *vma); void io_free_region(struct user_struct *user, struct io_mapped_region *mr); -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset); diff --git a/io_uring/net.c b/io_uring/net.c index 050ed274170a17..f5f4f3ce6ef35c 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1741,7 +1742,8 @@ void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - if (conn->addr_len < (int)sizeof(*sin6)) + /* The IPv6 stack accepts SIN6_LEN_RFC2133; port and addr are in it. */ + if (conn->addr_len < SIN6_LEN_RFC2133) break; bctx->connect.port = sin6->sin6_port; memcpy(bctx->connect.v6_addr, &sin6->sin6_addr, diff --git a/io_uring/register.c b/io_uring/register.c index 02bc103bcc9d54..dab3eb4258dffe 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -480,8 +480,9 @@ struct io_ring_ctx_rings { static void io_register_free_rings(struct io_ring_ctx *ctx, struct io_ring_ctx_rings *r) { - io_free_region(ctx->user, &r->sq_region); - io_free_region(ctx->user, &r->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &r->sq_region); + io_free_region(NULL, &r->ring_region); } #define swap_old(ctx, o, n, field) \ @@ -529,7 +530,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.ring_region, &rd, IORING_OFF_CQ_RING); + ret = io_create_region(NULL, &n.ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; @@ -559,7 +560,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &n.sq_region, &rd, IORING_OFF_SQES); if (ret) { io_register_free_rings(ctx, &n); return ret; @@ -730,7 +731,8 @@ static int io_register_mem_region(struct io_ring_ctx *ctx, void __user *uarg) !(ctx->flags & IORING_SETUP_R_DISABLED)) return -EINVAL; - ret = io_create_region(ctx, ®ion, &rd, IORING_MAP_OFF_PARAM_REGION); + ret = io_create_region(ctx->user, ®ion, &rd, + IORING_MAP_OFF_PARAM_REGION); if (ret) return ret; if (copy_to_user(rd_uptr, &rd, sizeof(rd))) { @@ -764,7 +766,14 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, if (ctx->submitter_task && ctx->submitter_task != current) return -EEXIST; - if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && !(ctx->flags & IORING_SETUP_R_DISABLED)) { + /* + * A disabled ring is exempt from its own register restrictions, so + * that the creator can set them up and then enable the ring. That + * does not apply to restrictions inherited from the task. + */ + if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && + (!(ctx->flags & IORING_SETUP_R_DISABLED) || + (ctx->int_flags & IO_RING_F_RESTRICT_INHERITED))) { opcode = array_index_nospec(opcode, IORING_REGISTER_LAST); if (!test_bit(opcode, ctx->restrictions.register_op)) return -EACCES; diff --git a/io_uring/tw.c b/io_uring/tw.c index f573bcc3af6a63..f9affd68dd263d 100644 --- a/io_uring/tw.c +++ b/io_uring/tw.c @@ -210,6 +210,9 @@ void io_req_normal_work_add(struct io_kiocb *req) struct io_uring_task *tctx = req->tctx; struct io_ring_ctx *ctx = req->ctx; + /* SQPOLL can retire the request on push, see io_ring_exit_work() */ + guard(rcu)(); + /* tw run already pending, nothing else to do */ if (!mpscq_push(&tctx->task_list, &req->io_task_work.node)) return; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 86d580d4410d9c..cc9df7e64a407f 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -374,6 +374,7 @@ struct io_zcrx_args { struct io_kiocb *req; struct io_zcrx_ifq *ifq; unsigned nr_skbs; + bool stopped_early; }; static const struct memory_provider_ops io_uring_pp_zc_ops; @@ -431,7 +432,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, mmap_offset = IORING_MAP_OFF_ZCRX_REGION; mmap_offset += (u64)id << IORING_OFF_ZCRX_SHIFT; - ret = io_create_region(ctx, &ifq->rq_region, rd, mmap_offset); + ret = io_create_region(ctx->user, &ifq->rq_region, rd, mmap_offset); if (ret < 0) return ret; @@ -1899,6 +1900,9 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, } out: + /* Bytes left in len mean an error stopped the walk early. */ + if (len) + args->stopped_early = true; if (offset == start_off) return ret; desc->count -= (offset - start_off); @@ -1935,8 +1939,9 @@ static int io_zcrx_tcp_recvmsg(struct io_kiocb *req, struct io_zcrx_ifq *ifq, ret = -ENOTCONN; else ret = -EAGAIN; - } else if (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) && - (issue_flags & IO_URING_F_MULTISHOT)) { + } else if ((issue_flags & IO_URING_F_MULTISHOT) && + (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) || + args.stopped_early)) { ret = IOU_REQUEUE; } else if (sock_flag(sk, SOCK_DONE)) { /* Make it to retry until it finally gets 0. */ diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c index 1ed19b7759129a..f2e81be8265e84 100644 --- a/kernel/audit_tree.c +++ b/kernel/audit_tree.c @@ -545,22 +545,38 @@ static void kill_rules(struct audit_context *context, struct audit_tree *tree) { struct audit_krule *rule, *next; struct audit_entry *entry; + bool need_sync = false; list_for_each_entry_safe(rule, next, &tree->rules, rlist) { entry = container_of(rule, struct audit_entry, rule); - list_del_init(&rule->rlist); if (rule->tree) { /* not a half-baked one */ audit_tree_log_remove_rule(context, rule); - if (entry->rule.exe) - audit_remove_mark(entry->rule.exe); rule->tree = NULL; list_del_rcu(&entry->list); list_del(&entry->rule.list); - call_rcu(&entry->rcu, audit_free_rule_rcu); + if (entry->rule.exe) + need_sync = true; + } else { + list_del_init(&rule->rlist); } } + + if (list_empty(&tree->rules)) + return; + + if (need_sync) + synchronize_rcu(); + + list_for_each_entry_safe(rule, next, &tree->rules, rlist) { + entry = container_of(rule, struct audit_entry, rule); + + list_del_init(&rule->rlist); + if (entry->rule.exe) + audit_remove_mark(entry->rule.exe); + call_rcu(&entry->rcu, audit_free_rule_rcu); + } } /* diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 2e3bf8113ae9b0..7f11555a50702d 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1362,7 +1362,7 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, { struct bpf_insn *to = to_buff; u32 imm_rnd = get_random_u32(); - s16 off; + int off; BUILD_BUG_ON(BPF_REG_PARAMS + 2 != MAX_BPF_JIT_REG); BUILD_BUG_ON(BPF_REG_AX + 1 != MAX_BPF_JIT_REG); @@ -1438,6 +1438,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, off = from->off; if (off < 0) off -= 2; + if (off < S16_MIN) + return -ERANGE; *to++ = BPF_ALU64_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm); *to++ = BPF_ALU64_IMM(BPF_XOR, BPF_REG_AX, imm_rnd); *to++ = BPF_JMP_REG(from->code, from->dst_reg, BPF_REG_AX, off); @@ -1458,6 +1460,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, off = from->off; if (off < 0) off -= 2; + if (off < S16_MIN) + return -ERANGE; *to++ = BPF_ALU32_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm); *to++ = BPF_ALU32_IMM(BPF_XOR, BPF_REG_AX, imm_rnd); *to++ = BPF_JMP32_REG(from->code, from->dst_reg, BPF_REG_AX, @@ -1606,7 +1610,9 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp if (!rewritten) continue; - if (env) + if (rewritten < 0) + tmp = ERR_PTR(rewritten); + else if (env) tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten); else tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten); diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index f9464e566f1094..13a2356c84cf90 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -128,6 +128,7 @@ struct htab_elem { struct htab_btf_record { struct btf_record *record; + struct btf *btf; u32 key_size; }; @@ -497,8 +498,13 @@ static void htab_dtor_ctx_free(void *ctx) { struct htab_btf_record *hrec = ctx; + /* + * The duplicated record still points into the map BTF, so free it + * before dropping the reference that keeps that BTF alive. + */ btf_record_free(hrec->record); - kfree(ctx); + btf_put(hrec->btf); + kfree(hrec); } static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, @@ -521,6 +527,15 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, kfree(hrec); return err; } + /* + * btf_record_dup() only acquires kernel and module BTF. Fields whose + * types live in the map BTF keep pointing into it: kptrs to local + * types refer to map->btf, and graph roots carry a value record owned + * by its struct meta table. The context can outlive the map when the + * allocator defers its teardown, so hold a reference of our own. + */ + hrec->btf = map->btf; + btf_get(hrec->btf); bpf_mem_alloc_set_dtor(ma, dtor, htab_dtor_ctx_free, hrec); return 0; } @@ -3359,8 +3374,10 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map, } if (do_delete) { + migrate_disable(); for (i = 0; i < total; i++) rhtab_delete_elem(rhtab, del_elems[i], NULL, 0); + migrate_enable(); } rcu_read_unlock(); diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index 8a8f088e83e6f9..15684d0fc883b7 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -118,6 +118,11 @@ struct bpf_mem_cache { struct llist_head free_by_rcu_ttrace; struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; + /* + * 0 - idle + * 1 - __free_rcu() is queued + * 2 - __free_rcu() is queued and free_by_rcu_ttrace got more objects since + */ atomic_t call_rcu_ttrace_in_progress; raw_spinlock_t lock; }; @@ -276,6 +281,8 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per return cnt; } +static void __do_call_rcu_ttrace(struct bpf_mem_cache *c); + static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); @@ -285,7 +292,19 @@ static void __free_rcu(struct rcu_head *head) llnode = llist_del_all(&c->waiting_for_gp_ttrace); free_all(c, llnode, !!c->percpu_size); - atomic_set(&c->call_rcu_ttrace_in_progress, 0); + + /* + * do_call_rcu_ttrace() that ran while GP was in flight left its objects + * in free_by_rcu_ttrace. This cache may never free or alloc in bulk + * again, so start the next GP from here. + * 'c' can be freed as soon as call_rcu_ttrace_in_progress is zero. + */ + if (atomic_cmpxchg(&c->call_rcu_ttrace_in_progress, 1, 0) == 1) + return; + + /* Pairs with synchronize_rcu() in free_mem_alloc() */ + guard(rcu)(); + __do_call_rcu_ttrace(c); } static void enque_to_free(struct bpf_mem_cache *c, void *obj) @@ -298,18 +317,15 @@ static void enque_to_free(struct bpf_mem_cache *c, void *obj) llist_add(llnode, &c->free_by_rcu_ttrace); } -static void do_call_rcu_ttrace(struct bpf_mem_cache *c) +static void __do_call_rcu_ttrace(struct bpf_mem_cache *c) { struct llist_node *llnode, *t; - if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { - if (unlikely(READ_ONCE(c->draining))) { - scoped_guard(raw_spinlock_irqsave, &c->lock) - llnode = llist_del_all(&c->free_by_rcu_ttrace); - free_all(c, llnode, !!c->percpu_size); - } - return; - } + /* + * Must be done before llist_del_all(). Objects that it misses were + * added by do_call_rcu_ttrace() that will set 2 after this store. + */ + atomic_set(&c->call_rcu_ttrace_in_progress, 1); WARN_ON_ONCE(!llist_empty(&c->waiting_for_gp_ttrace)); llist_for_each_safe(llnode, t, llist_del_all(&c->free_by_rcu_ttrace)) @@ -328,6 +344,22 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) call_rcu_tasks_trace(&c->rcu_ttrace, __free_rcu); } +static void do_call_rcu_ttrace(struct bpf_mem_cache *c) +{ + struct llist_node *llnode; + + if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 2)) { + if (unlikely(READ_ONCE(c->draining))) { + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); + free_all(c, llnode, !!c->percpu_size); + } + return; + } + + __do_call_rcu_ttrace(c); +} + static void free_bulk(struct bpf_mem_cache *c) { struct bpf_mem_cache *tgt = c->tgt; @@ -700,7 +732,12 @@ static void free_mem_alloc(struct bpf_mem_alloc *ma) * to wait for the pending __free_by_rcu(), and __free_rcu(). RCU Tasks * Trace grace period implies RCU grace period, so all __free_rcu don't * need extra call_rcu() (and thus extra rcu_barrier() here). + * + * __free_rcu() queues itself again unless it sees 'draining'. After + * synchronize_rcu() it either did that already or will not do it, so + * rcu_barrier_tasks_trace() cannot miss it. */ + synchronize_rcu(); rcu_barrier(); /* wait for __free_by_rcu */ rcu_barrier_tasks_trace(); /* wait for __free_rcu */ free_mem_alloc_no_barrier(ma); diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 244a939b9d2da4..96217b99399d52 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2448,6 +2448,21 @@ static void __bpf_prog_put_rcu(struct rcu_head *rcu) bpf_prog_free(aux->prog); } +/* + * Progs called from a trampoline can also be reached by a task that was + * preempted in the trampoline before the prog's enter helper took its RCU + * read lock, wait for those first. + */ +static void __bpf_prog_put_rcu_tasks(struct rcu_head *rcu) +{ + struct bpf_prog *prog = container_of(rcu, struct bpf_prog_aux, rcu)->prog; + + if (prog->sleepable) + call_rcu_tasks_trace(rcu, __bpf_prog_put_rcu); + else + call_rcu(rcu, __bpf_prog_put_rcu); +} + static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred) { bpf_prog_kallsyms_del_all(prog); @@ -2461,7 +2476,9 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred) btf_put(prog->aux->attach_btf); if (deferred) { - if (prog->sleepable) + if (IS_ENABLED(CONFIG_TASKS_RCU) && prog->aux->tramp_linked) + call_rcu_tasks(&prog->aux->rcu, __bpf_prog_put_rcu_tasks); + else if (prog->sleepable) call_rcu_tasks_trace(&prog->aux->rcu, __bpf_prog_put_rcu); else call_rcu(&prog->aux->rcu, __bpf_prog_put_rcu); diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 90b70ea0d37092..bf4cb3dd444d67 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -401,6 +401,7 @@ static struct bpf_trampoline *bpf_trampoline_lookup(u64 key, unsigned long ip) head = &trampoline_ip_table[hash_64(tr->ip, TRAMPOLINE_HASH_BITS)]; hlist_add_head(&tr->hlist_ip, head); refcount_set(&tr->refcnt, 1); + INIT_LIST_HEAD(&tr->images); for (i = 0; i < BPF_TRAMP_MAX; i++) INIT_HLIST_HEAD(&tr->progs_hlist[i]); out: @@ -565,15 +566,22 @@ static void bpf_tramp_image_free(struct bpf_tramp_image *im) arch_free_bpf_trampoline(im->image, im->size); bpf_jit_uncharge_modmem(im->size); percpu_ref_exit(&im->pcref); + kfree(im->skips); kfree_rcu(im, rcu); } static void __bpf_tramp_image_put_deferred(struct work_struct *work) { struct bpf_tramp_image *im; + struct bpf_trampoline *tr; im = container_of(work, struct bpf_tramp_image, work); + tr = im->tr; + trampoline_lock(tr); + list_del(&im->list); + trampoline_unlock(tr); bpf_tramp_image_free(im); + bpf_trampoline_put(tr); } /* callback, fexit step 3 or fentry step 2 */ @@ -601,7 +609,7 @@ static void __bpf_tramp_image_put_rcu_tasks(struct rcu_head *rcu) struct bpf_tramp_image *im; im = container_of(rcu, struct bpf_tramp_image, rcu); - if (im->ip_after_call) + if (im->call_orig) /* the case of fmod_ret/fexit trampoline and CONFIG_PREEMPTION=y */ percpu_ref_kill(&im->pcref); else @@ -621,9 +629,9 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) * * The trampoline is unreachable before bpf_tramp_image_put(). * - * First, patch the trampoline to avoid calling into fexit progs. - * The progs will be freed even if the original function is still - * executing or sleeping. + * Progs are patched out of the image when they are detached, see + * bpf_trampoline_skip_prog(), so they can be freed even if a task is + * still in the image. * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on * first few asm instructions to execute and call into * __bpf_tramp_enter->percpu_ref_get. @@ -637,11 +645,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) * percpu_ref_kill will be waiting for. Hence the first * call_rcu_tasks() is not necessary. */ - if (im->ip_after_call) { - int err = bpf_arch_text_poke(im->ip_after_call, BPF_MOD_NOP, - BPF_MOD_JUMP, NULL, - im->ip_epilogue); - WARN_ON(err); + if (im->call_orig) { if (IS_ENABLED(CONFIG_TASKS_RCU)) call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu_tasks); else @@ -658,7 +662,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) call_rcu_tasks_trace(&im->rcu, __bpf_tramp_image_put_rcu_tasks); } -static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) +static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size, int nr_progs) { struct bpf_tramp_image *im; struct bpf_ksym *ksym; @@ -669,6 +673,10 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) if (!im) goto out; + im->skips = kzalloc_objs(*im->skips, nr_progs); + if (!im->skips) + goto out_free_im; + err = bpf_jit_charge_modmem(size); if (err) goto out_free_im; @@ -695,6 +703,7 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) out_uncharge: bpf_jit_uncharge_modmem(size); out_free_im: + kfree(im->skips); kfree(im); out: return ERR_PTR(err); @@ -771,11 +780,12 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut goto out; } - im = bpf_tramp_image_alloc(tr->key, size); + im = bpf_tramp_image_alloc(tr->key, size, total); if (IS_ERR(im)) { err = PTR_ERR(im); goto out; } + im->call_orig = tr->flags & BPF_TRAMP_F_CALL_ORIG; err = arch_prepare_bpf_trampoline(im, im->image, im->image + size, &tr->func.model, tr->flags, tnodes, @@ -806,8 +816,14 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut #endif out_free: - if (err) + if (err) { bpf_tramp_image_free(im); + } else { + /* track the image until it is freed, for bpf_trampoline_skip_prog() */ + refcount_inc(&tr->refcnt); + im->tr = tr; + list_add(&im->list, &tr->images); + } out: /* If any error happens, restore previous flags */ if (err) @@ -907,6 +923,7 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr, } hlist_add_head(&node->tramp_hlist, prog_list); + node->link->prog->aux->tramp_linked = true; if (kind == BPF_TRAMP_FSESSION) { tr->progs_cnt[BPF_TRAMP_FENTRY]++; fexit = fsession_exit(node); @@ -920,6 +937,41 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr, return 0; } +/* + * Patch the nop in front of a prog call to a jump over it. A task can be + * preempted anywhere in the image, so archs that need several instructions for + * a jump of any range patch a single near branch here instead. + */ +int __weak arch_bpf_trampoline_skip(void *nop, void *target) +{ + return bpf_arch_text_poke(nop, BPF_MOD_NOP, BPF_MOD_JUMP, NULL, target); +} + +/* + * prog was detached and can be freed, but tasks may still be running in images + * that call it, sleeping in an earlier prog for example. They can be in any + * image that is not freed yet, not only in cur_image, so patch all of them to + * jump over prog. + */ +static void bpf_trampoline_skip_prog(struct bpf_trampoline *tr, struct bpf_prog *prog) +{ + struct bpf_tramp_image *im; + int i, err; + + list_for_each_entry(im, &tr->images, list) { + for (i = 0; i < im->nr_skips; i++) { + struct bpf_tramp_skip *skip = &im->skips[i]; + + if (skip->prog != prog) + continue; + err = arch_bpf_trampoline_skip(skip->nop, skip->target); + WARN_ON_ONCE(err); + /* not a nop anymore, and prog's address can be reused */ + skip->prog = NULL; + } + } +} + static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr, struct bpf_tramp_node *node) { @@ -937,6 +989,7 @@ static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr, } hlist_del_init(&node->tramp_hlist); tr->progs_cnt[kind]--; + bpf_trampoline_skip_prog(tr, node->link->prog); } static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node, @@ -1245,11 +1298,9 @@ void bpf_trampoline_put(struct bpf_trampoline *tr) if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[i]))) goto out; - /* This code will be executed even when the last bpf_tramp_image - * is alive. All progs are detached from the trampoline and the - * trampoline image is patched with jmp into epilogue to skip - * fexit progs. The fentry-only trampoline will be freed via - * multiple rcu callbacks. + /* + * All progs are detached and the last image has been freed, images + * hold a reference on the trampoline until then. */ hlist_del(&tr->hlist_key); hlist_del(&tr->hlist_ip); diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 41b49c56e1237c..5f874979b8d751 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14826,7 +14826,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range."); return -EINVAL; } - reg_bounds_sync(dst_reg); + /* + * A packet pointer that keeps its id or range is checked against a + * range set from the checked pointer's umax, so var_off must not tighten + * its umax. r32 must still match var_off for reg_bounds_sanity_check(). + */ + if (reg_is_pkt_pointer(dst_reg) && (known || dst_reg->range > 0)) + __update_reg32_bounds(dst_reg); + else + reg_bounds_sync(dst_reg); bounds_ret = sanitize_check_bounds(env, insn, dst_reg); if (bounds_ret == -EACCES) return bounds_ret; diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 3f52717c19654e..8b220e9df91e97 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -152,6 +152,12 @@ static cpumask_var_t isolated_cpus; /* CSCB */ */ static bool update_housekeeping; /* RWCS */ +/* + * Set if "cpuset_v2_mode" mount option is used + * Cached at bind time and not lock protected; accessed via {READ,WRITE}_ONCE + */ +static bool cpuset_v2_mode; + /* * Copy of isolated_cpus to be passed to housekeeping_update() */ @@ -439,8 +445,7 @@ static inline bool cpuset_v2(void) */ static inline bool is_in_v2_mode(void) { - return cpuset_v2() || - (cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE); + return cpuset_v2() || READ_ONCE(cpuset_v2_mode); } /** @@ -513,10 +518,26 @@ static void guarantee_active_cpus(struct task_struct *tsk, rcu_read_lock(); cs = task_cs(tsk); - while (!cpumask_intersects(cs->effective_cpus, pmask)) + while (!cpumask_intersects(cs->effective_cpus, pmask)) { cs = parent_cs(cs); - + if (unlikely(!cs)) { + /* + * The top cpuset doesn't have any active cpu as a + * consequence of a race between its caller and the cpu + * hotplug operation where cpu_active_mask is updated + * asynchronously before cpuset_handle_hotplug() is + * being called to adjust the effective_cpus of the + * affected cpusets. But we know the top cpuset's + * effective_cpus is on its way to be identical to + * cpu_active_mask minus the exclusive CPUs dedicated + * to other valid cpuset partitions. Just pass back + * the filtered cpu_active_mask in this case. + */ + goto out_unlock; + } + } cpumask_and(pmask, pmask, cs->effective_cpus); +out_unlock: rcu_read_unlock(); } @@ -3732,6 +3753,8 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css) mutex_lock(&cpuset_mutex); spin_lock_irq(&callback_lock); + WRITE_ONCE(cpuset_v2_mode, + !!(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE)); if (is_in_v2_mode()) { cpumask_copy(top_cpuset.cpus_allowed, cpu_possible_mask); cpumask_copy(top_cpuset.effective_xcpus, cpu_possible_mask); @@ -4044,15 +4067,16 @@ static void cpuset_handle_hotplug(void) static cpumask_t new_cpus; static nodemask_t new_mems; bool cpus_updated, mems_updated; - bool on_dfl = is_in_v2_mode(); + bool on_dfl; struct tmpmasks tmp, *ptmp = NULL; - if (on_dfl && !alloc_tmpmasks(&tmp)) - ptmp = &tmp; - lockdep_assert_cpus_held(); mutex_lock(&cpuset_mutex); + on_dfl = is_in_v2_mode(); + if (on_dfl && !alloc_tmpmasks(&tmp)) + ptmp = &tmp; + /* fetch the available cpus/mems and find out which changed how */ cpumask_copy(&new_cpus, cpu_active_mask); new_mems = node_states[N_MEMORY]; diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index aa2f1c4588b973..5edea93ec63545 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -1844,10 +1844,10 @@ phys_addr_t default_swiotlb_base(void) phys_addr_t default_swiotlb_limit(void) { #ifdef CONFIG_SWIOTLB_DYNAMIC - return io_tlb_default_mem.phys_limit; -#else - return io_tlb_default_mem.defpool.end - 1; + if (io_tlb_default_mem.can_grow) + return io_tlb_default_mem.phys_limit; #endif + return io_tlb_default_mem.defpool.end - 1; } #ifdef CONFIG_DEBUG_FS diff --git a/kernel/events/core.c b/kernel/events/core.c index 634d2ccbab82d8..7846d70be57f6c 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7626,9 +7626,11 @@ static void perf_sigtrap(struct perf_event *event) { /* * Both perf_pending_task() and perf_pending_irq() can race with the - * task exiting. + * task exiting or exec-ing. We can determine if such a race has + * occurred by checking if perf_event_exit_task(), which will set + * ctx->task to TASK_TOMBSTONE, has already been called. */ - if (current->flags & PF_EXITING) + if (event->ctx->task == TASK_TOMBSTONE) return; /* @@ -8129,10 +8131,15 @@ static void __perf_event_header__init_id(struct perf_sample_data *data, } } -void perf_event_header__init_id(struct perf_event_header *header, - struct perf_sample_data *data, - struct perf_event *event) +void perf_event_header__init(struct perf_event_header *header, + struct perf_sample_data *data, + u32 type, u16 misc, u16 size, + struct perf_event *event) { + header->type = type; + header->misc = misc; + header->size = size; + if (event->attr.sample_id_all) { header->size += event->id_header_size; __perf_event_header__init_id(data, event, event->attr.sample_type); @@ -8969,17 +8976,16 @@ perf_event_read_event(struct perf_event *event, struct perf_output_handle handle; struct perf_sample_data sample; struct perf_read_event read_event = { - .header = { - .type = PERF_RECORD_READ, - .misc = 0, - .size = sizeof(read_event) + event->read_size, - }, .pid = perf_event_pid(event, task), .tid = perf_event_tid(event, task), }; int ret; - perf_event_header__init_id(&read_event.header, &sample, event); + perf_event_header__init(&read_event.header, &sample, + PERF_RECORD_READ, + /* misc= */ 0, + sizeof(read_event) + event->read_size, + event); ret = perf_output_begin(&handle, &sample, event, read_event.header.size); if (ret) return; @@ -9220,6 +9226,7 @@ struct perf_task_event { u32 ptid; u64 time; } event_id; + int new; }; static int perf_event_task_match(struct perf_event *event) @@ -9236,17 +9243,21 @@ static void perf_event_task_output(struct perf_event *event, struct perf_output_handle handle; struct perf_sample_data sample; struct task_struct *task = task_event->task; - int ret, size = task_event->event_id.header.size; + int ret; if (!perf_event_task_match(event)) return; - perf_event_header__init_id(&task_event->event_id.header, &sample, event); + perf_event_header__init(&task_event->event_id.header, &sample, + task_event->new ? PERF_RECORD_FORK : PERF_RECORD_EXIT, + /* misc= */ 0, + sizeof(task_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, task_event->event_id.header.size); if (ret) - goto out; + return; task_event->event_id.pid = perf_event_pid(event, task); task_event->event_id.tid = perf_event_tid(event, task); @@ -9268,8 +9279,6 @@ static void perf_event_task_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - task_event->event_id.header.size = size; } static void perf_event_task(struct task_struct *task, @@ -9286,18 +9295,7 @@ static void perf_event_task(struct task_struct *task, task_event = (struct perf_task_event){ .task = task, .task_ctx = task_ctx, - .event_id = { - .header = { - .type = new ? PERF_RECORD_FORK : PERF_RECORD_EXIT, - .misc = 0, - .size = sizeof(task_event.event_id), - }, - /* .pid */ - /* .ppid */ - /* .tid */ - /* .ptid */ - /* .time */ - }, + .new = new, }; perf_iterate_sb(perf_event_task_output, @@ -9374,6 +9372,7 @@ struct perf_comm_event { u32 pid; u32 tid; } event_id; + bool exec; }; static int perf_event_comm_match(struct perf_event *event) @@ -9387,18 +9386,21 @@ static void perf_event_comm_output(struct perf_event *event, struct perf_comm_event *comm_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int size = comm_event->event_id.header.size; int ret; if (!perf_event_comm_match(event)) return; - perf_event_header__init_id(&comm_event->event_id.header, &sample, event); + perf_event_header__init(&comm_event->event_id.header, &sample, + PERF_RECORD_COMM, + comm_event->exec ? PERF_RECORD_MISC_COMM_EXEC : 0, + sizeof(comm_event->event_id) + comm_event->comm_size, + event); ret = perf_output_begin(&handle, &sample, event, comm_event->event_id.header.size); if (ret) - goto out; + return; comm_event->event_id.pid = perf_event_pid(event, comm_event->task); comm_event->event_id.tid = perf_event_tid(event, comm_event->task); @@ -9410,8 +9412,6 @@ static void perf_event_comm_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - comm_event->event_id.header.size = size; } static void perf_event_comm_event(struct perf_comm_event *comm_event) @@ -9426,8 +9426,6 @@ static void perf_event_comm_event(struct perf_comm_event *comm_event) comm_event->comm = comm; comm_event->comm_size = size; - comm_event->event_id.header.size = sizeof(comm_event->event_id) + size; - perf_iterate_sb(perf_event_comm_output, comm_event, NULL); @@ -9444,15 +9442,8 @@ void perf_event_comm(struct task_struct *task, bool exec) .task = task, /* .comm */ /* .comm_size */ - .event_id = { - .header = { - .type = PERF_RECORD_COMM, - .misc = exec ? PERF_RECORD_MISC_COMM_EXEC : 0, - /* .size */ - }, - /* .pid */ - /* .tid */ - }, + /* .event_id */ + .exec = exec, }; perf_event_comm_event(&comm_event); @@ -9486,18 +9477,20 @@ static void perf_event_namespaces_output(struct perf_event *event, struct perf_namespaces_event *namespaces_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - u16 header_size = namespaces_event->event_id.header.size; int ret; if (!perf_event_namespaces_match(event)) return; - perf_event_header__init_id(&namespaces_event->event_id.header, - &sample, event); + perf_event_header__init(&namespaces_event->event_id.header, &sample, + PERF_RECORD_NAMESPACES, + /* misc= */ 0, + sizeof(namespaces_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, namespaces_event->event_id.header.size); if (ret) - goto out; + return; namespaces_event->event_id.pid = perf_event_pid(event, namespaces_event->task); @@ -9509,8 +9502,6 @@ static void perf_event_namespaces_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - namespaces_event->event_id.header.size = header_size; } static void perf_fill_ns_link_info(struct perf_ns_link_info *ns_link_info, @@ -9541,11 +9532,7 @@ void perf_event_namespaces(struct task_struct *task) namespaces_event = (struct perf_namespaces_event){ .task = task, .event_id = { - .header = { - .type = PERF_RECORD_NAMESPACES, - .misc = 0, - .size = sizeof(namespaces_event.event_id), - }, + /* .header */ /* .pid */ /* .tid */ .nr_namespaces = NR_NAMESPACES, @@ -9613,18 +9600,19 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data) struct perf_cgroup_event *cgroup_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - u16 header_size = cgroup_event->event_id.header.size; int ret; + u16 size = sizeof(cgroup_event->event_id) + cgroup_event->path_size; if (!perf_event_cgroup_match(event)) return; - perf_event_header__init_id(&cgroup_event->event_id.header, - &sample, event); + perf_event_header__init(&cgroup_event->event_id.header, &sample, + PERF_RECORD_CGROUP, /* misc= */ 0, size, + event); ret = perf_output_begin(&handle, &sample, event, cgroup_event->event_id.header.size); if (ret) - goto out; + return; perf_output_put(&handle, cgroup_event->event_id); __output_copy(&handle, cgroup_event->path, cgroup_event->path_size); @@ -9632,8 +9620,6 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data) perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - cgroup_event->event_id.header.size = header_size; } static void perf_event_cgroup(struct cgroup *cgrp) @@ -9648,11 +9634,6 @@ static void perf_event_cgroup(struct cgroup *cgrp) cgroup_event = (struct perf_cgroup_event){ .event_id = { - .header = { - .type = PERF_RECORD_CGROUP, - .misc = 0, - .size = sizeof(cgroup_event.event_id), - }, .id = cgroup_id(cgrp), }, }; @@ -9675,7 +9656,6 @@ static void perf_event_cgroup(struct cgroup *cgrp) while (!IS_ALIGNED(size, sizeof(u64))) cgroup_event.path[size++] = '\0'; - cgroup_event.event_id.header.size += size; cgroup_event.path_size = size; perf_iterate_sb(perf_event_cgroup_output, @@ -9731,38 +9711,40 @@ static void perf_event_mmap_output(struct perf_event *event, struct perf_mmap_event *mmap_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int size = mmap_event->event_id.header.size; - u32 type = mmap_event->event_id.header.type; - bool use_build_id; + int size = sizeof(mmap_event->event_id) + mmap_event->file_size; + u32 type = PERF_RECORD_MMAP; + u16 misc = PERF_RECORD_MISC_USER; + bool use_build_id = false; int ret; if (!perf_event_mmap_match(event, data)) return; if (event->attr.mmap2) { - mmap_event->event_id.header.type = PERF_RECORD_MMAP2; - mmap_event->event_id.header.size += sizeof(mmap_event->maj); - mmap_event->event_id.header.size += sizeof(mmap_event->min); - mmap_event->event_id.header.size += sizeof(mmap_event->ino); - mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation); - mmap_event->event_id.header.size += sizeof(mmap_event->prot); - mmap_event->event_id.header.size += sizeof(mmap_event->flags); - } - - perf_event_header__init_id(&mmap_event->event_id.header, &sample, event); + type = PERF_RECORD_MMAP2; + size += sizeof(mmap_event->maj); + size += sizeof(mmap_event->min); + size += sizeof(mmap_event->ino); + size += sizeof(mmap_event->ino_generation); + size += sizeof(mmap_event->prot); + size += sizeof(mmap_event->flags); + use_build_id = event->attr.build_id && mmap_event->build_id_size; + if (use_build_id) + misc |= PERF_RECORD_MISC_MMAP_BUILD_ID; + } + if (!(mmap_event->vma->vm_flags & VM_EXEC)) + misc |= PERF_RECORD_MISC_MMAP_DATA; + + perf_event_header__init(&mmap_event->event_id.header, &sample, + type, misc, size, event); ret = perf_output_begin(&handle, &sample, event, mmap_event->event_id.header.size); if (ret) - goto out; + return; mmap_event->event_id.pid = perf_event_pid(event, current); mmap_event->event_id.tid = perf_event_tid(event, current); - use_build_id = event->attr.build_id && mmap_event->build_id_size; - - if (event->attr.mmap2 && use_build_id) - mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_BUILD_ID; - perf_output_put(&handle, mmap_event->event_id); if (event->attr.mmap2) { @@ -9787,9 +9769,6 @@ static void perf_event_mmap_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - mmap_event->event_id.header.size = size; - mmap_event->event_id.header.type = type; } static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) @@ -9885,11 +9864,6 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) mmap_event->prot = prot; mmap_event->flags = flags; - if (!(vma->vm_flags & VM_EXEC)) - mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA; - - mmap_event->event_id.header.size = sizeof(mmap_event->event_id) + size; - if (atomic_read(&nr_build_id_events)) build_id_parse_nofault(vma, mmap_event->build_id, &mmap_event->build_id_size); @@ -10009,11 +9983,7 @@ void perf_event_mmap(struct vm_area_struct *vma) /* .file_name */ /* .file_size */ .event_id = { - .header = { - .type = PERF_RECORD_MMAP, - .misc = PERF_RECORD_MISC_USER, - /* .size */ - }, + /* .header */ /* .pid */ /* .tid */ .start = vma->vm_start, @@ -10043,18 +10013,15 @@ void perf_event_aux_event(struct perf_event *event, unsigned long head, u64 size; u64 flags; } rec = { - .header = { - .type = PERF_RECORD_AUX, - .misc = 0, - .size = sizeof(rec), - }, .offset = head, .size = size, .flags = flags, }; int ret; - perf_event_header__init_id(&rec.header, &sample, event); + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_AUX, /* misc= */ 0, sizeof(rec), + event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) @@ -10079,15 +10046,14 @@ void perf_log_lost_samples(struct perf_event *event, u64 lost) struct perf_event_header header; u64 lost; } lost_samples_event = { - .header = { - .type = PERF_RECORD_LOST_SAMPLES, - .misc = 0, - .size = sizeof(lost_samples_event), - }, .lost = lost, }; - perf_event_header__init_id(&lost_samples_event.header, &sample, event); + perf_event_header__init(&lost_samples_event.header, &sample, + PERF_RECORD_LOST_SAMPLES, + /* misc= */ 0, + sizeof(lost_samples_event), + event); ret = perf_output_begin(&handle, &sample, event, lost_samples_event.header.size); @@ -10112,6 +10078,8 @@ struct perf_switch_event { u32 next_prev_pid; u32 next_prev_tid; } event_id; + bool sched_in; + bool preempt; }; static int perf_event_switch_match(struct perf_event *event) @@ -10124,6 +10092,9 @@ static void perf_event_switch_output(struct perf_event *event, void *data) struct perf_switch_event *se = data; struct perf_output_handle handle; struct perf_sample_data sample; + u32 type; + u16 misc; + u16 size; int ret; if (!perf_event_switch_match(event)) @@ -10131,18 +10102,22 @@ static void perf_event_switch_output(struct perf_event *event, void *data) /* Only CPU-wide events are allowed to see next/prev pid/tid */ if (event->ctx->task) { - se->event_id.header.type = PERF_RECORD_SWITCH; - se->event_id.header.size = sizeof(se->event_id.header); + type = PERF_RECORD_SWITCH; + size = sizeof(se->event_id.header); } else { - se->event_id.header.type = PERF_RECORD_SWITCH_CPU_WIDE; - se->event_id.header.size = sizeof(se->event_id); + type = PERF_RECORD_SWITCH_CPU_WIDE; + size = sizeof(se->event_id); se->event_id.next_prev_pid = perf_event_pid(event, se->next_prev); se->event_id.next_prev_tid = perf_event_tid(event, se->next_prev); } + misc = se->sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT; + if (se->preempt) + misc |= PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; - perf_event_header__init_id(&se->event_id.header, &sample, event); + perf_event_header__init(&se->event_id.header, &sample, + type, misc, size, event); ret = perf_output_begin(&handle, &sample, event, se->event_id.header.size); if (ret) @@ -10168,22 +10143,11 @@ static void perf_event_switch(struct task_struct *task, switch_event = (struct perf_switch_event){ .task = task, .next_prev = next_prev, - .event_id = { - .header = { - /* .type */ - .misc = sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT, - /* .size */ - }, - /* .next_prev_pid */ - /* .next_prev_tid */ - }, + /* .event_id */ + .sched_in = sched_in, + .preempt = !sched_in && task_is_runnable(task), }; - if (!sched_in && task_is_runnable(task)) { - switch_event.event_id.header.misc |= - PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; - } - perf_iterate_sb(perf_event_switch_output, &switch_event, NULL); } @@ -10203,20 +10167,17 @@ static void perf_log_throttle(struct perf_event *event, int enable) u64 id; u64 stream_id; } throttle_event = { - .header = { - .type = PERF_RECORD_THROTTLE, - .misc = 0, - .size = sizeof(throttle_event), - }, .time = perf_event_clock(event), .id = primary_event_id(event), .stream_id = event->id, }; - if (enable) - throttle_event.header.type = PERF_RECORD_UNTHROTTLE; - - perf_event_header__init_id(&throttle_event.header, &sample, event); + perf_event_header__init(&throttle_event.header, &sample, + enable ? PERF_RECORD_UNTHROTTLE + : PERF_RECORD_THROTTLE, + /* misc= */ 0, + sizeof(throttle_event), + event); ret = perf_output_begin(&handle, &sample, event, throttle_event.header.size); @@ -10255,12 +10216,14 @@ static void perf_event_ksymbol_output(struct perf_event *event, void *data) struct perf_output_handle handle; struct perf_sample_data sample; int ret; + u16 size = sizeof(ksymbol_event->event_id) + ksymbol_event->name_len; if (!perf_event_ksymbol_match(event)) return; - perf_event_header__init_id(&ksymbol_event->event_id.header, - &sample, event); + perf_event_header__init(&ksymbol_event->event_id.header, &sample, + PERF_RECORD_KSYMBOL, /* misc= */ 0, size, + event); ret = perf_output_begin(&handle, &sample, event, ksymbol_event->event_id.header.size); if (ret) @@ -10301,11 +10264,6 @@ void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister, .name = name, .name_len = name_len, .event_id = { - .header = { - .type = PERF_RECORD_KSYMBOL, - .size = sizeof(ksymbol_event.event_id) + - name_len, - }, .addr = addr, .len = len, .ksym_type = ksym_type, @@ -10349,8 +10307,11 @@ static void perf_event_bpf_output(struct perf_event *event, void *data) if (!perf_event_bpf_match(event)) return; - perf_event_header__init_id(&bpf_event->event_id.header, - &sample, event); + perf_event_header__init(&bpf_event->event_id.header, &sample, + PERF_RECORD_BPF_EVENT, + /* misc= */ 0, + sizeof(bpf_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, bpf_event->event_id.header.size); if (ret) @@ -10406,10 +10367,6 @@ void perf_event_bpf_event(struct bpf_prog *prog, bpf_event = (struct perf_bpf_event){ .prog = prog, .event_id = { - .header = { - .type = PERF_RECORD_BPF_EVENT, - .size = sizeof(bpf_event.event_id), - }, .type = type, .flags = flags, .id = prog->aux->id, @@ -10437,18 +10394,23 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data) struct perf_callchain_deferred_event *deferred_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int ret, size = deferred_event->event.header.size; + int ret; + u16 size = sizeof(deferred_event->event) + (deferred_event->trace->nr * sizeof(u64)); if (!event->attr.defer_output) return; /* XXX do we really need sample_id_all for this ??? */ - perf_event_header__init_id(&deferred_event->event.header, &sample, event); + perf_event_header__init(&deferred_event->event.header, &sample, + PERF_RECORD_CALLCHAIN_DEFERRED, + PERF_RECORD_MISC_USER, + size, + event); ret = perf_output_begin(&handle, &sample, event, deferred_event->event.header.size); if (ret) - goto out; + return; perf_output_put(&handle, deferred_event->event); for (int i = 0; i < deferred_event->trace->nr; i++) { @@ -10458,8 +10420,6 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data) perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - deferred_event->event.header.size = size; } static void perf_unwind_deferred_callback(struct unwind_work *work, @@ -10468,12 +10428,6 @@ static void perf_unwind_deferred_callback(struct unwind_work *work, struct perf_callchain_deferred_event deferred_event = { .trace = trace, .event = { - .header = { - .type = PERF_RECORD_CALLCHAIN_DEFERRED, - .misc = PERF_RECORD_MISC_USER, - .size = sizeof(deferred_event.event) + - (trace->nr * sizeof(u64)), - }, .cookie = cookie, .nr = trace->nr, }, @@ -10485,7 +10439,8 @@ static void perf_unwind_deferred_callback(struct unwind_work *work, struct perf_text_poke_event { const void *old_bytes; const void *new_bytes; - size_t pad; + u16 tot; + u16 pad; u16 old_len; u16 new_len; @@ -10506,13 +10461,18 @@ static void perf_event_text_poke_output(struct perf_event *event, void *data) struct perf_text_poke_event *text_poke_event = data; struct perf_output_handle handle; struct perf_sample_data sample; + u16 size = sizeof(text_poke_event->event_id) + text_poke_event->tot + text_poke_event->pad; u64 padding = 0; int ret; if (!perf_event_text_poke_match(event)) return; - perf_event_header__init_id(&text_poke_event->event_id.header, &sample, event); + perf_event_header__init(&text_poke_event->event_id.header, &sample, + PERF_RECORD_TEXT_POKE, + PERF_RECORD_MISC_KERNEL, + size, + event); ret = perf_output_begin(&handle, &sample, event, text_poke_event->event_id.header.size); @@ -10550,15 +10510,11 @@ void perf_event_text_poke(const void *addr, const void *old_bytes, text_poke_event = (struct perf_text_poke_event){ .old_bytes = old_bytes, .new_bytes = new_bytes, + .tot = tot, .pad = pad, .old_len = old_len, .new_len = new_len, .event_id = { - .header = { - .type = PERF_RECORD_TEXT_POKE, - .misc = PERF_RECORD_MISC_KERNEL, - .size = sizeof(text_poke_event.event_id) + tot + pad, - }, .addr = (unsigned long)addr, }, }; @@ -10589,13 +10545,12 @@ static void perf_log_itrace_start(struct perf_event *event) event->attach_state & PERF_ATTACH_ITRACE) return; - rec.header.type = PERF_RECORD_ITRACE_START; - rec.header.misc = 0; - rec.header.size = sizeof(rec); rec.pid = perf_event_pid(event, current); rec.tid = perf_event_tid(event, current); - perf_event_header__init_id(&rec.header, &sample, event); + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_ITRACE_START, /* misc= */ 0, sizeof(rec), + event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) @@ -10620,12 +10575,10 @@ void perf_report_aux_output_id(struct perf_event *event, u64 hw_id) if (event->parent) event = event->parent; - rec.header.type = PERF_RECORD_AUX_OUTPUT_HW_ID; - rec.header.misc = 0; - rec.header.size = sizeof(rec); - rec.hw_id = hw_id; - - perf_event_header__init_id(&rec.header, &sample, event); + rec.hw_id = hw_id; + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_AUX_OUTPUT_HW_ID, /* misc= */ 0, + sizeof(rec), event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) @@ -13953,7 +13906,7 @@ SYSCALL_DEFINE5(perf_event_open, if (err) return err; - if (!attr.exclude_kernel || + if (!attr.exclude_kernel || attr.text_poke || ((attr.sample_type & PERF_SAMPLE_CALLCHAIN) && !attr.exclude_callchain_kernel)) { err = perf_allow_kernel(); diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 1b1ffe0533e58a..13bd42e5493d54 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -246,14 +246,13 @@ __perf_output_begin(struct perf_output_handle *handle, handle->size = (1UL << page_shift) - offset; if (unlikely(have_lost)) { - lost_event.header.size = sizeof(lost_event); - lost_event.header.type = PERF_RECORD_LOST; - lost_event.header.misc = 0; lost_event.id = event->id; lost_event.lost = local_xchg(&rb->lost, 0); /* XXX mostly redundant; @data is already fully initializes */ - perf_event_header__init_id(&lost_event.header, data, event); + perf_event_header__init(&lost_event.header, data, + PERF_RECORD_LOST, /* misc= */ 0, + sizeof(lost_event), event); perf_output_put(handle, lost_event); perf_event__output_id_sample(event, handle, data); } diff --git a/kernel/fork.c b/kernel/fork.c index 10f2d05d816a5f..eb198c8dfdc719 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3317,14 +3317,17 @@ int ksys_unshare(unsigned long unshare_flags) shm_init_task(current); } + if (new_fs) { + new_fs = switch_fs_struct(new_fs); + if (new_fs) + free_fs_struct(no_free_ptr(new_fs)); + } + if (new_nsproxy) { switch_task_namespaces(current, new_nsproxy); new_nsproxy = NULL; } - if (new_fs) - new_fs = switch_fs_struct(new_fs); - if (new_fd) { guard(task_lock)(current); swap(current->files, new_fd); @@ -3340,8 +3343,9 @@ int ksys_unshare(unsigned long unshare_flags) perf_event_namespaces(current); bad_unshare_cleanup_nsproxy: + /* never installed, so no active references to drop */ if (new_nsproxy) - put_nsproxy(new_nsproxy); + nsproxy_free(new_nsproxy); bad_unshare_cleanup_cred: if (new_cred) put_cred(new_cred); diff --git a/kernel/futex/core.c b/kernel/futex/core.c index a061f54b606dd0..095f9fe440e379 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -213,10 +213,12 @@ static bool __futex_pivot_hash(struct mm_struct *mm, struct futex_private_hash * futex_rehash_private(fph, new); } new->state = FR_PERCPU; - scoped_guard(rcu) { - mmph->batches = get_state_synchronize_rcu(); - rcu_assign_pointer(mmph->hash, new); - } + rcu_assign_pointer(mmph->hash, new); + /* + * mmph->batches must reference a grace period which started after + * mmph->hash was assigned. See futex_ref_drop(). + */ + mmph->batches = get_state_synchronize_rcu(); kvfree_rcu(fph, rcu); return true; } diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 05cba4e16dad12..6923f37eaab400 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -150,6 +150,18 @@ config IRQ_KUNIT_TEST If unsure, say N. +config REFCOUNT_INTERRUPT_KUNIT_TEST + tristate "Test refcounted interrupt enable/disable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the kunit tests for the refcounted interrupt + infrastructure. It verifies the correctness of single, nested, + and multiple interrupt enable/disable state changes and ensures + that the underlying reference counting mechanisms work as expected. + + If unsure, say N. + endmenu config GENERIC_IRQ_MULTI_HANDLER diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index 44c4d6fc502ac1..0e5df962a14970 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -16,4 +16,4 @@ obj-$(CONFIG_SMP) += affinity.o obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o obj-$(CONFIG_IRQ_KUNIT_TEST) += irq_test.o -obj-$(CONFIG_KUNIT) += refcount_interrupt_test.o +obj-$(CONFIG_REFCOUNT_INTERRUPT_KUNIT_TEST) += refcount_interrupt_test.o diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 4edd8ca5c65782..e787e4948c8b6c 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -496,14 +496,16 @@ static bool kprobe_queued(struct kprobe *p) static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr) { int i; - struct kprobe *p = NULL; + struct kprobe *p; struct optimized_kprobe *op; /* Don't check i == 0, since that is a breakpoint case. */ - for (i = 1; !p && i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++) + for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++) { p = get_kprobe(addr - i); + /* A disabled probe can have prepared, but inactive, optinsns. */ + if (!p || !kprobe_optready(p) || kprobe_disarmed(p)) + continue; - if (p && kprobe_optready(p)) { op = container_of(p, struct optimized_kprobe, kp); if (arch_within_optimized_kprobe(op, addr)) return p; diff --git a/kernel/module/main.c b/kernel/module/main.c index d0e1e0bd2ad06b..c1b34dc1e89ac6 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -3581,8 +3581,9 @@ static int load_module(struct load_info *info, const char __user *uargs, goto sysfs_cleanup; } - if (codetag_load_module(mod)) - goto sysfs_cleanup; + err = codetag_load_module(mod); + if (err) + goto livepatch_cleanup; /* Get rid of temporary copy. */ free_copy(info, flags); @@ -3592,6 +3593,9 @@ static int load_module(struct load_info *info, const char __user *uargs, return do_init_module(mod); + livepatch_cleanup: + if (is_livepatch_module(mod)) + free_module_elf(mod); sysfs_cleanup: mod_sysfs_teardown(mod); coming_cleanup: diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c index d9d3d5973bf523..3fb1595a4a5934 100644 --- a/kernel/nsproxy.c +++ b/kernel/nsproxy.c @@ -61,7 +61,7 @@ static inline struct nsproxy *create_nsproxy(void) return nsproxy; } -static inline void nsproxy_free(struct nsproxy *ns) +void nsproxy_free(struct nsproxy *ns) { put_mnt_ns(ns->mnt_ns); put_uts_ns(ns->uts_ns); diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index d17704fe93ae14..fb1b3774195277 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1887,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con) memset(ctxt, 0, sizeof(*ctxt)); ctxt->console = con; + /* Keep in sync with nbcon_braille_try_acquire(). */ ctxt->prio = NBCON_PRIO_NORMAL; if (!nbcon_context_try_acquire(ctxt, false)) @@ -2002,3 +2003,81 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt) */ __nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb)); } + +/** + * nbcon_is_braille - Checks whether the nbcon write context is using Braille console + * + * @wctxt: checked nbcon write context + * + * Return: True when the write context is associated with a Braille console. + * Otherwise, return false. + * + * Context: Can be called in any context but only when Braille console is + * registered and the struct console could not disappear. + */ +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + struct console *con = ctxt->console; + + return con && con->flags & CON_BRL; +} +EXPORT_SYMBOL_GPL(nbcon_write_context_is_braille); + +/** + * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write() + * + * @con: The nbcon console to acquire + * @wctxt: The nbcon write context to be used on success + * + * Context: braille_write() for emitting a single buffer on Braille console. + * + * Return: True if the console was acquired. False otherwise. + * + * Braille console is not registered as a proper printk consoles. Instead, + * it is integrated with the graphical virtual terminal. + * + * This function is going to synchronize the Braille write against other + * operations on the used serial port. The port can be used also for a user + * input but printk() won't emit the messages there directly. It means + * the other operations will get synchronized using nbcon_device_try_acquire(). + */ +bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + bool success; + + memset(ctxt, 0, sizeof(*ctxt)); + ctxt->console = con; + /* Keep in sync with nbcon_device_try_acquire(). */ + if (panic_on_this_cpu()) + ctxt->prio = NBCON_PRIO_PANIC; + else + ctxt->prio = NBCON_PRIO_NORMAL; + + success = nbcon_context_try_acquire(ctxt, false); + /* + * Try hard in panic, otherwise the Braille console would + * miss a text on the related VT. + */ + if (panic_on_this_cpu() && !success) { + ctxt->allow_unsafe_takeover = true; + success = nbcon_context_try_acquire(ctxt, false); + } + + return success; +} + +/** + * nbcon_braille_release - Release the nbcon console + * + * @wctxt: The nbcon write context initialized by a successful + * nbcon_braille_try_acquire() + */ +void nbcon_braille_release(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + + nbcon_context_release(ctxt); +} diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index ed204b3f4b8440..ad27880dd690f4 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -704,7 +704,7 @@ void cleanup_srcu_struct(struct srcu_struct *ssp) // Call srcu_barrier() before this cleanup_srcu_struct() // to avoid triggering this WARN_ON(). if (WARN_ON(timer_delete_sync(&sdp->delay_work) && - rcu_segcblist_n_cbs(&sdp->srcu_cblist)) && + !rcu_segcblist_empty(&sdp->srcu_cblist)) && rcu_cpu_beenfullyonline(sdp->cpu)) queue_work_on(sdp->cpu, rcu_gp_wq, &sdp->work); flush_work(&sdp->work); diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e56c3c95018fd4..5fe980da545dba 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2057,8 +2057,14 @@ void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, if (unlikely(!SCX_HAS_OP(sch, enqueue))) goto global; - /* DSQ bypass didn't trigger, enqueue on the BPF scheduler */ - qseq = rq->scx.ops_qseq++ << SCX_OPSS_QSEQ_SHIFT; + /* + * DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the + * per-task qseq counter where the QSEQ field wraps and skip 0, which is + * what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING. + */ + p->scx.ops_qseq = ((p->scx.ops_qseq + 1) & + (SCX_OPSS_QSEQ_MASK >> SCX_OPSS_QSEQ_SHIFT)) ?: 1; + qseq = (unsigned long)p->scx.ops_qseq << SCX_OPSS_QSEQ_SHIFT; WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE); atomic_long_set(&p->scx.ops_state, SCX_OPSS_QUEUEING | qseq); @@ -2123,8 +2129,8 @@ static void set_task_runnable(struct rq *rq, struct task_struct *p) } /* - * list_add_tail() must be used. scx_bypass() depends on tasks being - * appended to the runnable_list. + * list_add_tail() must be used. scx_bypass() and rq_offline_scx() + * depend on tasks being appended to the runnable_list. */ list_add_tail(&p->scx.runnable_node, &rq->scx.runnable_list); @@ -2152,6 +2158,13 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; + /* + * An SCX-internal migration ends on arrival. Clear sticky_cpu so @p can + * leave custody when inserted into the destination DSQ. + */ + if (sticky_cpu >= 0) + p->scx.sticky_cpu = -1; + /* * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue * returns. Only the core's wakeup path delivers one. The flags stashed @@ -2190,9 +2203,6 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ dl_server_start(&rq->ext_server); scx_do_enqueue_task(rq, p, enq_flags, sticky_cpu); - - if (sticky_cpu >= 0) - p->scx.sticky_cpu = -1; out: rq->scx.flags &= ~SCX_RQ_IN_WAKEUP; @@ -3731,8 +3741,26 @@ static void rq_online_scx(struct rq *rq) static void rq_offline_scx(struct rq *rq) { + struct task_struct *p, *n; + rq->scx.flags &= ~SCX_RQ_ONLINE; + + /* sched domain rebuilds call rq_offline with the CPU staying alive */ + if (cpu_active(cpu_of(rq))) + return; + scx_rescue_flush(rq); + + /* + * An offline CPU no longer calls ops.dispatch(). Re-enqueue its tasks + * onto the local DSQ so that they run here and balance_push() moves + * them off. + */ + list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list, scx.runnable_node) { + if (p->scx.dsq == &rq->scx.local_dsq) + continue; + guard(sched_change)(p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK); + } } static bool check_rq_for_timeouts(struct rq *rq) @@ -6947,9 +6975,9 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s, seq_buf_init(&ns, buf, avail); dump_newline(&ns); - scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu", + scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu", cpu, rq->scx.nr_running, rq->scx.flags, rq->scx.cpu_released, - rq->scx.ops_qseq, rq->scx.kick_sync); + rq->scx.kick_sync); scx_rescue_dump(&ns, rq); scx_dump_line(&ns, " curr=%s[%d] class=%ps", rq->curr->comm, rq->curr->pid, rq->curr->sched_class); diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index 2ff5479334cba9..8de9d8fb4b25fa 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -70,7 +70,13 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, #endif /* CONFIG_EXT_SUB_SCHED */ } - if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !scx_rq_online(rq)) + /* + * scx_rq_online() can't be used. Its cpu_active() test goes false + * before CPU hotplug waits for an RCU grace period, and + * rq_offline_scx() moves this CPU's tasks to the local DSQ only after + * the wait. The grace period can depend on those tasks running. + */ + if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !(rq->scx.flags & SCX_RQ_ONLINE)) return SCX_DSP_NONE; dspc->rq = rq; diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 1df8f583b0eca5..5b37faa532d674 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1998,6 +1998,11 @@ enum scx_ops_state { * dequeue/requeue, the dispatcher can tell whether it still has a claim * on the task being dispatched. * + * QSEQ is generated from the per-task p->scx.ops_qseq counter so that + * it doesn't repeat across QUEUED instances of the same task even if + * the task moves between rqs. 0 is never used as a valid QSEQ since + * NONE and DISPATCHING map to this value. + * * As some 32bit archs can't do 64bit store_release/load_acquire, * p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on * 32bit machines. The dispatch race window QSEQ protects is very narrow diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 0472eaf41c7c5c..90441206d3b95a 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -585,10 +585,6 @@ void scx_rescue_flush(struct rq *rq) lockdep_assert_rq_held(rq); - /* sched domain rebuilds call rq_offline with the CPU staying alive */ - if (cpu_active(cpu_of(rq))) - return; - /* end the current rescue */ if (rq->scx.rescue.curr) scx_task_slice_ended(rq, rq->scx.rescue.curr); diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index e656c7059bf864..4c25fbe84fb541 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -816,7 +816,6 @@ struct scx_rq { #endif struct list_head runnable_list; /* runnable tasks on this rq */ struct list_head ddsp_deferred_locals; /* deferred ddsps from enq */ - unsigned long ops_qseq; /* both stashed across the activate_task() in move_remote_task_to_local_dsq() */ u64 remote_activate_enq_flags; struct scx_sched *remote_activate_sch; @@ -4222,8 +4221,9 @@ extern void balance_callbacks(struct rq *rq, struct balance_callback *head); * after which it is enqueued again. * * Typically this must be called while holding task_rq_lock, since most/all - * properties are serialized under those locks. There is currently one - * exception to this rule in sched/ext which only holds rq->lock. + * properties are serialized under those locks. There are currently two + * exceptions to this rule in sched/ext which only hold rq->lock: scx_bypass() + * and rq_offline_scx(). */ /* diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f7b75985d5420a..38597f26b34c37 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -483,7 +483,7 @@ int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, if (val < 0) { *negp = true; - *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val; + *u_ptr = k_ptr_op ? k_ptr_op(-(ulong)val) : -(ulong)val; } else { *negp = false; *u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val; diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 80c35481153841..9b3487d40cd6a1 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -101,6 +101,8 @@ void __init register_refined_jiffies(long cycles_per_second) #ifdef CONFIG_SYSCTL static ulong mult_hz(const ulong val) { + if (val >= ULONG_MAX / HZ) + return ULONG_MAX; return val * HZ; } diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index 9f2d98181779af..da286619c5d898 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -47,6 +47,10 @@ static struct rhltable fprobe_ip_table; static DEFINE_MUTEX(fprobe_mutex); static struct fgraph_ops fprobe_graph_ops; +DEFINE_LOCK_GUARD_0(rcu_sched_notrace, + rcu_read_lock_sched_notrace(), + rcu_read_unlock_sched_notrace()) + static u32 fprobe_node_hashfn(const void *data, u32 len, u32 seed) { return hash_ptr(*(unsigned long **)data, 32); @@ -329,16 +333,14 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip, struct fprobe *fp; int bit; + if (!rcu_is_watching()) + return; + bit = ftrace_test_recursion_trylock(ip, parent_ip); if (bit < 0) return; - /* - * ftrace_test_recursion_trylock() disables preemption, but - * rhltable_lookup() checks whether rcu_read_lcok is held. - * So we take rcu_read_lock() here. - */ - rcu_read_lock(); + guard(rcu_sched_notrace)(); head = rhltable_lookup(&fprobe_ip_table, &ip, fprobe_rht_params); rhl_for_each_entry_rcu(node, pos, head, hlist) { @@ -353,7 +355,6 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip, else __fprobe_handler(ip, parent_ip, fp, fregs, NULL); } - rcu_read_unlock(); ftrace_test_recursion_unlock(bit); } NOKPROBE_SYMBOL(fprobe_ftrace_entry); @@ -567,10 +568,13 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops struct fprobe *fp; int used, ret; + if (!rcu_is_watching()) + return 0; + if (WARN_ON_ONCE(!fregs)) return 0; - guard(rcu)(); + guard(rcu_sched_notrace)(); head = rhltable_lookup(&fprobe_ip_table, &func, fprobe_rht_params); reserved_words = 0; rhl_for_each_entry_rcu(node, pos, head, hlist) { @@ -665,13 +669,16 @@ static void fprobe_return(struct ftrace_graph_ret *trace, int size, curr; int size_words; + if (!rcu_is_watching()) + return; + fgraph_data = (unsigned long *)fgraph_retrieve_data(gops->idx, &size); if (WARN_ON_ONCE(!fgraph_data)) return; size_words = SIZE_IN_LONG(size); ret_ip = ftrace_regs_get_instruction_pointer(fregs); - preempt_disable_notrace(); + guard(rcu_sched_notrace)(); curr = 0; while (size_words > curr) { @@ -687,7 +694,6 @@ static void fprobe_return(struct ftrace_graph_ret *trace, } curr += size; } - preempt_enable_notrace(); } NOKPROBE_SYMBOL(fprobe_return); diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 95952539373975..c56c042d1c3519 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2290,7 +2290,7 @@ static bool is_chained_work(struct workqueue_struct *wq) * Return %true iff I'm a worker executing a work item on @wq. If * I'm @worker, it's safe to dereference it without locking. */ - return worker && worker->current_pwq->wq == wq; + return worker && worker->current_pwq && worker->current_pwq->wq == wq; } /* diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625e8..6b24a2c89b84d4 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2906,6 +2906,21 @@ config LIVEUPDATE_TEST If unsure, say N +config DIMLIB_KUNIT_TEST + tristate "KUnit test for the DIM library" if !KUNIT_ALL_TESTS + depends on KUNIT && NET + select DIMLIB + default KUNIT_ALL_TESTS + help + This builds the KUnit test for the Dynamic Interrupt Moderation + library (lib/dim/), which checks the rate statistics computed by + dim_calc_stats(). + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config CMDLINE_KUNIT_TEST tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/dim/Makefile b/lib/dim/Makefile index 5b9bfaac7ac1fd..47e1c36230e4ca 100644 --- a/lib/dim/Makefile +++ b/lib/dim/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_DIMLIB) += dimlib.o dimlib-y := dim.o net_dim.o rdma_dim.o + +obj-$(CONFIG_DIMLIB_KUNIT_TEST) += dim_kunit.o diff --git a/lib/dim/dim.c b/lib/dim/dim.c index 97c3d084ebf023..7f3eebae73cb92 100644 --- a/lib/dim/dim.c +++ b/lib/dim/dim.c @@ -69,11 +69,15 @@ bool dim_calc_stats(const struct dim_sample *start, if (!delta_us) return false; - curr_stats->ppms = DIV_ROUND_UP(npkts * USEC_PER_MSEC, delta_us); - curr_stats->bpms = DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us); + /* u32 * USEC_PER_MSEC overflows a 32-bit long */ + curr_stats->ppms = DIV_ROUND_UP_ULL((u64)npkts * USEC_PER_MSEC, + delta_us); + curr_stats->bpms = DIV_ROUND_UP_ULL((u64)nbytes * USEC_PER_MSEC, + delta_us); curr_stats->epms = DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, delta_us); - curr_stats->cpms = DIV_ROUND_UP(ncomps * USEC_PER_MSEC, delta_us); + curr_stats->cpms = DIV_ROUND_UP_ULL((u64)ncomps * USEC_PER_MSEC, + delta_us); if (curr_stats->epms != 0) curr_stats->cpe_ratio = DIV_ROUND_DOWN_ULL( curr_stats->cpms * 100, curr_stats->epms); diff --git a/lib/dim/dim_kunit.c b/lib/dim/dim_kunit.c new file mode 100644 index 00000000000000..2e984f6b9fe4fb --- /dev/null +++ b/lib/dim/dim_kunit.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Dynamic Interrupt Moderation library + */ + +#include +#include +#include +#include + +struct dim_calc_stats_case { + const char *name; + u32 delta_us; + u32 start_pkts, end_pkts; + u32 start_bytes, end_bytes; + u32 start_comps, end_comps; + int ppms, bpms, cpms; +}; + +static const struct dim_calc_stats_case dim_calc_stats_cases[] = { + { + .name = "small", + .delta_us = 1000, + .end_pkts = 640, .end_bytes = 640 * 1500, .end_comps = 64, + .ppms = 640, .bpms = 960000, .cpms = 64, + }, + { + .name = "round_up", + .delta_us = 3000, + .end_pkts = 10, .end_bytes = 10, .end_comps = 1, + .ppms = 4, .bpms = 4, .cpms = 1, + }, + { + .name = "counter_wrap", + .delta_us = 1000, + .start_pkts = 0xffffff00, .end_pkts = 0x100, + .start_bytes = 0xfffff000, .end_bytes = 0x1000, + .start_comps = 0xfffffff0, .end_comps = 0x10, + .ppms = 0x200, .bpms = 0x2000, .cpms = 0x20, + }, + { + /* 30 MB in 10 ms (24 Gbit/s): nbytes * 1000 exceeds 32 bits */ + .name = "many_bytes", + .delta_us = 10000, + .end_pkts = 20000, .end_bytes = 30000000, .end_comps = 64, + .ppms = 2000, .bpms = 3000000, .cpms = 7, + }, + { + /* 4.3 MB in 16 ms (2.15 Gbit/s), just above the 32-bit limit */ + .name = "bytes_32bit_limit", + .delta_us = 16000, + .end_pkts = 2900, .end_bytes = 4300000, .end_comps = 64, + .ppms = 182, .bpms = 268750, .cpms = 4, + }, + { + /* 5 MB in 40 ms: a 1 Gbit/s link at line rate */ + .name = "gigabit", + .delta_us = 40000, + .end_pkts = 3300, .end_bytes = 5000000, .end_comps = 64, + .ppms = 83, .bpms = 125000, .cpms = 2, + }, + { + /* 5 million packets and completions in 2 s */ + .name = "many_packets", + .delta_us = 2000000, + .end_pkts = 5000000, .end_bytes = 5000000, .end_comps = 5000000, + .ppms = 2500, .bpms = 2500, .cpms = 2500, + }, +}; + +static void dim_calc_stats_case_desc(const struct dim_calc_stats_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(dim_calc_stats, dim_calc_stats_cases, + dim_calc_stats_case_desc); + +static void dim_calc_stats_test(struct kunit *test) +{ + const struct dim_calc_stats_case *t = test->param_value; + struct dim_sample start = {}, end = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, t->start_pkts, t->start_bytes, + t->start_comps, &start); + dim_update_sample_with_comps(DIM_NEVENTS, t->end_pkts, t->end_bytes, + t->end_comps, &end); + /* dim_update_sample() stamps ktime_get(); use fixed times instead */ + start.time = ktime_set(1000, 0); + end.time = ktime_add_us(start.time, t->delta_us); + + KUNIT_ASSERT_TRUE(test, dim_calc_stats(&start, &end, &stats)); + KUNIT_EXPECT_EQ(test, stats.ppms, t->ppms); + KUNIT_EXPECT_EQ(test, stats.bpms, t->bpms); + KUNIT_EXPECT_EQ(test, stats.cpms, t->cpms); + KUNIT_EXPECT_EQ(test, stats.epms, + (int)DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, + t->delta_us)); +} + +static void dim_calc_stats_no_time_test(struct kunit *test) +{ + struct dim_sample sample = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, 100, 1000, 10, &sample); + KUNIT_EXPECT_FALSE(test, dim_calc_stats(&sample, &sample, &stats)); +} + +static struct kunit_case dim_test_cases[] = { + KUNIT_CASE_PARAM(dim_calc_stats_test, dim_calc_stats_gen_params), + KUNIT_CASE(dim_calc_stats_no_time_test), + {} +}; + +static struct kunit_suite dim_test_suite = { + .name = "dim", + .test_cases = dim_test_cases, +}; + +kunit_test_suite(dim_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the DIM library"); +MODULE_LICENSE("GPL"); diff --git a/mm/damon/core.c b/mm/damon/core.c index 1764620903f2d3..ab3c4d75496449 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3147,7 +3147,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) unsigned long cumulated_sz, cached_esz; unsigned int score, max_score = 0; - if (!quota->ms && !quota->sz && list_empty("a->goals)) + if (!damos_quota_is_set(quota)) return; /* First charge window */ diff --git a/mm/kasan/common.c b/mm/kasan/common.c index b7d05c2a6d93dd..7d21c4db68d20f 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -114,12 +114,23 @@ void kasan_unpoison_task_stack(struct task_struct *task) /* Unpoison the stack for the current task beyond a watermark sp value. */ asmlinkage void kasan_unpoison_task_stack_below(const void *watermark) { + void *base; + + /* + * Only the generic mode needs this. In the tag-based mode the + * compiler fully initializes the shadow of stack variables on + * function entry, so stale tags left below the watermark are + * harmless. + */ + if (!IS_ENABLED(CONFIG_KASAN_GENERIC)) + return; + /* * Calculate the task stack base address. Avoid using 'current' * because this function is called by early resume code which hasn't * yet set up the percpu register (%gs). */ - void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1)); + base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1)); kasan_unpoison(base, watermark - base, false); } diff --git a/mm/mremap.c b/mm/mremap.c index 7c368440fafe24..444f37d7cd28ec 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1037,6 +1037,7 @@ static void vrm_stat_account(struct vma_remap_struct *vrm, } static bool __check_map_count_against_split(struct mm_struct *mm, + bool pre_split, bool before_unmaps) { const int sys_map_count = get_sysctl_max_map_count(); @@ -1088,26 +1089,42 @@ static bool __check_map_count_against_split(struct mm_struct *mm, * Therefore we must check to ensure we have headroom of 2 additional * VMAs. */ - return map_count + 2 <= sys_map_count; + map_count += 2; + + /* If pre-split, the -1 observed above doesn't apply. */ + if (pre_split) + map_count++; + + return map_count <= sys_map_count; +} + +static bool needs_pre_split(struct vma_remap_struct *vrm) +{ + /* + * An MREMAP_DONTUNMAP of a mlock()'d VMA needs to unlock the + * source VMA, so split in this case. + */ + return (vrm->flags & MREMAP_DONTUNMAP) && + vma_test(vrm->vma, VMA_LOCKED_BIT); } /* Do we violate the map count limit if we split VMAs when moving the VMA? */ -static bool check_map_count_against_split(void) +static bool check_map_count_against_split(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/false); + needs_pre_split(vrm), /*before_unmaps=*/false); } /* Do we violate the map count limit if we split VMAs prior to early unmaps? */ -static bool check_map_count_against_split_early(void) +static bool check_map_count_against_split_early(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/true); + vrm->flags & MREMAP_DONTUNMAP, /*before_unmaps=*/true); } /* - * Perform checks before attempting to write a VMA prior to it being - * moved. + * Perform checks and preparation before attempting to write a VMA prior to it + * being moved. */ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) { @@ -1116,19 +1133,17 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) unsigned long old_addr = vrm->addr; unsigned long old_len = vrm->old_len; vm_flags_t dummy = vma->vm_flags; + const bool split_before = vma->vm_start != old_addr; + const bool split_after = vma->vm_end != old_addr + old_len; - /* - * We'd prefer to avoid failure later on in do_munmap: we copy a VMA, - * which may not merge, then (if MREMAP_DONTUNMAP is not set) unmap the - * source, which may split, causing a net increase of 2 mappings. - */ - if (!check_map_count_against_split()) + /* Avoid failure later on. */ + if (!check_map_count_against_split(vrm)) return -ENOMEM; if (vma->vm_ops && vma->vm_ops->may_split) { - if (vma->vm_start != old_addr) + if (split_before) err = vma->vm_ops->may_split(vma, old_addr); - if (!err && vma->vm_end != old_addr + old_len) + if (!err && split_after) err = vma->vm_ops->may_split(vma, old_addr + old_len); if (err) return err; @@ -1146,6 +1161,21 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) if (err) return err; + /* + * To account mlock()'d pages correctly in the MREMAP_DONTUNMAP + * case perform any split ahead of time for an mlock()'d VMA. + */ + if (needs_pre_split(vrm)) { + VMA_ITERATOR(vmi, vma->vm_mm, old_addr); + + if (split_before) + err = split_vma(&vmi, vma, old_addr, 1); + if (!err && split_after) + err = split_vma(&vmi, vma, old_addr + old_len, 0); + vrm->vmi_needs_invalidate = true; + return err; + } + return 0; } @@ -1275,7 +1305,8 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm, PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len); new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff, - new_anon_pgoff, &pmc.need_rmap_locks); + new_anon_pgoff, &pmc.need_rmap_locks, + vrm->flags & MREMAP_DONTUNMAP); if (!new_vma) { vrm_uncharge(vrm); *new_vma_ptr = NULL; @@ -1335,6 +1366,9 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, unsigned long old_start = vma->vm_start; unsigned long old_end = vma->vm_end; + /* Self-merge is disallowed. */ + VM_WARN_ON_ONCE(new_vma == vma); + /* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */ vma_clear_flags_mask(vma, VMA_LOCKED_MASK); @@ -1342,7 +1376,7 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, * anon_vma links of the old vma is no longer needed after its page * table has been moved. */ - if (new_vma != vma && start == old_start && end == old_end) { + if (start == old_start && end == old_end) { const pgoff_t pgoff_unfaulted = vma->vm_start >> PAGE_SHIFT; unlink_anon_vmas(vma); @@ -2002,7 +2036,7 @@ static unsigned long do_mremap(struct vma_remap_struct *vrm) return -EINTR; vrm->mmap_locked = true; - if (!check_map_count_against_split_early()) { + if (!check_map_count_against_split_early(vrm)) { mmap_write_unlock(mm); return -ENOMEM; } diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c index b91b1a98029c7f..cd227fc05d2d8f 100644 --- a/mm/pgtable-generic.c +++ b/mm/pgtable-generic.c @@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) __pagetable_free(pt); } +static void schedule_kernel_pgtable_free(void) +{ + schedule_work(&kernel_pgtable_work.work); +} + void pagetable_free_kernel(struct ptdesc *pt) { spin_lock(&kernel_pgtable_work.lock); list_add(&pt->pt_list, &kernel_pgtable_work.list); spin_unlock(&kernel_pgtable_work.lock); - schedule_work(&kernel_pgtable_work.work); + /* + * The workqueue may not exist yet while the system is booting. + * kernel_pgtable_drain_early() schedules the work once it does. + */ + if (system_state != SYSTEM_BOOTING) + schedule_kernel_pgtable_free(); +} + +static int __init kernel_pgtable_drain_early(void) +{ + /* Free the kernel page tables queued while booting. */ + schedule_kernel_pgtable_free(); + return 0; } +core_initcall(kernel_pgtable_drain_early); #endif diff --git a/mm/shmem.c b/mm/shmem.c index 897fa2b61346f6..848316eaa7f4fb 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1847,8 +1847,11 @@ unsigned long shmem_allowable_huge_orders(struct inode *inode, global_orders = shmem_huge_global_enabled(inode, index, write_end, shmem_huge_force, vma, vm_flags); - /* Tmpfs huge pages allocation */ - if (!vma || !vma_is_anon_shmem(vma)) + /* + * Tmpfs huge pages allocation or forced collapse ignores + * sysfs configs. + */ + if (!vma || !vma_is_anon_shmem(vma) || shmem_huge_force) return global_orders; /* diff --git a/mm/slub.c b/mm/slub.c index 54ec1250335711..544cff39762c5a 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5969,7 +5969,8 @@ __pcs_replace_full_main(struct kmem_cache *s, struct slub_percpu_sheaves *pcs, if (!allow_spin) return NULL; - empty = alloc_empty_sheaf(s, GFP_NOWAIT, SLAB_ALLOC_DEFAULT); + /* Don't wake up kswapd, it will cause deadlock under pi_lock */ + empty = alloc_empty_sheaf(s, __GFP_NOWARN, SLAB_ALLOC_DEFAULT); if (empty) goto got_empty; @@ -6128,7 +6129,6 @@ bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj, unsigned int free_flags) struct slab_sheaf *empty; struct node_barn *barn; unsigned int alloc_flags = to_alloc_flags(free_flags); - gfp_t gfp = allow_spin ? GFP_NOWAIT : __GFP_NOWARN; /* Bootstrap or debug cache, fall back */ if (unlikely(!cache_has_sheaves(s))) { @@ -6157,7 +6157,8 @@ bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj, unsigned int free_flags) local_unlock(&s->cpu_sheaves->lock); - empty = alloc_empty_sheaf(s, gfp, alloc_flags); + /* Don't wake up kswapd, it will cause deadlock under pi_lock */ + empty = alloc_empty_sheaf(s, __GFP_NOWARN, alloc_flags); if (!empty) goto fail; diff --git a/mm/vma.c b/mm/vma.c index 9f0a0acf694aa7..6c35f0d775ab6e 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -634,8 +634,8 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, * Split a vma into two pieces at address 'addr', a new vma is allocated * either for the first part or the tail. */ -static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, - unsigned long addr, int new_below) +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below) { if (vma->vm_mm->map_count >= get_sysctl_max_map_count()) return -ENOMEM; @@ -1943,7 +1943,7 @@ static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma) */ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks) + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source) { struct vm_area_struct *vma = *vmap; unsigned long old_vma_start = vma->vm_start; @@ -1981,6 +1981,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, vmg.pgoff = pgoff; vmg.anon_pgoff = anon_pgoff; vmg.next = vma_iter_next_rewind(&vmi, NULL); + + /* + * If the original VMA is kept (MREMAP_DONTUNMAP), the source and + * destination VMA must be treated distinctly. + * + * A merge violates this, so in this case disallow a self-merge. + */ + if (can_self_merge && keep_source) { + if (vmg.prev == vma) + vmg.prev = NULL; + if (vmg.next == vma) + vmg.next = NULL; + can_self_merge = false; + } + new_vma = vma_merge_copied_range(&vmg); if (new_vma) { @@ -2849,7 +2864,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, { struct mm_struct *mm = current->mm; struct vm_area_struct *vma = NULL; - bool have_mmap_prepare = file && file->f_op->mmap_prepare; + const bool have_mmap_prepare = file && file->f_op->mmap_prepare; VMA_ITERATOR(vmi, mm, addr); const pgoff_t anon_pgoff = addr >> PAGE_SHIFT; MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file); @@ -2892,7 +2907,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, allocated_new = true; } - if (have_mmap_prepare) + if (have_mmap_prepare && allocated_new) set_vma_user_defined_fields(vma, &map); __mmap_complete(&map, vma); diff --git a/mm/vma.h b/mm/vma.h index 024fabe63560a0..36973abaa015a8 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -535,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb, struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks); + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source); struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *vma); @@ -556,6 +556,11 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *brkvma, unsigned long unmapped_area(struct vm_unmapped_area_info *info); unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info); +#ifdef CONFIG_MMU +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below); +#endif + static inline bool vma_wants_manual_pte_write_upgrade(struct vm_area_struct *vma) { /* diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 96195d2fd10fca..cf44452e0766e9 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); + hci_dev_lock(hdev); + switch (conn->codec.id) { case BT_CODEC_MSBC: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x05; @@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) case BT_CODEC_TRANSPARENT: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x03; @@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) if (conn->parent && lmp_esco_capable(conn->parent)) { if (!find_next_esco_param(conn, esco_param_cvsd, ARRAY_SIZE(esco_param_cvsd))) - return -EINVAL; + goto unlock; param = &esco_param_cvsd[conn->attempt - 1]; } else { if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) - return -EINVAL; + goto unlock; param = &sco_param_cvsd[conn->attempt - 1]; } cp.tx_coding_format.id = 2; @@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.out_transport_unit_size = 16; break; default: - return -EINVAL; + goto unlock; } + hci_dev_unlock(hdev); + cp.retrans_effort = param->retrans_effort; cp.pkt_type = __cpu_to_le16(param->pkt_type); cp.max_latency = __cpu_to_le16(param->max_latency); @@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) return -EIO; return 0; + +unlock: + hci_dev_unlock(hdev); + return -EINVAL; } static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d183efaf906321..74d9865e08c224 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1489,8 +1489,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + mutex_lock(&hdev->remote_oob_lock); list_del(&data->list); kfree(data); + mutex_unlock(&hdev->remote_oob_lock); return 0; } @@ -1499,10 +1501,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) { struct oob_data *data, *n; + mutex_lock(&hdev->remote_oob_lock); list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { list_del(&data->list); kfree(data); } + mutex_unlock(&hdev->remote_oob_lock); } int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, @@ -1511,11 +1515,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, { struct oob_data *data; + mutex_lock(&hdev->remote_oob_lock); data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); if (!data) { data = kmalloc_obj(*data); - if (!data) + if (!data) { + mutex_unlock(&hdev->remote_oob_lock); return -ENOMEM; + } bacpy(&data->bdaddr, bdaddr); data->bdaddr_type = bdaddr_type; @@ -1548,6 +1555,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, BT_DBG("%s for %pMR", hdev->name, bdaddr); + mutex_unlock(&hdev->remote_oob_lock); + return 0; } @@ -2485,6 +2494,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) mutex_init(&hdev->lock); mutex_init(&hdev->req_lock); mutex_init(&hdev->mgmt_pending_lock); + mutex_init(&hdev->remote_oob_lock); ida_init(&hdev->unset_handle_ida); @@ -2557,8 +2567,10 @@ int hci_register_dev(struct hci_dev *hdev) return id; error = dev_set_name(&hdev->dev, "hci%u", id); - if (error) + if (error) { + ida_free(&hci_index_ida, id); return error; + } hdev->name = dev_name(&hdev->dev); hdev->id = id; @@ -3325,6 +3337,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, skb_shinfo(skb)->frag_list = NULL; + spin_lock_bh(&queue->lock); + __skb_queue_tail(queue, skb); do { @@ -3339,6 +3353,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, __skb_queue_tail(queue, skb); } while (list); + + spin_unlock_bh(&queue->lock); } bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); @@ -3402,9 +3418,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, struct hci_conn *conn = NULL, *c; unsigned int num = 0, min = ~0; - /* We don't have to lock device here. Connections are always - * added and removed with TX task disabled. */ - rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -3609,13 +3622,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) } /* Schedule SCO */ -static void hci_sched_sco(struct hci_dev *hdev, __u8 type) +static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) { struct hci_conn *conn; struct sk_buff *skb; int quote, *cnt; unsigned int pkts = hdev->sco_pkts; + lockdep_assert_held(&hdev->lock); + bt_dev_dbg(hdev, "type %u", type); if (!hci_conn_num(hdev, type) || !pkts) @@ -3650,6 +3665,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) queue_work(hdev->workqueue, &hdev->tx_work); } +static void hci_sched_sco(struct hci_dev *hdev, __u8 type) +{ + hci_dev_lock(hdev); + __hci_sched_sco(hdev, type); + hci_dev_unlock(hdev); +} + static void hci_sched_acl_pkt(struct hci_dev *hdev) { unsigned int cnt = hdev->acl_cnt; @@ -3659,6 +3681,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) __check_timeout(hdev, cnt, ACL_LINK); + hci_dev_lock(hdev); + while (hdev->acl_cnt && (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { u32 priority = (skb_peek(&chan->data_q))->priority; @@ -3683,13 +3707,15 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) chan->conn->sent++; /* Send pending SCO packets right away */ - hci_sched_sco(hdev, SCO_LINK); - hci_sched_sco(hdev, ESCO_LINK); + __hci_sched_sco(hdev, SCO_LINK); + __hci_sched_sco(hdev, ESCO_LINK); } } if (cnt != hdev->acl_cnt) hci_prio_recalculate(hdev, ACL_LINK); + + hci_dev_unlock(hdev); } static void hci_sched_acl(struct hci_dev *hdev) @@ -3718,6 +3744,8 @@ static void hci_sched_le(struct hci_dev *hdev) __check_timeout(hdev, *cnt, LE_LINK); + hci_dev_lock(hdev); + tmp = *cnt; while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { u32 priority = (skb_peek(&chan->data_q))->priority; @@ -3739,13 +3767,15 @@ static void hci_sched_le(struct hci_dev *hdev) chan->conn->sent++; /* Send pending SCO packets right away */ - hci_sched_sco(hdev, SCO_LINK); - hci_sched_sco(hdev, ESCO_LINK); + __hci_sched_sco(hdev, SCO_LINK); + __hci_sched_sco(hdev, ESCO_LINK); } } if (*cnt != tmp) hci_prio_recalculate(hdev, LE_LINK); + + hci_dev_unlock(hdev); } /* Schedule iso */ @@ -3764,6 +3794,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) __check_timeout(hdev, *cnt, type); + hci_dev_lock(hdev); + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { while (quote-- && (skb = skb_dequeue(&conn->data_q))) { BT_DBG("skb %p len %d", skb, skb->len); @@ -3777,6 +3809,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) (*cnt)--; } } + + hci_dev_unlock(hdev); } static void hci_tx_work(struct work_struct *work) @@ -4081,9 +4115,11 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) bt_dev_dbg(hdev, "skb %p", skb); + hci_dev_lock(hdev); kfree_skb(hdev->sent_cmd); hdev->sent_cmd = skb_clone(skb, GFP_KERNEL); + hci_dev_unlock(hdev); if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); @@ -4104,8 +4140,10 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = skb_get(hdev->sent_cmd); + hci_dev_unlock(hdev); } return err; diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c index aadffaaff20e13..2559fb6324d63d 100644 --- a/net/bluetooth/hci_debugfs.c +++ b/net/bluetooth/hci_debugfs.c @@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) list_for_each_entry(e, &cache->all, all) { struct inquiry_data *data = &e->data; - seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", + seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", &data->bdaddr, data->pscan_rep_mode, data->pscan_period_mode, data->pscan_mode, data->dev_class[2], diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6d56c77741e194..81068b585764e8 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1223,8 +1223,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, */ hdev = hci_pi(sk)->hdev; if (hdev && hci_dev_test_flag(hdev, HCI_UNREGISTER)) { + write_lock(&hci_sk_list.lock); hci_pi(sk)->hdev = NULL; sk->sk_state = BT_OPEN; + write_unlock(&hci_sk_list.lock); hci_dev_put(hdev); } hdev = NULL; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 74e2b04c84b260..a92a81846e9df9 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -32,8 +32,10 @@ static void hci_cmd_sync_complete(struct hci_dev *hdev, u8 result, u16 opcode, WRITE_ONCE(hdev->req_status, HCI_REQ_DONE); /* Free the request command so it is not used as response */ + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_unlock(hdev); if (skb) { struct sock *sk = hci_skb_sk(skb); @@ -312,6 +314,10 @@ static void hci_cmd_sync_work(struct work_struct *work) while (1) { struct hci_cmd_sync_work_entry *entry; + /* Leave the backlog to hci_cmd_sync_clear() */ + if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) + break; + mutex_lock(&hdev->cmd_sync_work_lock); entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, struct hci_cmd_sync_work_entry, @@ -658,6 +664,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) { struct hci_cmd_sync_work_entry *entry, *tmp; + /* cmd_work is disabled, the pending request can only time out */ + hci_cmd_sync_cancel_sync(hdev, ENODEV); cancel_work_sync(&hdev->cmd_sync_work); cancel_work_sync(&hdev->reenable_adv_work); @@ -3058,15 +3066,21 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, */ if (hci_dev_test_flag(hdev, HCI_PA_SYNC)) { struct hci_cp_le_add_to_accept_list *sent; + bdaddr_t bdaddr; + hci_dev_lock(hdev); sent = hci_sent_cmd_data(hdev, HCI_OP_LE_ADD_TO_ACCEPT_LIST); + if (sent) + bacpy(&bdaddr, &sent->bdaddr); + hci_dev_unlock(hdev); + if (sent) { struct hci_conn *conn; rcu_read_lock(); conn = hci_conn_hash_lookup_ba(hdev, PA_LINK, - &sent->bdaddr); + &bdaddr); if (conn) { struct bt_iso_qos *qos = &conn->iso_qos; @@ -5764,6 +5778,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) { struct discovery_state *d = &hdev->discovery; struct inquiry_entry *e; + bdaddr_t addr; int err; bt_dev_dbg(hdev, "state %u", hdev->discovery.state); @@ -5799,15 +5814,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) return 0; if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { + hci_dev_lock(hdev); e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_PENDING); - if (!e) + if (!e) { + hci_dev_unlock(hdev); return 0; + } + + bacpy(&addr, &e->data.bdaddr); + hci_dev_unlock(hdev); /* Ignore cancel errors since it should interfere with stopping * of the discovery. */ - hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); + hci_remote_name_cancel_sync(hdev, &addr); } return 0; @@ -7236,6 +7257,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) bacpy(&cp.bdaddr, &conn->dst); cp.pscan_rep_mode = 0x02; + hci_dev_lock(hdev); ie = hci_inquiry_cache_lookup(hdev, &conn->dst); if (ie) { if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { @@ -7247,6 +7269,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) memcpy(conn->dev_class, ie->data.dev_class, 3); } + hci_dev_unlock(hdev); cp.pkt_type = cpu_to_le16(conn->pkt_type); if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 7657c2a0abbf7c..d6ac5b1f49bc13 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -61,6 +61,7 @@ enum { BT_SK_BIG_SYNC, BT_SK_PA_SYNC, BT_SK_KILLED, + BT_SK_CONNECTING, }; struct iso_pinfo { @@ -350,6 +351,9 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (iso_pi(sk)->conn) + return -EISCONN; + if (!conn->hcon) { BT_ERR("conn->hcon missing"); return -EIO; @@ -410,6 +414,11 @@ static int iso_connect_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (iso_pi(sk)->conn) { + err = -EISCONN; + goto unlock; + } + if (!bis_capable(hdev)) { err = -EOPNOTSUPP; goto unlock; @@ -562,6 +571,13 @@ static int iso_connect_cis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + /* The socket lock keeps the current attachment and its hcon stable. */ + if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon != hcon) { + hci_conn_drop(hcon); + err = -EISCONN; + goto unlock; + } + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -1269,17 +1285,26 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, addr->sa_family != AF_BLUETOOTH) return -EINVAL; - if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) - return -EBADFD; + lock_sock(sk); - if (sk->sk_type != SOCK_SEQPACKET) - return -EINVAL; + if ((sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) || + test_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags)) { + err = -EBADFD; + goto done; + } + + if (sk->sk_type != SOCK_SEQPACKET) { + err = -EINVAL; + goto done; + } /* Check if the address type is of LE type */ - if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) - return -EINVAL; + if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) { + err = -EINVAL; + goto done; + } - lock_sock(sk); + set_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr); iso_pi(sk)->dst_type = sa->iso_bdaddr_type; @@ -1291,16 +1316,18 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, else err = iso_connect_bis(sk); - if (err) - return err; - lock_sock(sk); + clear_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); + if (err) + goto done; + if (!test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { err = bt_sock_wait_state(sk, BT_CONNECTED, sock_sndtimeo(sk, flags & O_NONBLOCK)); } +done: release_sock(sk); return err; } @@ -1340,6 +1367,11 @@ static int iso_listen_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (sk->sk_state != BT_BOUND || iso_pi(sk)->conn) { + err = -EBADFD; + goto unlock; + } + /* Fail if user set invalid QoS */ if (iso_pi(sk)->qos_user_set && !check_bcast_qos(&iso_pi(sk)->qos)) { iso_pi(sk)->qos = default_qos; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 41956cdde982f6..251a896babd599 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1495,7 +1495,7 @@ static void cmd_complete_rsp(struct mgmt_pending_cmd *cmd, void *data) return; } - cmd_status_rsp(cmd, data); + cmd_status_rsp(cmd, &match->mgmt_status); } static int generic_cmd_complete(struct mgmt_pending_cmd *cmd, u8 status) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index d91e2a6ee26c90..54ec1136f87ea1 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); static void rfcomm_process_connect(struct rfcomm_session *s); -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err); +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err); +static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s); @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel) return channel < 1 || channel > 30; } -static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s, + u8 channel) { - struct rfcomm_session *s; - int err = 0; u8 dlci; - BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", - d, d->state, src, dst, channel); - - if (rfcomm_check_channel(channel)) - return -EINVAL; + BT_DBG("dlc %p state %ld session %p channel %d", + d, d->state, s, channel); if (d->state != BT_OPEN && d->state != BT_CLOSED) return 0; - s = rfcomm_session_get(src, dst); - if (!s) { - s = rfcomm_session_create(src, dst, d->sec_level, &err); - if (!s) - return err; - } - dlci = __dlci(__session_dir(s), channel); /* Check if DLCI already exists */ @@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) { - int r; + struct rfcomm_session *s; + struct socket *sock; + int err; + + BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", + d, d->state, src, dst, channel); + + if (rfcomm_check_channel(channel)) + return -EINVAL; rfcomm_lock(); - r = __rfcomm_dlc_open(d, src, dst, channel); + clear_bit(RFCOMM_CLOSED, &d->flags); + /* Do not page the remote device for a DLC that cannot be opened + * anyway. __rfcomm_dlc_open() looks at the state again once the + * lock has been re-acquired below. + */ + if (d->state != BT_OPEN && d->state != BT_CLOSED) { + rfcomm_unlock(); + return 0; + } + + s = rfcomm_session_get(src, dst); + if (s) { + err = __rfcomm_dlc_open(d, s, channel); + rfcomm_unlock(); + return err; + } rfcomm_unlock(); - return r; + + /* There is no session for this pair yet. kernel_connect() ends up in + * l2cap_chan_connect(), which takes hdev->lock, and the HCI event + * path takes rfcomm_mutex while holding hdev->lock, so the socket has + * to be connected with rfcomm_mutex released. + */ + sock = rfcomm_session_connect(src, dst, d->sec_level, &err); + if (!sock) + return err; + + rfcomm_lock(); + + /* The DLC may have been closed while the socket was connecting. It + * was not on a session, so rfcomm_dlc_close() could only mark it; + * attaching it now would leave it with no owner. + */ + if (test_bit(RFCOMM_CLOSED, &d->flags)) { + rfcomm_unlock(); + sock_release(sock); + return -ECONNRESET; + } + + /* Another opener may have added a session for the same pair in the + * meantime; that one is used and this socket is dropped. + */ + s = rfcomm_session_get(src, dst); + if (!s) { + s = rfcomm_session_add(sock, BT_BOUND); + if (s) { + s->initiator = 1; + sock = NULL; + } + } + + err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM; + + rfcomm_unlock(); + + if (sock) + sock_release(sock); + + /* Over an existing ACL link the connection can complete before the + * session reaches the list, and that wakeup is then lost, so let + * krfcommd look at the socket state now. + */ + rfcomm_schedule(); + + return err; } static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d) @@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) rfcomm_lock(); s = d->session; - if (!s) + if (!s) { + /* Not on a session yet: rfcomm_dlc_open() may be connecting a + * socket for it with rfcomm_mutex released. Leave a mark so + * that it does not attach the DLC once it holds the lock again. + */ + set_bit(RFCOMM_CLOSED, &d->flags); goto no_session; + } /* after waiting on the mutex check the session still exists * then check the dlc still exists @@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, return rfcomm_session_del(s); } -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err) +/* Creates the L2CAP socket a new session will run on and starts connecting + * it. Must be called with rfcomm_mutex released. + */ +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err) { - struct rfcomm_session *s = NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; @@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM; release_sock(sk); - s = rfcomm_session_add(sock, BT_BOUND); - if (!s) { - *err = -ENOMEM; - goto failed; - } - - s->initiator = 1; - bacpy(&addr.l2_bdaddr, dst); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM); addr.l2_cid = 0; addr.l2_bdaddr_type = BDADDR_BREDR; *err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK); - if (*err == 0 || *err == -EINPROGRESS) - return s; + if (*err && *err != -EINPROGRESS) + goto failed; - return rfcomm_session_del(s); + return sock; failed: sock_release(sock); diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index fb924d0e34ec3c..b26918dc9e9e9c 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -48,8 +48,11 @@ static void rfcomm_sock_kill(struct sock *sk); static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) { struct sock *sk = d->owner; - if (!sk) + + if (!sk) { + kfree_skb(skb); return; + } atomic_add(skb->len, &sk->sk_rmem_alloc); skb_queue_tail(&sk->sk_receive_queue, skb); diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index b2c1060394e601..198c7dd1a95b42 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -128,7 +128,7 @@ static void rfcomm_dev_shutdown(struct tty_port *port) { struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port); - if (dev->tty_dev->parent) + if (dev->tty_dev && dev->tty_dev->parent) device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST); /* close the dlc */ @@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) /* Shut down TTY synchronously before freeing rfcomm_dev */ tty_port_tty_vhangup(&dev->port); - if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) + if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) tty_port_put(&dev->port); tty_port_put(&dev->port); @@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) * when the last process closes the tty. The behaviour is expected by * userspace. */ - if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { - set_bit(RFCOMM_TTY_OWNED, &dev->status); + if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && + !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) tty_port_put(&dev->port); - } return 0; } diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 3d4362a09df4cd..f4a20d7b5f9cb9 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -84,12 +84,14 @@ static void sco_conn_free(struct kref *ref) if (conn->sk) sco_pi(conn->sk)->conn = NULL; - if (conn->hcon) { - conn->hcon->sco_data = NULL; - hci_conn_drop(conn->hcon); - } + /* hcon->sco_data is cleared and the association's reference on the + * sco_conn is dropped in sco_conn_del() under hdev->lock, and the + * hci_conn is now owned by the socket (held in __sco_chan_add() and + * dropped in sco_chan_del()/sco_sock_destruct()), so there is nothing + * left to release towards hcon here. + */ - /* Ensure no more work items will run since hci_conn has been dropped */ + /* Ensure no more work items will run before the connection is freed */ disable_delayed_work_sync(&conn->timeout_work); kfree(conn); @@ -188,25 +190,19 @@ static void sco_sock_clear_timer(struct sock *sk) } /* ---- SCO connections ---- */ -/* Consumes a reference on @hcon, which the returned sco_conn owns until it is - * freed. On failure (NULL return) the reference is left for the caller to drop. +/* Returns a new reference the caller must drop with sco_conn_put(). The + * hcon->sco_data association holds its own reference on the sco_conn for the + * connection's lifetime; it is dropped in sco_conn_del() under hdev->lock. + * @hcon is not consumed: the hci_conn reference is taken and owned by the + * socket in __sco_chan_add(). */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { struct sco_conn *conn = hcon->sco_data; conn = sco_conn_hold_unless_zero(conn); - if (conn) { - if (!conn->hcon) { - sco_conn_lock(conn); - conn->hcon = hcon; - sco_conn_unlock(conn); - } else { - /* conn already owns a reference on hcon */ - hci_conn_drop(hcon); - } + if (conn) return conn; - } conn = kzalloc_obj(struct sco_conn); if (!conn) @@ -227,7 +223,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) BT_DBG("hcon %p conn %p", hcon, conn); - return conn; + /* kref_init() above set the association reference owned by + * hcon->sco_data; hand the caller its own reference. + */ + return sco_conn_hold(conn); } /* Delete channel. @@ -242,6 +241,19 @@ static void sco_chan_del(struct sock *sk, int err) BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + /* Drop the socket's hci_conn reference BEFORE clearing + * conn->sk, so sco_conn_del() on another CPU cannot free + * the hci_conn while we still hold a pointer to it. + */ + if (hcon) + hci_conn_drop(hcon); + sco_conn_lock(conn); conn->sk = NULL; sco_conn_unlock(conn); @@ -266,6 +278,13 @@ static void sco_conn_del(struct hci_conn *hcon, int err) BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + /* Detach from the hci_conn and drop the association's reference. + * The caller holds hdev->lock, which serialises this against the + * read of hcon->sco_data in sco_recv_scodata(). + */ + hcon->sco_data = NULL; + sco_conn_put(conn); + sco_conn_lock(conn); sk = sco_sock_hold(conn); sco_conn_unlock(conn); @@ -290,6 +309,11 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, sco_pi(sk)->conn = sco_conn_hold(conn); conn->sk = sk; + /* The socket owns an hci_conn reference for as long as it stays + * attached; it is dropped in sco_chan_del()/sco_sock_destruct(). + */ + hci_conn_hold(conn->hcon); + if (parent) bt_accept_enqueue(parent, sk, true); } @@ -371,6 +395,7 @@ static int sco_connect(struct sock *sk) if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) { release_sock(sk); sco_conn_put(conn); + hci_conn_drop(hcon); err = -EBADFD; goto unlock; } @@ -379,9 +404,13 @@ static int sco_connect(struct sock *sk) sco_conn_put(conn); if (err) { release_sock(sk); + hci_conn_drop(hcon); goto unlock; } + /* __sco_chan_add() took its own hci_conn reference; drop ours. */ + hci_conn_drop(hcon); + /* Update source addr of the socket */ bacpy(&sco_pi(sk)->src, &hcon->src); @@ -495,9 +524,25 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src) static void sco_sock_destruct(struct sock *sk) { + struct sco_conn *conn = sco_pi(sk)->conn; + BT_DBG("sk %p", sk); - sco_conn_put(sco_pi(sk)->conn); + /* If the channel was not already torn down via sco_chan_del(), drop + * the socket's own references here. + */ + if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + if (hcon) + hci_conn_drop(hcon); + sco_pi(sk)->conn = NULL; + sco_conn_put(conn); + } skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); @@ -1511,12 +1556,10 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (!status) { struct sco_conn *conn; - conn = sco_conn_add(hci_conn_hold(hcon)); + conn = sco_conn_add(hcon); if (conn) { sco_conn_ready(conn); sco_conn_put(conn); - } else { - hci_conn_drop(hcon); } } else sco_conn_del(hcon, bt_to_errno(status)); diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index d23f9d0729c40e..2df303f6a38fa0 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -662,6 +662,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, else bdaddr_type = BDADDR_LE_RANDOM; + mutex_lock(&hdev->remote_oob_lock); oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, bdaddr_type); if (oob_data && oob_data->present) { @@ -672,6 +673,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); SMP_DBG("OOB Remote Random: %16phN", smp->rr); } + mutex_unlock(&hdev->remote_oob_lock); } else { authreq &= ~SMP_AUTH_SC; diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 2f9bb30e1a1f69..3fbad7b5976953 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -81,6 +81,10 @@ __br_multicast_add_group(struct net_bridge_mcast *brmctx, bool blocked); static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg); +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports); static void __br_multicast_stop(struct net_bridge_mcast *brmctx); static int br_mc_disabled_update(struct net_device *dev, bool value, @@ -458,7 +462,7 @@ static void br_multicast_sg_del_exclude_ports(struct net_bridge_mdb_entry *sgmp) for (pp = &sgmp->ports; (p = mlock_dereference(*pp, sgmp->br)) != NULL;) { if (!(p->flags & MDB_PG_FLAGS_PERMANENT)) - br_multicast_del_pg(sgmp, p, pp); + __br_multicast_del_pg(sgmp, p, pp, false); else pp = &p->next; } @@ -799,9 +803,10 @@ static void br_multicast_destroy_port_group(struct net_bridge_mcast_gc *gc) kfree_rcu(pg, rcu); } -void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, - struct net_bridge_port_group *pg, - struct net_bridge_port_group __rcu **pp) +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports) { struct net_bridge *br = pg->key.port->br; struct net_bridge_group_src *ent; @@ -820,7 +825,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, if (!br_multicast_is_star_g(&mp->addr)) { rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode, br_sg_port_rht_params); - br_multicast_sg_del_exclude_ports(mp); + if (sg_del_exclude_ports) + br_multicast_sg_del_exclude_ports(mp); } else { br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE); } @@ -832,6 +838,13 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, mod_timer(&mp->timer, jiffies); } +void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp) +{ + __br_multicast_del_pg(mp, pg, pp, true); +} + static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg) { diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 7ecb8a26bfa3a7..b5444335b86f2b 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -39,9 +39,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/can/af_can.c b/net/can/af_can.c index 7bc86b176b4de3..34fe3b28d576f3 100644 --- a/net/can/af_can.c +++ b/net/can/af_can.c @@ -641,13 +641,10 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf return matches; } -void can_set_skb_uid(struct sk_buff *skb) +void can_set_skb_uid(struct can_skb_ext *csx) { - /* create non-zero unique skb identifier together with *skb */ - while (!(skb->hash)) - skb->hash = atomic_inc_return(&skbcounter); - - skb->sw_hash = 1; + while (!(csx->can_skb_uid)) + csx->can_skb_uid = atomic_inc_return(&skbcounter); } EXPORT_SYMBOL(can_set_skb_uid); @@ -662,8 +659,6 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) atomic_long_inc(&pkg_stats->rx_frames); atomic_long_inc(&pkg_stats->rx_frames_delta); - can_set_skb_uid(skb); - rcu_read_lock(); /* deliver the packet to sockets listening on all devices */ @@ -687,8 +682,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) static int can_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_can_skb(skb))) { + !csx || !can_is_can_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -696,6 +693,14 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -703,8 +708,10 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canfd_skb(skb))) { + !csx || !can_is_canfd_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -712,6 +719,14 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -719,8 +734,10 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canxl_skb(skb))) { + !csx || !can_is_canxl_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -728,6 +745,14 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } diff --git a/net/can/gw.c b/net/can/gw.c index 0ec99f68aa452d..5793cb2420ed3c 100644 --- a/net/can/gw.c +++ b/net/can/gw.c @@ -527,6 +527,9 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data) /* put the incremented hop counter in the cloned skb */ ncsx->can_gw_hops = csx->can_gw_hops + 1; + /* force a new CAN UID generation for the routed frame */ + ncsx->can_skb_uid = 0; + /* first processing of this CAN frame -> adjust to private hop limit */ if (gwj->limit_hops && ncsx->can_gw_hops == 1) ncsx->can_gw_hops = max_hops - gwj->limit_hops + 1; diff --git a/net/can/isotp.c b/net/can/isotp.c index 155530aedce2e3..f5dc9d04bd6862 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -891,7 +891,7 @@ static void isotp_send_cframe(struct isotp_sock *so) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); cf = (struct canfd_frame *)skb->data; skb_put_zero(skb, so->ll.mtu); @@ -917,7 +917,7 @@ static void isotp_send_cframe(struct isotp_sock *so) pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho); /* set consecutive frame echo tag */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); /* send frame with local echo enabled */ can_send_ret = can_send(skb, 1); @@ -969,18 +969,22 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) { struct sock *sk = (struct sock *)data; struct isotp_sock *so = isotp_sk(sk); + struct can_skb_ext *csx = can_skb_ext_find(skb); /* only handle my own local echo CF/SF skb's (no FF!) */ if (skb->sk != sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock * (no isotp_rcv() caller here), so take it ourselves */ spin_lock(&so->rx_lock); /* so->cfecho may since belong to a new transfer; recheck under lock */ - if (READ_ONCE(so->cfecho) != skb->hash) + if (READ_ONCE(so->cfecho) != csx->can_skb_uid) goto out_unlock; /* cancel local echo timeout */ @@ -1222,7 +1226,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); so->tx.len = size; so->tx.idx = 0; @@ -1261,7 +1265,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf->data[ae] |= size; /* set CF echo tag for isotp_rcv_echo() (SF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* send first frame */ @@ -1278,7 +1282,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->txfc.bs = 0; /* set CF echo tag for isotp_rcv_echo() (CF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* standard flow control check */ new_state = ISOTP_WAIT_FIRST_FC; diff --git a/net/can/raw.c b/net/can/raw.c index 82d9c0499c95fa..0acd4f6c6dd672 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -77,7 +77,7 @@ MODULE_ALIAS("can-proto-1"); struct uniqframe { const struct sk_buff *skb; - u32 hash; + u32 can_skb_uid; unsigned int join_rx_count; }; @@ -133,12 +133,16 @@ static void raw_rcv(struct sk_buff *oskb, void *data) enum skb_drop_reason reason; struct sockaddr_can *addr; struct sk_buff *skb; + struct can_skb_ext *csx = can_skb_ext_find(oskb); unsigned int *pflags; /* check the received tx sock reference */ if (!ro->recv_own_msgs && oskb->sk == sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* make sure to not pass oversized frames to the socket */ if (!ro->fd_frames && can_is_canfd_skb(oskb)) return; @@ -165,7 +169,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && - this_cpu_ptr(ro->uniq)->hash == oskb->hash) { + this_cpu_ptr(ro->uniq)->can_skb_uid == csx->can_skb_uid) { if (!ro->join_filters) return; @@ -175,7 +179,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) return; } else { this_cpu_ptr(ro->uniq)->skb = oskb; - this_cpu_ptr(ro->uniq)->hash = oskb->hash; + this_cpu_ptr(ro->uniq)->can_skb_uid = csx->can_skb_uid; this_cpu_ptr(ro->uniq)->join_rx_count = 1; /* drop first frame to check all enabled filters? */ if (ro->join_filters && ro->count > 1) diff --git a/net/core/dev.c b/net/core/dev.c index f660fccfc0dbc5..e76762e29360ea 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3818,20 +3818,36 @@ static netdev_features_t dflt_features_check(struct sk_buff *skb, return vlan_features_check(skb, features); } -static bool skb_gso_has_extension_hdr(const struct sk_buff *skb) -{ - if (!skb->encapsulation) - return ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 || - (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && - vlan_get_protocol(skb) == htons(ETH_P_IPV6))) && - skb_transport_header_was_set(skb) && - skb_network_header_len(skb) != sizeof(struct ipv6hdr)); - else - return (!skb_inner_network_header_was_set(skb) || - ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 || - (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && - inner_ip_hdr(skb)->version == 6)) && - skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr))); +static bool __skb_has_ipv6_ext_hdr(const struct sk_buff *skb, int nhoff) +{ + const struct ipv6hdr *ip6h; + struct ipv6hdr _ip6h; + + ip6h = skb_header_pointer(skb, nhoff, sizeof(_ip6h), &_ip6h); + return ip6h && ip6h->version == 6 && ipv6_ext_hdr(ip6h->nexthdr); +} + +static bool skb_has_ipv6_extension_hdr(const struct sk_buff *skb) +{ + if (vlan_get_protocol(skb) == htons(ETH_P_IPV6)) { + if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb))) + return true; + + /* Catch tunnels without skb->encapsulation (e.g., virtio). */ + if (!skb->encapsulation && + skb_transport_header_was_set(skb) && + skb_network_header_len(skb) != sizeof(struct ipv6hdr)) + return true; + } + + /* Tunnels without an inner network header, such as SCTP-in-UDP or + * PSP, have no inner IP header and thus no inner extension header. + */ + if (skb->encapsulation && skb_inner_network_header_was_set(skb) && + __skb_has_ipv6_ext_hdr(skb, skb_inner_network_offset(skb))) + return true; + + return false; } static netdev_features_t gso_features_check(const struct sk_buff *skb, @@ -3886,7 +3902,7 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb, * so neither does TSO that depends on it. */ if (features & NETIF_F_IPV6_CSUM && - skb_gso_has_extension_hdr(skb)) + skb_has_ipv6_extension_hdr(skb)) features &= ~(NETIF_F_IPV6_CSUM | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4); return features; @@ -3988,8 +4004,7 @@ int skb_csum_hwoffload_help(struct sk_buff *skb, return 0; if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) { - if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) && - skb_network_header_len(skb) != sizeof(struct ipv6hdr)) + if (skb_has_ipv6_extension_hdr(skb)) goto sw_checksum; switch (skb->csum_offset) { @@ -4404,9 +4419,14 @@ EXPORT_SYMBOL(dev_loopback_xmit); static struct netdev_queue * netdev_tx_queue_mapping(struct net_device *dev, struct sk_buff *skb) { - int qm = skb_get_queue_mapping(skb); + int queue = skb_get_queue_mapping(skb); + int capped; - return netdev_get_tx_queue(dev, netdev_cap_txqueue(dev, qm)); + capped = netdev_cap_txqueue(dev, queue); + if (unlikely(capped != queue)) + skb_set_queue_mapping(skb, capped); + + return netdev_get_tx_queue(dev, capped); } #ifndef CONFIG_PREEMPT_RT @@ -4415,9 +4435,13 @@ static bool netdev_xmit_txqueue_skipped(void) return __this_cpu_read(softnet_data.xmit.skip_txqueue); } -void netdev_xmit_skip_txqueue(bool skip) +bool netdev_xmit_skip_txqueue(bool skip) { + bool prev = netdev_xmit_txqueue_skipped(); + __this_cpu_write(softnet_data.xmit.skip_txqueue, skip); + + return prev; } EXPORT_SYMBOL_GPL(netdev_xmit_skip_txqueue); @@ -4427,9 +4451,13 @@ static bool netdev_xmit_txqueue_skipped(void) return current->net_xmit.skip_txqueue; } -void netdev_xmit_skip_txqueue(bool skip) +bool netdev_xmit_skip_txqueue(bool skip) { + bool prev = netdev_xmit_txqueue_skipped(); + current->net_xmit.skip_txqueue = skip; + + return prev; } EXPORT_SYMBOL_GPL(netdev_xmit_skip_txqueue); #endif @@ -4848,21 +4876,25 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev) tcx_set_ingress(skb, false); #ifdef CONFIG_NET_EGRESS if (static_branch_unlikely(&egress_needed_key)) { + bool skip_txq; + if (nf_hook_egress_active()) { skb = nf_hook_egress(skb, &rc, dev); if (!skb) goto out; } - netdev_xmit_skip_txqueue(false); + skip_txq = netdev_xmit_skip_txqueue(false); nf_skip_egress(skb, true); skb = sch_handle_egress(skb, &rc, dev); - if (!skb) + if (!skb) { + netdev_xmit_skip_txqueue(skip_txq); goto out; + } nf_skip_egress(skb, false); - if (netdev_xmit_txqueue_skipped()) + if (netdev_xmit_skip_txqueue(skip_txq)) txq = netdev_tx_queue_mapping(dev, skb); } #endif @@ -10332,13 +10364,15 @@ EXPORT_SYMBOL_GPL(dev_xdp_prog_count); u8 dev_xdp_sb_prog_count(struct net_device *dev) { + struct bpf_prog *prog; u8 count = 0; int i; - for (i = 0; i < __MAX_XDP_MODE; i++) - if (dev->xdp_state[i].prog && - !dev->xdp_state[i].prog->aux->xdp_has_frags) + for (i = 0; i < __MAX_XDP_MODE; i++) { + prog = dev_xdp_prog(dev, i); + if (prog && !prog->aux->xdp_has_frags) count++; + } return count; } @@ -10718,11 +10752,12 @@ static int bpf_xdp_link_update(struct bpf_link *link, struct bpf_prog *new_prog, bpf_op = dev_xdp_bpf_op(xdp_link->dev, mode); err = dev_xdp_install(xdp_link->dev, mode, bpf_op, NULL, xdp_link->flags, new_prog); + if (!err) + old_prog = xchg(&link->prog, new_prog); netdev_unlock_ops(xdp_link->dev); if (err) goto out_unlock; - old_prog = xchg(&link->prog, new_prog); bpf_prog_put(old_prog); out_unlock: diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index 164643140a5234..f6029f60c1dc96 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -267,7 +267,8 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, hwprov->phydev) return phy_hwtstamp_get(hwprov->phydev, cfg); - if (hwprov->source == HWTSTAMP_SOURCE_NETDEV) + if (hwprov->source == HWTSTAMP_SOURCE_NETDEV && + dev->netdev_ops->ndo_hwtstamp_get) return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); return -EOPNOTSUPP; diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 8aa4f9b4df8101..1be6c739e3c718 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1023,6 +1023,8 @@ u32 bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx, result = bpf_prog_run_pin_on_cpu(prog, ctx); + /* bpf_flow_keys offsets are u16: do not let @hlen be truncated. */ + hlen = min_t(int, hlen, U16_MAX); flow_keys->nhoff = clamp_t(u16, flow_keys->nhoff, nhoff, hlen); flow_keys->thoff = clamp_t(u16, flow_keys->thoff, flow_keys->nhoff, hlen); @@ -1071,6 +1073,7 @@ bool __skb_flow_dissect(const struct net *net, int mpls_lse = 0; int num_hdrs = 0; u8 ip_proto = 0; + u32 thoff; bool ret; if (!data) { @@ -1692,7 +1695,13 @@ bool __skb_flow_dissect(const struct net *net, ret = true; out: - key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); + thoff = min_t(u32, nhoff, skb ? skb->len : hlen); + if (unlikely(thoff > U16_MAX)) { + /* Cannot be represented in key_control->thoff. */ + thoff = U16_MAX; + ret = false; + } + key_control->thoff = thoff; key_basic->n_proto = proto; key_basic->ip_proto = ip_proto; diff --git a/net/core/gso.c b/net/core/gso.c index bcd156372f4df0..e96ef635006487 100644 --- a/net/core/gso.c +++ b/net/core/gso.c @@ -117,6 +117,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb, SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb); SKB_GSO_CB(skb)->encap_level = 0; + SKB_GSO_CB(skb)->recursion_counter = 0; skb_reset_mac_header(skb); skb_reset_mac_len(skb); diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 08d7f35cf60894..d7c88c0b67e532 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -951,8 +951,8 @@ static void page_pool_recycle_ring_bulk(struct page_pool *pool, } } - page_pool_producer_unlock(pool, in_softirq); recycle_stat_add(pool, ring, i); + page_pool_producer_unlock(pool, in_softirq); /* Hopefully all pages were returned into ptr_ring */ if (likely(i == bulk_len)) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index c3042d822afa7a..41beaf625421a6 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3633,8 +3633,12 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, pos = copy; } - if (!skb_frags_readable(skb)) + if (!skb_frags_readable(skb)) { + /* Don't hand the caller a buffer with stale bytes in it. */ + if (len > 0) + memset(to, 0, len); return 0; + } for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; @@ -7409,7 +7413,8 @@ nodefer: kfree_skb_napi_cache(skb); } static void skb_splice_csum_page(struct sk_buff *skb, struct page *page, - size_t offset, size_t len) + size_t offset, size_t len, + unsigned int csum_offset) { const char *kaddr; __wsum csum; @@ -7417,7 +7422,7 @@ static void skb_splice_csum_page(struct sk_buff *skb, struct page *page, kaddr = kmap_local_page(page); csum = csum_partial(kaddr + offset, len, 0); kunmap_local(kaddr); - skb->csum = csum_block_add(skb->csum, csum, skb->len); + skb->csum = csum_block_add(skb->csum, csum, csum_offset); } /** @@ -7477,7 +7482,8 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter, } if (skb->ip_summed == CHECKSUM_NONE) - skb_splice_csum_page(skb, page, off, part); + skb_splice_csum_page(skb, page, off, part, + skb->len + spliced); off = 0; spliced += part; diff --git a/net/core/sock.c b/net/core/sock.c index d23333bb4f3faf..e8551df8330ff4 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -1034,7 +1034,7 @@ static int sock_reserve_memory(struct sock *sk, int bytes) bool charged; int pages; - if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk)) + if (!mem_cgroup_sk_enabled(sk) || !sk_is_tcp(sk)) return -EOPNOTSUPP; if (!bytes) @@ -1661,7 +1661,7 @@ int sk_setsockopt(struct sock *sk, int level, int optname, { int delta; - if (val < 0) { + if (val < 0 || val > SZ_1G) { ret = -EINVAL; break; } diff --git a/net/devlink/core.c b/net/devlink/core.c index c53a42e17a589d..bddbbbf000fe1e 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -112,13 +112,19 @@ static void devlink_rel_nested_in_notify_work(struct work_struct *work) return; reschedule_work: - schedule_delayed_work(&rel->nested_in.notify_work, 1); + /* The work may have been queued again meanwhile, which took its own + * reference. Drop ours in that case. + */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 1)) + __devlink_rel_put(rel); } static void devlink_rel_nested_in_notify_work_schedule(struct devlink_rel *rel) { __devlink_rel_get(rel); - schedule_delayed_work(&rel->nested_in.notify_work, 0); + /* The pending work holds a reference already, drop the new one. */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 0)) + __devlink_rel_put(rel); } static struct devlink_rel *devlink_rel_alloc(void) diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c index 6be3aa5d4bc1ed..2db0e7ba8b9f56 100644 --- a/net/ethtool/tsconfig.c +++ b/net/ethtool/tsconfig.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include @@ -42,7 +43,9 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base, struct kernel_hwtstamp_config cfg = {}; int ret; - if (!dev->netdev_ops->ndo_hwtstamp_get) + if (!dev->netdev_ops->ndo_hwtstamp_get && + !phy_is_default_hwtstamp(dev->phydev) && + !netdev_ops_lock_dereference(dev->hwprov, dev)) return -EOPNOTSUPP; ret = ethnl_ops_begin(dev); @@ -248,17 +251,6 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info) return ret; } -static int ethnl_set_tsconfig_validate(struct ethnl_req_info *req_base, - struct genl_info *info) -{ - const struct net_device_ops *ops = req_base->dev->netdev_ops; - - if (!ops->ndo_hwtstamp_set || !ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - - return 1; -} - static struct hwtstamp_provider * tsconfig_set_hwprov_from_desc(struct net_device *dev, struct genl_info *info, @@ -272,7 +264,7 @@ tsconfig_set_hwprov_from_desc(struct net_device *dev, int ret; ret = ethtool_net_get_ts_info_by_phc(dev, &ts_info, hwprov_desc); - if (!ret) { + if (!ret && dev->netdev_ops->ndo_hwtstamp_set) { /* Found */ source = HWTSTAMP_SOURCE_NETDEV; } else { @@ -313,6 +305,11 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (!netif_device_present(dev)) return -ENODEV; + if (!dev->netdev_ops->ndo_hwtstamp_set && + !phy_is_default_hwtstamp(dev->phydev) && + !netdev_ops_lock_dereference(dev->hwprov, dev)) + return -EOPNOTSUPP; + if (tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER]) { struct hwtstamp_provider_desc __hwprov_desc = {.index = -1}; struct hwtstamp_provider *__hwprov; @@ -459,6 +456,5 @@ const struct ethnl_request_ops ethnl_tsconfig_request_ops = { .reply_size = tsconfig_reply_size, .fill_reply = tsconfig_fill_reply, - .set_validate = ethnl_set_tsconfig_validate, .set = ethnl_set_tsconfig, }; diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 32d006c1a8eeda..14ce01092fda65 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -161,7 +161,7 @@ void inet_sock_destruct(struct sock *sk) WARN_ON_ONCE(sk->sk_wmem_queued); WARN_ON_ONCE(sk->sk_forward_alloc); - kfree(rcu_dereference_protected(inet->inet_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); dst_release(rcu_dereference_protected(sk->sk_rx_dst, 1)); psp_sk_assoc_free(sk); @@ -1375,6 +1375,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb, int id; skb_reset_network_header(skb); + if (unlikely(gso_recursion_inc_test(skb, + IP_TUNNEL_RECURSION_LIMIT))) + goto out; nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*iph)))) goto out; diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c index a05aa075de1a5b..1aacbbeffbc647 100644 --- a/net/ipv4/cipso_ipv4.c +++ b/net/ipv4/cipso_ipv4.c @@ -2287,6 +2287,14 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb) new_hdr_len - new_hdr_len_actual); opt->optlen -= hdr_len_delta; + if (opt->srr > opt->cipso) + opt->srr -= cipso_len; + if (opt->rr > opt->cipso) + opt->rr -= cipso_len; + if (opt->ts > opt->cipso) + opt->ts -= cipso_len; + if (opt->router_alert > opt->cipso) + opt->router_alert -= cipso_len; opt->cipso = 0; opt->is_changed = 1; if (hdr_len_delta != 0) { diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 5c9021ea3a7995..47469b77580825 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -542,6 +542,9 @@ size_t fib_nlmsg_size(struct fib_info *fi) struct fib_nh_common *nhc = fib_info_nhc(fi, i); size_t nhsize; + if (!nhc) + break; + nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1); if (nhs != 1) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index acb1e43859143e..248514dce0cdd5 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -1079,8 +1079,6 @@ void fib_alias_hw_flags_set(struct net *net, const struct fib_rt_info *fri) err = fib_dump_info(skb, 0, 0, RTM_NEWROUTE, fri, 0); if (err < 0) { - /* -EMSGSIZE implies BUG in fib_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index a24cc8ee11d3ea..eaa6fabda347e9 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -771,9 +771,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 37674d76f90f04..268d8821f7e092 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1082,6 +1082,8 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu) for (nhsel = 0; nhsel < fib_info_num_path(res.fi); nhsel++) { nhc = fib_info_nhc(res.fi, nhsel); + if (!nhc) + break; update_or_create_fnhe(nhc, fl4->daddr, 0, mtu, lock, jiffies + net->ipv4.ip_rt_mtu_expires); } @@ -3168,6 +3170,9 @@ int fib_dump_info_fnhe(struct sk_buff *skb, struct netlink_callback *cb, struct fnhe_hash_bucket *bucket; int err; + if (!nhc) + break; + if (nhc->nhc_flags & RTNH_F_DEAD) continue; diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 562752352afe4d..87ef6d5cbfebaa 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1908,6 +1908,8 @@ static bool can_map_frag(const skb_frag_t *frag) if (skb_frag_size(frag) != PAGE_SIZE || skb_frag_off(frag)) return false; + if (skb_frag_is_net_iov(frag)) + return false; page = skb_frag_page(frag); diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 92bc60716f33d8..99baf14afdfd8f 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4465,6 +4465,12 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) return 1; old_ack: + /* An old ACK can carry new data. Update TS.Recent before SACK + * processing can trigger a retransmission. + */ + if (flag & FLAG_UPDATE_TS_RECENT) + tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq); + /* If data was SACKed, tag it and see if we should send more data. * If data was DSACKed, see if we can undo a cwnd reduction. */ diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 04dbb2babbcdc1..bebc5a8d1ab69f 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1209,7 +1209,7 @@ static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, */ static void tcp_v4_reqsk_destructor(struct request_sock *req) { - kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu); } #ifdef CONFIG_TCP_MD5SIG diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 9d89be7e0544be..c90ee6dd7446cd 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -4874,7 +4874,7 @@ static int modify_prefix_route(struct net *net, struct inet6_ifaddr *ifp, fib6_clean_expires(f6i); fib6_may_remove_gc_list(net, f6i); } else { - fib6_set_expires(f6i, expires); + fib6_set_expires(f6i, jiffies + clock_t_to_jiffies(expires)); fib6_add_gc_list(f6i); } diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index e61cb10b50dc96..04f7c70b7320ef 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -923,7 +923,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, { struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); - struct dst_entry *dst = skb_dst(skb); IP_TUNNEL_DECLARE_FLAGS(flags) = { }; bool truncate = false; int encap_limit = -1; @@ -1058,12 +1057,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, gre_build_header(skb, 8, flags, proto, 0, htonl(atomic_fetch_inc(&t->o_seqno))); - /* TooBig packet may have updated dst->dev's mtu */ - if (!t->parms.collect_md && dst) { - mtu = READ_ONCE(dst_dev(dst)->mtu); - if (dst_mtu(dst) > mtu) - dst->ops->update_pmtu(dst, NULL, skb, mtu, false); - } err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, NEXTHDR_GRE); if (err != 0) { diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c index 78f50c93c536cc..700f1861d8eb7d 100644 --- a/net/ipv6/ip6_offload.c +++ b/net/ipv6/ip6_offload.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "ip6_offload.h" #include "tcpv6_offload.c" @@ -104,6 +105,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, bool gso_partial; skb_reset_network_header(skb); + if (unlikely(gso_recursion_inc_test(skb, + IP_TUNNEL_RECURSION_LIMIT))) + goto out; nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h)))) goto out; diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 153ce16628c1e1..a5e955e56e794f 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6509,8 +6509,6 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0, 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { - /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 61c6a27bf202dd..7cbe0685f8bb9f 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -129,7 +129,7 @@ static __be32 seg6_make_flowlabel(struct net *net, struct sk_buff *skb, u32 hash; if (do_flowlabel > 0) { - hash = skb_get_hash(skb); + hash = skb_get_hash_net(net, skb); hash = rol32(hash, 16); flowlabel = (__force __be32)hash & IPV6_FLOWLABEL_MASK; } else if (!do_flowlabel && skb->protocol == htons(ETH_P_IPV6)) { diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index d1070aec7b72b9..53f7b245257598 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -278,13 +278,43 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) return true; } -static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr) +/* advance the SRH to the next segment and set the IPv6 DA accordingly. + * skb pointers may change: after this call, the caller must evaluate again + * any pointer into the packet data. + * + * This function returns: + * - the SRH on success; + * - NULL when the skb cannot be made writable. In this case, the function + * sets the drop reason to SKB_DROP_REASON_NOMEM. + */ +static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb, + struct ipv6_sr_hdr *srh, + enum skb_drop_reason *reason) { struct in6_addr *addr; + int srhoff; + int wlen; + + srhoff = (unsigned char *)srh - skb->data; + /* we write only the Segment Left field and the IPv6 DA. The segment + * list is only read, and seg6_get_srh() already pulled it into the + * linear area. + */ + wlen = srhoff + sizeof(*srh); + + if (unlikely(skb_ensure_writable(skb, wlen))) { + *reason = SKB_DROP_REASON_NOMEM; + return NULL; + } + + /* skb_ensure_writable() may change skb pointers; evaluate srh again */ + srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh->segments_left--; addr = srh->segments + srh->segments_left; - *daddr = *addr; + ipv6_hdr(skb)->daddr = *addr; + + return srh; } static int @@ -382,12 +412,26 @@ static bool seg6_next_csid_is_arg_zero(const struct in6_addr *addr, return true; } -/* assume that DA.Argument length > 0 */ -static void seg6_next_csid_advance_arg(struct in6_addr *addr, - const struct seg6_flavors_info *finfo) +/* assume that DA.Argument length > 0. + * skb pointers may change: after this call, the caller must evaluate again + * any pointer into the packet data. + * + * This function returns: + * - SKB_NOT_DROPPED_YET on success; + * - SKB_DROP_REASON_NOMEM when the skb cannot be made writable. + */ +static enum skb_drop_reason +seg6_next_csid_advance_arg(struct sk_buff *skb, + const struct seg6_flavors_info *finfo) { __u8 fnc_octects = seg6_flv_lcnode_func_octects(finfo); __u8 blk_octects = seg6_flv_lcblock_octects(finfo); + struct in6_addr *addr; + + if (unlikely(skb_ensure_writable(skb, sizeof(struct ipv6hdr)))) + return SKB_DROP_REASON_NOMEM; + + addr = &ipv6_hdr(skb)->daddr; /* advance DA.Argument */ memmove(&addr->s6_addr[blk_octects], @@ -395,6 +439,8 @@ static void seg6_next_csid_advance_arg(struct in6_addr *addr, 16 - blk_octects - fnc_octects); memset(&addr->s6_addr[16 - fnc_octects], 0x00, fnc_octects); + + return SKB_NOT_DROPPED_YET; } static int input_action_end_finish(struct sk_buff *skb, @@ -408,31 +454,42 @@ static int input_action_end_finish(struct sk_buff *skb, static int input_action_end_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } return input_action_end_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } static int end_next_csid_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { const struct seg6_flavors_info *finfo = &slwt->flv_info; struct in6_addr *daddr = &ipv6_hdr(skb)->daddr; + enum skb_drop_reason reason; if (seg6_next_csid_is_arg_zero(daddr, finfo)) return input_action_end_core(skb, slwt); /* update DA */ - seg6_next_csid_advance_arg(daddr, finfo); + reason = seg6_next_csid_advance_arg(skb, finfo); + if (reason) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } return input_action_end_finish(skb, slwt); } @@ -448,19 +505,25 @@ static int input_action_end_x_finish(struct sk_buff *skb, static int input_action_end_x_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } return input_action_end_x_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } static int end_x_next_csid_core(struct sk_buff *skb, @@ -468,12 +531,17 @@ static int end_x_next_csid_core(struct sk_buff *skb, { const struct seg6_flavors_info *finfo = &slwt->flv_info; struct in6_addr *daddr = &ipv6_hdr(skb)->daddr; + enum skb_drop_reason reason; if (seg6_next_csid_is_arg_zero(daddr, finfo)) return input_action_end_x_core(skb, slwt); /* update DA */ - seg6_next_csid_advance_arg(daddr, finfo); + reason = seg6_next_csid_advance_arg(skb, finfo); + if (reason) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } return input_action_end_x_finish(skb, slwt); } @@ -760,10 +828,12 @@ static bool seg6_pop_srh(struct sk_buff *skb, int srhoff) */ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; const struct seg6_flavors_info *finfo = &slwt->flv_info; enum seg6_local_flv_action action; enum seg6_local_pktinfo pinfo; struct ipv6_sr_hdr *srh; + int err = -EINVAL; __u32 flvmask; int srhoff; @@ -787,10 +857,18 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) switch (action) { case SEG6_LOCAL_FLV_ACT_END: /* process the packet as the "standard" End behavior */ - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } break; case SEG6_LOCAL_FLV_ACT_PSP: - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } if (unlikely(!seg6_pop_srh(skb, srhoff))) goto drop; @@ -807,8 +885,8 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) return input_action_end_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } /* regular endpoint function */ @@ -847,21 +925,27 @@ static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } seg6_lookup_nexthop(skb, NULL, slwt->table); return dst_input(skb); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } /* decapsulate and forward inner L2 frame on specified interface */ @@ -1375,6 +1459,7 @@ static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) static int input_action_end_b6_encap(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; int err = -EINVAL; @@ -1382,7 +1467,11 @@ static int input_action_end_b6_encap(struct sk_buff *skb, if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } skb_reset_inner_headers(skb); skb->encapsulation = 1; @@ -1398,7 +1487,7 @@ static int input_action_end_b6_encap(struct sk_buff *skb, return dst_input(skb); drop: - kfree_skb(skb); + kfree_skb_reason(skb, reason); return err; } @@ -1434,6 +1523,7 @@ static int input_action_end_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt) { struct seg6_bpf_srh_state *srh_state; + enum skb_drop_reason reason; struct ipv6_sr_hdr *srh; int ret; @@ -1442,7 +1532,11 @@ static int input_action_end_bpf(struct sk_buff *skb, kfree_skb(skb); return -EINVAL; } - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } /* The access to the per-CPU buffer srh_state is protected by running * always in softirq context (with disabled BH). On PREEMPT_RT the diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c index 9629e00069a1f3..01d6703767c638 100644 --- a/net/mac80211/agg-rx.c +++ b/net/mac80211/agg-rx.c @@ -102,7 +102,7 @@ void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, if (!tid_rx) return; - timer_delete_sync(&tid_rx->session_timer); + timer_shutdown_sync(&tid_rx->session_timer); /* make sure ieee80211_sta_reorder_release() doesn't re-arm the timer */ spin_lock_bh(&tid_rx->reorder_lock); diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index 0c54cdbd753cd7..a4e1f33c037797 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -632,6 +632,22 @@ static bool ieee80211_fill_rate_info(struct ieee80211_hw *hw, if (!ri || !sband) return false; + /* + * ieee80211_get_rate_duration() only handles these widths. Drivers + * may also report e.g. HE/EHT RU allocations, which cannot be used + * to estimate airtime here. + */ + switch (ri->bw) { + case RATE_INFO_BW_20: + case RATE_INFO_BW_40: + case RATE_INFO_BW_80: + case RATE_INFO_BW_160: + case RATE_INFO_BW_320: + break; + default: + return false; + } + stat->bw = ri->bw; stat->nss = ri->nss; stat->rate_idx = ri->mcs; @@ -770,6 +786,8 @@ u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + if (!duration) + return 0; /* * Assume that HT/VHT transmission on any AC except VO will diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 75bb204ad7432c..9cba795077673c 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -231,6 +231,13 @@ int ieee80211_chanctx_refcount(struct ieee80211_local *local, return num; } +static bool +ieee80211_chanctx_has_replace_partner(struct ieee80211_chanctx *ctx) +{ + return ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED && + ctx->replace_ctx; +} + static int ieee80211_num_chanctx(struct ieee80211_local *local, int radio_idx) { struct ieee80211_chanctx *ctx; @@ -1405,6 +1412,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) { struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_chanctx *ctx = link->reserved_chanctx; + struct ieee80211_chanctx *old_ctx = NULL; lockdep_assert_wiphy(sdata->local->hw.wiphy); @@ -1418,6 +1426,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) if (WARN_ON(!ctx->replace_ctx)) return; + old_ctx = ctx->replace_ctx; WARN_ON(ctx->replace_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED); WARN_ON(ctx->replace_ctx->replace_ctx != ctx); @@ -1428,6 +1437,11 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) list_del_rcu(&ctx->list); kfree_rcu(ctx, rcu_head); + + if (ieee80211_chanctx_refcount(sdata->local, + old_ctx) == 0) + ieee80211_free_chanctx(sdata->local, old_ctx, + false); } else { ieee80211_free_chanctx(sdata->local, ctx, false); } @@ -1707,7 +1721,8 @@ ieee80211_link_use_reserved_reassign(struct ieee80211_link_data *link) ieee80211_check_fast_xmit_iface(sdata); - if (ieee80211_chanctx_refcount(local, old_ctx) == 0) + if (ieee80211_chanctx_refcount(local, old_ctx) == 0 && + !ieee80211_chanctx_has_replace_partner(old_ctx)) ieee80211_free_chanctx(local, old_ctx, false); ieee80211_recalc_chanctx_min_def(local, new_ctx); @@ -1772,7 +1787,8 @@ ieee80211_link_use_reserved_assign(struct ieee80211_link_data *link) } static bool -ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) +ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link, + struct ieee80211_chanctx *ctx) { struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_chanctx *old_ctx, *new_ctx; @@ -1782,6 +1798,9 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) new_ctx = link->reserved_chanctx; old_ctx = ieee80211_link_get_chanctx(link); + if (new_ctx != ctx) + return false; + if (!old_ctx) return false; @@ -1794,6 +1813,12 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER) return false; + if (new_ctx->replace_ctx != old_ctx) + return false; + + if (old_ctx->replace_ctx != new_ctx) + return false; + return true; } @@ -1823,7 +1848,8 @@ static int ieee80211_chsw_switch_vifs(struct ieee80211_local *local, } for_each_chanctx_user_reserved(local, ctx, &iter) { - if (!ieee80211_link_has_in_place_reservation(iter.link)) + if (!ieee80211_link_has_in_place_reservation(iter.link, + ctx)) continue; old_ctx = ieee80211_link_get_chanctx(iter.link); @@ -1925,7 +1951,9 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) { n_assigned++; - if (iter.link && iter.link->reserved_chanctx) { + if (iter.link && + ieee80211_link_has_in_place_reservation(iter.link, + ctx)) { n_reserved++; if (iter.link->reserved_ready) n_ready++; @@ -1950,7 +1978,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) use_reserved: ctx->conf.radar_enabled = false; for_each_chanctx_user_reserved(local, ctx, &iter) { - if (ieee80211_link_has_in_place_reservation(iter.link) && + if (ieee80211_link_has_in_place_reservation(iter.link, + ctx) && !iter.link->reserved_ready) return -EAGAIN; @@ -1991,7 +2020,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) } for_each_chanctx_user_reserved(local, ctx, &iter) { - if (!ieee80211_link_has_in_place_reservation(iter.link)) + if (!ieee80211_link_has_in_place_reservation(iter.link, + ctx)) continue; ieee80211_chan_bw_change(local, @@ -2040,7 +2070,7 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) struct ieee80211_bss_conf *link_conf = link->conf; u64 changed = 0; - if (!ieee80211_link_has_in_place_reservation(link)) + if (!ieee80211_link_has_in_place_reservation(link, ctx)) continue; rcu_assign_pointer(link_conf->chanctx_conf, @@ -2091,7 +2121,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) for_each_chanctx_user_reserved(local, ctx, &iter) { struct ieee80211_link_data *link = iter.link; - if (WARN_ON(ieee80211_link_has_in_place_reservation(link))) + if (WARN_ON(ieee80211_link_has_in_place_reservation(link, + ctx))) continue; if (!link->reserved_ready) @@ -2176,7 +2207,8 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link, } ieee80211_assign_link_chanctx(link, NULL, false); - if (ieee80211_chanctx_refcount(local, ctx) == 0) + if (ieee80211_chanctx_refcount(local, ctx) == 0 && + !ieee80211_chanctx_has_replace_partner(ctx)) ieee80211_free_chanctx(local, ctx, skip_idle_recalc); link->radar_required = false; diff --git a/net/mac80211/drop.h b/net/mac80211/drop.h index f06a8aa905c53c..f433ee17cce514 100644 --- a/net/mac80211/drop.h +++ b/net/mac80211/drop.h @@ -108,6 +108,7 @@ typedef unsigned int __bitwise ieee80211_rx_result; R(RX_DROP_U_UNHANDLED_PREQ) \ R(RX_DROP_U_UNHANDLED_MGMT_STYPE) \ R(RX_DROP_U_NO_LINK) \ + R(RX_DROP_U_DEFRAG_OVERFLOW) \ /* this line for the trailing \ - add before this */ /* having two enums allows for checking ieee80211_rx_result use with sparse */ diff --git a/net/mac80211/fils_aead.c b/net/mac80211/fils_aead.c index 293590976489a0..9c4f00d61981d2 100644 --- a/net/mac80211/fils_aead.c +++ b/net/mac80211/fils_aead.c @@ -89,6 +89,14 @@ static int aes_siv_encrypt(const u8 *key, size_t key_len, if (res) return res; + /* With an empty plaintext there is no ciphertext to produce and + * the S2V result is the complete output. + */ + if (!plain_len) { + memcpy(out, v, AES_BLOCK_SIZE); + return 0; + } + /* Use a temporary buffer of the plaintext to handle need for * overwriting this during AES-CTR. */ diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d05f594673990a..9374c2942489ca 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2475,7 +2475,7 @@ static inline bool ieee80211_require_encrypted_assoc(__le16 fc, /* sta_out needs to be checked for ERR_PTR() before using */ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct sta_info **sta_out); + struct sta_info **sta_out, bool bss); static inline void ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 889c32fd8de19f..5f0c482bd4a5a9 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -998,8 +998,8 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, len_rthdr = ieee80211_get_radiotap_len(skb->data); hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr); - if (skb->len < len_rthdr + 2 || - skb->len < len_rthdr + ieee80211_hdrlen(hdr->frame_control)) + if (skb_headlen(skb) < len_rthdr + 2 || + skb_headlen(skb) < len_rthdr + ieee80211_hdrlen(hdr->frame_control)) return 0; /* doesn't matter, frame will be dropped */ return ieee80211_select_queue_80211(sdata, skb, hdr); @@ -1023,6 +1023,13 @@ static int ieee80211_netdev_fill_forward_path(struct net_device_path_ctx *ctx, struct sta_info *sta; int ret = -ENOENT; + if (ctx->ieee80211) { + path->type = DEV_PATH_IEEE80211; + path->dev = ctx->dev; + ctx->dev = NULL; + return 0; + } + sdata = IEEE80211_DEV_TO_SUB_IF(ctx->dev); local = sdata->local; diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 03171cf008557f..07a21dfa8674a6 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -577,6 +577,12 @@ void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata, goto unlock_sta; spin_lock(&cache->walk_lock); + if ((READ_ONCE(mpath->flags) & MESH_PATH_DELETED) || + (mppath && (READ_ONCE(mppath->flags) & MESH_PATH_DELETED))) { + kfree(entry); + goto unlock_cache; + } + prev = rhashtable_lookup_get_insert_fast(&cache->rht, &entry->rhash, fast_tx_rht_params); @@ -694,8 +700,10 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, return ERR_PTR(-ENOSPC); new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC); - if (!new_mpath) + if (!new_mpath) { + atomic_dec(&sdata->u.mesh.mpaths); return ERR_PTR(-ENOMEM); + } tbl = &sdata->u.mesh.mesh_paths; spin_lock_bh(&tbl->walk_lock); @@ -708,6 +716,7 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, if (mpath) { kfree(new_mpath); + atomic_dec(&sdata->u.mesh.mpaths); if (IS_ERR(mpath)) return mpath; @@ -733,10 +742,15 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata, if (is_multicast_ether_addr(dst)) return -EOPNOTSUPP; + if (!atomic_add_unless(&sdata->u.mesh.mpaths, 1, MESH_MAX_MPATHS)) + return -ENOSPC; + new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC); - if (!new_mpath) + if (!new_mpath) { + atomic_dec(&sdata->u.mesh.mpaths); return -ENOMEM; + } memcpy(new_mpath->mpp, mpp, ETH_ALEN); tbl = &sdata->u.mesh.mpp_paths; @@ -749,10 +763,12 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata, hlist_add_head_rcu(&new_mpath->walk_list, &tbl->walk_head); spin_unlock_bh(&tbl->walk_lock); - if (ret) + if (ret) { kfree(new_mpath); - else + atomic_dec(&sdata->u.mesh.mpaths); + } else { mesh_fast_tx_flush_addr(sdata, dst); + } sdata->u.mesh.mpp_paths_generation++; return ret; @@ -798,7 +814,8 @@ static void mesh_path_free_rcu(struct mesh_table *tbl, struct ieee80211_sub_if_data *sdata = mpath->sdata; spin_lock_bh(&mpath->state_lock); - mpath->flags |= MESH_PATH_RESOLVING | MESH_PATH_DELETED; + WRITE_ONCE(mpath->flags, + mpath->flags | MESH_PATH_RESOLVING | MESH_PATH_DELETED); mesh_gate_del(tbl, mpath); spin_unlock_bh(&mpath->state_lock); timer_shutdown_sync(&mpath->timer); @@ -812,6 +829,9 @@ static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath) { hlist_del_rcu(&mpath->walk_list); rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params); + spin_lock_bh(&mpath->state_lock); + WRITE_ONCE(mpath->flags, mpath->flags | MESH_PATH_DELETED); + spin_unlock_bh(&mpath->state_lock); if (tbl == &mpath->sdata->u.mesh.mpp_paths) mesh_fast_tx_flush_addr(mpath->sdata, mpath->dst); else diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f51167f0fc46d3..3c31c16860ab78 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5877,6 +5877,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ; const struct cfg80211_bss_ies *bss_ies = NULL; + struct ieee802_11_elems *bss_elems = NULL; struct ieee80211_supported_band *sband; struct ieee802_11_elems *elems; u16 capab_info; @@ -6007,7 +6008,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, (is_5ghz && link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT && (!elems->vht_cap_elem || !elems->vht_operation)))) { const struct cfg80211_bss_ies *ies; - struct ieee802_11_elems *bss_elems; rcu_read_lock(); ies = rcu_dereference(cbss->ies); @@ -6069,7 +6069,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, "AP bug: VHT operation missing from AssocResp\n"); } } - kfree(bss_elems); } /* @@ -6342,6 +6341,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, ret = true; out: kfree(elems); + kfree(bss_elems); kfree(bss_ies); return ret; } diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index b73ef3adfcc576..45e7a7ea769008 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1193,6 +1194,54 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) mi->sample_time = jiffies; } +/* + * Check whether an HT/VHT rate from a TX status entry maps to an entry + * in the minstrel_ht rate tables. Values that cannot be represented + * there must not be used for indexing mi->groups[] and the MCS groups. + */ +static bool +minstrel_ht_txstat_rate_valid(struct ieee80211_tx_rate *rate) +{ + unsigned int bw; + + if (!(rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS))) + return true; + + if (rate->flags & IEEE80211_TX_RC_MCS) { + /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */ + return rate->idx < MINSTREL_MAX_STREAMS * 8; + } + + /* minstrel_ht has no VHT groups for 160 MHz and wider */ + bw = !!(rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + + 2 * !!(rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH); + if ((rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH) || bw > BW_80) + return false; + + return ieee80211_rate_get_vht_nss(rate) <= MINSTREL_MAX_STREAMS && + ieee80211_rate_get_vht_mcs(rate) < MCS_GROUP_RATES; +} + +static bool +minstrel_ht_ri_txstat_rate_valid(struct rate_info *rate) +{ + if (!(rate->flags & (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS))) + return true; + + if (rate->flags & RATE_INFO_FLAGS_MCS) { + /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */ + return rate->mcs < MINSTREL_MAX_STREAMS * 8; + } + + /* minstrel_ht has VHT groups only for 20/40/80 MHz */ + if (rate->bw != RATE_INFO_BW_20 && rate->bw != RATE_INFO_BW_40 && + rate->bw != RATE_INFO_BW_80) + return false; + + return rate->nss <= MINSTREL_MAX_STREAMS && + rate->mcs < MCS_GROUP_RATES; +} + static bool minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, struct ieee80211_tx_rate *rate) @@ -1205,9 +1254,8 @@ minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, if (!rate->count) return false; - if (rate->flags & IEEE80211_TX_RC_MCS || - rate->flags & IEEE80211_TX_RC_VHT_MCS) - return true; + if (rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS)) + return minstrel_ht_txstat_rate_valid(rate); for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++) if (rate->idx == mp->cck_rates[i]) @@ -1235,9 +1283,9 @@ minstrel_ht_ri_txstat_valid(struct minstrel_priv *mp, if (!rate_status->try_count) return false; - if (rate_status->rate_idx.flags & RATE_INFO_FLAGS_MCS || - rate_status->rate_idx.flags & RATE_INFO_FLAGS_VHT_MCS) - return true; + if (rate_status->rate_idx.flags & + (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS)) + return minstrel_ht_ri_txstat_rate_valid(&rate_status->rate_idx); for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++) { if (rate_status->rate_idx.legacy == @@ -1947,14 +1995,39 @@ minstrel_ht_alloc(struct ieee80211_hw *hw) } #ifdef CONFIG_MAC80211_DEBUGFS +static int minstrel_ht_fixed_rate_idx_get(void *data, u64 *val) +{ + *val = *(u32 *)data; + return 0; +} + +static int minstrel_ht_fixed_rate_idx_set(void *data, u64 val) +{ + u32 idx = val; + + /* U32_MAX is the default and keeps fixed rate processing disabled */ + if (val != U32_MAX && + (val > U16_MAX || + MI_RATE_GROUP(idx) >= ARRAY_SIZE(minstrel_mcs_groups) || + MI_RATE_IDX(idx) >= MCS_GROUP_RATES)) + return -EINVAL; + + *(u32 *)data = idx; + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(minstrel_ht_fixed_rate_idx_fops, + minstrel_ht_fixed_rate_idx_get, + minstrel_ht_fixed_rate_idx_set, "%llu\n"); + static void minstrel_ht_add_debugfs(struct ieee80211_hw *hw, void *priv, struct dentry *debugfsdir) { struct minstrel_priv *mp = priv; mp->fixed_rate_idx = (u32) -1; - debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, - &mp->fixed_rate_idx); + debugfs_create_file("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, + &mp->fixed_rate_idx, &minstrel_ht_fixed_rate_idx_fops); } #endif diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 5e26be8e27d8f0..7ead509304ebe3 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -2499,6 +2499,12 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx) } skb_pull(rx->skb, ieee80211_hdrlen(fc)); + if (unlikely((u32)entry->extra_len + rx->skb->len > U16_MAX)) { + I802_DEBUG_INC(rx->local->rx_handlers_drop_defrag); + __skb_queue_purge(&entry->skb_list); + return RX_DROP_U_DEFRAG_OVERFLOW; + } + __skb_queue_tail(&entry->skb_list, rx->skb); entry->last_frag = frag; entry->extra_len += rx->skb->len; diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index 880f4625775dce..f5e4970869b6d4 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -111,8 +111,8 @@ validate_chandef_by_ht_vht_oper(struct ieee80211_sub_if_data *sdata, switch (chan_width) { case NL80211_CHAN_WIDTH_320: - WARN_ON(1); - break; + chandef->chan = NULL; + return; case NL80211_CHAN_WIDTH_160: vht_oper.chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ; vht_oper.center_freq_seg1_idx = vht_oper.center_freq_seg0_idx; diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index fdf00cbf49d8b4..e81cd155197b92 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -137,6 +137,8 @@ static void __cleanup_single_sta(struct sta_info *sta) struct ieee80211_local *local = sdata->local; struct ps_data *ps; + cancel_work_sync(&sta->drv_deliver_wk); + if (test_sta_flag(sta, WLAN_STA_PS_STA) || test_sta_flag(sta, WLAN_STA_PS_DRIVER) || test_sta_flag(sta, WLAN_STA_PS_DELIVER)) { @@ -166,8 +168,6 @@ static void __cleanup_single_sta(struct sta_info *sta) if (ieee80211_vif_is_mesh(&sdata->vif)) mesh_sta_cleanup(sta); - cancel_work_sync(&sta->drv_deliver_wk); - /* * Destroy aggregation state here. It would be nice to wait for the * driver to finish aggregation stop and then clean up, but for now @@ -1582,6 +1582,8 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc) sta->dead = true; + cancel_work_sync(&sta->drv_deliver_wk); + local->num_sta--; local->sta_generation++; diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 3d811f652603ef..414979e2a1d930 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -1164,6 +1164,73 @@ void ieee80211_tx_status_skb(struct ieee80211_hw *hw, struct sk_buff *skb) } EXPORT_SYMBOL(ieee80211_tx_status_skb); +/* + * Check whether the HT/VHT rate information in a TX status entry is + * valid for its encoding. This is not specific to any rate control + * algorithm; all status consumers rely on the values being sane. + */ +static bool ieee80211_tx_status_rate_info_valid(const struct rate_info *rate) +{ + if (rate->flags & RATE_INFO_FLAGS_MCS) + return rate->mcs < 32; + + if (rate->flags & RATE_INFO_FLAGS_VHT_MCS) + return rate->nss >= 1 && rate->nss <= 8 && rate->mcs <= 11; + + return true; +} + +static bool +ieee80211_tx_status_tx_rate_valid(const struct ieee80211_tx_rate *rate) +{ + if (rate->flags & IEEE80211_TX_RC_MCS) + return rate->idx < 32; + + if (rate->flags & IEEE80211_TX_RC_VHT_MCS) + return ieee80211_rate_get_vht_mcs(rate) <= 11; + + return true; +} + +/* + * Drop TX status rate entries that don't describe a valid rate. The + * status information is used by rate control and by other mac80211 + * code, and a malformed entry must not be able to corrupt state beyond + * the driver that reported it. + */ +static void +ieee80211_tx_status_drop_invalid_rates(struct ieee80211_tx_status *status) +{ + int i; + + for (i = 0; i < status->n_rates; i++) { + struct ieee80211_rate_status *rs = &status->rates[i]; + + if (ieee80211_tx_status_rate_info_valid(&rs->rate_idx)) + continue; + + rs->try_count = 0; + memset(&rs->rate_idx, 0, sizeof(rs->rate_idx)); + } + + if (!status->info) + return; + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + struct ieee80211_tx_rate *rate; + + rate = &status->info->status.rates[i]; + if (rate->idx < 0) + break; + + if (ieee80211_tx_status_tx_rate_valid(rate)) + continue; + + rate->idx = -1; + rate->count = 0; + } +} + void ieee80211_tx_status_ext(struct ieee80211_hw *hw, struct ieee80211_tx_status *status) { @@ -1176,6 +1243,8 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw, bool acked, noack_success, ack_signal_valid; u16 tx_time_est; + ieee80211_tx_status_drop_invalid_rates(status); + if (pubsta) { sta = container_of(pubsta, struct sta_info, sta); @@ -1300,6 +1369,7 @@ void ieee80211_tx_rate_update(struct ieee80211_hw *hw, .sta = pubsta, }; + ieee80211_tx_status_drop_invalid_rates(&status); rate_control_tx_status(local, &status); if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL)) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 814399989b5e48..d51f810dd2c5b8 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2091,7 +2091,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) (struct ieee80211_radiotap_header *)skb->data; /* check for not even having the fixed radiotap header part */ - if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header))) + if (unlikely(skb_headlen(skb) < sizeof(struct ieee80211_radiotap_header))) return false; /* too short to be possibly valid */ /* is it a header version we can trust to find length from? */ @@ -2099,7 +2099,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) return false; /* only version 0 is supported */ /* does the skb contain enough to deliver on the alleged length? */ - if (unlikely(skb->len < ieee80211_get_radiotap_len(skb->data))) + if (unlikely(skb_headlen(skb) < ieee80211_get_radiotap_len(skb->data))) return false; /* skb too short for claimed rt header extent */ return true; @@ -2388,13 +2388,13 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, skb_set_network_header(skb, len_rthdr); skb_set_transport_header(skb, len_rthdr); - if (skb->len < len_rthdr + 2) + if (skb_headlen(skb) < len_rthdr + 2) goto fail; hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr); hdrlen = ieee80211_hdrlen(hdr->frame_control); - if (skb->len < len_rthdr + hdrlen) + if (skb_headlen(skb) < len_rthdr + hdrlen) goto fail; /* @@ -2402,7 +2402,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * carrying a rfc1042 header */ if (ieee80211_is_data(hdr->frame_control) && - skb->len >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) { + skb_headlen(skb) >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) { u8 *payload = (u8 *)hdr + hdrlen; if (ether_addr_equal(payload, rfc1042_header)) @@ -2532,7 +2532,7 @@ static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb) int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct sta_info **sta_out) + struct sta_info **sta_out, bool bss) { struct sta_info *sta; @@ -2553,7 +2553,10 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, *sta_out = ERR_PTR(-ENOENT); return 0; } - sta = sta_info_get_bss(sdata, skb->data); + if (bss) + sta = sta_info_get_bss(sdata, skb->data); + else + sta = sta_info_get(sdata, skb->data); break; #ifdef CONFIG_MAC80211_MESH case NL80211_IFTYPE_MESH_POINT: @@ -4419,7 +4422,8 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags)) goto out; - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, + skb->protocol == sdata->control_port_protocol)) goto out_free; if (IS_ERR(sta)) @@ -4731,6 +4735,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, { struct ieee80211_tx_info *info; struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *chanctx_conf; struct tid_ampdu_tx *tid_tx = NULL; struct sk_buff *seg, *next; unsigned int skbs = 0, len = 0; @@ -4780,6 +4785,16 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); + /* MLD transmissions must not rely on the band */ + if (!ieee80211_vif_is_mld(&sdata->vif)) { + chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); + if (unlikely(!chanctx_conf)) { + kfree_skb_list(skb); + return; + } + info->band = chanctx_conf->def.chan->band; + } + info->flags |= IEEE80211_TX_CTL_HW_80211_ENCAP; info->control.vif = &sdata->vif; @@ -4848,7 +4863,10 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, rcu_read_lock(); - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (unlikely(sdata->control_port_protocol == ehdr->h_proto)) + goto skip_offload; + + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) { kfree_skb(skb); goto out; } @@ -4869,8 +4887,7 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, link = &sdata->deflink; key = rcu_dereference(link->default_multicast_key); } else if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded || - !test_sta_flag(sta, WLAN_STA_AUTHORIZED) || - sdata->control_port_protocol == ehdr->h_proto)) { + !test_sta_flag(sta, WLAN_STA_AUTHORIZED))) { goto skip_offload; } else { key = rcu_dereference(sta->ptk[sta->ptk_idx]); @@ -4931,7 +4948,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, rcu_read_lock(); - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) { kfree_skb(skb); skb = ERR_PTR(-EINVAL); goto out; @@ -5002,7 +5019,7 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local, } result = ieee80211_tx(sdata, NULL, skb, true); } else if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) { - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, true)) { dev_kfree_skb(skb); return true; } @@ -6639,7 +6656,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, * AF_PACKET */ rcu_read_lock(); - err = ieee80211_lookup_ra_sta(sdata, skb, &sta); + err = ieee80211_lookup_ra_sta(sdata, skb, &sta, true); if (err) { dev_kfree_skb(skb); rcu_read_unlock(); diff --git a/net/mctp/device.c b/net/mctp/device.c index 822120e860c82a..9f76ff092559ab 100644 --- a/net/mctp/device.c +++ b/net/mctp/device.c @@ -344,13 +344,13 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) mdev->net = mctp_default_net(dev_net(dev)); - /* associate to net_device */ refcount_set(&mdev->refs, 1); - rcu_assign_pointer(dev->mctp_ptr, mdev); - dev_hold(dev); mdev->dev = dev; + /* Associate to net_device, publish the mdev. */ + rcu_assign_pointer(dev->mctp_ptr, mdev); + return mdev; } diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h index d6a7e66045422f..ae376fa3e7a3b5 100644 --- a/net/netfilter/ipset/ip_set_bitmap_gen.h +++ b/net/netfilter/ipset/ip_set_bitmap_gen.h @@ -159,7 +159,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, if (SET_WITH_COUNTER(set)) ip_set_init_counter(ext_counter(x, set), ext); - if (SET_WITH_COMMENT(set)) + if (SET_WITH_COMMENT(set) && !ext->target) ip_set_init_comment(set, ext_comment(x, set), ext); if (SET_WITH_SKBINFO(set)) ip_set_init_skbinfo(ext_skbinfo(x, set), ext); diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 6fa3e1dc534c3e..cb009208826f0d 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1102,6 +1102,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) if (cp->protocol != IPPROTO_UDP) conn_flags &= ~IP_VS_CONN_F_ONE_PACKET; flags = cp->flags; + /* Only visible templates can control multiple connections */ + if (flags & IP_VS_CONN_F_TEMPLATE) + conn_flags &= ~IP_VS_CONN_F_ONE_PACKET; /* Bind with the destination and its corresponding transmitter */ if (flags & IP_VS_CONN_F_SYNC) { /* Synced conns are hashed, so they can not get this flag */ diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index 693bcc82ccb77b..55af77a5692926 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -203,7 +203,11 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr, if (en->dest == dest) return en; ip_vs_lblc_del(en); + atomic_dec(&tbl->entries); } + if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2) + return NULL; + en = kmalloc_obj(*en, GFP_ATOMIC); if (!en) return NULL; diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c index f53f05ceea36f0..858393b1d2d174 100644 --- a/net/netfilter/ipvs/ip_vs_lblcr.c +++ b/net/netfilter/ipvs/ip_vs_lblcr.c @@ -363,6 +363,9 @@ ip_vs_lblcr_new(struct ip_vs_lblcr_table *tbl, const union nf_inet_addr *daddr, en = ip_vs_lblcr_get(af, tbl, daddr); if (!en) { + if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2) + return NULL; + en = kmalloc_obj(*en, GFP_ATOMIC); if (!en) return NULL; diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 5383aeafb0aed4..69dc28153ec12b 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -949,6 +949,21 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa ip_vs_conn_put(cp); } +/* Check for incompatible flags */ +static bool ip_vs_sync_validate_flags(u32 flags) +{ + /* We do not expect NO_CPORT, especially to allow lookups + * to hit templates + */ + if (flags & IP_VS_CONN_F_NO_CPORT) { + if (flags & IP_VS_CONN_F_TEMPLATE) + return false; + } + if (flags & IP_VS_CONN_F_ONE_PACKET) + return false; + return true; +} + /* * Process received multicast message for Version 0 */ @@ -972,8 +987,7 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer return; } s = (struct ip_vs_sync_conn_v0 *) p; - flags = ntohs(s->flags) | IP_VS_CONN_F_SYNC; - flags &= ~IP_VS_CONN_F_HASHED; + flags = ntohs(s->flags); if (flags & IP_VS_CONN_F_SEQ_MASK) { opt = (struct ip_vs_sync_conn_options *)&s[1]; p += FULL_CONN_SIZE; @@ -986,6 +1000,13 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer p += SIMPLE_CONN_SIZE; } + if (!ip_vs_sync_validate_flags(flags)) { + IP_VS_DBG(2, "BACKUP v0, Invalid flags 0x%X\n", flags); + continue; + } + flags &= IP_VS_CONN_F_BACKUP_MASK; + flags |= IP_VS_CONN_F_SYNC; + state = ntohs(s->state); if (!(flags & IP_VS_CONN_F_TEMPLATE)) { pp = ip_vs_proto_get(s->protocol); @@ -1141,7 +1162,13 @@ static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *m } /* Get flags and Mask off unsupported */ - flags = ntohl(s->v4.flags) & IP_VS_CONN_F_BACKUP_MASK; + flags = ntohl(s->v4.flags); + if (!ip_vs_sync_validate_flags(flags)) { + IP_VS_DBG(3, "BACKUP, Invalid flags 0x%X\n", flags); + retc = 25; + goto out; + } + flags &= IP_VS_CONN_F_BACKUP_MASK; flags |= IP_VS_CONN_F_SYNC; state = ntohs(s->v4.state); diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 934c6151f558b1..36bbc7be2f7450 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -575,7 +575,12 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct) static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table, struct flow_offload *flow, void *data) { - bool teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags); + bool teardown; + + if (test_bit(NF_FLOW_PENDING, &flow->flags)) + return; + + teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags); if (nf_flow_has_expired(flow) || nf_ct_is_dying(flow->ct) || diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 6757fd89c1f1a6..4365859220e6a4 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -995,6 +995,7 @@ static void flow_offload_work_del(struct flow_offload_work *offload) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL); if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags)) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY); + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); } static void flow_offload_tuple_stats(struct flow_offload_work *offload, @@ -1056,13 +1057,8 @@ static void flow_offload_work_handler(struct work_struct *work) default: WARN_ON_ONCE(1); } - - clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags); - if (offload->cmd == FLOW_CLS_DESTROY) { - /* Publish after the worker's last flow access. */ - smp_mb__before_atomic(); - set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); - } + smp_mb__before_atomic(); + clear_bit(NF_FLOW_PENDING, &offload->flow->flags); kfree(offload); } @@ -1089,12 +1085,12 @@ nf_flow_offload_work_alloc(struct nf_flowtable *flowtable, { struct flow_offload_work *offload; - if (test_and_set_bit(NF_FLOW_HW_PENDING, &flow->flags)) + if (test_and_set_bit(NF_FLOW_PENDING, &flow->flags)) return NULL; offload = kmalloc_obj(struct flow_offload_work, GFP_ATOMIC); if (!offload) { - clear_bit(NF_FLOW_HW_PENDING, &flow->flags); + clear_bit(NF_FLOW_PENDING, &flow->flags); return NULL; } diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 1e55644f2edb86..d90013685bf12c 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -53,6 +53,7 @@ static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, struct net_device_path_ctx ctx = { .dev = dev, .ether_type = ether_type, + .ieee80211 = true, }; struct neighbour *n; u8 nud_state; @@ -114,6 +115,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack, path = &stack->path[i]; switch (path->type) { case DEV_PATH_ETHERNET: + case DEV_PATH_IEEE80211: case DEV_PATH_DSA: case DEV_PATH_VLAN: case DEV_PATH_PPPOE: @@ -123,6 +125,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack, memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); if (path->type == DEV_PATH_ETHERNET || + path->type == DEV_PATH_IEEE80211 || path->type == DEV_PATH_DSA) break; diff --git a/net/netfilter/nf_nat_bpf.c b/net/netfilter/nf_nat_bpf.c index f9dd85ccea01bf..7572b58c448b4a 100644 --- a/net/netfilter/nf_nat_bpf.c +++ b/net/netfilter/nf_nat_bpf.c @@ -39,6 +39,9 @@ __bpf_kfunc int bpf_ct_set_nat_info(struct nf_conn___init *nfct, if (proto != NFPROTO_IPV4 && proto != NFPROTO_IPV6) return -EINVAL; + if (manip != NF_NAT_MANIP_SRC && manip != NF_NAT_MANIP_DST) + return -EINVAL; + memset(&range, 0, sizeof(struct nf_nat_range2)); range.flags = NF_NAT_RANGE_MAP_IPS; range.min_addr = *addr; diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index a4858c2b2d65a4..cc8e1e81006d54 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -767,8 +767,9 @@ nf_nat_setup_info(struct nf_conn *ct, if (nf_ct_is_confirmed(ct)) return NF_ACCEPT; - WARN_ON(maniptype != NF_NAT_MANIP_SRC && - maniptype != NF_NAT_MANIP_DST); + if (WARN_ON(maniptype != NF_NAT_MANIP_SRC && + maniptype != NF_NAT_MANIP_DST)) + return NF_DROP; if (WARN_ON(nf_nat_initialized(ct, maniptype))) return NF_DROP; diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index a3bc0028005109..477fc632657f4c 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -674,6 +675,12 @@ static int nfqnl_put_bridge(struct nf_queue_entry *entry, struct sk_buff *skb) static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c index 32b4281038dd35..d3c5651dd6997e 100644 --- a/net/netfilter/nft_flow_offload.c +++ b/net/netfilter/nft_flow_offload.c @@ -160,6 +160,7 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, struct nft_flow_offload *priv = nft_expr_priv(expr); u8 genmask = nft_genmask_next(ctx->net); struct nft_flowtable *flowtable; + int err; if (!tb[NFTA_FLOW_TABLE_NAME]) return -EINVAL; @@ -174,7 +175,11 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, priv->flowtable = flowtable; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + nft_use_dec(&flowtable->use); + + return err; } static void nft_flow_offload_deactivate(const struct nft_ctx *ctx, diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 9894832281c4b8..12431b55752fb4 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -900,6 +900,8 @@ static void nft_rbtree_gc_scan(struct nft_set *set) next = rb_next(node); rbe = rb_entry(node, struct nft_rbtree_elem, node); + if (!nft_set_elem_active(&rbe->ext, NFT_GENMASK_ANY)) + continue; /* elements are reversed in the rbtree for historical reasons, * from highest to lowest value, that is why end element is diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d1764b..1fb0f8118404d6 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index dc5ff859f11451..27152bca8a8ce7 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -358,7 +358,12 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh, static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, __be32 addr[4], const __be32 new_addr[4]) { - int transport_len = skb->len - skb_transport_offset(skb); + int transport_len; + + if (l4_proto == NEXTHDR_FRAGMENT) + return; + + transport_len = skb->len - skb_transport_offset(skb); if (l4_proto == NEXTHDR_TCP) { if (likely(transport_len >= sizeof(struct tcphdr))) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 631a03136fa142..4fc5d0bebd85e7 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1532,6 +1532,12 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) return -ENODEV; } + /* + * Not needed for safety. Stops every spin_unlock_bh() in + * ovs_flow_stats_get() from running the softirq backlog. + */ + local_bh_disable(); + ti = rcu_dereference(dp->table.ti); for (;;) { struct sw_flow *flow; @@ -1552,6 +1558,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = bucket; cb->args[1] = obj; } + + local_bh_enable(); rcu_read_unlock(); return skb->len; } diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 7c83e01526edc5..52ae3d875e971b 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1911,7 +1911,8 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev, spkt = &PACKET_SKB_CB(skb)->sa.pkt; - skb_push(skb, skb->data - skb_mac_header(skb)); + if (dev_has_header(dev)) + skb_push(skb, skb->data - skb_mac_header(skb)); /* * The SOCK_PACKET socket receives _all_ frames. @@ -1942,8 +1943,7 @@ static void packet_parse_headers(struct sk_buff *skb, struct socket *sock) /* On TX skb->data is the L2 header; anchor it for all socket types. */ skb_reset_mac_header(skb); - if ((!skb->protocol || skb->protocol == htons(ETH_P_ALL)) && - sock->type == SOCK_RAW) + if (!skb->protocol && sock->type == SOCK_RAW) skb->protocol = dev_parse_header_protocol(skb); skb_probe_transport_header(skb); @@ -2613,7 +2613,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, struct page *page; int err; - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; @@ -2677,8 +2678,6 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, if (unlikely(!skb->len)) return -EINVAL; - packet_parse_headers(skb, sock); - return tp_len; } @@ -2874,6 +2873,9 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) vnet_hdr.hdr_len); has_vnet_hdr = true; } + /* Prevent the byte count from wrapping into a negative errno */ + if (unlikely(tp_len > INT_MAX - len_sum)) + break; copylen = max_t(int, copylen, hard_header_len); skb = sock_alloc_send_skb(&po->sk, hlen + tlen + sizeof(struct sockaddr_ll) + @@ -2915,9 +2917,13 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) tp_len = -EINVAL; goto tpacket_error; } - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, po->sk.sk_socket); + + if (has_vnet_hdr) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); if (unlikely(!uarg)) { if (likely(len_sum > 0)) @@ -3118,7 +3124,8 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_free; } - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc.priority; skb->mark = sockc.mark; @@ -3127,16 +3134,18 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) if (unlikely(extra_len == 4)) skb->no_fcs = 1; - packet_parse_headers(skb, sock); - if (vnet_hdr_sz) { err = virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le()); if (err) goto out_free; len += vnet_hdr_sz; - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, sock); + + if (vnet_hdr_sz) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + err = packet_xmit(po, skb); if (unlikely(err != 0)) { diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 411e3dd92d072e..b48e6d2bb5a7b8 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -289,7 +289,7 @@ static bool tcf_ct_flow_is_outdated(const struct flow_offload *flow) { return test_bit(IPS_SEEN_REPLY_BIT, &flow->ct->status) && test_bit(IPS_HW_OFFLOAD_BIT, &flow->ct->status) && - !test_bit(NF_FLOW_HW_PENDING, &flow->flags) && + !test_bit(NF_FLOW_PENDING, &flow->flags) && !test_bit(NF_FLOW_HW_ESTABLISHED, &flow->flags); } diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c index bfec6b66841031..26258b353cdeec 100644 --- a/net/sched/act_skbedit.c +++ b/net/sched/act_skbedit.c @@ -72,9 +72,8 @@ TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb, } if (params->flags & SKBEDIT_F_QUEUE_MAPPING && skb->dev->real_num_tx_queues > params->queue_mapping) { -#ifdef CONFIG_NET_EGRESS - netdev_xmit_skip_txqueue(true); -#endif + if (skb_at_tc_egress(skb)) + netdev_xmit_skip_txqueue(true); skb_set_queue_mapping(skb, tcf_skbedit_hash(params, skb)); } if (params->flags & SKBEDIT_F_MARK) { diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index c47d2ee13641d4..a9f54988561f2f 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -2463,7 +2463,20 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, } errout: - if (err && tp_state == TP_CREATED) + if (err && !IS_ERR_OR_NULL(tp) && + (tp_state == TP_CREATED || tp->ops->delete_empty)) + /* + * The request is dropping its reference to tp. If it was + * the last user (the idr is empty now), reclaim the proto. + * A tp this request created is reclaimed unconditionally: + * it is the only owner, so marking it for deletion is + * safe. Otherwise only classifiers with a delete_empty + * callback are reclaimed -- the callback admits an empty + * proto only, so a live shared proto is never unlinked. + * Classifiers without one (deleting is set + * unconditionally) are rtnl-serialized, so the raced + * window this guard closes cannot arise for them. + */ tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL); errout_tp: if (chain) { diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0f211f030fd9a7..dca812a7526981 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -434,6 +434,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; + if (fold && !tb[TCA_ROUTE4_TO] && !tb[TCA_ROUTE4_FROM] && + !tb[TCA_ROUTE4_IIF] && nhandle != fold->handle) { + NL_SET_ERR_MSG(extack, "Routing attributes required"); + return -EINVAL; + } + if (handle && !fold && nhandle != handle) { NL_SET_ERR_MSG_FMT(extack, "Handle mismatch constructed: %x (expected: %x)", diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 4132f8c3c5fc95..bc45edb8c03bff 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -58,6 +58,9 @@ static int em_text_change(struct net *net, void *data, int len, if (len < sizeof(*conf) || len < (sizeof(*conf) + conf->pattern_len)) return -EINVAL; + if (!memchr(conf->algo, '\0', sizeof(conf->algo))) + return -EINVAL; + if (conf->from_layer > conf->to_layer) return -EINVAL; diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index 6aa5829d696124..a1ba2e355f2d18 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -205,7 +205,7 @@ static int codel_init(struct Qdisc *sch, struct nlattr *opt, codel_params_init(&q->params); codel_vars_init(&q->vars); codel_stats_init(&q->stats); - q->params.mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, 1 << 20); + q->params.mtu = min_t(u32, psched_mtu(qdisc_dev(sch)), 1 << 20); if (opt) { int err = codel_change(sch, opt, extack); diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 969b2510b0b86e..e6c87a32950f9b 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -517,8 +517,8 @@ static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, q->flows_cnt = 1024; q->memory_limit = 32 << 20; /* 32 MBytes */ q->drop_batch_size = 64; - mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, FQ_CODEL_QUANTUM_MAX); - q->quantum = mtu; + mtu = min_t(u32, psched_mtu(qdisc_dev(sch)), FQ_CODEL_QUANTUM_MAX); + q->quantum = clamp_t(u32, mtu, 256, FQ_CODEL_QUANTUM_MAX); INIT_LIST_HEAD(&q->new_flows); INIT_LIST_HEAD(&q->old_flows); codel_params_init(&q->cparams); diff --git a/net/sctp/socket.c b/net/sctp/socket.c index c7b9e325ec1cd3..e35692dd8d303b 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1844,7 +1844,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc, if (asoc->ep->intl_enable) { timeo = sock_sndtimeo(sk, 0); err = sctp_wait_for_connect(asoc, &timeo); - if (err) { + if (err || sk->sk_shutdown & RCV_SHUTDOWN) { err = -ESRCH; goto err; } @@ -7569,11 +7569,7 @@ static int sctp_getsockopt_pr_streamstatus(struct sock *sk, int len, /* Not allocated yet, means all stats are 0 */ params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; - retval = 0; - goto out; - } - - if (policy == SCTP_PR_SCTP_ALL) { + } else if (policy == SCTP_PR_SCTP_ALL) { params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; for (policy = 0; policy <= SCTP_PR_INDEX(MAX); policy++) { diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index e9f93b3ab435bb..8b3ee70f8433f4 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -2784,6 +2784,7 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, int peer) { struct smc_sock *smc; + int rc = -EBADF; if (peer && (sock->sk->sk_state != SMC_ACTIVE) && (sock->sk->sk_state != SMC_APPCLOSEWAIT1)) @@ -2791,7 +2792,11 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, smc = smc_sk(sock->sk); - return smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_lock(&smc->clcsock_release_lock); + if (smc->clcsock) + rc = smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_unlock(&smc->clcsock_release_lock); + return rc; } int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c index a23f4b4dfc6733..d5b17d099a0e78 100644 --- a/net/strparser/strparser.c +++ b/net/strparser/strparser.c @@ -505,12 +505,16 @@ void strp_unpause(struct strparser *strp) EXPORT_SYMBOL_GPL(strp_unpause); /* strp must already be stopped so that strp_recv will no longer be called. - * Note that strp_done is not called with the lower socket held. + * Note that strp_done must not be called with the lower socket held. */ void strp_done(struct strparser *strp) { WARN_ON(!strp->stopped); + lock_sock(strp->sk); + /* sync with pending strp_recv */ + release_sock(strp->sk); + cancel_delayed_work_sync(&strp->msg_timer_work); cancel_work_sync(&strp->work); diff --git a/net/tipc/crypto.c b/net/tipc/crypto.c index 16f1ed1f6b1b51..4409bdb70fe981 100644 --- a/net/tipc/crypto.c +++ b/net/tipc/crypto.c @@ -144,7 +144,7 @@ struct tipc_tfm { * @rcu: struct rcu_head * @key: the aead key * @gen: the key's generation - * @seqno: the key seqno (cluster scope) + * @seqno: the per-key TX nonce counter * @refcnt: the key reference counter */ struct tipc_aead { @@ -190,7 +190,6 @@ struct tipc_crypto_stats { * @rekeying_intv: rekeying interval (in minutes) * @stats: the crypto statistics * @name: the crypto name - * @sndnxt: the per-peer sndnxt (TX) * @timer1: general timer 1 (jiffies) * @timer2: general timer 2 (jiffies) * @working: the crypto is working or not @@ -219,7 +218,6 @@ struct tipc_crypto { struct tipc_crypto_stats __percpu *stats; char name[48]; - atomic64_t sndnxt ____cacheline_aligned; unsigned long timer1; unsigned long timer2; union { @@ -1051,14 +1049,11 @@ static int tipc_ehdr_build(struct net *net, struct tipc_aead *aead, WARN_ON(skb_headroom(skb) < ehsz); ehdr = (struct tipc_ehdr *)skb_push(skb, ehsz); - /* Obtain a seqno first: - * Use the key seqno (= cluster wise) if dest is unknown or we're in - * cluster key mode, otherwise it's better for a per-peer seqno! + /* + * Keep the nonce unique for the lifetime of the TX key, + * including key state changes and peer reconnection. */ - if (!__rx || aead->mode == CLUSTER_KEY) - seqno = atomic64_inc_return(&aead->seqno); - else - seqno = atomic64_inc_return(&__rx->sndnxt); + seqno = atomic64_inc_return(&aead->seqno); /* Revoke the key if seqno is wrapped around */ if (unlikely(!seqno)) @@ -1237,7 +1232,6 @@ void tipc_crypto_key_flush(struct tipc_crypto *c) tipc_crypto_key_set_state(c, 0, 0, 0); for (k = KEY_MIN; k <= KEY_MAX; k++) tipc_crypto_key_detach(c->aead[k], &c->lock); - atomic64_set(&c->sndnxt, 0); spin_unlock_bh(&c->lock); } @@ -1384,8 +1378,6 @@ static struct tipc_aead *tipc_crypto_key_pick_tx(struct tipc_crypto *tx, * It also considers if peer has no key, then we need to make own master key * (if any) taking over i.e. starting grace period and also trigger key * distributing process. - * - * The "per-peer" sndnxt is also reset when the peer key has switched. */ static void tipc_crypto_key_synch(struct tipc_crypto *rx, struct sk_buff *skb) { @@ -1436,7 +1428,6 @@ static void tipc_crypto_key_synch(struct tipc_crypto *rx, struct sk_buff *skb) if (cur) tipc_aead_users_dec(tx->aead[cur], 0); - atomic64_set(&rx->sndnxt, 0); /* Mark the point TX key users changed */ tx->timer1 = jiffies; @@ -1501,7 +1492,6 @@ int tipc_crypto_start(struct tipc_crypto **crypto, struct net *net, tipc_crypto_key_set_state(c, 0, 0, 0); atomic_set(&c->key_distr, 0); atomic_set(&c->peer_rx_active, 0); - atomic64_set(&c->sndnxt, 0); c->timer1 = jiffies; c->timer2 = jiffies; c->rekeying_intv = TIPC_REKEYING_INTV_DEF; diff --git a/net/tipc/node.c b/net/tipc/node.c index bd91378b754071..d7cbfa786c1329 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -2421,8 +2421,11 @@ static struct tipc_node *tipc_node_find_by_name(struct net *net, } } tipc_node_read_unlock(n); - if (found_node) + if (found_node) { + if (!kref_get_unless_zero(&found_node->kref)) + found_node = NULL; break; + } } rcu_read_unlock(); @@ -2507,6 +2510,7 @@ int tipc_nl_node_set_link(struct sk_buff *skb, struct genl_info *info) tipc_node_read_unlock(node); tipc_bearer_xmit(net, bearer_id, &xmitq, &node->links[bearer_id].maddr, NULL); + tipc_node_put(node); return res; } @@ -2558,12 +2562,14 @@ int tipc_nl_node_get_link(struct sk_buff *skb, struct genl_info *info) link = node->links[bearer_id].link; if (!link) { tipc_node_read_unlock(node); + tipc_node_put(node); err = -EINVAL; goto err_free; } err = __tipc_nl_add_link(net, &msg, link, 0); tipc_node_read_unlock(node); + tipc_node_put(node); if (err) goto err_free; } @@ -2634,11 +2640,13 @@ int tipc_nl_node_reset_link_stats(struct sk_buff *skb, struct genl_info *info) if (!link) { spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return -EINVAL; } tipc_link_reset_stats(link); spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return 0; } diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index af530c9ed8402c..82e9f44e2fe46e 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -55,7 +55,7 @@ /** * struct tipc_topsrv - TIPC server structure * @conn_idr: identifier set of connection - * @idr_lock: protect the connection identifier set + * @idr_lock: protect the connection identifier set and listener * @idr_in_use: amount of allocated identifier entry * @net: network namespace instance * @awork: accept work item @@ -218,6 +218,10 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_topsrv *s, int conid) struct tipc_conn *con; spin_lock_bh(&s->idr_lock); + if (!s->listener) { + spin_unlock_bh(&s->idr_lock); + return NULL; + } con = idr_find(&s->conn_idr, conid); if (!connected(con) || !kref_get_unless_zero(&con->kref)) con = NULL; @@ -301,10 +305,20 @@ static void tipc_conn_send_to_sock(struct tipc_conn *con) static void tipc_conn_send_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, swork); + struct tipc_topsrv *srv; + + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); if (connected(con)) tipc_conn_send_to_sock(con); +out: conn_put(con); } @@ -334,8 +348,14 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, list_add_tail(&e->list, &con->outqueue); spin_unlock_bh(&con->outqueue_lock); - if (queue_work(srv->send_wq, &con->swork)) - return; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + if (queue_work(srv->send_wq, &con->swork)) { + spin_unlock_bh(&srv->idr_lock); + return; + } + } + spin_unlock_bh(&srv->idr_lock); err: conn_put(con); } @@ -346,14 +366,20 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, */ static void tipc_conn_write_space(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->send_wq, &con->swork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->send_wq, &con->swork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -418,8 +444,17 @@ static int tipc_conn_rcv_from_sock(struct tipc_conn *con) static void tipc_conn_recv_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, rwork); + struct tipc_topsrv *srv; int count = 0; + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); + while (connected(con)) { if (tipc_conn_rcv_from_sock(con)) break; @@ -430,6 +465,7 @@ static void tipc_conn_recv_work(struct work_struct *work) count = 0; } } +out: conn_put(con); } @@ -438,6 +474,7 @@ static void tipc_conn_recv_work(struct work_struct *work) */ static void tipc_conn_data_ready(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; trace_sk_data_ready(sk); @@ -445,9 +482,14 @@ static void tipc_conn_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->rcv_wq, &con->rwork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->rcv_wq, &con->rwork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -503,8 +545,12 @@ static void tipc_topsrv_listener_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); srv = sk->sk_user_data; - if (srv) - queue_work(srv->rcv_wq, &srv->awork); + if (srv) { + spin_lock_bh(&srv->idr_lock); + if (srv->listener) + queue_work(srv->rcv_wq, &srv->awork); + spin_unlock_bh(&srv->idr_lock); + } read_unlock_bh(&sk->sk_callback_lock); } @@ -700,23 +746,27 @@ static void tipc_topsrv_stop(struct net *net) struct tipc_conn *con; int id; + spin_lock_bh(&srv->idr_lock); + srv->listener = NULL; + spin_unlock_bh(&srv->idr_lock); + tipc_topsrv_work_stop(srv); + spin_lock_bh(&srv->idr_lock); for (id = 0; srv->idr_in_use; id++) { con = idr_find(&srv->conn_idr, id); if (con) { - conn_get(con); spin_unlock_bh(&srv->idr_lock); tipc_conn_close(con); - conn_put(con); spin_lock_bh(&srv->idr_lock); + continue; } + spin_unlock_bh(&srv->idr_lock); + spin_lock_bh(&srv->idr_lock); } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); - srv->listener = NULL; spin_unlock_bh(&srv->idr_lock); - tipc_topsrv_work_stop(srv); sock_release(lsock); idr_destroy(&srv->conn_idr); kfree(srv); diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 9b71479a2b2959..533c733618df7e 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1048,6 +1048,8 @@ static void vsock_sk_destruct(struct sock *sk) { struct vsock_sock *vsk = vsock_sk(sk); + /* Socket buffers may remain in the queue in VMCI datagram mode */ + __skb_queue_purge(&sk->sk_receive_queue); /* Flush MSG_ZEROCOPY leftovers. */ __skb_queue_purge(&sk->sk_error_queue); diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index f225f53ed4babe..1e762a480df490 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1927,6 +1927,7 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto struct sock *sk = sk_vsock(vsk); struct virtio_vsock_hdr *hdr; struct sk_buff *skb; + u32 bytes_read; u32 pkt_len; int off = 0; int err; @@ -1946,7 +1947,8 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto vvs->msg_count--; pkt_len = le32_to_cpu(hdr->len); - virtio_transport_dec_rx_pkt(vvs, pkt_len, pkt_len); + bytes_read = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + virtio_transport_dec_rx_pkt(vvs, bytes_read, pkt_len); spin_unlock_bh(&vvs->rx_lock); virtio_transport_send_credit_update(vsk); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9fafd7673d853b..ddce61ec016ae6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4563,12 +4563,13 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) rdev->wiphy.retry_long = retry_long; if (changed & WIPHY_PARAM_FRAG_THRESHOLD) rdev->wiphy.frag_threshold = frag_threshold; - if ((changed & WIPHY_PARAM_RTS_THRESHOLD) && - old_radio_rts_threshold) { + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { rdev->wiphy.rts_threshold = rts_threshold; - for (i = 0 ; i < rdev->wiphy.n_radio; i++) - rdev->wiphy.radio_cfg[i].rts_threshold = - rdev->wiphy.rts_threshold; + if (old_radio_rts_threshold) { + for (i = 0; i < rdev->wiphy.n_radio; i++) + rdev->wiphy.radio_cfg[i].rts_threshold = + rdev->wiphy.rts_threshold; + } } if (changed & WIPHY_PARAM_COVERAGE_CLASS) rdev->wiphy.coverage_class = coverage_class; diff --git a/net/wireless/scan.c b/net/wireless/scan.c index caa9c6495f20cc..d35c826c4b1a70 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1992,20 +1992,22 @@ cfg80211_update_known_bss(struct cfg80211_registered_device *rdev, return false; } - old = rcu_access_pointer(known->pub.beacon_ies); + if (!new->pub.hidden_beacon_bss) { + old = rcu_access_pointer(known->pub.beacon_ies); - rcu_assign_pointer(known->pub.beacon_ies, new->pub.beacon_ies); + rcu_assign_pointer(known->pub.beacon_ies, new->pub.beacon_ies); - /* Override IEs if they were from a beacon before */ - if (old == rcu_access_pointer(known->pub.ies)) - rcu_assign_pointer(known->pub.ies, new->pub.beacon_ies); + /* Override IEs if they were from a beacon before */ + if (old == rcu_access_pointer(known->pub.ies)) + rcu_assign_pointer(known->pub.ies, new->pub.beacon_ies); - cfg80211_update_hidden_bsses(known, - rcu_access_pointer(new->pub.beacon_ies), - old); + cfg80211_update_hidden_bsses(known, + rcu_access_pointer(new->pub.beacon_ies), + old); - if (old) - kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); + if (old) + kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); + } } known->pub.beacon_interval = new->pub.beacon_interval; diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index e305ba32e356b7..f1f612cd7b438d 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -823,16 +823,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output); diff --git a/rust/kernel/net/netlink.rs b/rust/kernel/net/netlink.rs index 22ef3dde36fa1a..2f8733a694a95f 100644 --- a/rust/kernel/net/netlink.rs +++ b/rust/kernel/net/netlink.rs @@ -11,6 +11,7 @@ use kernel::{ alloc::{self, AllocError}, error::to_result, + num::casts::u16_as_usize, prelude::*, transmute::AsBytes, types::Opaque, @@ -86,9 +87,17 @@ impl GenlMsg { where T: ?Sized + AsBytes, { + // `nla_len` is a 16-bit field that encodes the total attribute length + // (header + payload). Subtracting the header size from `u16::MAX` gives + // the largest payload that still fits within that field. + const MAX_PAYLOAD_LEN: usize = u16_as_usize(u16::MAX) - size_of::(); + let skb = self.skb.skb.as_ptr(); let len = size_of_val(value); let ptr = core::ptr::from_ref(value).cast::(); + if len > MAX_PAYLOAD_LEN { + return Err(EMSGSIZE); + } // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and the provided value is // readable and initialized for its `size_of` bytes. to_result(unsafe { bindings::nla_put(skb, attrtype, len as c_int, ptr) }) diff --git a/security/keys/key.c b/security/keys/key.c index b34a64d81d47ab..a438c4508595d9 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -840,9 +840,6 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, key_check(keyring); - if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) - restrict_link = keyring->restrict_link; - key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) goto error_put_type; @@ -880,6 +877,9 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, goto error_link_end; } + if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) + restrict_link = keyring->restrict_link; + if (restrict_link && restrict_link->check) { ret = restrict_link->check(keyring, index_key.type, &prep.payload, restrict_link->key); diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 97af230aa4b22b..bdab9953932613 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -33,25 +33,31 @@ static int key_create_persistent_register(struct user_namespace *ns) } /* - * Create the persistent keyring for the specified user. + * Get or create the persistent keyring for the specified user. * * Called with the namespace's sem locked for writing. + * + * Returns a boolean indicating whether the keyring already existed, + * or a negative error. On success, *persistent_ref holds a reference + * to the keyring. */ -static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, - struct keyring_index_key *index_key) +static int key_get_or_create_persistent(struct user_namespace *ns, kuid_t uid, + struct keyring_index_key *index_key, + key_ref_t *persistent_ref) { struct key *persistent; - key_ref_t reg_ref, persistent_ref; + key_ref_t reg_ref; if (!ns->persistent_keyring_register) { - long err = key_create_persistent_register(ns); + int err = key_create_persistent_register(ns); + if (err < 0) - return ERR_PTR(err); + return err; } else { reg_ref = make_key_ref(ns->persistent_keyring_register, true); - persistent_ref = find_key_to_update(reg_ref, index_key); - if (persistent_ref) - return persistent_ref; + *persistent_ref = find_key_to_update(reg_ref, index_key); + if (*persistent_ref) + return 1; } persistent = keyring_alloc(index_key->description, @@ -61,9 +67,10 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, KEY_ALLOC_NOT_IN_QUOTA, NULL, ns->persistent_keyring_register); if (IS_ERR(persistent)) - return ERR_CAST(persistent); + return PTR_ERR(persistent); - return make_key_ref(persistent, true); + *persistent_ref = make_key_ref(persistent, true); + return 0; } /* @@ -78,6 +85,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, key_ref_t reg_ref, persistent_ref; char buf[32]; long ret; + bool created = false; /* Look in the register if it exists */ memset(&index_key, 0, sizeof(index_key)); @@ -100,23 +108,24 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, * also need to create the register. */ down_write(&ns->keyring_sem); - persistent_ref = key_create_persistent(ns, uid, &index_key); + ret = key_get_or_create_persistent(ns, uid, &index_key, + &persistent_ref); up_write(&ns->keyring_sem); - if (!IS_ERR(persistent_ref)) - goto found; - - return PTR_ERR(persistent_ref); + if (ret < 0) + return ret; + created = ret == 0; found: + persistent = key_ref_to_ptr(persistent_ref); ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK); - if (ret == 0) { - persistent = key_ref_to_ptr(persistent_ref); + if (ret == 0) ret = key_link(key_ref_to_ptr(dest_ref), persistent); - if (ret == 0) { - key_set_timeout(persistent, persistent_keyring_expiry); - ret = persistent->serial; - } - } + + if (ret == 0 || created) + key_set_timeout(persistent, persistent_keyring_expiry); + + if (ret == 0) + ret = persistent->serial; key_ref_put(persistent_ref); return ret; diff --git a/security/landlock/domain.h b/security/landlock/domain.h index caa3d19d2c4321..428c1d9890dbfd 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -242,9 +242,14 @@ struct landlock_domain { * to properly handle merged rulesets without * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. + * + * Not annotated with __counted_by(num_layers): clang + * miscomputes the counter address for a FAM nested in + * an anonymous struct within a union, which trips + * FORTIFY: + * https://github.com/llvm/llvm-project/pull/228309 */ - struct access_masks - handled_masks[] __counted_by(num_layers); + struct access_masks handled_masks[]; }; }; }; diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 560a82c6d68262..b91f914d292e58 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -578,7 +578,7 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) } } else { if (seqno > selinux_avc.avc_cache.latest_notif) - selinux_avc.avc_cache.latest_notif = seqno; + WRITE_ONCE(selinux_avc.avc_cache.latest_notif, seqno); } spin_unlock_irqrestore(¬if_lock, flag); @@ -1206,5 +1206,6 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 avc_policy_seqno(void) { - return selinux_avc.avc_cache.latest_notif; + /* Writers hold notif_lock; readers only sample the sequence number. */ + return READ_ONCE(selinux_avc.avc_cache.latest_notif); } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3f4a6ddee32251..30776b0107f291 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -690,8 +690,12 @@ static int selinux_set_mnt_opts(struct super_block *sb, * will be used for both mounts) */ if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - && !opts) + && !opts) { + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; goto out; + } root_isec = backing_inode_security_novalidate(root); @@ -735,6 +739,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, /* previously mounted with options, but not on this attempt? */ if ((sbsec->flags & SE_MNTMASK) && !opts) goto out_double_mount; + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; rc = 0; goto out; } diff --git a/sound/core/control_led.c b/sound/core/control_led.c index ec0e6c7ad65770..501387287ae693 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -563,12 +563,43 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si return count; } +/* + * snd_ctl_led_disconnect() removes these files with card->controls_rwsem + * held. so don't let the removal wait for a callback that takes it. + */ +static struct kernfs_node *snd_ctl_led_sysfs_enter(struct device *dev, + struct device_attribute *attr) +{ + struct kernfs_node *kn; + + if (!try_module_get(THIS_MODULE)) + return NULL; + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) + module_put(THIS_MODULE); + return kn; +} + +static void snd_ctl_led_sysfs_exit(struct kernfs_node *kn) +{ + sysfs_unbreak_active_protection(kn); + module_put(THIS_MODULE); +} + static ssize_t attach_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, true); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, true); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t detach_store(struct device *dev, @@ -576,7 +607,15 @@ static ssize_t detach_store(struct device *dev, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, false); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, false); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t reset_store(struct device *dev, @@ -594,10 +633,8 @@ static ssize_t reset_store(struct device *dev, return count; } -static ssize_t list_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t snd_ctl_led_list(struct snd_ctl_led_card *led_card, char *buf) { - struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); struct snd_ctl_led_ctl *lctl; size_t l = 0; struct snd_card *card __free(snd_card_unref) = @@ -620,6 +657,21 @@ static ssize_t list_show(struct device *dev, return l; } +static ssize_t list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = snd_ctl_led_list(led_card, buf); + snd_ctl_led_sysfs_exit(kn); + return ret; +} + static DEVICE_ATTR_WO(attach); static DEVICE_ATTR_WO(detach); static DEVICE_ATTR_WO(reset); diff --git a/sound/core/info.c b/sound/core/info.c index 3b8ccda04e1a5d..c4d57d0809adff 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -528,6 +528,8 @@ int snd_info_card_register(struct snd_card *card) void snd_info_card_id_change(struct snd_card *card) { guard(mutex)(&info_mutex); + if (card->shutdown) + return; if (card->proc_root_link) { proc_remove(card->proc_root_link); card->proc_root_link = NULL; @@ -555,7 +557,6 @@ void snd_info_card_disconnect(struct snd_card *card) if (card->proc_root) snd_info_clear_entries(card->proc_root); card->proc_root_link = NULL; - card->proc_root = NULL; } /* diff --git a/sound/core/init.c b/sound/core/init.c index a51b71812fd9b3..9deb5e920d0d49 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -216,11 +216,13 @@ static void __snd_card_release(struct device *dev, void *data) * is added automatically. In that way, the resource disconnection is assured * at first, then released in the expected order. * - * If an error happens at the probe before snd_card_register() is called and - * there have been other devres resources, you'd need to free the card manually - * via snd_card_free() call in the error; otherwise it may lead to UAF due to - * devres call orders. You can use snd_card_free_on_error() helper for - * handling it more easily. + * Caveat: if an error happens at the probe before snd_card_register() is + * called and there have been other devres resources that are referred at the + * card-level destructor call, you'd still need to free the card manually; + * otherwise it may lead to UAF due to devres call orders. Instead of a manual + * snd_card_free() call, you can use the automatic clean-up for the card + * pointer via __free(snd_card_free), too. In this case, don't forget to do + * NULL-clear the card pointer before the successful return! * * Return: zero if successful, or a negative error code */ @@ -250,30 +252,6 @@ int snd_devm_card_new(struct device *parent, int idx, const char *xid, } EXPORT_SYMBOL_GPL(snd_devm_card_new); -/** - * snd_card_free_on_error - a small helper for handling devm probe errors - * @dev: the managed device object - * @ret: the return code from the probe callback - * - * This function handles the explicit snd_card_free() call at the error from - * the probe callback. It's just a small helper for simplifying the error - * handling with the managed devices. - * - * Return: zero if successful, or a negative error code - */ -int snd_card_free_on_error(struct device *dev, int ret) -{ - struct snd_card *card; - - if (!ret) - return 0; - card = devres_find(dev, __snd_card_release, NULL, NULL); - if (card) - snd_card_free(card); - return ret; -} -EXPORT_SYMBOL_GPL(snd_card_free_on_error); - static int snd_card_init(struct snd_card *card, struct device *parent, int idx, const char *xid, struct module *module, size_t extra_size) @@ -356,7 +334,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, err = snd_info_card_create(card); if (err < 0) { dev_err(parent, "unable to create card info\n"); - goto __error_ctl; + goto __error; } #ifdef CONFIG_SND_DEBUG @@ -365,16 +343,16 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #endif #ifdef CONFIG_SND_CTL_DEBUG card->value_buf = kmalloc_obj(*card->value_buf); - if (!card->value_buf) - return -ENOMEM; + if (!card->value_buf) { + err = -ENOMEM; + goto __error; + } #endif return 0; - __error_ctl: - snd_device_free_all(card); __error: - put_device(&card->card_dev); - return err; + snd_card_free(card); + return err; } /** @@ -557,6 +535,8 @@ void snd_card_disconnect(struct snd_card *card) clear_bit(card->number, snd_cards_lock); } + /* order card->shutdown store against power_ref read */ + smp_mb(); snd_power_sync_ref(card); } EXPORT_SYMBOL(snd_card_disconnect); @@ -1174,6 +1154,8 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); + /* order power_ref increment against card->shutdown read */ + smp_mb__after_atomic(); if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { wait_event_cmd(card->power_sleep, card->shutdown || diff --git a/sound/core/jack.c b/sound/core/jack.c index 0f04018181acb2..d9132b885ea3cf 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -68,6 +68,9 @@ static int snd_jack_dev_free(struct snd_device *device) struct snd_card *card = device->card; struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + /* Remove debugfs while the controls are still linked. */ + snd_jack_remove_debugfs(jack); + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { list_del_init(&jack_kctl->list); snd_ctl_remove(card, jack_kctl->kctl); @@ -450,7 +453,7 @@ static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const cha return jack_kctl; error: - snd_ctl_free_one(kctl); + snd_ctl_remove(card, kctl); return NULL; } diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 55ecf87586c4dd..c54cb07304ad49 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -219,8 +219,8 @@ static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream, .audio_tstamp_data = status.audio_tstamp_data, .audio_tstamp_sec = status.audio_tstamp_sec, .audio_tstamp_nsec = status.audio_tstamp_nsec, - .driver_tstamp_sec = status.audio_tstamp_sec, - .driver_tstamp_nsec = status.audio_tstamp_nsec, + .driver_tstamp_sec = status.driver_tstamp_sec, + .driver_tstamp_nsec = status.driver_tstamp_nsec, .audio_tstamp_accuracy = status.audio_tstamp_accuracy, }; @@ -252,7 +252,7 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, return -EFAULT; if (refine) { - err = snd_pcm_hw_refine(substream, data); + err = snd_pcm_hw_refine_with_lock(substream, data); if (err < 0) return err; err = fixup_unreferenced_params(substream, data); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 5abe16a03b34a0..7ffbf2c0ea921f 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -580,29 +580,6 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream, } EXPORT_SYMBOL(snd_pcm_hw_refine); -static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params __user * _params) -{ - int err; - struct snd_pcm_hw_params *params __free(kfree) = - memdup_user(_params, sizeof(*params)); - - if (IS_ERR(params)) - return PTR_ERR(params); - - err = snd_pcm_hw_refine(substream, params); - if (err < 0) - return err; - - err = fixup_unreferenced_params(substream, params); - if (err < 0) - return err; - - if (copy_to_user(_params, params, sizeof(*params))) - return -EFAULT; - return 0; -} - static int period_to_usecs(struct snd_pcm_runtime *runtime) { int usecs; @@ -778,11 +755,48 @@ EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); #define is_oss_stream(substream) false #endif +static void pcm_qos_req_clear(struct snd_pcm_substream *substream) +{ + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); +} + +static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) +{ + if (usecs < 0) { + pcm_qos_req_clear(substream); + return; + } + + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_update_request(&substream->latency_pm_qos_req, + usecs); + else + cpu_latency_qos_add_request(&substream->latency_pm_qos_req, + usecs); +} + +static int hw_params_state_check(struct snd_pcm_substream *substream) +{ + guard(pcm_stream_lock_irq)(substream); + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (!is_oss_stream(substream) && + atomic_read(&substream->mmap_count)) + return -EBADFD; + return 0; + default: + return -EBADFD; + } +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime; - int err, usecs; + int err; unsigned int bits; snd_pcm_uframes_t frames; @@ -792,20 +806,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - scoped_guard(pcm_stream_lock_irq, substream) { - switch (runtime->state) { - case SNDRV_PCM_STATE_OPEN: - case SNDRV_PCM_STATE_SETUP: - case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && - atomic_read(&substream->mmap_count)) - err = -EBADFD; - break; - default: - err = -EBADFD; - break; - } - } + err = hw_params_state_check(substream); if (err) goto unlock; @@ -889,12 +890,6 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_timer_resolution_change(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); - usecs = period_to_usecs(runtime); - if (usecs >= 0) - cpu_latency_qos_add_request(&substream->latency_pm_qos_req, - usecs); err = 0; _error: if (err) { @@ -932,6 +927,57 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, return err; } +/* + * call snd_pcm_hw_refine() after buffer lock and sanity check; + * this one should be used for ioctls and other external paths instead of + * direct call of snd_pcm_hw_refine() + */ +static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + err = hw_params_state_check(substream); + if (err) + goto unlock; + + err = snd_pcm_hw_refine(substream, params); + + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); + + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine_with_lock(substream, params); + if (err < 0) + return err; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + return err; + + if (copy_to_user(_params, params, sizeof(*params))) + return -EFAULT; + return 0; +} + static int do_hw_free(struct snd_pcm_substream *substream) { int result = 0; @@ -971,7 +1017,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) goto unlock; result = do_hw_free(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); unlock: snd_pcm_buffer_access_unlock(runtime); return result; @@ -1191,8 +1237,8 @@ static int snd_pcm_status_user32(struct snd_pcm_substream *substream, .audio_tstamp_data = status64.audio_tstamp_data, .audio_tstamp_sec = status64.audio_tstamp_sec, .audio_tstamp_nsec = status64.audio_tstamp_nsec, - .driver_tstamp_sec = status64.audio_tstamp_sec, - .driver_tstamp_nsec = status64.audio_tstamp_nsec, + .driver_tstamp_sec = status64.driver_tstamp_sec, + .driver_tstamp_nsec = status64.driver_tstamp_nsec, .audio_tstamp_accuracy = status64.audio_tstamp_accuracy, }; @@ -2005,6 +2051,7 @@ static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, err = substream->ops->prepare(substream); if (err < 0) return err; + pcm_qos_req_update(substream, period_to_usecs(substream->runtime)); return snd_pcm_do_reset(substream, state); } @@ -2273,11 +2320,11 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) return -ENXIO; runtime = substream->runtime; + guard(pcm_stream_lock_irq)(substream); if (runtime->state == SNDRV_PCM_STATE_OPEN || runtime->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; - guard(pcm_stream_lock_irq)(substream); /* resume pause */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); @@ -2285,6 +2332,8 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + snd_pcm_sync_stop(substream, true); + return result; } @@ -2776,18 +2825,14 @@ static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) return 0; } -static void pcm_release_private(struct snd_pcm_substream *substream) -{ - if (snd_pcm_stream_linked(substream)) - snd_pcm_unlink(substream); -} - void snd_pcm_release_substream(struct snd_pcm_substream *substream) { substream->ref_count--; if (substream->ref_count > 0) return; + snd_pcm_unlink(substream); + snd_pcm_drop(substream); if (substream->hw_opened) { if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) @@ -2795,8 +2840,7 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream) substream->ops->close(substream); substream->hw_opened = 0; } - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); if (substream->pcm_release) { substream->pcm_release(substream); substream->pcm_release = NULL; @@ -2872,8 +2916,6 @@ static int snd_pcm_open_file(struct file *file, return -ENOMEM; } pcm_file->substream = substream; - if (substream->ref_count == 1) - substream->pcm_release = pcm_release_private; file->private_data = pcm_file; return 0; @@ -3063,11 +3105,15 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3076,17 +3122,22 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3095,20 +3146,26 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static int snd_pcm_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) { + struct snd_pcm_runtime *runtime = substream->runtime; int err; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { err = do_pcm_hwsync(substream); if (delay && !err) *delay = snd_pcm_calc_delay(substream); } snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + atomic_dec(&runtime->buffer_accessing); return err; } @@ -4188,7 +4245,7 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, if (IS_ERR(oparams)) return PTR_ERR(oparams); snd_pcm_hw_convert_from_old_params(params, oparams); - err = snd_pcm_hw_refine(substream, params); + err = snd_pcm_hw_refine_with_lock(substream, params); if (err < 0) return err; diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index c43cedb5c12158..630d9fc8e9da0a 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -607,6 +607,9 @@ static void close_substream(struct snd_rawmidi *rmidi, substream->opened = 0; substream->append = 0; } + /* Drain input work queued after the earlier cancellation. */ + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + cancel_work_sync(&substream->runtime->event_work); substream->ops->close(substream); if (substream->runtime->private_free) substream->runtime->private_free(substream); diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c index 70f94df651446e..b456bd37d87bc3 100644 --- a/sound/core/seq/oss/seq_oss_midi.c +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -211,6 +211,11 @@ snd_seq_oss_midi_check_exit_port(int client, int port) mdev = find_slot(client, port); if (mdev) { scoped_guard(spinlock_irqsave, ®ister_lock) { + /* Only one callback may release the device. */ + if (midi_devs[mdev->seq_device] != mdev) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } midi_devs[mdev->seq_device] = NULL; } snd_use_lock_free(&mdev->use_lock); diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 239809ce48d725..0711a9e23b5fdc 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -95,23 +95,18 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) return snd_seq_total_cells(client->pool) > 0; } -/* return pointer to client structure for specified id; call under RCU read-lock */ -static struct snd_seq_client *__clientptr(int clientid) +/* return pointer to client structure for specified id; + * the caller must guarantee the client's lifetime by itself, as neither RCU + * nor a use_lock reference is taken here. + */ +static struct snd_seq_client *clientptr(int clientid) { if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } - return rcu_dereference_check(clienttab[clientid], - lockdep_is_held(&clients_lock)); -} - -/* return pointer to client structure for specified id */ -static struct snd_seq_client *clientptr(int clientid) -{ - guard(rcu)(); - return __clientptr(clientid); + return rcu_dereference_protected(clienttab[clientid], true); } static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) @@ -124,7 +119,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) return NULL; } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); if (clienttablock[clientid]) @@ -159,7 +154,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) } } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); } @@ -485,11 +480,11 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, if (err < 0) { if (cell) snd_seq_fifo_cell_putback(fifo, cell); - if (err == -EAGAIN && result > 0) - err = 0; } - return (err < 0) ? err : result; + if (result > 0) + return result; + return err < 0 ? err : 0; } diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 80110501da6ff5..57aaa39afb9dc5 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -25,7 +25,9 @@ struct snd_seq_port_info32 { u32 kernel; /* reserved for kernel use (must be NULL) */ u32 flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, @@ -40,7 +42,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (copy_from_user(data, data32, sizeof(*data32)) || get_user(data->flags, &data32->flags) || - get_user(data->time_queue, &data32->time_queue)) + get_user(data->time_queue, &data32->time_queue) || + get_user(data->direction, &data32->direction) || + get_user(data->ump_group, &data32->ump_group)) return -EFAULT; data->kernel = NULL; @@ -50,9 +54,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (err < 0) return err; - if (copy_to_user(data32, data, sizeof(*data32)) || + if (copy_to_user(data32, data, + offsetof(struct snd_seq_port_info32, kernel)) || put_user(data->flags, &data32->flags) || - put_user(data->time_queue, &data32->time_queue)) + put_user(data->time_queue, &data32->time_queue) || + put_user(data->direction, &data32->direction) || + put_user(data->ump_group, &data32->ump_group)) return -EFAULT; return err; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index eb67eb0eeb147c..b582d034437f1b 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -248,17 +248,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, } /* delete port data */ -static int port_delete(struct snd_seq_client *client, - struct snd_seq_client_port *port) +static void port_free(struct snd_seq_client *client, + struct snd_seq_client_port *port) { - /* set closing flag and wait for all port access are gone */ - port->closing = 1; - /* the port has already been unlinked from the client's port list; - * wait for a grace period so that RCU readers still traversing the - * list can no longer take a new use_lock reference, then drain the - * outstanding references before freeing - */ - synchronize_rcu(); snd_use_lock_sync(&port->use_lock); /* clear subscribers info */ @@ -272,6 +264,20 @@ static int port_delete(struct snd_seq_client *client, snd_BUG_ON(port->c_dest.count != 0); kfree(port); +} + +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + /* the port has already been unlinked from the client's port list; + * wait for a grace period so that RCU readers still traversing the + * list can no longer take a new use_lock reference, then drain the + * outstanding references before freeing + */ + synchronize_rcu(); + port_free(client, port); return 0; } @@ -302,18 +308,25 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) int snd_seq_delete_all_ports(struct snd_seq_client *client) { struct snd_seq_client_port *port, *tmp; + LIST_HEAD(deleted); - /* unlink and delete each port; port_delete() waits for an RCU grace - * period before draining the port, so concurrent lockless readers can - * no longer take a new use_lock reference on it + /* unlink all ports first and wait for a single RCU grace period + * before draining and freeing them, so concurrent lockless readers + * can no longer take a new use_lock reference on any of them */ guard(mutex)(&client->ports_mutex); list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { list_del_rcu(&port->list); client->num_ports--; + port->closing = 1; + list_add_tail(&port->delete_list, &deleted); snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); - port_delete(client, port); } + if (list_empty(&deleted)) + return 0; + synchronize_rcu(); + list_for_each_entry_safe(port, tmp, &deleted, delete_list) + port_free(client, port); return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 12ad86bf148907..ef1787d6c2dd69 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -49,6 +49,7 @@ struct snd_seq_client_port { struct module *owner; /* owner of this port */ char name[64]; /* port name */ struct list_head list; /* port list */ + struct list_head delete_list; /* used by snd_seq_delete_all_ports() */ snd_use_lock_t use_lock; /* subscribers */ diff --git a/sound/core/timer.c b/sound/core/timer.c index 69a59b25fe0a80..905176bf6948d7 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2173,6 +2173,7 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon } static const struct file_operations snd_utimer_fops = { + .owner = THIS_MODULE, .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, diff --git a/sound/core/ump.c b/sound/core/ump.c index 3d1a2ed3b476d6..f6482a9bfe6144 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -66,7 +66,7 @@ static void safe_copy_string(void *dst, size_t max_dst_size, if (!max_dst_size--) return; - for (s = src; max_dst_size && *s && max_src_size--; s++) { + for (s = src; max_dst_size && max_src_size-- && *s; s++) { if (!isascii(*s) || !isprint(*s)) continue; *d++ = *s; diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index cd3dcaaf9f720e..3d57182031d933 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -463,6 +463,7 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); /** * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @card: the card instance * @kcontrol: vmaster kctl element * @hook: the hook function * @private_data: the private_data pointer to be saved @@ -470,13 +471,17 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); * Adds the given hook to the vmaster control element so that it's called * at each time when the value is changed. * + * Takes the card's controls_rwsem lock internally. + * * Return: Zero. */ -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kcontrol, void (*hook)(void *private_data, int), void *private_data) { struct link_master *master = snd_kcontrol_chip(kcontrol); + + guard(rwsem_write)(&card->controls_rwsem); master->hook = hook; master->hook_private_data = private_data; return 0; diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 81cb1f59f703bb..0aaa64b189daef 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1073,6 +1073,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream) return !substream->stream; } +/* + * In notify mode the playback side may change format, rate, channels and, + * with a sound timer, the period size. A running capture that no longer + * matches is stopped by loopback_check_format(). + * call in loopback->cable_lock + */ +static bool loopback_playback_unconstrained(struct loopback_pcm *dpcm) +{ + return dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + get_notify(dpcm); +} + static int rule_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -1082,6 +1094,8 @@ static int rule_format(struct snd_pcm_hw_params *params, snd_mask_none(&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; m.bits[0] = (u_int32_t)cable->hw.formats; m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); } @@ -1096,6 +1110,8 @@ static int rule_rate(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; } @@ -1112,6 +1128,8 @@ static int rule_channels(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; } @@ -1128,6 +1146,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; } @@ -1355,7 +1375,7 @@ static const struct loopback_ops loopback_snd_timer_ops = { .dpcm_info = loopback_snd_timer_dpcm_info, }; -static int loopback_open(struct snd_pcm_substream *substream) +static int __loopback_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct loopback *loopback = substream->private_data; @@ -1364,7 +1384,6 @@ static int loopback_open(struct snd_pcm_substream *substream) int err = 0; int dev = get_cable_index(substream); - guard(mutex)(&loopback->cable_lock); dpcm = kzalloc_obj(*dpcm); if (!dpcm) return -ENOMEM; @@ -1459,6 +1478,14 @@ static int loopback_open(struct snd_pcm_substream *substream) return err; } +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + + guard(mutex)(&loopback->cable_lock); + return __loopback_open(substream); +} + static int loopback_close(struct snd_pcm_substream *substream) { struct loopback *loopback = substream->private_data; diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 5d5281e4deb7c1..393f132a438b34 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -280,39 +280,35 @@ static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_ru fill_block_pattern_n(v_iter, runtime); } +static void fill_random_block(char *buf, size_t buf_size, size_t pos, + size_t count) +{ + while (count) { + size_t chunk = min(count, buf_size - pos); + + get_random_bytes(buf + pos, chunk); + count -= chunk; + pos = 0; + } +} + static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { unsigned int channels = runtime->channels; - // Remaining space in all channel buffers - size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + size_t pos = v_iter->buf_pos / channels; unsigned int i; - for (i = 0; i < channels; i++) { - if (v_iter->b_rw <= bytes_remain) { - //b_rw - count of bytes must be written for all channels at each timer tick - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - v_iter->b_rw / channels); - } else { - // Write to the end of buffer and start from the beginning of it - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - bytes_remain / channels); - get_random_bytes(runtime->dma_area + v_iter->chan_block * i, - (v_iter->b_rw - bytes_remain) / channels); - } - } + for (i = 0; i < channels; i++) + fill_random_block(runtime->dma_area + v_iter->chan_block * i, + v_iter->chan_block, pos, + v_iter->b_rw / channels); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { - size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; - - if (v_iter->b_rw <= in_cur_block) { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); - } else { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); - get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); - } + fill_random_block(runtime->dma_area, runtime->dma_bytes, + v_iter->buf_pos, v_iter->b_rw); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } @@ -349,6 +345,7 @@ static void timer_timeout(struct timer_list *data) v_iter = timer_container_of(v_iter, data, timer_instance); substream = v_iter->substream; + guard(pcm_stream_lock_irqsave)(substream); if (v_iter->suspend) return; @@ -362,7 +359,7 @@ static void timer_timeout(struct timer_list *data) v_iter->period_pos += v_iter->b_rw; if (v_iter->period_pos >= v_iter->period_bytes) { v_iter->period_pos %= v_iter->period_bytes; - snd_pcm_period_elapsed(substream); + snd_pcm_period_elapsed_under_stream_lock(substream); } if (!v_iter->suspend) @@ -449,24 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr return bytes_to_frames(substream->runtime, v_iter->buf_pos); } -static int snd_pcmtst_free(struct pcmtst *pcmtst) -{ - if (!pcmtst) - return 0; - kfree(pcmtst); - return 0; -} - -// These callbacks are required, but empty - all freeing occurs in pdev_remove -static int snd_pcmtst_dev_free(struct snd_device *device) -{ - return 0; -} - -static void pcmtst_pdev_release(struct device *dev) -{ -} - static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -566,40 +545,9 @@ static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) return err; } -static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, - struct pcmtst **r_pcmtst) -{ - struct pcmtst *pcmtst; - int err; - static const struct snd_device_ops ops = { - .dev_free = snd_pcmtst_dev_free, - }; - - pcmtst = kzalloc_obj(*pcmtst); - if (!pcmtst) - return -ENOMEM; - pcmtst->card = card; - pcmtst->pdev = pdev; - - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); - if (err < 0) - goto _err_free_chip; - - err = snd_pcmtst_new_pcm(pcmtst); - if (err < 0) - goto _err_free_chip; - - *r_pcmtst = pcmtst; - return 0; - -_err_free_chip: - snd_pcmtst_free(pcmtst); - return err; -} - static int pcmtst_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct pcmtst *pcmtst; int err; @@ -607,10 +555,16 @@ static int pcmtst_probe(struct platform_device *pdev) if (err) return err; - err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, + sizeof(*pcmtst), &card); if (err < 0) return err; - err = snd_pcmtst_create(card, pdev, &pcmtst); + + pcmtst = card->private_data; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_pcmtst_new_pcm(pcmtst); if (err < 0) return err; @@ -623,27 +577,19 @@ static int pcmtst_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, pcmtst); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void pdev_remove(struct platform_device *pdev) -{ - struct pcmtst *pcmtst = platform_get_drvdata(pdev); - - snd_pcmtst_free(pcmtst); -} +#define SND_PCMTEST_DRIVER "pcmtest" -static struct platform_device pcmtst_pdev = { - .name = "pcmtest", - .dev.release = pcmtst_pdev_release, -}; +static struct platform_device *pcmtst_pdev; static struct platform_driver pcmtst_pdrv = { .probe = pcmtst_probe, - .remove = pdev_remove, .driver = { - .name = "pcmtest", + .name = SND_PCMTEST_DRIVER, }, }; @@ -755,12 +701,14 @@ static int __init mod_init(void) err = init_debug_files(buf_allocated); if (err) goto err_free_patterns; - err = platform_device_register(&pcmtst_pdev); - if (err) + pcmtst_pdev = platform_device_register_simple(SND_PCMTEST_DRIVER, -1, NULL, 0); + if (IS_ERR(pcmtst_pdev)) { + err = PTR_ERR(pcmtst_pdev); goto err_clear_debug; + } err = platform_driver_register(&pcmtst_pdrv); if (err) { - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); goto err_clear_debug; } @@ -779,7 +727,7 @@ static void __exit mod_exit(void) free_pattern_buffers(); platform_driver_unregister(&pcmtst_pdrv); - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); } MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing"); diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 0f8fb62f021dea..896091c1144ffb 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1060,6 +1061,8 @@ enum dsp_download_state { * CA0132 specific */ +#define AE5_STRIP_MAX_LEDS 100 + struct ca0132_spec { struct hda_gen_spec gen; @@ -1162,6 +1165,13 @@ struct ca0132_spec { * Renames PlayEnhancement and CrystalVoice too. */ bool use_alt_controls; + +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + /* AE-5 WS2812 strip control */ + struct mutex ae5_strip_mutex; + u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS]; + int ae5_strip_cur_num_leds; +#endif }; /* @@ -6846,6 +6856,15 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) } +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +static int ae5_add_strip_controls(struct hda_codec *codec); +#else +static inline int ae5_add_strip_controls(struct hda_codec *codec) +{ + return 0; +} +#endif + /* * Add headphone gain enumerated control for the AE-5. This switches between * three modes, low, medium, and high. When non-headphone outputs are selected, @@ -7149,6 +7168,10 @@ static int ca0132_build_controls(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_AE5: + err = ae5_add_strip_controls(codec); + if (err < 0) + return err; + fallthrough; case QUIRK_AE7: err = ae5_add_headphone_gain_enum(codec); if (err < 0) @@ -8316,8 +8339,367 @@ static void sbz_setup_defaults(struct hda_codec *codec) } /* - * Setup default parameters for the Sound BlasterX AE-5 DSP. + * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport + */ +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +#define AE5_STRIP_DMA_SIZE 0x8000 +#define AE5_STRIP_TOTAL_WORDS (AE5_STRIP_DMA_SIZE / sizeof(u32)) +#define AE5_STRIP_PREAMBLE_WORDS 5 +#define AE5_STRIP_WORDS_PER_LED 4 +#define AE5_STRIP_RESET_GAP_WORDS 83 + +#define CA0113_SERIALIZER_BASE(spec, ch) ((spec)->mem_base + 0x42c + (ch) * 0x40) +#define CA0113_SERIALIZER_CLK(spec, ch) ((spec)->mem_base + 0x43c + (ch) * 0x40) +#define CA0113_SERIALIZER_OUT(spec, ch) ((spec)->mem_base + 0x454 + (ch) * 0x40) + +static void ae5_strip_encode_24(u32 u, u32 *words) +{ + /* + * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word). + * Hardware audio serializer transmits MSB first (bits 31 down to 0). + * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word) + * is placed at n = 29 - bit_idx * 4: + * Bit 0 -> 0b100 << n (375ns high, 1042ns low): BIT(n + 2) + * Bit 1 -> 0b111 << n (1083ns high, 333ns low): BIT(n + 2) | BIT(n + 1) | BIT(n) + */ + int i8 = 23; + int i, bit_idx; + + for (i = 0; i < 4; i++) { + u32 out = 0; + + for (bit_idx = 0; bit_idx < 6; bit_idx++) { + int bit = (u >> (i8 - bit_idx)) & 1; + int n = 29 - bit_idx * 4; + + out |= (bit ? (BIT(2) | BIT(1) | BIT(0)) : BIT(2)) << n; + } + words[i] = out; + i8 -= 6; + } +} + +static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct hdac_stream *s, *dsp = NULL, *first = NULL; + + guard(spinlock_irqsave)(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction != SNDRV_PCM_STREAM_PLAYBACK) + continue; + if (s->opened || s->running || s->locked) + continue; + if (!first) + first = s; + /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */ + if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id) + dsp = s; + } + + s = dsp ? dsp : first; + if (s) + s->opened = 1; + + return s; +} + +/* + * Caller must hold spec->ae5_strip_mutex. */ +static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct snd_dma_buffer dmab; + struct hdac_stream *hstr = NULL; + unsigned int format, stream_tag; + u32 enc_words[4]; + __le32 *dst; + int word_idx = 0; + int frame_words; + int led, w, i; + int retries = 20; /* ~100ms max */ + + if (!spec || !spec->mem_base) + return -ENODEV; + if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + lockdep_assert_held(&spec->ae5_strip_mutex); + + CLASS(snd_hda_power_pm, pm)(codec); + if (pm.err < 0) + return pm.err; + + /* + * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda + * HDAUDIO_STREAM_FORMAT. + */ + format = snd_hdac_stream_format(2, 24, 44100); + + for (;;) { + hstr = ae5_strip_find_stream(codec); + if (!hstr) { + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: no free azx stream after retries\n"); + return -EBUSY; + } + usleep_range(5000, 10000); + continue; + } + + stream_tag = snd_hdac_dsp_prepare(hstr, format, AE5_STRIP_DMA_SIZE, &dmab); + if ((int)stream_tag > 0) + break; + + /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */ + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n", + (int)stream_tag); + return (int)stream_tag; + } + usleep_range(5000, 10000); + } + + /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */ + memset(dmab.area, 0, AE5_STRIP_DMA_SIZE); + dst = (__le32 *)dmab.area; + + /* Words per frame: preamble + (num_leds * words_per_led) + reset gap */ + frame_words = AE5_STRIP_PREAMBLE_WORDS + (num_leds * AE5_STRIP_WORDS_PER_LED) + + AE5_STRIP_RESET_GAP_WORDS; + + while (word_idx + frame_words <= AE5_STRIP_TOTAL_WORDS) { + /* Preamble per Windows CtxHda FUN_00042944 */ + for (w = 0; w < AE5_STRIP_PREAMBLE_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + /* LEDs */ + for (led = 0; led < num_leds; led++) { + ae5_strip_encode_24(grb_colors[led], enc_words); + for (w = 0; w < AE5_STRIP_WORDS_PER_LED; w++) { + *dst++ = cpu_to_le32(enc_words[w]); + word_idx++; + } + } + /* Zero reset gap (> 1.8 ms; WS2812 requires > 50 us low) */ + for (w = 0; w < AE5_STRIP_RESET_GAP_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + } + while (word_idx < AE5_STRIP_TOTAL_WORDS) { + *dst++ = 0; + word_idx++; + } + /* Ensure DMA buffer writes are committed before triggering the HDA stream */ + wmb(); + + /* 2. Configure CA0113 BAR2 hardware registers */ + /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ + writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); + + /* Base serializer initialization (CtxHdb 0x14ba4) */ + writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400); + for (i = 0; i < 4; i++) { + writel(readl(CA0113_SERIALIZER_BASE(spec, i)) & ~1, + CA0113_SERIALIZER_BASE(spec, i)); + writel(0, CA0113_SERIALIZER_CLK(spec, i)); + } + writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408); + writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c); + writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410); + + /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_CLK(spec, i)) | 0x33, + CA0113_SERIALIZER_CLK(spec, i)); + + /* BAR2 + 0x100 format & channel enables: + * - base 0x70f + * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58) + * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4) + * - bits 4..7 = 0xf (stage 1 enables) + * - bits 12..15 = 0xf (stage 2 enables) + * => 0x3000ff8f + */ + writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100); + + /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_OUT(spec, i)) | 1, + CA0113_SERIALIZER_OUT(spec, i)); + + /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */ + writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4), + spec->mem_base + 0x104); + + /* Enable CA0113 MMIO GPIO 0 & 1 */ + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + + codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n", + num_leds, stream_tag, readl(spec->mem_base + 0x104)); + + /* 3. Trigger stream transmission on HDA link */ + snd_hdac_dsp_trigger(hstr, true); + /* + * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64 + * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete + * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes + * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep. + */ + usleep_range(15000, 20000); + snd_hdac_dsp_trigger(hstr, false); + codec_dbg(codec, "AE5 strip: trigger complete\n"); + + /* Teardown: clear 0x104 pin mux back to 0 */ + writel(0x00000000, spec->mem_base + 0x104); + + snd_hdac_dsp_cleanup(hstr, &dmab); + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + return 0; +} + +static int ae5_strip_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AE5_STRIP_MAX_LEDS * 3; + return 0; +} + +static int ae5_strip_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u32 grb = spec->ae5_strip_cur_colors[i]; + + ucontrol->value.bytes.data[i * 3 + 0] = (grb >> 8) & 0xff; + ucontrol->value.bytes.data[i * 3 + 1] = (grb >> 16) & 0xff; + ucontrol->value.bytes.data[i * 3 + 2] = grb & 0xff; + } + return 0; +} + +static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + u32 colors[AE5_STRIP_MAX_LEDS]; + int i, num_leds, change, ret; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u8 r = ucontrol->value.bytes.data[i * 3 + 0]; + u8 g = ucontrol->value.bytes.data[i * 3 + 1]; + u8 b = ucontrol->value.bytes.data[i * 3 + 2]; + + colors[i] = ((u32)g << 16) | ((u32)r << 8) | (u32)b; + } + change = memcmp(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)) != 0; + num_leds = spec->ae5_strip_cur_num_leds; + ret = ae5_strip_send_frame(codec, colors, num_leds); + if (ret < 0) + return ret; + if (change) + memcpy(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)); + return change; +} + +static int ae5_strip_num_leds_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = AE5_STRIP_MAX_LEDS; + return 0; +} + +static int ae5_strip_num_leds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + guard(mutex)(&spec->ae5_strip_mutex); + ucontrol->value.integer.value[0] = spec->ae5_strip_cur_num_leds; + return 0; +} + +static int ae5_strip_num_leds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 1 || val > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + guard(mutex)(&spec->ae5_strip_mutex); + if (spec->ae5_strip_cur_num_leds == val) + return 0; + ret = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, val); + if (ret < 0) + return ret; + spec->ae5_strip_cur_num_leds = val; + return 1; +} + +static const struct snd_kcontrol_new ae5_strip_ctls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_ctl_info, + .get = ae5_strip_ctl_get, + .put = ae5_strip_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip Count", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_num_leds_info, + .get = ae5_strip_num_leds_get, + .put = ae5_strip_num_leds_put, + }, +}; + +static int ae5_add_strip_controls(struct hda_codec *codec) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(ae5_strip_ctls); i++) { + err = snd_hda_ctl_add(codec, 0, + snd_ctl_new1(&ae5_strip_ctls[i], codec)); + if (err < 0) + return err; + } + return 0; +} +#endif /* CONFIG_SND_HDA_DSP_LOADER */ + static void ae5_setup_defaults(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -8741,6 +9123,10 @@ static void ca0132_init_chip(struct hda_codec *codec) unsigned int on; mutex_init(&spec->chipio_mutex); +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_init(&spec->ae5_strip_mutex); + spec->ae5_strip_cur_num_leds = 10; +#endif /* * The Windows driver always does this upon startup, which seems to @@ -9626,6 +10012,9 @@ static void ca0132_free(struct hda_codec *codec) #ifdef CONFIG_PCI if (spec->mem_base) pci_iounmap(codec->bus->pci, spec->mem_base); +#endif +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_destroy(&spec->ae5_strip_mutex); #endif kfree(spec->spec_init_verbs); kfree(codec->spec); diff --git a/sound/hda/codecs/cirrus/cs8409-tables.c b/sound/hda/codecs/cirrus/cs8409-tables.c index b9ec8fb8eab7c3..d0cef8492c9941 100644 --- a/sound/hda/codecs/cirrus/cs8409-tables.c +++ b/sound/hda/codecs/cirrus/cs8409-tables.c @@ -594,7 +594,7 @@ const struct hda_quirk cs8409_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN), - SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK_DELAYED_JD), SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), @@ -625,6 +625,7 @@ const struct hda_model_fixup cs8409_models[] = { { .id = CS8409_BULLSEYE, .name = "bullseye" }, { .id = CS8409_WARLOCK, .name = "warlock" }, { .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" }, + { .id = CS8409_WARLOCK_MLK_DELAYED_JD, .name = "warlock mlk delayed jd" }, { .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" }, { .id = CS8409_CYBORG, .name = "cyborg" }, { .id = CS8409_DOLPHIN, .name = "dolphin" }, @@ -652,6 +653,12 @@ const struct hda_fixup cs8409_fixups[] = { .chained = true, .chain_id = CS8409_FIXUPS, }, + [CS8409_WARLOCK_MLK_DELAYED_JD] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, [CS8409_WARLOCK_MLK_DUAL_MIC] = { .type = HDA_FIXUP_PINS, .v.pins = cs8409_cs42l42_pincfgs, diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index c43ff3ef75b6e9..12ee71d7a35c0d 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -56,6 +56,9 @@ static int cs8409_parse_auto_config(struct hda_codec *codec) } static void cs8409_disable_i2c_clock_worker(struct work_struct *work); +static void cs8409_jack_detect_worker(struct work_struct *work); +static void cs42l42_run_jack_detect(struct sub_codec *cs42l42); +static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr); static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) { @@ -69,6 +72,7 @@ static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) codec->power_save_node = 1; mutex_init(&spec->i2c_mux); INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker); + INIT_DELAYED_WORK(&spec->jack_detect_work, cs8409_jack_detect_worker); snd_hda_gen_spec_init(&spec->gen); return spec; @@ -115,6 +119,28 @@ static void cs8409_disable_i2c_clock_worker(struct work_struct *work) cs8409_disable_i2c_clock(spec->codec); } +/* + * cs8409_jack_detect_worker - Perform initial jack detection once hardware is settled + * + * Defer initial jack detection. On ChromeOS and possibly other platforms this + * delay is required for jack detection to work as expected. + */ +static void cs8409_jack_detect_worker(struct work_struct *work) +{ + struct cs8409_spec *spec = container_of(work, struct cs8409_spec, jack_detect_work.work); + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + if (spec->init_done && spec->build_ctrl_done && !cs42l42->hp_jack_in) { + int reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + /* error case */ + if (reg_ts_status < 0) + return; + + cs42l42_run_jack_detect(cs42l42); + } +} + /* * cs8409_enable_i2c_clock - Enable I2C clocks * @codec: the codec instance @@ -951,6 +977,8 @@ static void cs8409_remove(struct hda_codec *codec) { struct cs8409_spec *spec = codec->spec; + /* Disable jack detect work */ + disable_delayed_work_sync(&spec->jack_detect_work); /* Cancel i2c clock disable timer, and disable clock if left enabled */ cancel_delayed_work_sync(&spec->i2c_clk_work); cs8409_disable_i2c_clock(codec); @@ -1020,6 +1048,9 @@ static int cs8409_cs42l42_suspend(struct hda_codec *codec) cs8409_enable_ur(codec, 0); + /* Cancel jack detect work first as it accesses CS42L42 over I2C */ + cancel_delayed_work_sync(&spec->jack_detect_work); + for (i = 0; i < spec->num_scodecs; i++) cs42l42_suspend(spec->scodecs[i]); @@ -1153,6 +1184,9 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; break; + case CS8409_WARLOCK_MLK_DELAYED_JD: + spec->delay_jack_detect = 1; + fallthrough; case CS8409_WARLOCK_MLK: case CS8409_WARLOCK_MLK_DUAL_MIC: spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; @@ -1197,8 +1231,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, cs8409_cs42l42_hw_init(codec); spec->init_done = 1; if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; case HDA_FIXUP_ACT_BUILD: spec->build_ctrl_done = 1; @@ -1208,8 +1247,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, * Run immediately after init. */ if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; default: break; diff --git a/sound/hda/codecs/cirrus/cs8409.h b/sound/hda/codecs/cirrus/cs8409.h index be1714a84fff47..dacc1083b9d463 100644 --- a/sound/hda/codecs/cirrus/cs8409.h +++ b/sound/hda/codecs/cirrus/cs8409.h @@ -247,6 +247,7 @@ enum cs8409_coefficient_index_registers { #define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A #define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN #define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1 +#define CS8409_JACK_DETECT_DELAY_MS 100 /* Dolphin */ @@ -266,6 +267,7 @@ enum { CS8409_BULLSEYE, CS8409_WARLOCK, CS8409_WARLOCK_MLK, + CS8409_WARLOCK_MLK_DELAYED_JD, CS8409_WARLOCK_MLK_DUAL_MIC, CS8409_CYBORG, CS8409_FIXUPS, @@ -339,12 +341,14 @@ struct cs8409_spec { unsigned int i2c_clck_enabled; unsigned int dev_addr; struct delayed_work i2c_clk_work; + struct delayed_work jack_detect_work; unsigned int playback_started:1; unsigned int capture_started:1; unsigned int init_done:1; unsigned int build_ctrl_done:1; unsigned int speaker_muted:1; + unsigned int delay_jack_detect:1; /* verb exec op override */ int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 91cbd728372f1c..a141c94eee13b4 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -302,6 +302,7 @@ enum { CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, CXT_FIXUP_MUTE_LED_GPIO, + CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED, CXT_FIXUP_HP_ELITEONE_OUT_DIS, CXT_FIXUP_HP_ZBOOK_MUTE_LED, CXT_FIXUP_HEADSET_MIC, @@ -809,6 +810,13 @@ static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec, cxt_setup_mute_led(codec, 0x10, 0x20); } +static void cxt_fixup_acer_aspire_go_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0, 0x10); +} + static void cxt_fixup_hp_a_u(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -1069,6 +1077,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_mute_led_gpio, }, + [CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_acer_aspire_go_mute_led, + }, [CXT_FIXUP_HP_ELITEONE_OUT_DIS] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_update_pinctl, @@ -1178,6 +1190,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP), + SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), diff --git a/sound/hda/codecs/hdmi/eld.c b/sound/hda/codecs/hdmi/eld.c index 1464fd1c675bc3..fc6fe1b0873c3e 100644 --- a/sound/hda/codecs/hdmi/eld.c +++ b/sound/hda/codecs/hdmi/eld.c @@ -155,9 +155,11 @@ void snd_hdmi_write_eld_info(struct hdmi_eld *eld, e->aud_synch_delay = val; else if (!strcmp(name, "speakers")) e->spk_alloc = val; - else if (!strcmp(name, "sad_count")) + else if (!strcmp(name, "sad_count")) { + if (val > ELD_MAX_SAD) + continue; e->sad_count = val; - else if (!strncmp(name, "sad", 3)) { + } else if (!strncmp(name, "sad", 3)) { sname = name + 4; n = name[3] - '0'; if (name[4] >= '0' && name[4] <= '9') { diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index a8aac0b36c73f9..355fd3e7318368 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -983,7 +983,8 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) snd_hda_set_dev_select(codec, pin_nid, dev_id); if (spec->intel_hsw_fixup) { - conns = spec->num_cvts; + /* spec->cvt_nids[] only has ARRAY_SIZE(spec->cvt_nids) slots */ + conns = min_t(int, spec->num_cvts, ARRAY_SIZE(spec->cvt_nids)); memcpy(per_pin->mux_nids, spec->cvt_nids, sizeof(hda_nid_t) * conns); } else { diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 053c511669de55..1a5fb30cab2d26 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2763,17 +2763,6 @@ static void alc294_fixup_bass_speaker_15(struct hda_codec *codec, } } -/* route Speaker (0x1b) to DAC 0x02, the only DAC reachable from Bass Speaker (0x14) */ -static void alc256_fixup_honor_dra_xx_share_dac(struct hda_codec *codec, - const struct hda_fixup *fix, int action) -{ - if (action == HDA_FIXUP_ACT_PRE_PROBE) { - static const hda_nid_t conn[] = { 0x02 }; - - snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn), conn); - } -} - /* Hook to update amp GPIO4 for automute */ static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec, struct hda_jack_callback *jack) @@ -2820,6 +2809,19 @@ static void alc275_fixup_gpio4_off(struct hda_codec *codec, } } +static void alc256_fixup_asus_aio_gpio2_low(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + spec->gpio_data &= ~0x04; + } +} + /* Quirk for Thinkpad X1 7th and 8th Gen * The following fixed routing needed * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly @@ -4261,6 +4263,7 @@ enum { ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC256_FIXUP_ASUS_AIO_GPIO2_LOW, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, ALC233_FIXUP_LENOVO_MULTI_CODECS, @@ -4289,6 +4292,7 @@ enum { ALC294_FIXUP_ASUS_I2C_HEADSET_MIC, ALC294_FIXUP_ASUS_SPI_HEADSET_MIC, ALC294_FIXUP_ASUS_SPK, + ALC294_FIXUP_ASUS_Q423SA, ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, ALC255_FIXUP_ACER_HEADSET_MIC, @@ -4382,11 +4386,13 @@ enum { ALC295_FIXUP_HP_OMEN, ALC285_FIXUP_HP_SPECTRE_X360, ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED, ALC623_FIXUP_LENOVO_THINKSTATION_P340, ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + ALC287_FIXUP_LEGION_S7_15IMH05_AMP, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, @@ -4436,9 +4442,12 @@ enum { ALC245_FIXUP_HP_X360_MUTE_LEDS, ALC287_FIXUP_THINKPAD_I2S_SPK, ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD, ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED, ALC256_FIXUP_HEADPHONE_AMP_VOL, ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX, @@ -4473,10 +4482,9 @@ enum { ALC245_FIXUP_BASS_HP_DAC, ALC245_FIXUP_ACER_MICMUTE_LED, ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, - ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, - ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC, + ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -4539,6 +4547,53 @@ static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); } +static const struct hda_verb legion_amp_verbs[] = { + /* left speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + /* right speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} +}; + +static void alc287_fixup_legion_s7_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + snd_hda_sequence_write(codec, legion_amp_verbs); + /* + * The amplifiers accept the setup only a few ms after the first + * write (8 ms is the measured minimum on the test machine), so + * keep a margin. + */ + msleep(20); + snd_hda_sequence_write(codec, legion_amp_verbs); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -5544,6 +5599,10 @@ static const struct hda_fixup alc269_fixups[] = { /* Set up GPIO2 for the speaker amp */ .v.func = alc_fixup_gpio4, }, + [ALC256_FIXUP_ASUS_AIO_GPIO2_LOW] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_aio_gpio2_low, + }, [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5771,6 +5830,17 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC }, + [ALC294_FIXUP_ASUS_Q423SA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Restore bit 10 lost after reboot from Windows */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b }, + { } + }, + .chained = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC295_FIXUP_CHROME_BOOK] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_chromebook, @@ -6541,6 +6611,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_ideapad_acpi, + .chained = true, + .chain_id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + }, [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_yoga_pro7_audio, @@ -6592,41 +6668,13 @@ static const struct hda_fixup alc269_fixups[] = { }, [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = { .type = HDA_FIXUP_VERBS, - //.v.verbs = legion_15imhg05_coefs, - .v.verbs = (const struct hda_verb[]) { - // set left speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - // set right speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - {} - }, + .v.verbs = legion_amp_verbs, + .chained = true, + .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + }, + [ALC287_FIXUP_LEGION_S7_15IMH05_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_s7_amp, .chained = true, .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, }, @@ -6996,6 +7044,18 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, }, + [ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + }, + [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_tb_vmaster_led, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + }, [ALC2XX_FIXUP_HEADSET_MIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc2xx_fixup_headset_mic, @@ -7010,6 +7070,13 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_acer_sfg16_micmute_led, }, + [ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, + .chained = true, + .chained_before = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC256_FIXUP_HEADPHONE_AMP_VOL] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_decrease_headphone_amp_val, @@ -7194,13 +7261,6 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, - [ALC236_FIXUP_HP_DMIC] = { - .type = HDA_FIXUP_PINS, - .v.pins = (const struct hda_pintbl[]) { - { 0x12, 0x90a60160 }, /* use as internal mic */ - { } - }, - }, [ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -7214,15 +7274,16 @@ static const struct hda_fixup alc269_fixups[] = { [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { - { 0x14, 0x90170111 }, /* bass speakers */ + { 0x14, 0x90170111 }, /* tweeters (top speakers) */ + { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */ { } }, .chained = true, - .chain_id = ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE }, - [ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC] = { + [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = { .type = HDA_FIXUP_FUNC, - .v.func = alc256_fixup_honor_dra_xx_share_dac, + .v.func = alc_fixup_headset_mode, }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, @@ -7368,7 +7429,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + /* + * Shared SSID: A315-44P needs GPIO3 micmute LED; AG15-42P needs + * the headset pin remap (BIOS leaves 0x19 as 0x411111f0). One + * entry applies both. GPIO3 is unused on AG15. + */ + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P / AG15-42P", + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), @@ -7376,6 +7443,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), @@ -7666,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), + SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), @@ -7703,7 +7772,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), @@ -7779,9 +7848,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bfd, "HP Pavilion 15-eg3xxx", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), @@ -7862,7 +7932,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), - SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC), + SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS), SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), @@ -7955,7 +8025,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), - SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS S5406SA/Q423SA", ALC294_FIXUP_ASUS_Q423SA), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), @@ -8085,6 +8155,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3500, "ASUS E5402WHA", ALC256_FIXUP_ASUS_AIO_GPIO2_LOW), SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), @@ -8345,7 +8416,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38d5, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x38d6, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), - SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_S7_15IMH05_AMP), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), @@ -8355,6 +8426,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD), + /* IdeaPad Pro 5 14IAH10 shares PCI SSID 17aa:3843 with Yoga Book 9i; + * use codec SSID to distinguish them + */ + HDA_CODEC_QUIRK(0x17aa, 0x3914, "Lenovo IdeaPad Pro 5 14IAH10", + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them @@ -8383,6 +8460,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + /* Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Lenovo Yoga + * Pro 7 14AHP9; use codec SSID to distinguish them. + */ + HDA_CODEC_QUIRK(0x17aa, 0x3875, "ThinkBook Plus G3 IAP", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C), @@ -8487,6 +8568,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1849, 0x0269, "Positivo Master C6400", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK), + SND_PCI_QUIRK(0x1849, 0x6256, "ASRock NUC Box 358H", ALC233_FIXUP_NO_AUDIO_JACK), SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1854, 0x0440, "LG CQ6", ALC256_FIXUP_HEADPHONE_AMP_VOL), SND_PCI_QUIRK(0x1854, 0x0441, "LG CQ6 AIO", ALC256_FIXUP_HEADPHONE_AMP_VOL), diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index eff7c83b9c592f..c1c7277b6754a8 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -198,6 +198,24 @@ static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec, } } +static void alc897_fixup_limit_linein_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PROBE) + return; + + snd_hda_override_amp_caps(codec, 0x1a, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + snd_hda_override_amp_caps(codec, 0x08, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x02 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + static const struct coef_fw alc668_coefs[] = { WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), @@ -326,6 +344,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN3, ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, + ALC897_FIXUP_LIMIT_LINEIN_BOOST, }; static const struct hda_fixup alc662_fixups[] = { @@ -787,6 +806,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_gpio4, }, + [ALC897_FIXUP_LIMIT_LINEIN_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_limit_linein_boost, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -857,6 +880,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e0b, 0x1297, "DECENTA MATX03", ALC897_FIXUP_LIMIT_LINEIN_BOOST), SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index db365a746b1adc..bc5226fad52bc9 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) } switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_write_coef_idx(codec, 0x45, 0xc489); snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); @@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) alc_process_coef_fw(codec, coef0225); alc_hp_enable_unmute(codec, 75); break; + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) } switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); msleep(300); diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f90f1dfcec6834..b7e2a6fa425881 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -22,6 +22,7 @@ config SND_HDA_SCODEC_AW88399_I2C depends on I2C depends on ACPI depends on SND_SOC + imply SERIAL_MULTI_INSTANTIATE select SND_HDA_SCODEC_AW88399 select SND_SOC_AW88399_LIB help diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 11cef49230242d..69d113af8ae301 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -118,7 +118,6 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) mutex_init(&core->lock); core->reset_gpio = aw88399->reset_gpio; core->regmap = aw88399->regmap; - core->bsts_unreliable = aw88399->bsts_unreliable; aw88399_hw_reset(core); @@ -155,41 +154,20 @@ static int aw88399_swap_channels(struct aw88399_hda *aw88399) return 0; } -static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) -{ - /* - * BSTS (boost-finished) status bit does not reliably report on - * some hardware. On certain Lenovo Legion laptops, both amps - * report BSTS=0 (boost not finished) during normal playback - * despite clean audio output. Skip BSTS in the startup status - * check to avoid false init failures. - */ - aw88399->bsts_unreliable = true; - dev_dbg(aw88399->dev, "BSTS status check disabled\n"); - return 0; -} - -static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) -{ - aw88399_swap_channels(aw88399); - aw88399_skip_bsts_check(aw88399); - return 0; -} - struct aw88399_prop_model { const char *ssid; int (*apply_prop)(struct aw88399_hda *aw88399); }; static const struct aw88399_prop_model aw88399_prop_model_table[] = { - { "17AA3906", aw88399_apply_legion_quirks }, - { "17AA3907", aw88399_apply_legion_quirks }, - { "17AA3927", aw88399_apply_legion_quirks }, - { "17AA3928", aw88399_apply_legion_quirks }, - { "17AA3936", aw88399_apply_legion_quirks }, - { "17AA3937", aw88399_apply_legion_quirks }, - { "17AA3938", aw88399_apply_legion_quirks }, - { "17AA3939", aw88399_apply_legion_quirks }, + { "17AA3906", aw88399_swap_channels }, + { "17AA3907", aw88399_swap_channels }, + { "17AA3927", aw88399_swap_channels }, + { "17AA3928", aw88399_swap_channels }, + { "17AA3936", aw88399_swap_channels }, + { "17AA3937", aw88399_swap_channels }, + { "17AA3938", aw88399_swap_channels }, + { "17AA3939", aw88399_swap_channels }, { } }; diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h index ca0aa5279298e0..af71083124bdc6 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.h +++ b/sound/hda/codecs/side-codecs/aw88399_hda.h @@ -19,7 +19,6 @@ struct aw88399_hda { struct gpio_desc *reset_gpio; struct aw_device *aw_dev; struct aw88399 *core; - bool bsts_unreliable; const char *acpi_subsystem_id; int index; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 62296be45c55e7..d50c3d8f5303c7 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1907,7 +1907,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, hw_cfg->valid = false; hw_cfg->gpio1.valid = false; hw_cfg->gpio2.valid = false; - acpi_dev_put(cs35l41->dacpi); return ret; } @@ -1928,10 +1927,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->dacpi = adev; struct device *physdev __free(put_device) = get_device(acpi_get_first_physical_node(adev)); - if (!physdev) { - acpi_dev_put(adev); + if (!physdev) return -ENODEV; - } sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) @@ -1986,8 +1983,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i dev_set_drvdata(dev, cs35l41); ret = cs35l41_hda_read_acpi(cs35l41, device_name, id); - if (ret) - return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + goto err_acpi; + } if (IS_ERR(cs35l41->reset_gpio)) { ret = PTR_ERR(cs35l41->reset_gpio); @@ -2074,6 +2073,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); +err_acpi: gpiod_put(cs35l41->reset_gpio); gpiod_put(cs35l41->cs_gpio); acpi_dev_put(cs35l41->dacpi); diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index c4f2bbed07480a..272421d1bc8bfd 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -126,6 +126,8 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "10433A60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "17AA3865", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3845", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3846", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, { "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, @@ -579,6 +581,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "10433A40", generic_dsd_config }, { "CSC3551", "10433A50", generic_dsd_config }, { "CSC3551", "10433A60", generic_dsd_config }, + { "CSC3551", "17AA3845", generic_dsd_config }, + { "CSC3551", "17AA3846", generic_dsd_config }, { "CSC3551", "17AA3865", generic_dsd_config }, { "CSC3551", "17AA3866", generic_dsd_config }, { "CSC3551", "17AA386E", generic_dsd_config }, diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index b22f93424c6211..e0da299df780c2 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -139,9 +139,21 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) */ crc = crc32(~0, data, 84) ^ ~0; if (crc != tmp_val[21]) { - cali_data->total_sz = 0; - dev_err(p->dev, "%s: V1 CRC error\n", __func__); - return; + /* + * Some Lenovo BIOSes (e.g. Legion Pro 7 16IRX9H, + * SSID 17aa:38cd) store valid V1 calibration data + * but leave TimeStamp and CRC zeroed. + */ + if (!tmp_val[20] && !tmp_val[21] && + tmp_val[0] && tmp_val[1]) { + dev_warn(p->dev, + "%s: V1 data without CRC, accepting\n", + __func__); + } else { + cali_data->total_sz = 0; + dev_err(p->dev, "%s: V1 CRC error\n", __func__); + return; + } } for (j = p->ndev - 1; j >= 0; j--) { diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b7e..44855f593daf80 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -335,10 +335,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N; + cd->total_sz = 0; /* extra byte for each device is the device number */ - cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev; - data = cd->data = devm_kzalloc(p->dev, cd->total_sz, + data = cd->data = devm_kzalloc(p->dev, + (cd->cali_dat_sz_per_dev + 1) * p->ndev, GFP_KERNEL); if (!data) return -ENOMEM; @@ -352,7 +353,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) if (ret < 0 || ret >= sizeof(var8) - 1) { dev_err(p->dev, "%s: Read %s failed\n", __func__, var8); - cd->total_sz = 0; return -EINVAL; } /* @@ -370,7 +370,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) dev_warn(p->dev, "Dev %d: Caldat[%d] read failed %ld\n", i, j, status); - cd->total_sz = 0; + return -EINVAL; + } + if (*(uint32_t *)&data[offset] == 0) { + dev_warn(p->dev, + "Dev %d: Caldat[%d] is zero\n", i, j); return -EINVAL; } bedata = cpu_to_be32(*(uint32_t *)&data[offset]); @@ -379,12 +383,7 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } } - if (cd->total_sz != offset) { - dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) mismatch\n", - __func__, cd->total_sz, offset); - cd->total_sz = 0; - return -EINVAL; - } + cd->total_sz = offset; r->r0_reg = TAS2563_CAL_R0; r->invr0_reg = TAS2563_CAL_INVR0; diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 6a728a77355607..957feace0f58ee 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -89,8 +89,12 @@ static int hda_codec_driver_probe(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct module *owner = dev->driver->owner; struct hda_codec_driver *driver = hda_codec_to_driver(codec); + bool probed = false; int err; + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) return -EINVAL; @@ -119,7 +123,8 @@ static int hda_codec_driver_probe(struct device *dev) err = driver->ops->probe(codec, codec->preset); if (err < 0) - goto error_module_put; + goto error_module; + probed = true; err = snd_hda_codec_build_pcms(codec); if (err < 0) goto error_module; @@ -138,7 +143,9 @@ static int hda_codec_driver_probe(struct device *dev) return 0; error_module: - if (driver->ops->remove) + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: module_put(owner); @@ -154,6 +161,10 @@ static int hda_codec_driver_remove(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct hda_codec_driver *driver = hda_codec_to_driver(codec); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) return -EINVAL; diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index c61fd79c48f3fa..31e5fad78b0f7e 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -757,11 +757,15 @@ static void codec_release_pcms(struct hda_codec *codec) */ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) { /* pm_runtime_put() is called in snd_hdac_device_exit() */ pm_runtime_get_noresume(hda_codec_dev(codec)); pm_runtime_disable(hda_codec_dev(codec)); - codec->core.registered = 0; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = false; + spin_unlock_irq(&bus->core.reg_lock); } snd_hda_codec_disconnect_pcms(codec); @@ -808,6 +812,8 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) */ void snd_hda_codec_register(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) return; if (device_is_registered(hda_codec_dev(codec))) { @@ -815,7 +821,9 @@ void snd_hda_codec_register(struct hda_codec *codec) pm_runtime_enable(hda_codec_dev(codec)); /* it was powered up in snd_hda_codec_new(), now all done */ snd_hda_power_down(codec); - codec->core.registered = 1; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = true; + spin_unlock_irq(&bus->core.reg_lock); } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); @@ -921,7 +929,6 @@ snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr, snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16); snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8); snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16); - snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16); snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); INIT_LIST_HEAD(&codec->conn_list); INIT_LIST_HEAD(&codec->pcm_list_head); @@ -2018,7 +2025,7 @@ int snd_hda_add_vmaster_hook(struct hda_codec *codec, if (!hook->hook || !hook->sw_kctl) return 0; hook->codec = codec; - snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook); + snd_ctl_add_vmaster_hook(codec->card, hook->sw_kctl, vmaster_hook, hook); return 0; } EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook); @@ -3040,6 +3047,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec) codec->jackpoll_interval = 0; /* don't poll any longer */ cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hdac_device_disable_unsol(&codec->core); list_for_each_entry(cpcm, &codec->pcm_list_head, list) snd_pcm_suspend_all(cpcm->pcm); diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h index e9b9970c59ed4a..a06b315e4b055a 100644 --- a/sound/hda/common/hda_jack.h +++ b/sound/hda/common/hda_jack.h @@ -44,6 +44,7 @@ struct hda_jack_tbl { int type; int button_state; struct snd_jack *jack; + struct hda_jack_tbl *next; }; struct hda_jack_keymap { diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c index 1d6b0f0e6f27e8..6b8aa1a6c00849 100644 --- a/sound/hda/common/jack.c +++ b/sound/hda/common/jack.c @@ -76,12 +76,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) struct hda_jack_tbl * snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid && jack->dev_id == dev_id) return jack; return NULL; @@ -98,12 +97,11 @@ struct hda_jack_tbl * snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!tag || !jack) + if (!tag) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->tag == tag && jack->dev_id == dev_id) return jack; return NULL; @@ -113,12 +111,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); static struct hda_jack_tbl * any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid) return jack; return NULL; @@ -142,12 +139,19 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) if (jack) return jack; - jack = snd_array_new(&codec->jacktbl); + jack = kzalloc_obj(*jack); if (!jack) return NULL; jack->nid = nid; jack->dev_id = dev_id; jack->jack_dirty = 1; + if (!codec->jacktbl) + codec->jacktbl = jack; + else + codec->jacktbl_last->next = jack; + codec->jacktbl_last = jack; + codec->jacktbl_used++; + if (existing_nid_jack) { jack->tag = existing_nid_jack->tag; @@ -158,7 +162,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) */ jack->jack_detect = existing_nid_jack->jack_detect; } else { - jack->tag = codec->jacktbl.used; + jack->tag = codec->jacktbl_used; } return jack; @@ -166,10 +170,9 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { if (!codec->bus->shutdown && jack->jack) snd_device_disconnect(codec->card, jack->jack); } @@ -177,22 +180,31 @@ void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) void snd_hda_jack_tbl_clear(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack, *jack_next; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack_next) { struct hda_jack_callback *cb, *next; - /* free jack instances manually when clearing/reconfiguring */ - if (!codec->bus->shutdown && jack->jack) - snd_device_free(codec->card, jack->jack); + jack_next = jack->next; + + if (jack->jack) { + /* fingers away from stale data */ + jack->jack->private_data = NULL; + jack->jack->private_free = NULL; + /* free jack instances manually when clearing/reconfiguring */ + if (!codec->bus->shutdown) + snd_device_free(codec->card, jack->jack); + } for (cb = jack->callback; cb; cb = next) { next = cb->next; kfree(cb); } + kfree(jack); } - snd_array_free(&codec->jacktbl); + + codec->jacktbl = codec->jacktbl_last = NULL; + codec->jacktbl_used = 0; } #define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) @@ -238,10 +250,9 @@ static void jack_detect_update(struct hda_codec *codec, */ void snd_hda_jack_set_dirty_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack->jack_dirty = 1; } @@ -389,12 +400,15 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, hda_nid_t gating_nid) { - struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0); - struct hda_jack_tbl *gating = - snd_hda_jack_tbl_new(codec, gating_nid, 0); + struct hda_jack_tbl *gated, *gating; WARN_ON(codec->dp_mst); + if (!snd_hda_jack_tbl_new(codec, gated_nid, 0) || + !snd_hda_jack_tbl_new(codec, gating_nid, 0)) + return -ENOMEM; + gated = snd_hda_jack_tbl_get_mst(codec, gated_nid, 0); + gating = snd_hda_jack_tbl_get_mst(codec, gating_nid, 0); if (!gated || !gating) return -EINVAL; @@ -475,19 +489,17 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state); void snd_hda_jack_report_sync(struct hda_codec *codec) { struct hda_jack_tbl *jack; - int i, state; + int state; /* update all jacks at first */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack_detect_update(codec, jack); /* report the updated jacks; it's done after updating all jacks * to make sure that all gating jacks properly have been set */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) { if (!jack->jack || jack->block_report) continue; @@ -755,10 +767,10 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); */ void snd_hda_jack_poll_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i, changes = 0; + struct hda_jack_tbl *jack; + int changes = 0; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { unsigned int old_sense; if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) continue; diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 24015c73a67f0c..6b80b59b87430d 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1184,7 +1184,7 @@ static const struct dev_pm_ops azx_pm = { }; -static int azx_probe_continue(struct azx *chip); +static int azx_probe_continue(struct azx *chip, bool free_on_error); #ifdef SUPPORT_VGA_SWITCHEROO static struct pci_dev *get_bound_vga(struct pci_dev *pci); @@ -1211,8 +1211,10 @@ static void azx_vs_set_state(struct pci_dev *pci, if (!disabled) { dev_info(chip->card->dev, "Start delayed initialization\n"); - if (azx_probe_continue(chip) < 0) + if (azx_probe_continue(chip, false) < 0) { + hda->init_failed = 1; dev_err(chip->card->dev, "initialization error\n"); + } } } else { dev_info(chip->card->dev, "%s via vga_switcheroo\n", @@ -1251,8 +1253,6 @@ static bool azx_vs_can_switch(struct pci_dev *pci) struct hda_intel *hda = container_of(chip, struct hda_intel, chip); wait_for_completion(&hda->probe_wait); - if (hda->init_failed) - return false; if (chip->disabled || !hda->probe_continued) return true; if (snd_hda_lock_devices(&chip->bus)) @@ -1380,7 +1380,6 @@ static void azx_free(struct azx *chip) * actually stop the chip) to allow GPU to suspend via * vga_switcheroo, and print a warning. */ - dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); pm_runtime_disable(&pci->dev); pm_runtime_set_suspended(&pci->dev); pm_runtime_enable(&pci->dev); @@ -1738,7 +1737,12 @@ static void azx_check_snoop_available(struct azx *chip) static void azx_probe_work(struct work_struct *work) { struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); - azx_probe_continue(&hda->chip); + + if (azx_probe_continue(&hda->chip, false) < 0) { + hda->init_failed = 1; + complete_all(&hda->probe_wait); + snd_card_disconnect(hda->chip.card); + } } static int default_bdl_pos_adj(struct azx *chip) @@ -2356,7 +2360,7 @@ static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { [AZX_DRIVER_TERA] = 1, }; -static int azx_probe_continue(struct azx *chip) +static int azx_probe_continue(struct azx *chip, bool free_on_error) { struct hda_intel *hda = container_of(chip, struct hda_intel, chip); struct hdac_bus *bus = azx_bus(chip); @@ -2447,8 +2451,12 @@ static int azx_probe_continue(struct azx *chip) out_free: if (err < 0) { - pci_set_drvdata(pci, NULL); - snd_card_free(chip->card); + if (hda->vga_switcheroo_registered) + dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); + if (free_on_error) { + pci_set_drvdata(pci, NULL); + snd_card_free(chip->card); + } return err; } @@ -2481,12 +2489,17 @@ static void azx_remove(struct pci_dev *pci) static void azx_shutdown(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); + struct hda_intel *hda; struct azx *chip; if (!card) return; chip = card->private_data; - if (chip && chip->running) + if (!chip) + return; + hda = container_of(chip, struct hda_intel, chip); + cancel_delayed_work_sync(&hda->probe_work); + if (chip->running) __azx_shutdown_chip(chip, true); } diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 20fe1c4a2977e1..02462bd035e731 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -121,11 +121,15 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, break; /* process pending verbs */ err = bus->ops->get_response(bus, addr, &tmp); - if (err) + if (err) { + snd_hdac_bus_reset_response_counter(bus, addr); break; + } } if (!err && res) { err = bus->ops->get_response(bus, addr, res); + if (err) + snd_hdac_bus_reset_response_counter(bus, addr); trace_hda_get_response(bus, addr, *res); } return err; @@ -180,17 +184,39 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) if (!(caddr & (1 << 4))) /* no unsolicited event? */ continue; codec = bus->caddr_tbl[caddr & 0x0f]; - if (!codec || !codec->registered) + if (!codec || !codec->registered || codec->unsol_disabled) + continue; + if (!codec->dev.driver) continue; - spin_unlock_irq(&bus->reg_lock); drv = drv_to_hdac_driver(codec->dev.driver); - if (drv->unsol_event) - drv->unsol_event(codec, res); + if (!drv->unsol_event) + continue; + spin_unlock_irq(&bus->reg_lock); + drv->unsol_event(codec, res); spin_lock_irq(&bus->reg_lock); } spin_unlock_irq(&bus->reg_lock); } +/** + * snd_hdac_device_disable_unsol - block and flush unsol events for the codec + * @codec: the HDA core device + * + * Stop dispatching the unsolicited events to the given codec, and wait for + * the pending unsol event handler to finish. Called at unbinding the codec + * driver before releasing the driver resources. + */ +void snd_hdac_device_disable_unsol(struct hdac_device *codec) +{ + struct hdac_bus *bus = codec->bus; + + spin_lock_irq(&bus->reg_lock); + codec->unsol_disabled = true; + spin_unlock_irq(&bus->reg_lock); + flush_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_device_disable_unsol); + /** * snd_hdac_bus_add_device - Add a codec to bus * @bus: HDA core bus diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index e8d40c24ed00ba..a7acd3418ae8a2 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -396,6 +396,25 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, } EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response); +/** + * snd_hdac_bus_reset_response_counter - reset the pending response count + * @bus: HD-audio core bus + * @addr: codec address + * + * Drop the number of commands that are still waiting for a response from + * the given codec address. This is called when a verb is known to be dead, + * e.g. after a response timeout, so that a lost response won't leave the + * counter non-zero forever and block all later verbs to the same codec. + */ +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, unsigned int addr) +{ + if (addr >= HDA_MAX_CODECS) + return; + + guard(spinlock_irq)(&bus->reg_lock); + bus->rirb.cmds[addr] = 0; +} + #define HDAC_MAX_CAPS 10 /** * snd_hdac_bus_parse_capabilities - parse capability structure diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index a45f61e12c1acf..6187ffaedbfa3f 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -572,14 +572,12 @@ EXPORT_SYMBOL_GPL(snd_hdac_power_up); /** * snd_hdac_power_down - power down the codec * @codec: the codec object - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down(struct hdac_device *codec) +void snd_hdac_power_down(struct hdac_device *codec) { struct device *dev = &codec->dev; - return pm_runtime_put_autosuspend(dev); + pm_runtime_put_autosuspend(dev); } EXPORT_SYMBOL_GPL(snd_hdac_power_down); @@ -624,14 +622,11 @@ int snd_hdac_keep_power_up(struct hdac_device *codec) * * Like snd_hdac_power_up_pm(), this function is used in a recursive * code path like init code which may be called by PM suspend/resume again. - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down_pm(struct hdac_device *codec) +void snd_hdac_power_down_pm(struct hdac_device *codec) { if (atomic_dec_if_positive(&codec->in_pm) < 0) - return snd_hdac_power_down(codec); - return 0; + snd_hdac_power_down(codec); } EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm); #endif diff --git a/sound/hda/core/local.h b/sound/hda/core/local.h index 5f03b203c4163c..b9e58395decb0a 100644 --- a/sound/hda/core/local.h +++ b/sound/hda/core/local.h @@ -16,6 +16,8 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_remove_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, + unsigned int addr); int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, unsigned int cmd, unsigned int *res); diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c index b0f1562d0fc0d1..aa31744ed582d4 100644 --- a/sound/isa/galaxy/galaxy.c +++ b/sound/isa/galaxy/galaxy.c @@ -478,11 +478,11 @@ static void snd_galaxy_free(struct snd_card *card) galaxy_set_config(galaxy, galaxy->config); } -static int __snd_galaxy_probe(struct device *dev, unsigned int n) +static int snd_galaxy_probe(struct device *dev, unsigned int n) { struct snd_galaxy *galaxy; struct snd_wss *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; u8 type; int err; @@ -595,14 +595,10 @@ static int __snd_galaxy_probe(struct device *dev, unsigned int n) return err; dev_set_drvdata(dev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_galaxy_probe(struct device *dev, unsigned int n) -{ - return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n)); -} - static struct isa_driver snd_galaxy_driver = { .match = snd_galaxy_match, .probe = snd_galaxy_probe, diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index cd3a63c7c2a7e4..f75533fe03a44e 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -572,14 +572,14 @@ static void snd_sc6000_free(struct snd_card *card) sc6000_setup_board(card->dev, sc6000->vport, 0); } -static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) +static int snd_sc6000_probe(struct device *devptr, unsigned int dev) { static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; static const int possible_dmas[] = { 1, 3, 0, -1 }; int err; int xirq = irq[dev]; int xdma = dma[dev]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; @@ -696,14 +696,10 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) return err; dev_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sc6000_probe(struct device *devptr, unsigned int dev) -{ - return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); -} - #ifdef CONFIG_PM static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, pm_message_t state) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 84f265335cf9c9..6decbaa5800e10 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -837,30 +837,24 @@ static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip) static int hal2_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_hal2 *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = hal2_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_pcm_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_mixer_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI HAL2 Audio"); strscpy(card->shortname, "SGI HAL2 Audio"); @@ -869,24 +863,16 @@ static int hal2_probe(struct platform_device *pdev) SGI_HPCDMA_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void hal2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver hal2_driver = { .probe = hal2_probe, - .remove = hal2_remove, .driver = { .name = "sgihal2", } diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 35648b0b97b908..25c1871f72c1bb 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -859,30 +859,24 @@ static int snd_sgio2audio_create(struct snd_card *card, static int snd_sgio2audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sgio2audio *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_sgio2audio_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_pcm(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_mixer(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI O2 Audio"); strscpy(card->shortname, "SGI O2 Audio"); @@ -892,24 +886,16 @@ static int snd_sgio2audio_probe(struct platform_device *pdev) MACEISA_AUDIO3_MERR_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void snd_sgio2audio_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver sgio2audio_driver = { .probe = snd_sgio2audio_probe, - .remove = snd_sgio2audio_remove, .driver = { .name = "sgio2audio", } diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c index f17e63f2ff5a67..da889570cebcf9 100644 --- a/sound/mips/snd-n64.c +++ b/sound/mips/snd-n64.c @@ -273,6 +273,15 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { .close = n64audio_pcm_close, }; +static void n64audio_free(struct snd_card *card) +{ + struct n64audio *priv = card->private_data; + + if (priv->ring_base) + dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, + priv->ring_base_dma); +} + /* * The target device is embedded and RAM-constrained. We save RAM * by initializing in __init code that gets dropped late in boot. @@ -280,14 +289,14 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { */ static int __init n64audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pcm *pcm; struct n64audio *priv; int err, irq; - err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, - SNDRV_DEFAULT_STR1, - THIS_MODULE, sizeof(*priv), &card); + err = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(*priv), &card); if (err < 0) return err; @@ -296,29 +305,24 @@ static int __init n64audio_probe(struct platform_device *pdev) spin_lock_init(&priv->chan.lock); priv->card = card; + card->private_free = n64audio_free; priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma, - GFP_DMA|GFP_KERNEL); - if (!priv->ring_base) { - err = -ENOMEM; - goto fail_card; - } + GFP_KERNEL); + if (!priv->ring_base) + return -ENOMEM; priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->mi_reg_base)) { - err = PTR_ERR(priv->mi_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->mi_reg_base)) + return PTR_ERR(priv->mi_reg_base); priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(priv->ai_reg_base)) { - err = PTR_ERR(priv->ai_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->ai_reg_base)) + return PTR_ERR(priv->ai_reg_base); err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm); if (err < 0) - goto fail_dma_alloc; + return err; pcm->private_data = priv; strscpy(pcm->name, "N64 Audio"); @@ -331,28 +335,19 @@ static int __init n64audio_probe(struct platform_device *pdev) strscpy(card->longname, "N64 Audio"); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = -EINVAL; - goto fail_dma_alloc; - } + if (irq < 0) + return -EINVAL; + if (devm_request_irq(&pdev->dev, irq, n64audio_isr, - IRQF_SHARED, "N64 Audio", priv)) { - err = -EBUSY; - goto fail_dma_alloc; - } + IRQF_SHARED, "N64 Audio", priv)) + return -EBUSY; err = snd_card_register(card); if (err < 0) - goto fail_dma_alloc; + return err; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -fail_dma_alloc: - dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma); - -fail_card: - snd_card_free(card); - return err; } static struct platform_driver n64audio_driver = { diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index a9625aedf2a477..6cdd09f5855b3b 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -897,24 +897,24 @@ static int __init snd_harmony_probe(struct parisc_device *padev) { int err; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_harmony *h; - err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_harmony_create(card, padev, &h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_pcm_init(h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_mixer_init(h); if (err < 0) - goto free_and_ret; + return err; strscpy(card->driver, "harmony"); strscpy(card->shortname, "Harmony"); @@ -923,27 +923,17 @@ snd_harmony_probe(struct parisc_device *padev) err = snd_card_register(card); if (err < 0) - goto free_and_ret; + return err; parisc_set_drvdata(padev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -free_and_ret: - snd_card_free(card); - return err; -} - -static void __exit -snd_harmony_remove(struct parisc_device *padev) -{ - snd_card_free(parisc_get_drvdata(padev)); } static struct parisc_driver snd_harmony_driver __refdata = { .name = "harmony", .id_table = snd_harmony_devtable, .probe = snd_harmony_probe, - .remove = __exit_p(snd_harmony_remove), }; static int __init diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index f4ec404c0d15a2..42a8fa3f4fdcbb 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -837,12 +837,12 @@ snd_ad1889_create(struct snd_card *card, struct pci_dev *pci) } static int -__snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { int err; static int devno; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ad1889 *chip; /* (1) */ @@ -892,17 +892,12 @@ __snd_ad1889_probe(struct pci_dev *pci, /* (7) */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ devno++; return 0; } -static int snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id)); -} - static const struct pci_device_id snd_ad1889_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) }, { 0, }, diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index f07c5f4c23801b..8295eee9b977f9 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -2086,10 +2086,10 @@ static int snd_ali_create(struct snd_card *card, return 0; } -static int __snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ali *codec; int err; @@ -2129,15 +2129,10 @@ static int __snd_ali_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id)); -} - static struct pci_driver ali5451_driver = { .name = KBUILD_MODNAME, .id_table = snd_ali_ids, diff --git a/sound/pci/als300.c b/sound/pci/als300.c index 00f0720169c3a9..5832f4d38c4eb4 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -675,7 +675,7 @@ static int snd_als300_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_als300 *chip; int err, chip_type; @@ -696,7 +696,7 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_als300_create(card, pci, chip_type); if (err < 0) - goto error; + return err; strscpy(card->driver, "ALS300"); if (chip->chip_type == DEVICE_ALS300_PLUS) @@ -711,15 +711,12 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver als300_driver = { diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c index 6396aa6c3bf071..12bb868fb9ce63 100644 --- a/sound/pci/als4000.c +++ b/sound/pci/als4000.c @@ -798,11 +798,11 @@ static void snd_card_als4000_free( struct snd_card *card ) snd_als4000_free_gameport(acard); } -static int __snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_card_als4000 *acard; unsigned long iobase; struct snd_sb *chip; @@ -918,16 +918,11 @@ static int __snd_card_als4000_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id)); -} - static int snd_als4000_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c index b738295b41e5d0..e1d36cd7945bb6 100644 --- a/sound/pci/atiixp.c +++ b/sound/pci/atiixp.c @@ -1550,10 +1550,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp *chip; int err; @@ -1598,15 +1598,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c index 8aaeb197ce4583..4984a345b4b2a7 100644 --- a/sound/pci/atiixp_modem.c +++ b/sound/pci/atiixp_modem.c @@ -1182,10 +1182,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp_modem *chip; int err; @@ -1225,15 +1225,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c index bb02945793f0c3..87a24f0c86e7c9 100644 --- a/sound/pci/au88x0/au88x0.c +++ b/sound/pci/au88x0/au88x0.c @@ -192,10 +192,10 @@ snd_vortex_create(struct snd_card *card, struct pci_dev *pci) // constructor -- see "Constructor" sub-section static int -__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; vortex_t *chip; int err; @@ -303,18 +303,13 @@ __snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) return err; // (7) pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; vortex_connect_default(chip, 1); vortex_enable_int(chip); return 0; } -static int -snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id)); -} - // pci_driver definition static struct pci_driver vortex_driver = { .name = KBUILD_MODNAME, diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 60a87322eae49d..513249b9bb7848 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -251,7 +251,7 @@ static int snd_aw2_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct aw2 *chip; int err; @@ -273,7 +273,7 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (3) Create main component */ err = snd_aw2_create(card, pci); if (err < 0) - goto error; + return err; /* initialize mutex */ mutex_init(&chip->mtx); @@ -292,17 +292,14 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (6) Register card instance */ err = snd_card_register(card); if (err < 0) - goto error; + return err; /* (7) Set PCI driver data */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* open callback */ diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index ccca82417657b5..8cf94c41a54ccb 100644 --- a/sound/pci/azt3328.c +++ b/sound/pci/azt3328.c @@ -2403,10 +2403,10 @@ snd_azf3328_create(struct snd_card *card, } static int -__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_azf3328 *chip; struct snd_opl3 *opl3; int err; @@ -2492,16 +2492,11 @@ __snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) snd_azf3328_gameport(chip, dev); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id)); -} - static inline void snd_azf3328_suspend_regs(const struct snd_azf3328 *chip, unsigned long io_addr, unsigned count, u32 *saved_regs) diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index 383def1f2af745..db8f60261a0f41 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -798,11 +798,11 @@ static int snd_bt87x_detect_card(struct pci_dev *pci) return SND_BT87X_BOARD_UNKNOWN; } -static int __snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_bt87x *chip; int err; enum snd_bt87x_boardid boardid; @@ -878,16 +878,11 @@ static int __snd_bt87x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ ++dev; return 0; } -static int snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id)); -} - /* default entries for all Bt87x cards - it's not exported */ /* driver_data is set to 0 to call detection */ static const struct pci_device_id snd_bt87x_default_ids[] = { diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index cf923f551211ee..6429c2bfc310e3 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -1710,11 +1710,11 @@ static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel) } -static int __snd_ca0106_probe(struct pci_dev *pci, +static int snd_ca0106_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ca0106 *chip; int i, err; @@ -1767,16 +1767,11 @@ static int __snd_ca0106_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_ca0106_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_ca0106_suspend(struct device *dev) { diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 9d9f784e3a8c10..d120b7bbe024e8 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -3170,7 +3170,7 @@ static int snd_cmipci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -3201,19 +3201,16 @@ static int snd_cmipci_probe(struct pci_dev *pci, err = snd_cmipci_create(card, pci, dev); if (err < 0) - goto error; + return err; err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index 27ceda6278c5e9..b831f1d50750f5 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1811,11 +1811,11 @@ static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd, udelay(30); } -static int __snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs4281 *chip; struct snd_opl3 *opl3; int err; @@ -1868,16 +1868,11 @@ static int __snd_cs4281_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id)); -} - /* * Power Management */ diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index 0cb7c2a2929fed..a4a0b36290d9d1 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -55,7 +55,7 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_cs46xx *chip; int err; @@ -74,36 +74,36 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_cs46xx_create(card, pci, external_amp[dev], thinkpad[dev]); if (err < 0) - goto error; + return err; card->private_data = chip; chip->accept_valid = mmap_valid[dev]; err = snd_cs46xx_pcm(chip, 0); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP err = snd_cs46xx_pcm_rear(chip, 1); if (err < 0) - goto error; + return err; err = snd_cs46xx_pcm_iec958(chip, 2); if (err < 0) - goto error; + return err; #endif err = snd_cs46xx_mixer(chip, 2); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP if (chip->nr_ac97_codecs ==2) { err = snd_cs46xx_pcm_center_lfe(chip, 3); if (err < 0) - goto error; + return err; } #endif err = snd_cs46xx_midi(chip, 0); if (err < 0) - goto error; + return err; err = snd_cs46xx_start_dsp(chip); if (err < 0) - goto error; + return err; snd_cs46xx_gameport(chip); @@ -117,15 +117,12 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver cs46xx_driver = { diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index 6fd4f629d1d57d..a77e6f5a14ada1 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -393,7 +393,7 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m dev_dbg(chip->card->dev, "dsp_spos: module got no code segment\n"); } else { - if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + if ((u64)ins->code.offset + code->size > DSP_CODE_BYTE_SIZE / 4) { dev_err(chip->card->dev, "dsp_spos: no space available in DSP\n"); return -ENOMEM; diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c index 0ebf6c02b1effd..e09f7bc9e23a50 100644 --- a/sound/pci/cs5535audio/cs5535audio.c +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -283,11 +283,11 @@ static int snd_cs5535audio_create(struct snd_card *card, return 0; } -static int __snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs5535audio *cs5535au; int err; @@ -329,16 +329,11 @@ static int __snd_cs5535audio_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); -} - static struct pci_driver cs5535audio_driver = { .name = KBUILD_MODNAME, .id_table = snd_cs5535audio_ids, diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 9be70c6862ab58..27e502fa493e21 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -221,14 +221,18 @@ static int dao_set_right_input(struct dao *dao, struct rsc *input) static int dao_clear_input(struct dao *dao, unsigned int start, unsigned int end) { + struct imapper *entry; unsigned int i; - if (!dao->imappers[start]) + entry = dao->imappers[start]; + if (!entry) return 0; + for (i = start; i < end; i++) { dao->mgr->imap_delete(dao->mgr, dao->imappers[i]); dao->imappers[i] = NULL; } + kfree(entry); return 0; } diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c index 36066ffe8784c9..26a6fa69f5f558 100644 --- a/sound/pci/ctxfi/cthw20k2.c +++ b/sound/pci/ctxfi/cthw20k2.c @@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) if (hw->model == CTSB1270) { /* Set up the PCM4220 ADC on Titanium HD */ + data = hw_read_20kx(hw, GPIO_DATA); data &= ~0x0C; if (1 == info->msr) data |= 0x00; /* Single Speed Mode 32-50kHz */ @@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) else data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ hw_write_20kx(hw, GPIO_DATA, data); + usleep_range(10000, 11000); } hw_adc_start(hw); diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index d8dd84d41c8762..99319d75e9631b 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -56,7 +56,7 @@ static int ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ct_atc *atc; int err; @@ -67,8 +67,8 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) dev++; return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - 0, &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); if (err) return err; if ((reference_rate != 48000) && (reference_rate != 44100)) { @@ -89,14 +89,14 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = ct_atc_create(card, pci, reference_rate, multiple, pci_id->driver_data, subsystem[dev], &atc); if (err < 0) - goto error; + return err; card->private_data = atc; /* Create alsa devices supported by this card */ err = ct_atc_create_alsa_devs(atc); if (err < 0) - goto error; + return err; strscpy(card->driver, "SB-XFi"); strscpy(card->shortname, "Creative X-Fi"); @@ -105,21 +105,13 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - -error: - snd_card_free(card); - return err; -} - -static void ct_card_remove(struct pci_dev *pci) -{ - snd_card_free(pci_get_drvdata(pci)); } #ifdef CONFIG_PM_SLEEP @@ -149,7 +141,6 @@ static struct pci_driver ct_driver = { .name = KBUILD_MODNAME, .id_table = ct_pci_dev_ids, .probe = ct_card_probe, - .remove = ct_card_remove, .driver = { .pm = CT_CARD_PM_OPS, }, diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 8b4637231debe0..4dcad05fd57e95 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1934,11 +1934,11 @@ static int snd_echo_create(struct snd_card *card, } /* constructor */ -static int __snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct echoaudio *chip; char *dsp; int err; @@ -2097,17 +2097,11 @@ static int __snd_echo_probe(struct pci_dev *pci, dev_info(card->dev, "Card registered: %s\n", card->longname); pci_set_drvdata(pci, chip); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id)); -} - - static int snd_echo_suspend(struct device *dev) { struct echoaudio *chip = dev_get_drvdata(dev); diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index ed4630c8342b43..03d277dd02d990 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1447,11 +1447,11 @@ static int snd_emu10k1x_midi(struct emu10k1x *emu) return 0; } -static int __snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct emu10k1x *chip; int err; @@ -1506,16 +1506,11 @@ static int __snd_emu10k1x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id)); -} - // PCI IDs static const struct pci_device_id snd_emu10k1x_ids[] = { { PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */ diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 0b9eb55a212181..c2af7eab633c47 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -2267,11 +2267,11 @@ static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ensoniq *ensoniq; int err; @@ -2328,16 +2328,11 @@ static int __snd_audiopci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id)); -} - static struct pci_driver ens137x_driver = { .name = KBUILD_MODNAME, .id_table = snd_audiopci_ids, diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c index 217beb9376aca8..a612368fd0b1f5 100644 --- a/sound/pci/es1938.c +++ b/sound/pci/es1938.c @@ -1683,11 +1683,11 @@ static int snd_es1938_mixer(struct es1938 *chip) } -static int __snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1938 *chip; struct snd_opl3 *opl3; int idx, err; @@ -1759,16 +1759,11 @@ static int __snd_es1938_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id)); -} - static struct pci_driver es1938_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1938_ids, diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index f04628c8cbb729..b0abbea2a99210 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -2692,11 +2692,11 @@ static int snd_es1968_create(struct snd_card *card, /* */ -static int __snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1968 *chip; unsigned int i; int err; @@ -2795,16 +2795,11 @@ static int __snd_es1968_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id)); -} - static struct pci_driver es1968_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1968_ids, diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c index 9cc96b807dd798..507ea0fe22a9b0 100644 --- a/sound/pci/fm801.c +++ b/sound/pci/fm801.c @@ -1264,11 +1264,11 @@ static int snd_fm801_create(struct snd_card *card, return 0; } -static int __snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct fm801 *chip; struct snd_opl3 *opl3; int err; @@ -1325,16 +1325,11 @@ static int __snd_card_fm801_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id)); -} - static const unsigned char saved_regs[] = { FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC, FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2, diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index d6bfd8fb20e65e..1fad8c418132c9 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info * const *tbl, *c; diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 859bb87393b413..651d9266356751 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2524,11 +2524,11 @@ static int snd_vt1724_create(struct snd_card *card, * */ -static int __snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info *c; @@ -2663,16 +2663,11 @@ static int __snd_vt1724_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_vt1724_suspend(struct device *dev) { diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index e2ea9016a73e46..339024182a26eb 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -3167,10 +3167,10 @@ static int check_default_spdif_aclink(struct pci_dev *pci) return 0; } -static int __snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0 *chip; int err; struct shortname_table *name; @@ -3244,13 +3244,9 @@ static int __snd_intel8x0_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); - return 0; -} + card = NULL; /* probe succeeded, don't release as error */ -static int snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id)); + return 0; } static struct pci_driver intel8x0_driver = { diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c index 27dbf612542783..38a15ebf94b21b 100644 --- a/sound/pci/intel8x0m.c +++ b/sound/pci/intel8x0m.c @@ -1218,10 +1218,10 @@ static struct shortname_table { { 0 }, }; -static int __snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0m *chip; int err; struct shortname_table *name; @@ -1262,15 +1262,10 @@ static int __snd_intel8x0m_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id)); -} - static struct pci_driver intel8x0m_driver = { .name = KBUILD_MODNAME, .id_table = snd_intel8x0m_ids, diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c index f4d8402f835ff6..bb442b556158be 100644 --- a/sound/pci/korg1212/korg1212.c +++ b/sound/pci/korg1212/korg1212.c @@ -2216,7 +2216,7 @@ snd_korg1212_probe(struct pci_dev *pci, { static int dev; struct snd_korg1212 *korg1212; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -2234,7 +2234,7 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_korg1212_create(card, pci); if (err < 0) - goto error; + return err; strscpy(card->driver, "korg1212"); strscpy(card->shortname, "korg1212"); @@ -2245,14 +2245,11 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver korg1212_driver = { diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c index 34a3ba17deb4b7..1f7ffca57fc92e 100644 --- a/sound/pci/lola/lola.c +++ b/sound/pci/lola/lola.c @@ -641,11 +641,11 @@ static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) return 0; } -static int __lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lola *chip; int err; @@ -687,16 +687,11 @@ static int __lola_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); -} - /* PCI IDs */ static const struct pci_device_id lola_ids[] = { { PCI_VDEVICE(DIGIGRAM, 0x0001) }, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index 3b40ec071379ea..881f462fa9291a 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -981,7 +981,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lx6464es *chip; int err; @@ -1003,7 +1003,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_lx6464es_create(card, pci); if (err < 0) { dev_err(card->dev, "error during snd_lx6464es_create\n"); - goto error; + return err; } strscpy(card->driver, "LX6464ES"); @@ -1020,16 +1020,13 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; dev_dbg(chip->card->dev, "initialization successful\n"); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver lx6464es_driver = { diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index dd45ffa171e183..2114f9f16a3460 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -2629,10 +2629,10 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, /* */ static int -__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_m3 *chip; int err; @@ -2690,16 +2690,11 @@ __snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) #endif pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id)); -} - static struct pci_driver m3_driver = { .name = KBUILD_MODNAME, .id_table = snd_m3_ids, diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c index 6b096d654f9f87..587785d5451bbe 100644 --- a/sound/pci/oxygen/oxygen_lib.c +++ b/sound/pci/oxygen/oxygen_lib.c @@ -572,7 +572,7 @@ static void oxygen_card_free(struct snd_card *card) mutex_destroy(&chip->mutex); } -static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, struct module *owner, const struct pci_device_id *ids, int (*get_model)(struct oxygen *chip, @@ -580,7 +580,7 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, ) ) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct oxygen *chip; const struct pci_device_id *pci_id; int err; @@ -695,18 +695,9 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } - -int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, - struct module *owner, - const struct pci_device_id *ids, - int (*get_model)(struct oxygen *chip, - const struct pci_device_id *id)) -{ - return snd_card_free_on_error(&pci->dev, - __oxygen_pci_probe(pci, index, id, owner, ids, get_model)); -} EXPORT_SYMBOL(oxygen_pci_probe); static int oxygen_pci_suspend(struct device *dev) diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 697fece744e7f5..0d9110558ce427 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -2020,10 +2020,10 @@ static void snd_riptide_joystick_remove(struct pci_dev *pci) #endif static int -__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_riptide *chip; unsigned short val; int err; @@ -2117,16 +2117,11 @@ __snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id)); -} - static struct pci_driver driver = { .name = KBUILD_MODNAME, .id_table = snd_riptide_ids, diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 1d5390621908e9..7ea9a71aaab9cf 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -1810,11 +1810,11 @@ static void snd_rme32_card_free(struct snd_card *card) } static int -__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; struct rme32 *rme32; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -1858,16 +1858,11 @@ __snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id)); -} - static struct pci_driver rme32_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme32_ids, diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c index 892fcc5985575d..926783f0410ef3 100644 --- a/sound/pci/rme96.c +++ b/sound/pci/rme96.c @@ -2356,12 +2356,12 @@ static void snd_rme96_card_free(struct snd_card *card) } static int -__snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; struct rme96 *rme96; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; u8 val; @@ -2420,16 +2420,11 @@ __snd_rme96_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id)); -} - static struct pci_driver rme96_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme96_ids, diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 0ade7305773183..7dce548b9f363e 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -5328,7 +5328,7 @@ static int snd_hdsp_probe(struct pci_dev *pci, { static int dev; struct hdsp *hdsp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -5349,21 +5349,18 @@ static int snd_hdsp_probe(struct pci_dev *pci, hdsp->pci = pci; err = snd_hdsp_create(card, hdsp); if (err) - goto error; + return err; strscpy(card->shortname, "Hammerfall DSP"); sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, hdsp->port, hdsp->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdsp_driver = { diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index 95fd82bb1c6d85..cc4cf43e5a5b6a 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -6796,7 +6796,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, { static int dev; struct hdspm *hdspm; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -6818,7 +6818,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_hdspm_create(card, hdspm); if (err < 0) - goto error; + return err; if (hdspm->io_type != MADIface) { snprintf(card->shortname, sizeof(card->shortname), "%s_%x", @@ -6837,16 +6837,12 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); - + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdspm_driver = { diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index 3be30cb57a2e0d..46ed6b7a2c53a5 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -2472,7 +2472,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, { static int dev; struct snd_rme9652 *rme9652; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -2494,7 +2494,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, rme9652->pci = pci; err = snd_rme9652_create(card, rme9652, precise_ptr[dev]); if (err) - goto error; + return err; strscpy(card->shortname, rme9652->card_name); @@ -2502,14 +2502,11 @@ static int snd_rme9652_probe(struct pci_dev *pci, card->shortname, rme9652->port, rme9652->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver rme9652_driver = { diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c index 55c1b11a290050..fe5bda5af6a5f5 100644 --- a/sound/pci/sis7019.c +++ b/sound/pci/sis7019.c @@ -1309,10 +1309,10 @@ static int sis_chip_create(struct snd_card *card, return 0; } -static int __snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sis7019 *sis; int rc; @@ -1361,15 +1361,10 @@ static int __snd_sis7019_probe(struct pci_dev *pci, return rc; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id)); -} - static struct pci_driver sis7019_driver = { .name = KBUILD_MODNAME, .id_table = snd_sis7019_ids, diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c index a885d544acd654..b5bb20440c6e5f 100644 --- a/sound/pci/sonicvibes.c +++ b/sound/pci/sonicvibes.c @@ -1362,11 +1362,11 @@ static int snd_sonicvibes_midi(struct sonicvibes *sonic, return 0; } -static int __snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sonicvibes *sonic; struct snd_rawmidi *midi_uart; struct snd_opl3 *opl3; @@ -1430,16 +1430,11 @@ static int __snd_sonic_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id)); -} - static struct pci_driver sonicvibes_driver = { .name = KBUILD_MODNAME, .id_table = snd_sonic_ids, diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 58816dea49963e..c13fc554c0c829 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -2428,10 +2428,10 @@ static int check_dxs_list(struct pci_dev *pci, int revision) return VIA_DXS_48K; }; -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx *chip; int chip_type = 0, card_type; unsigned int i; @@ -2536,15 +2536,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_ids, diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c index b32f84ac17cc1f..7934bc5d646387 100644 --- a/sound/pci/via82xx_modem.c +++ b/sound/pci/via82xx_modem.c @@ -1088,10 +1088,10 @@ static int snd_via82xx_create(struct snd_card *card, } -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx_modem *chip; int chip_type = 0, card_type; unsigned int i; @@ -1139,15 +1139,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_modem_ids, diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c index d3fb047c9a450a..5e8c7e318138cf 100644 --- a/sound/pci/ymfpci/ymfpci.c +++ b/sound/pci/ymfpci/ymfpci.c @@ -152,11 +152,11 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } #endif /* SUPPORT_JOYSTICK */ -static int __snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct resource *fm_res = NULL; struct resource *mpu_res = NULL; struct snd_ymfpci *chip; @@ -344,16 +344,11 @@ static int __snd_card_ymfpci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); -} - static struct pci_driver ymfpci_driver = { .name = KBUILD_MODNAME, .id_table = snd_ymfpci_ids, diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c index e685d245883ec8..30f4cd1a6d32a3 100644 --- a/sound/ppc/powermac.c +++ b/sound/ppc/powermac.c @@ -39,18 +39,18 @@ static struct platform_device *device; static int snd_pmac_probe(struct platform_device *devptr) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pmac *chip; char *name_ext; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_pmac_new(card, &chip); if (err < 0) - goto __error; + return err; card->private_data = chip; switch (chip->model) { @@ -61,7 +61,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_burgundy_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_DACA: strscpy(card->driver, "PMac DACA"); @@ -70,7 +70,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_daca_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_TUMBLER: case PMAC_SNAPPER: @@ -81,10 +81,10 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_tumbler_init(chip); if (err < 0) - goto __error; + return err; err = snd_pmac_tumbler_post_init(); if (err < 0) - goto __error; + return err; break; case PMAC_AWACS: case PMAC_SCREAMER: @@ -101,17 +101,16 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, name_ext, chip->revision); err = snd_pmac_awacs_init(chip); if (err < 0) - goto __error; + return err; break; default: dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model); - err = -EINVAL; - goto __error; + return -EINVAL; } err = snd_pmac_pcm_new(chip); if (err < 0) - goto __error; + return err; chip->initialized = 1; if (enable_beep) @@ -119,20 +118,11 @@ static int snd_pmac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto __error; + return err; platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -__error: - snd_card_free(card); - return err; -} - - -static void snd_pmac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); } #ifdef CONFIG_PM_SLEEP @@ -160,7 +150,6 @@ static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_r static struct platform_driver snd_pmac_driver = { .probe = snd_pmac_probe, - .remove = snd_pmac_remove, .driver = { .name = SND_PMAC_DRIVER, .pm = SND_PMAC_PM_OPS, diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c index 2d9a2d5b824e92..5896cce424afdf 100644 --- a/sound/sh/sh_dac_audio.c +++ b/sound/sh/sh_dac_audio.c @@ -236,12 +236,6 @@ static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device) /* END OF PCM INTERFACE */ -/* driver .remove -- destructor */ -static void snd_sh_dac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); -} - /* free -- it has been defined by create */ static int snd_sh_dac_free(struct snd_sh_dac *chip) { @@ -341,10 +335,10 @@ static int snd_sh_dac_create(struct snd_card *card, static int snd_sh_dac_probe(struct platform_device *devptr) { struct snd_sh_dac *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) { dev_err(&devptr->dev, "cannot allocate the card\n"); return err; @@ -352,11 +346,11 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_sh_dac_create(card, devptr, &chip); if (err < 0) - goto probe_error; + return err; err = snd_sh_dac_pcm(chip, 0); if (err < 0) - goto probe_error; + return err; strscpy(card->driver, "snd_sh_dac"); strscpy(card->shortname, "SuperH DAC audio driver"); @@ -364,16 +358,13 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto probe_error; + return err; dev_info(&devptr->dev, "ALSA driver for SuperH DAC audio\n"); platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -probe_error: - snd_card_free(card); - return err; } /* @@ -381,7 +372,6 @@ static int snd_sh_dac_probe(struct platform_device *devptr) */ static struct platform_driver sh_dac_driver = { .probe = snd_sh_dac_probe, - .remove = snd_sh_dac_remove, .driver = { .name = "dac_audio", }, diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index b3b73096ac74a6..30e01bc47ee0c2 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -30,6 +30,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HN7306EA"), }, }, + { + /* ASUS EXPERTBOOK AM7406CKA (Kraken Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ASUS EXPERTBOOK AM7406CKA"), + }, + }, { /* ASUS Zenbook S16 UM5606GA (Strix Point, ACP 7.0) */ .matches = { @@ -421,17 +428,29 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); -struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { + { + .id = "AMDI1010", + .drv_name = "acp7f-dsp", + .pdata = &acp_quirk_data, + .fw_filename = "sof-acp7f.ri", + .sof_tplg_filename = "sof-acp7f.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[] = { { .id = "AMDI1010", - .drv_name = "acp7x-dsp", + .drv_name = "acp7b-dsp", .pdata = &acp_quirk_data, - .fw_filename = "sof-acp7x.ri", - .sof_tplg_filename = "sof-acp7x.tplg", + .fw_filename = "sof-acp7b.ri", + .sof_tplg_filename = "sof-acp7b.tplg", }, {}, }; -EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_machines); MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index b7b7b4f4a55ca3..a62bf0fb1efccc 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW + select SND_SOC_ES9356 if SND_SOC_SDCA select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI help This option enables Legacy(No DSP) sound card support for SoundWire diff --git a/sound/soc/amd/acp/acp-mach-common.h b/sound/soc/amd/acp/acp-mach-common.h new file mode 100644 index 00000000000000..a2cb25be6bf8f8 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved + */ +/* + * acp-mach-common.h - Structures shared across all AMD ACP machine drivers + * (I2S, SoundWire, and SOF paths). + */ + +#ifndef __ACP_MACH_COMMON_H +#define __ACP_MACH_COMMON_H + +/** + * struct amd_pdm_pdata - PDM controller platform data passed via mach->pdata + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD for + * ACP7.B/7.F platforms. + * + * Passed via mach->pdata by machine select logic to both I2S and SoundWire + * machine drivers so each can configure the correct DMIC DAI link. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + +#endif /* __ACP_MACH_COMMON_H */ diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index 6b1e18b31c1c69..ba6dfe8fb6b72e 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -248,7 +248,7 @@ static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_subs list_add_tail(&stream->list, &chip->stream_list); spin_unlock_irq(&chip->acp_lock); - return ret; + return 0; } static int acp_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/acp/amd-acp63-acpi-match.c b/sound/soc/amd/acp/amd-acp63-acpi-match.c index 1dbbaba3c75b38..4f448c711c2cbd 100644 --- a/sound/soc/amd/acp/amd-acp63-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp63-acpi-match.c @@ -117,6 +117,42 @@ static const struct snd_soc_acpi_adr_device rt722_0_single_adr[] = { } }; +static const struct snd_soc_acpi_endpoint es9356_endpoints[] = { + { /* Jack Playback Endpoint */ + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* DMIC Capture Endpoint */ + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Jack Capture Endpoint */ + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Speaker Playback Endpoint */ + .num = 3, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device es9356_0_adr[] = { + { + .adr = 0x00003004b3935601ull, + .num_endpoints = ARRAY_SIZE(es9356_endpoints), + .endpoints = es9356_endpoints, + .name_prefix = "es9356" + } +}; + static const struct snd_soc_acpi_endpoint cs42l43_endpoints[] = { { /* Jack Playback Endpoint */ .num = 0, @@ -465,6 +501,15 @@ static const struct snd_soc_acpi_link_adr acp63_rt722_only[] = { {} }; +static const struct snd_soc_acpi_link_adr acp63_es9356_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(es9356_0_adr), + .adr_d = es9356_0_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { { .link_mask = BIT(0) | BIT(1), @@ -478,6 +523,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { EXPORT_SYMBOL(snd_soc_acpi_amd_acp63_sof_sdw_machines); struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sdw_machines[] = { + { + .link_mask = BIT(0), + .links = acp63_es9356_only, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0), .links = acp63_rt722_only, diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c index 2a9da6de7bbcae..c2f0479301ebd5 100644 --- a/sound/soc/amd/acp/amd-acp70-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c @@ -335,6 +335,27 @@ static const struct snd_soc_acpi_adr_device cs42l43b_l0u1_adr[] = { } }; +static const struct snd_soc_acpi_adr_device cs42l43b_l0u1_cs35l57x2_l0u01_adr[] = { + { + .adr = 0x00003101FA2A3B01ull, + .num_endpoints = ARRAY_SIZE(cs42l43_endpoints), + .endpoints = cs42l43_endpoints, + .name_prefix = "cs42l43" + }, + { + .adr = 0x00003001FA355701ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "AMP1" + }, + { + .adr = 0x00003101FA355701ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "AMP2" + }, +}; + static const struct snd_soc_acpi_adr_device cs42l43_l1u0_cs35l56x4_l1u0123_adr[] = { { .adr = 0x00013001FA424301ull, @@ -438,6 +459,15 @@ static const struct snd_soc_acpi_link_adr acp70_cs42l43_l0u0_cs35l56x4_l1u3210[] {} }; +static const struct snd_soc_acpi_link_adr acp70_cs42l43b_l0u1_cs35l57x2_l0u01[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(cs42l43b_l0u1_cs35l57x2_l0u01_adr), + .adr_d = cs42l43b_l0u1_cs35l57x2_l0u01_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr acp70_cs42l43_l1u0_cs35l56x4_l1u0123[] = { { .mask = BIT(1), @@ -820,6 +850,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { .links = acp70_cs42l45_l1u0_cs35l63x2_l0u01, .drv_name = "amd_sdw", }, + { + .link_mask = BIT(0), + .links = acp70_cs42l43b_l0u1_cs35l57x2_l0u01, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(1), .links = acp70_cs42l43_l1u0_cs35l56x4_l1u0123, diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 17e4e97fb3d1d6..034c9ab6e4ea67 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -13,6 +13,7 @@ #include #include #include +#include "acp-mach-common.h" #define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 @@ -23,19 +24,6 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 -/** - * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver - * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), - * non-zero when a PDM controller was identified via ACPI _DSD - * - * Carries the PDM controller selection for ACP7.B/7.F platforms, derived - * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata - * to the machine driver. - */ -struct amd_pdm_pdata { - unsigned int pdm_sel; -}; - #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index b602a983feb7f3..02ce044bcbf722 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,7 +28,8 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; -extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { u32 flags; diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 62cb6bef17ab98..d0cfbd4dafc177 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -294,6 +294,7 @@ struct acp63_dev_data; * struct acp_hw_ops - ACP PCI driver platform specific ops * @acp_init: ACP initialization * @acp_deinit: ACP de-initialization + * @disable_interrupts: disable ACP interrupt sources * @acp_get_config: function to read the acp pin configuration * @acp_sdw_dma_irq_thread: ACP SoundWire DMA interrupt thread * acp_suspend: ACP system level suspend callback @@ -304,6 +305,7 @@ struct acp63_dev_data; struct acp_hw_ops { int (*acp_init)(void __iomem *acp_base, struct device *dev); int (*acp_deinit)(void __iomem *acp_base, struct device *dev); + void (*disable_interrupts)(void __iomem *acp_base); void (*acp_get_config)(struct pci_dev *pci, struct acp63_dev_data *acp_data); void (*acp_sdw_dma_irq_thread)(struct acp63_dev_data *acp_data); int (*acp_suspend)(struct device *dev); @@ -397,6 +399,12 @@ static inline int acp_hw_deinit(struct acp63_dev_data *adata, struct device *dev return -EOPNOTSUPP; } +static inline void acp_hw_disable_interrupts(struct acp63_dev_data *adata) +{ + if (adata && adata->hw_ops && adata->hw_ops->disable_interrupts) + ACP_HW_OPS(adata, disable_interrupts)(adata->acp63_base); +} + static inline void acp_hw_get_config(struct pci_dev *pci, struct acp63_dev_data *adata) { if (adata && adata->hw_ops && adata->hw_ops->acp_get_config) diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 729f9aaba69e76..b604db4942dd68 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -738,6 +738,8 @@ static void snd_acp63_remove(struct pci_dev *pci) int ret; adata = pci_get_drvdata(pci); + acp_hw_disable_interrupts(adata); + devm_free_irq(&pci->dev, pci->irq, adata); if (adata->sdw) { amd_sdw_exit(adata); platform_device_unregister(adata->sdw_dma_dev); diff --git a/sound/soc/amd/ps/ps-common.c b/sound/soc/amd/ps/ps-common.c index 7b4966b75dc675..ea71cdf19c1ea1 100644 --- a/sound/soc/amd/ps/ps-common.c +++ b/sound/soc/amd/ps/ps-common.c @@ -248,6 +248,7 @@ void acp63_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp63_init; hw_ops->acp_deinit = acp63_deinit; + hw_ops->disable_interrupts = acp63_disable_interrupts; hw_ops->acp_get_config = acp63_get_config; hw_ops->acp_sdw_dma_irq_thread = acp63_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp63_suspend; @@ -484,6 +485,7 @@ void acp70_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp70_init; hw_ops->acp_deinit = acp70_deinit; + hw_ops->disable_interrupts = acp70_disable_interrupts; hw_ops->acp_get_config = acp70_get_config; hw_ops->acp_sdw_dma_irq_thread = acp70_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp70_suspend; diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 04c014349347ef..28e0686e9373d5 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -211,7 +211,7 @@ static int acp63_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp63_base = adata->acp63_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp63_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index f27ebbd213798b..553fe11a2e5099 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -337,7 +337,7 @@ static int acp63_sdw_dma_open(struct snd_soc_component *component, stream->stream_id = cpu_dai->id; stream->instance = amd_manager->instance; runtime->private_data = stream; - return ret; + return 0; } static int acp63_sdw_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 37ea5c572eb948..072f3dc35d4022 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -237,7 +237,7 @@ static int acp3x_dma_open(struct snd_soc_component *component, i2s_data->acp3x_base = adata->acp3x_base; runtime->private_data = i2s_data; - return ret; + return 0; } diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index 3f59b753243d56..c0481a9ba8586c 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -233,7 +233,7 @@ static int acp_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp_base = adata->acp_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 831e30e9b04263..33d59d992ee0c6 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -231,7 +231,7 @@ static int acp5x_dma_open(struct snd_soc_component *component, } i2s_data->acp5x_base = adata->acp5x_base; runtime->private_data = i2s_data; - return ret; + return 0; } static int acp5x_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index e798bf6a9cd065..1be8504571ad31 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -787,6 +787,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "Herbag_MDU"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "MDC"), + DMI_MATCH(DMI_BOARD_NAME, "Herbag2_MDU"), + } + }, { .driver_data = &acp6x_card, .matches = { diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 40c4d833f4ed30..afa9f66533e343 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -209,7 +209,7 @@ static int acp6x_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp6x_base = adata->acp6x_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp6x_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/yc/pci-acp6x.c b/sound/soc/amd/yc/pci-acp6x.c index 671443204e046f..bacbfc55c598b0 100644 --- a/sound/soc/amd/yc/pci-acp6x.c +++ b/sound/soc/amd/yc/pci-acp6x.c @@ -301,7 +301,7 @@ static int snd_acp6x_suspend(struct device *dev) return ret; } -static int snd_acp6x_resume(struct device *dev) +static int snd_acp6x_runtime_resume(struct device *dev) { struct acp6x_dev_data *adata; int ret; @@ -313,8 +313,42 @@ static int snd_acp6x_resume(struct device *dev) return ret; } +static void acp6x_disable_msi_on_resume(struct pci_dev *pdev) +{ + u16 control; + + if (!pdev->msi_cap) + return; + + pci_read_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, &control); + if (control & PCI_MSI_FLAGS_ENABLE) { + dev_warn(&pdev->dev, + "ACP: MSI unexpectedly enabled after resume (flags=0x%04x), disabling\n", + control); + control &= ~PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, control); + } +} + +static int snd_acp6x_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* + * BIOS/firmware may re-enable MSI in PCI config space during + * system resume even though this driver only uses legacy INTx + * interrupts. If MSI is left enabled with stale address/data + * registers, the device will write interrupts to a bogus address + * causing IOMMU IO_PAGE_FAULT and interrupt delivery failure. + * Explicitly clear the MSI Enable bit before reinitializing + * the ACP hardware. + */ + acp6x_disable_msi_on_resume(pdev); + return snd_acp6x_runtime_resume(dev); +} + static const struct dev_pm_ops acp6x_pm = { - RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL) + RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_runtime_resume, NULL) SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume) }; diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index be702b2942a53e..3585432ad49bbd 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -516,6 +516,7 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, { struct mca_cluster *cl = mca_dai_to_cluster(dai); unsigned int mask, nchannels; + int ret; if (cl->tdm_slots) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -528,9 +529,13 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, nchannels = 2; } - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - 1, nchannels); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, nchannels); + if (ret < 0) + return ret; + + return 0; } static int mca_fe_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index d9ad262ec44de2..e1d8af0562341e 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -466,11 +466,21 @@ static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); if (ret) - return ret; + goto err_disable_gclk; /* Finally enable the I2S Master Clock generator. */ - return regmap_write(dev->regmap, ATMEL_I2SC_CR, - ATMEL_I2SC_CR_CKEN); + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, ATMEL_I2SC_CR_CKEN); + if (ret) + goto err_reset_mr; + + return 0; + +err_reset_mr: + regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, 0); +err_disable_gclk: + clk_disable_unprepare(dev->gclk); + + return ret; } static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, @@ -579,21 +589,15 @@ static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, struct device_node *np) { struct clk *muxclk; - int err; if (!dev->gclk) return 0; - /* muxclk is optional, so we return error for probe defer only */ - muxclk = devm_clk_get(dev->dev, "muxclk"); - if (IS_ERR(muxclk)) { - err = PTR_ERR(muxclk); - if (err == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_dbg(dev->dev, - "failed to get the I2S clock control: %d\n", err); - return 0; - } + /* muxclk is optional */ + muxclk = devm_clk_get_optional(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) + return dev_err_probe(dev->dev, PTR_ERR(muxclk), + "failed to get the I2S clock control\n"); return clk_set_parent(muxclk, dev->gclk); } @@ -658,21 +662,15 @@ static int atmel_i2s_probe(struct platform_device *pdev) /* Get the peripheral clock. */ dev->pclk = devm_clk_get(&pdev->dev, "pclk"); - if (IS_ERR(dev->pclk)) { - err = PTR_ERR(dev->pclk); - dev_err(&pdev->dev, - "failed to get the peripheral clock: %d\n", err); - return err; - } + if (IS_ERR(dev->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->pclk), + "failed to get the peripheral clock\n"); /* Get audio clock to generate the I2S Master Clock (I2S_MCK) */ - dev->gclk = devm_clk_get(&pdev->dev, "gclk"); - if (IS_ERR(dev->gclk)) { - if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* Master Mode not supported */ - dev->gclk = NULL; - } + dev->gclk = devm_clk_get_optional(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) + return PTR_ERR(dev->gclk); + dev->dev = &pdev->dev; dev->regmap = regmap; platform_set_drvdata(pdev, dev); @@ -701,7 +699,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) &atmel_i2s_component, &atmel_i2s_dai, 1); if (err) { - dev_err(&pdev->dev, "failed to register DAI: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } @@ -716,7 +713,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); if (err) { - dev_err(&pdev->dev, "failed to register PCM: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index 374f93fa6e0b03..552bfa30d4ba4a 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -295,6 +295,8 @@ static int atmel_pcm_open(struct snd_soc_component *component, } runtime->private_data = prtd; + return 0; + out: return ret; } diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index 0830d7d2dd7c59..25d7b38c28ad82 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -798,7 +798,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) if (ret) return ret; if (adau1372->pd_gpio) - gpiod_set_value(adau1372->pd_gpio, 0); + gpiod_set_value_cansleep(adau1372->pd_gpio, 0); if (adau1372->switch_mode) adau1372->switch_mode(adau1372->dev); @@ -818,8 +818,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) ADAU1372_CLK_CTRL_PLL_EN, 0); regcache_cache_only(adau1372->regmap, true); - if (adau1372->pd_gpio) - gpiod_set_value(adau1372->pd_gpio, 1); + gpiod_set_value_cansleep(adau1372->pd_gpio, 1); clk_disable_unprepare(adau1372->mclk); return ret; } @@ -836,7 +835,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) * map. No need to do any register writes to manually * turn things off. */ - gpiod_set_value(adau1372->pd_gpio, 1); + gpiod_set_value_cansleep(adau1372->pd_gpio, 1); regcache_mark_dirty(adau1372->regmap); } else { regmap_update_bits(adau1372->regmap, ADAU1372_REG_CLK_CTRL, @@ -1015,6 +1014,17 @@ int adau1372_probe(struct device *dev, struct regmap *regmap, regmap_write(regmap, ADAU1372_REG_OP_STAGE_MUTE, 0x0); + /* + * Set sane default values for the Output ASRC and DAC input muxes, + * since the power-on reset defaults are invalid "Reserved" states. + */ + regmap_write(regmap, ADAU1372_REG_ASRCO_SOURCE_0_1, + 0x54); /* Decimator0, Decimator1 */ + regmap_write(regmap, ADAU1372_REG_ASRCO_SOURCE_2_3, + 0x76); /* Decimator2, Decimator3 */ + regmap_write(regmap, ADAU1372_REG_DAC_SOURCE, + 0xdc); /* Input ASRC0, Input ASRC1 */ + regmap_write(regmap, 0x7, 0x01); /* CLOCK OUT */ return devm_snd_soc_register_component(dev, &adau1372_driver, &adau1372_dai_driver, 1); diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index b8d06534bc8ff1..7303dc9eb4e037 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -853,13 +853,13 @@ static int aw88261_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT; - } - if ((rx_mask & ~BIT(chan)) != 0) { - if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) - return -EINVAL; + if ((rx_mask & ~BIT(chan)) != 0) { + if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) + return -EINVAL; - aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + } } return 0; @@ -1014,6 +1014,33 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol, return 0; } +static int aw88261_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = !aw88261->mute_st; + return 0; +} + +static int aw88261_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + bool mute = !ucontrol->value.integer.value[0]; + + if (aw88261->mute_st == mute) + return 0; + + aw88261->mute_st = mute; + if (aw88261->aw_pa->status == AW88261_DEV_PW_ON) + aw88261_dev_mute(aw88261->aw_pa, mute); + + return 1; +} + /* * The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB * which is too precise for TLV (!) so we have to multiply the scale by 2. @@ -1026,6 +1053,8 @@ static const struct snd_kcontrol_new aw88261_controls[] = { SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG, 6, AW88261_CTL_MAX_VOL, 1, aw88261_volume_get, aw88261_volume_set, volume_tlv), + SOC_SINGLE_EXT("Playback Switch", 0, 0, 1, 0, + aw88261_mute_get, aw88261_mute_put), AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info, aw88261_profile_get, aw88261_profile_set), }; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 809c2faa0c0c7d..75a533c9b752eb 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -167,9 +167,8 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) } EXPORT_SYMBOL_GPL(aw_dev_check_syspll); -static int aw_dev_check_sysst(struct aw88399 *aw88399) +static int aw_dev_check_sysst(struct aw_device *aw_dev) { - struct aw_device *aw_dev = aw88399->aw_pa; unsigned int check_val; unsigned int reg_val; int ret, i; @@ -184,11 +183,16 @@ static int aw_dev_check_sysst(struct aw88399 *aw88399) check_val = AW88399_BIT_SYSST_SWS_CHECK; /* - * On some hardware the BSTS (boost-finished) status bit does not - * reliably assert even when audio output is working normally. - * Allow per-instance bypass when flagged by the side-codec driver. + * When an external power supply (EPS) is used to feed the amplifier + * (EN_HVBAT bit in BSTCTRL10 set), the boost converter is bypassed, + * and BSTS (boost finished) will never assert. Skip the BSTS check in + * this case, matching the vendor driver's behavior. */ - if (aw88399->bsts_unreliable) + ret = regmap_read(aw_dev->regmap, AW88399_BSTCTRL10_REG, ®_val); + if (ret) + return ret; + + if (reg_val & (~AW88399_EPS_EN_MASK)) check_val &= ~AW88399_BSTS_FINISHED_VALUE; for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { @@ -719,7 +723,7 @@ static int aw88399_dev_start(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 50); /* check i2s status */ - ret = aw_dev_check_sysst(aw88399); + ret = aw_dev_check_sysst(aw_dev); if (ret) { dev_err(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index dda68165b8173c..eaaf141dea57e3 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1625,7 +1625,6 @@ static int cpcap_soc_probe(struct snd_soc_component *component) struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); int err; - snd_soc_component_set_drvdata(component, cpcap); cpcap->component = component; err = snd_soc_card_jack_new(card, "Headphones", diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 57982b8ae645b2..554c8121a74f25 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -53,6 +53,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, { CS35L45_SYNC_TX_RX_ENABLES, 0x00000200 }, { CS35L45_SYNC_SW_TX_ID, 0x00000000 }, + { CS35L45_BOOST_LPMODE_CFG, 0x00000002 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, { CS35L45_ASP_CONTROL1, 0x00000028 }, { CS35L45_ASP_CONTROL2, 0x18180200 }, @@ -76,6 +77,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_DSP1RX6_INPUT, 0x00000028 }, { CS35L45_DSP1RX7_INPUT, 0x0000003A }, { CS35L45_DSP1RX8_INPUT, 0x00000028 }, + { CS35L45_LDPM_CONFIG, 0x00003636 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, { CS35L45_AMP_GAIN, 0x00002300 }, { CS35L45_IRQ1_CFG, 0x00000000 }, @@ -138,6 +140,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_GLOBAL_SAMPLE_RATE: case CS35L45_SYNC_TX_RX_ENABLES: case CS35L45_SYNC_SW_TX_ID: + case CS35L45_BOOST_LPMODE_CFG: case CS35L45_ASP_ENABLES1: case CS35L45_ASP_CONTROL1: case CS35L45_ASP_CONTROL2: @@ -162,6 +165,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_DSP1RX7_INPUT: case CS35L45_DSP1RX8_INPUT: case CS35L45_HVLV_CONFIG: + case CS35L45_LDPM_CONFIG: case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_GAIN: case CS35L45_AMP_PCM_HPF_TST: @@ -334,3 +338,57 @@ int cs35l45_get_clk_freq_id(unsigned int freq) return -EINVAL; } EXPORT_SYMBOL_NS_GPL(cs35l45_get_clk_freq_id, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + s32 pcm_thld_db; +} cs35l45_ldpm_pcm_thld[] = { + { 0x0, -66 }, + { 0x1, -72 }, + { 0x2, -78 }, + { 0x3, -84 }, + { 0x4, -90 }, + { 0x5, -96 }, + { 0x6, -102 }, + { 0x7, -2147483648 }, +}; + +int cs35l45_get_ldpm_pcm_thld(s32 pcm_thld_db) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_pcm_thld); i++) { + if (cs35l45_ldpm_pcm_thld[i].pcm_thld_db == pcm_thld_db) + return cs35l45_ldpm_pcm_thld[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_pcm_thld, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + u32 delay_ms; +} cs35l45_ldpm_delay[] = { + { 0x0, 5 }, + { 0x1, 10 }, + { 0x2, 25 }, + { 0x3, 50 }, + { 0x4, 100 }, + { 0x5, 250 }, + { 0x6, 500 }, + { 0x7, 1000 }, +}; + +int cs35l45_get_ldpm_delay(u32 delay_ms) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_delay); i++) { + if (cs35l45_ldpm_delay[i].delay_ms == delay_ms) + return cs35l45_ldpm_delay[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_delay, "SND_SOC_CS35L45"); diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 9eef7c8b1837e7..064ee612f05ef8 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -1148,6 +1148,82 @@ static void cs35l45_apply_sync_property_config(struct cs35l45_private *cs35l45) CS35L45_SYNC_SW_TXID_MASK, val); } +static int cs35l45_apply_ldpm_config(struct cs35l45_private *cs35l45) +{ + char prop_name[CS35L45_LDPM_PROP_NAME_MAX]; + int i, ret; + u32 val; + + for (i = LDPM_GROUP1; i < NUM_LDPM_GROUP; i++) { + sprintf(prop_name, "cirrus,ldpm-group%d-pcm-threshold-db", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_pcm_thld((s32)val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_PCM_THLD_MASK, + ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_PCM_THLD_MASK, + ret)); + break; + default: + return -EINVAL; + } + } + + sprintf(prop_name, "cirrus,ldpm-group%d-delay-ms", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_delay(val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_DELAY_MASK, ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_DELAY_MASK, ret)); + break; + default: + return -EINVAL; + } + } + } + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-boost-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_BOOST_SEL); + + if (device_property_read_u32(cs35l45->dev, "cirrus,boost-low-power-mode", &val) == 0) + regmap_write(cs35l45->regmap, CS35L45_BOOST_LPMODE_CFG, val); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-amp-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_AMP_SEL); + + if ((device_property_read_u32(cs35l45->dev, "cirrus,amplifier-low-power-mode", &val) == 0) + && (val == 0)) + regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES, CS35L45_NFR_EN_MASK); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-vmon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_VMON_SEL); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-imon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_IMON_SEL); + + return 0; +} + static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) { struct device_node *node = cs35l45->dev->of_node; @@ -1223,6 +1299,10 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT); } + ret = cs35l45_apply_ldpm_config(cs35l45); + if (ret < 0) + return ret; + return 0; } diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index f8a4713ae92f78..5014c765adcb09 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -38,6 +38,7 @@ #define CS35L45_SYNC_TX_RX_ENABLES 0x00003400 #define CS35L45_SYNC_SW_TX_ID 0x00003408 #define CS35L45_BOOST_CCM_CFG 0x00003808 +#define CS35L45_BOOST_LPMODE_CFG 0x00003810 #define CS35L45_BOOST_DCM_CFG 0x0000380C #define CS35L45_BOOST_OV_CFG 0x0000382C #define CS35L45_ASP_ENABLES1 0x00004800 @@ -174,6 +175,8 @@ #define CS35L45_BST_EN_MASK GENMASK(5, 4) #define CS35L45_RCV_EN_SHIFT 2 #define CS35L45_RCV_EN_MASK BIT(2) +#define CS35L45_NFR_EN_SHIFT 1 +#define CS35L45_NFR_EN_MASK BIT(1) #define CS35L45_AMP_EN_SHIFT 0 #define CS35L45_AMP_EN_MASK BIT(0) @@ -240,6 +243,9 @@ #define CS35L45_SYNC_SW_TXID_SHIFT 0 #define CS35L45_SYNC_SW_TXID_MASK GENMASK(2, 0) +/* BOOST_LPMODE_CFG */ +#define CS35L45_BST_LPMODE_SEL GENMASK(1, 0) + /* ASP_ENABLES_1 */ #define CS35L45_ASP_RX2_EN_SHIFT 17 #define CS35L45_ASP_RX1_EN_SHIFT 16 @@ -303,6 +309,20 @@ #define CS35L45_HVLV_MODE_SHIFT 0 #define CS35L45_HVLV_MODE_MASK GENMASK(1, 0) +/* LDPM_CONFIG */ +#define CS35L45_LDPM_GP1_BOOST_SEL BIT(15) +#define CS35L45_LDPM_GP1_AMP_SEL BIT(14) +#define CS35L45_LDPM_GP1_DELAY_SHIFT 11 +#define CS35L45_LDPM_GP1_DELAY_MASK GENMASK(13, 11) +#define CS35L45_LDPM_GP1_PCM_THLD_SHIFT 8 +#define CS35L45_LDPM_GP1_PCM_THLD_MASK GENMASK(10, 8) +#define CS35L45_LDPM_GP2_IMON_SEL BIT(7) +#define CS35L45_LDPM_GP2_VMON_SEL BIT(6) +#define CS35L45_LDPM_GP2_DELAY_SHIFT 3 +#define CS35L45_LDPM_GP2_DELAY_MASK GENMASK(5, 3) +#define CS35L45_LDPM_GP2_PCM_THLD_SHIFT 0 +#define CS35L45_LDPM_GP2_PCM_THLD_MASK GENMASK(2, 0) + /* AMP_PCM_CONTROL */ #define CS35L45_AMP_VOL_PCM_SHIFT 0 #define CS35L45_AMP_VOL_PCM_WIDTH 11 @@ -448,6 +468,12 @@ enum amp_mode { AMP_MODE_RCV = 1, }; +enum ldpm_groups { + LDPM_GROUP1 = 1, + LDPM_GROUP2 = 2, + NUM_LDPM_GROUP, +}; + #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE| \ SNDRV_PCM_FMTBIT_S24_LE) @@ -457,6 +483,8 @@ enum amp_mode { SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000) +#define CS35L45_LDPM_PROP_NAME_MAX 37 + /* * IRQs */ @@ -531,6 +559,8 @@ extern const struct regmap_config cs35l45_i2c_regmap; extern const struct regmap_config cs35l45_spi_regmap; int cs35l45_apply_patch(struct cs35l45_private *cs35l45); int cs35l45_get_clk_freq_id(unsigned int freq); +int cs35l45_get_ldpm_pcm_thld(int pcm_thld_db); +int cs35l45_get_ldpm_delay(u32 delay_ms); int cs35l45_probe(struct cs35l45_private *cs35l45); void cs35l45_remove(struct cs35l45_private *cs35l45); diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 98bb4542b9143f..e10f04d677f4d6 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -265,11 +265,11 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) if (ret == 0) prop->clk_stop_mode1 = !!clock_stop_1; - ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL); + ports = devm_kcalloc(cs35l56->base.dev, 3, sizeof(*ports), GFP_KERNEL); if (!ports) return -ENOMEM; - prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT) | BIT(CS35L56_OT25_CAPTURE_PORT); prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); prop->paging_support = true; prop->use_domain_irq = true; @@ -286,6 +286,11 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) ports[1].num = CS35L56_SDW1_CAPTURE_PORT; ports[1].type = SDW_DPN_FULL; ports[1].ch_prep_timeout = 10; + + /* DP4 - capture */ + ports[2].num = CS35L56_OT25_CAPTURE_PORT; + ports[2].type = SDW_DPN_FULL; + ports[2].ch_prep_timeout = 10; prop->src_dpn_prop = &ports[1]; dev_dbg(&peripheral->dev, "clock stop mode 1 supported: %s\n", diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 74129fcc79dce3..dd264c310e1eba 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -324,6 +325,7 @@ static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = { SND_SOC_DAPM_SIGGEN("VDDBMON ADC"), SND_SOC_DAPM_SIGGEN("VBSTMON ADC"), SND_SOC_DAPM_SIGGEN("TEMPMON ADC"), + SND_SOC_DAPM_SIGGEN("OT25 Reference"), SND_SOC_DAPM_INPUT("Calibrate"), }; @@ -388,6 +390,8 @@ static const struct snd_soc_dapm_route cs35l56_audio_map[] = { { "SDW1 Capture", NULL, "SDW1 TX2 Source" }, { "SDW1 Capture", NULL, "SDW1 TX3 Source" }, { "SDW1 Capture", NULL, "SDW1 TX4 Source" }, + + { "OT25", NULL, "OT25 Reference" }, }; static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, @@ -722,6 +726,37 @@ static int cs35l56_sdw_dai_set_stream(struct snd_soc_dai *dai, return 0; } +static int cs35l56_ot25_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component); + struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + struct sdw_stream_config sconfig = { }; + struct sdw_port_config pconfig = { }; + int ret; + + dev_dbg(cs35l56->base.dev, "%s: rate %d\n", __func__, params_rate(params)); + + if (!cs35l56->base.init_done) + return -ENODEV; + + if (!sdw_stream) + return -EINVAL; + + snd_sdw_params_to_config(substream, params, &sconfig, &pconfig); + pconfig.num = CS35L56_OT25_CAPTURE_PORT; + + ret = sdw_stream_add_slave(cs35l56->sdw_peripheral, &sconfig, &pconfig, + 1, sdw_stream); + if (ret) { + dev_err(dai->dev, "Failed to add OT25 stream: %d\n", ret); + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = { .set_tdm_slot = cs35l56_sdw_dai_set_tdm_slot, .shutdown = cs35l56_sdw_dai_shutdown, @@ -730,6 +765,13 @@ static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = { .set_stream = cs35l56_sdw_dai_set_stream, }; +static const struct snd_soc_dai_ops cs35l56_ot25_dai_ops = { + .shutdown = cs35l56_sdw_dai_shutdown, + .hw_params = cs35l56_ot25_dai_hw_params, + .hw_free = cs35l56_sdw_dai_hw_free, + .set_stream = cs35l56_sdw_dai_set_stream, +}; + static struct snd_soc_dai_driver cs35l56_dai[] = { { .name = "cs35l56-asp1", @@ -778,6 +820,18 @@ static struct snd_soc_dai_driver cs35l56_dai[] = { .symmetric_rate = 1, .ops = &cs35l56_sdw_dai_ops, }, + { + .name = "cs35l56-ot25", + .id = 3, + .capture = { + .stream_name = "OT25", + .channels_min = 1, + .channels_max = 2, + .rates = CS35L56_RATES, + .formats = CS35L56_TX_FORMATS, + }, + .ops = &cs35l56_ot25_dai_ops, + }, }; static int cs35l56_write_cal(struct cs35l56_private *cs35l56) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index b5d69d5516a664..f6e4bf6206b7b0 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -890,6 +890,7 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s { struct snd_soc_component *component = dai->component; struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + int ret; /* * Sample rates < 44.1 kHz would produce an out-of-range SCLK with @@ -900,9 +901,13 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s return 0; /* Machine driver has not set a SCLK, limit bottom end to 44.1 kHz */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 44100, 96000); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 44100, 96000); + if (ret < 0) + return ret; + + return 0; } static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index 5f033556099a20..1d3334886402a8 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -290,7 +290,7 @@ static const char * const cs42l52_ng_type_text[] = { }; static SOC_ENUM_SINGLE_DECL(ng_type_enum, - CS42L52_NOISE_GATE_CTL, 6, + CS42L52_NOISE_GATE_CTL, 7, cs42l52_ng_type_text); static const char * const left_swap_text[] = { diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index ad45e8fdb39375..54d23857b0ad6f 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -481,7 +481,7 @@ static const struct snd_kcontrol_new cs42l56_snd_controls[] = { 0, 0x3f, 0, 0), SOC_SINGLE_TLV("Limiter MAX", CS42L56_LIM_THRESHOLD_CTL, 5, 0x07, 1, alc_tlv), - SOC_SINGLE_TLV("Limiter Cushion", CS42L56_ALC_THRESHOLD, + SOC_SINGLE_TLV("Limiter Cushion", CS42L56_LIM_THRESHOLD_CTL, 2, 0x07, 1, alc_tlv), SOC_SINGLE("NG Switch", CS42L56_NOISE_GATE_CTL, 6, 1, 1), diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 923b997efbc43d..4990feaa8259c9 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -2091,16 +2091,6 @@ static int da7213_probe(struct snd_soc_component *component) pm_runtime_put_sync(component->dev); - /* Check if MCLK provided */ - da7213->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7213->mclk)) - return PTR_ERR(da7213->mclk); - if (da7213->mclk) - /* Do automatic PLL handling assuming fixed clock until - * set_pll() has been called. This makes the codec usable - * with the simple-audio-card driver. */ - da7213->fixed_clk_auto_pll = true; - /* Default infinite tone gen, start/stop by Kcontrol */ snd_soc_component_write(component, DA7213_TONE_GEN_CYCLES, DA7213_BEEP_CYCLES_MASK); @@ -2229,6 +2219,19 @@ static int da7213_i2c_probe(struct i2c_client *i2c) mutex_init(&da7213->ctrl_lock); + /* Check if MCLK provided */ + da7213->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7213->mclk)) + return dev_err_probe(&i2c->dev, PTR_ERR(da7213->mclk), + "failed to get mclk\n"); + if (da7213->mclk) + /* + * Do automatic PLL handling assuming fixed clock until + * set_pll() has been called. This makes the codec usable + * with the simple-audio-card driver. + */ + da7213->fixed_clk_auto_pll = true; + pm_runtime_set_autosuspend_delay(&i2c->dev, 100); pm_runtime_use_autosuspend(&i2c->dev); pm_runtime_set_active(&i2c->dev); diff --git a/sound/soc/codecs/es8375.c b/sound/soc/codecs/es8375.c index 80707d2768c67e..bed872eafc955d 100644 --- a/sound/soc/codecs/es8375.c +++ b/sound/soc/codecs/es8375.c @@ -677,7 +677,7 @@ static int es8375_read_device_properities(struct device *dev, struct es8375_priv return ret; } - es8375->mclk = devm_clk_get(dev, "mclk"); + es8375->mclk = devm_clk_get_optional(dev, "mclk"); if (IS_ERR(es8375->mclk)) return dev_err_probe(dev, PTR_ERR(es8375->mclk), "unable to get mclk\n"); diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c index 80db0f2f4aef62..b8b3ffc13fd591 100644 --- a/sound/soc/codecs/es9356.c +++ b/sound/soc/codecs/es9356.c @@ -26,6 +26,20 @@ #include #include "es9356.h" +/* + * The DAI stream capabilities were never filled in. snd_soc_runtime_calc_hw() + * intersects the capabilities of the CPU and codec DAIs to derive hw->rates + * and hw->formats, so an empty mask on the codec side leaves the PCM with no + * usable configuration and soc_hw_sanity_check() fails every open with -EINVAL + * ("No matching rates"). es9356_sdw_pcm_hw_params() already programs 16 kHz, + * 44.1 kHz, 48 kHz and 96 kHz, so advertise exactly that set, with the formats + * used by the other SDCA codecs in the tree. + */ +#define ES9356_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define ES9356_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + struct es9356_sdw_priv { struct sdw_slave *slave; struct device *dev; @@ -617,6 +631,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_DMIC, .capture = { .stream_name = "DP1 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -627,6 +643,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_IN, .capture = { .stream_name = "DP2 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -637,6 +655,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_AMP, .playback = { .stream_name = "DP3 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -647,6 +667,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_OUT, .playback = { .stream_name = "DP4 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index 084659106d5d95..c481ef2dd789c0 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -217,6 +217,9 @@ static int hda_codec_probe(struct snd_soc_component *component) goto err; } + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + ret = driver->ops->probe(codec, codec->preset); if (ret < 0) { dev_err(&hdev->dev, "codec init failed: %d\n", ret); @@ -275,6 +278,10 @@ static void hda_codec_remove(struct snd_soc_component *component) /* Don't allow any more runtime suspends */ pm_runtime_forbid(&hdev->dev); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(hdev); + cancel_delayed_work_sync(&codec->jackpoll_work); + hda_codec_unregister_dais(codec, component); if (driver->ops->remove) diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 7aa50c5bd3df8a..0e1e532dfc6340 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -475,8 +475,12 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, if (ret) return ret; - snd_parse_eld(dai->dev, &hcp->eld_parsed, - hcp->eld, sizeof(hcp->eld)); + /* An empty ELD means no sink is connected; nothing to parse. */ + if (hcp->eld[0]) + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + else + memset(&hcp->eld_parsed, 0, sizeof(hcp->eld_parsed)); ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); if (ret) diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index b62bd98f2252a2..72bcdb0d1289e1 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -166,7 +166,7 @@ static const struct snd_kcontrol_new max98371_snd_controls[] = { 0, (1<jd_workqueue); + destroy_workqueue(priv->accdet_workqueue); +} + static int mt6359_accdet_probe(struct platform_device *pdev) { struct mt6359_accdet *priv; @@ -955,6 +963,26 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } mutex_init(&priv->res_lock); + priv->accdet_workqueue = create_singlethread_workqueue("accdet"); + INIT_WORK(&priv->accdet_work, mt6359_accdet_work); + if (!priv->accdet_workqueue) { + dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); + return -ENOMEM; + } + + priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); + INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); + if (!priv->jd_workqueue) { + dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); + destroy_workqueue(priv->accdet_workqueue); + return -ENOMEM; + } + + ret = devm_add_action_or_reset(&pdev->dev, + mt6359_accdet_destroy_workqueues, priv); + if (ret) + return ret; + priv->accdet_irq = platform_get_irq(pdev, 0); if (priv->accdet_irq >= 0) { ret = devm_request_threaded_irq(&pdev->dev, priv->accdet_irq, @@ -1000,22 +1028,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } } - priv->accdet_workqueue = create_singlethread_workqueue("accdet"); - INIT_WORK(&priv->accdet_work, mt6359_accdet_work); - if (!priv->accdet_workqueue) { - dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); - ret = -1; - goto err_accdet_wq; - } - - priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); - INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); - if (!priv->jd_workqueue) { - dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); - ret = -1; - goto err_eint_wq; - } - platform_set_drvdata(pdev, priv); ret = devm_snd_soc_register_component(&pdev->dev, &mt6359_accdet_soc_driver, @@ -1034,12 +1046,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) mt6359_accdet_jack_report(priv); return 0; - -err_eint_wq: - destroy_workqueue(priv->accdet_workqueue); -err_accdet_wq: - dev_err(&pdev->dev, "%s error. now exit.!\n", __func__); - return ret; } static struct platform_driver mt6359_accdet_driver = { diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 2c95ed00a681f9..0902a7d7730d65 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -489,14 +489,19 @@ static int nau8325_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8325 *nau8325 = snd_soc_component_get_drvdata(component); const struct nau8325_osr_attr *osr; + int ret; osr = nau8325_get_osr(nau8325); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8325_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index d4211a76b096dc..9484388ab262aa 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -419,14 +419,19 @@ static int nau8540_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); const struct nau8540_osr_attr *osr; + int ret; osr = nau8540_get_osr(nau8540); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_ADC_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_ADC_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8540_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index ecadbaee103798..935df063822c4c 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -829,14 +829,19 @@ static int nau8821_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); const struct nau8821_osr_attr *osr; + int ret; osr = nau8821_get_osr(nau8821, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8821_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index db1931d7b90183..d91a0bed202527 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -383,9 +383,9 @@ static const struct snd_kcontrol_new nau8822_snd_controls[] = { NAU8822_REG_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv), SOC_SINGLE("DAC 128x Oversampling Switch", - NAU8822_REG_DAC_CONTROL, 5, 1, 0), + NAU8822_REG_DAC_CONTROL, 3, 1, 0), SOC_SINGLE("ADC 128x Oversampling Switch", - NAU8822_REG_ADC_CONTROL, 5, 1, 0), + NAU8822_REG_ADC_CONTROL, 3, 1, 0), }; /* LMAIN and RMAIN Mixer */ diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index 853eb9472af92d..c89889aa420e62 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1061,14 +1061,19 @@ static int nau8824_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); const struct nau8824_osr_attr *osr; + int ret; osr = nau8824_get_osr(nau8824, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8824_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 63d927f3140902..10bbfbb460b19b 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -1261,14 +1261,19 @@ static int nau8825_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); const struct nau8825_osr_attr *osr; + int ret; osr = nau8825_get_osr(nau8825, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8825_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index cb6a6f08f2f80a..6f45ea4869a75d 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -103,7 +103,7 @@ static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF, static const char *const pcm3168a_pol[] = { "Active High", "Active Low" }; static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF, - PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol); + PCM3168A_DAC_ZREV_SHIFT, pcm3168a_pol); static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" }; diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c index abed31ba660c8f..a3d41cd8150d05 100644 --- a/sound/soc/codecs/rk3308_codec.c +++ b/sound/soc/codecs/rk3308_codec.c @@ -192,6 +192,90 @@ static int rk3308_codec_pop_sound_set(struct snd_soc_dapm_widget *w, return 0; } +static int rk3308_codec_dac_output_vcm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Keep the DAC outputs at VCM while their reference is charged. */ + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + for (val = 1; val <= 0xf; val++) { + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, val); + usleep_range(200, 400); + } + msleep(20); + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_INTERNAL | + RK3308_DAC_R_SEL_DC_FROM_INTERNAL); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + break; + default: + break; + } + + return 0; +} + +static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int mask = RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << w->shift; + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL << w->shift; + break; + case SND_SOC_DAPM_POST_PMD: + val = RK3308_DAC_L_SEL_DC_FROM_INTERNAL << w->shift; + break; + default: + return 0; + } + + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, mask, val); + usleep_range(20, 40); + + return 0; +} + static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), @@ -322,6 +406,10 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUT_DRV_E("HPOUT_POP_SOUND_R", SND_SOC_NOPM, 4, 0, NULL, 0, rk3308_codec_pop_sound_set, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("DAC_OUTPUT_VCM", SND_SOC_NOPM, 0, 0, + rk3308_codec_dac_output_vcm_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUT_DRV("L_HPOUT_EN", RK3308_DAC_ANA_CON03, 1, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("R_HPOUT_EN", RK3308_DAC_ANA_CON03, 5, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("L_HPOUT_WORK", RK3308_DAC_ANA_CON03, 2, 0, NULL, 0), @@ -329,8 +417,12 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HPOUT_L"), SND_SOC_DAPM_OUTPUT("HPOUT_R"), - SND_SOC_DAPM_OUT_DRV("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, NULL, 0), - SND_SOC_DAPM_OUT_DRV("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("LINEOUT_L"), SND_SOC_DAPM_OUTPUT("LINEOUT_R"), }; @@ -466,6 +558,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "HPOUT_R", NULL, "DAC_BUF_REF_R" }, { "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "L_HPOUT_WORK", NULL, "L_HPOUT_EN" }, { "R_HPOUT_WORK", NULL, "R_HPOUT_EN" }, { "HPOUT_POP_SOUND_L", NULL, "L_HPOUT_WORK" }, @@ -475,6 +569,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "LINEOUT_L", NULL, "L_LINEOUT_EN" }, { "LINEOUT_R", NULL, "R_LINEOUT_EN" }, }; @@ -866,7 +962,7 @@ static int rk3308_codec_get_version(struct rk3308_codec_priv *rk3308) break; case 0x3308: rk3308->codec_ver = ACODEC_VERSION_B; - return dev_err_probe(rk3308->dev, -EINVAL, "Chip version B not supported\n"); + break; case 0x3308c: rk3308->codec_ver = ACODEC_VERSION_C; break; diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 15d21097ed0c2e..63e3719059ead9 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -4045,26 +4045,16 @@ static int rt5645_i2c_probe(struct i2c_client *i2c) rt5645->gpiod_hp_det = devm_gpiod_get_optional(&i2c->dev, "hp-detect", GPIOD_IN); - if (IS_ERR(rt5645->gpiod_hp_det)) { - dev_info(&i2c->dev, "failed to initialize gpiod\n"); - ret = PTR_ERR(rt5645->gpiod_hp_det); - /* - * Continue if optional gpiod is missing, bail for all other - * errors, including -EPROBE_DEFER - */ - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_hp_det)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_hp_det), + "failed to get hp-detect gpio\n"); rt5645->gpiod_cbj_sleeve = devm_gpiod_get_optional(&i2c->dev, "cbj-sleeve", GPIOD_OUT_LOW); - if (IS_ERR(rt5645->gpiod_cbj_sleeve)) { - ret = PTR_ERR(rt5645->gpiod_cbj_sleeve); - dev_info(&i2c->dev, "failed to initialize gpiod, ret=%d\n", ret); - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_cbj_sleeve)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_cbj_sleeve), + "failed to get cbj-sleeve gpio\n"); for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++) rt5645->supplies[i].supply = rt5645_supply_names[i]; diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 389be7cbfe05b4..af8051fa69f892 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -4121,10 +4121,14 @@ static int rt5659_i2c_probe(struct i2c_client *i2c) rt5659->gpiod_ldo1_en = devm_gpiod_get_optional(&i2c->dev, "ldo1-en", GPIOD_OUT_HIGH); if (IS_ERR(rt5659->gpiod_ldo1_en)) - dev_warn(&i2c->dev, "Request ldo1-en GPIO failed\n"); + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_ldo1_en), + "failed to get ldo1-en GPIO\n"); rt5659->gpiod_reset = devm_gpiod_get_optional(&i2c->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(rt5659->gpiod_reset)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_reset), + "failed to get reset GPIO\n"); /* Sleep for 300 ms minimum */ msleep(300); diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c index 23c7d2a9dbeeb3..5f2897c58ad7d6 100644 --- a/sound/soc/codecs/rt5682-sdw.c +++ b/sound/soc/codecs/rt5682-sdw.c @@ -704,8 +704,7 @@ static void rt5682_sdw_remove(struct sdw_slave *slave) { struct rt5682_priv *rt5682 = dev_get_drvdata(&slave->dev); - if (rt5682->hw_init) - cancel_delayed_work_sync(&rt5682->jack_detect_work); + cancel_delayed_work_sync(&rt5682->jack_detect_work); pm_runtime_disable(&slave->dev); } diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 0f80e4c32f573b..49ed7793d3a205 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -3318,8 +3318,8 @@ static void rt5682s_i2c_shutdown(struct i2c_client *client) struct rt5682s_priv *rt5682s = i2c_get_clientdata(client); disable_irq(client->irq); - cancel_delayed_work_sync(&rt5682s->jack_detect_work); - cancel_delayed_work_sync(&rt5682s->jd_check_work); + disable_delayed_work_sync(&rt5682s->jack_detect_work); + disable_delayed_work_sync(&rt5682s->jd_check_work); rt5682s_reset(rt5682s); } diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c index e292b28b029e94..f74b193c720d4f 100644 --- a/sound/soc/codecs/rt711-sdca-sdw.c +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -369,10 +369,8 @@ static void rt711_sdca_sdw_remove(struct sdw_slave *slave) { struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c index 4ad2b1da195444..798d181d5c9020 100644 --- a/sound/soc/codecs/rt711-sdw.c +++ b/sound/soc/codecs/rt711-sdw.c @@ -461,11 +461,9 @@ static void rt711_sdw_remove(struct sdw_slave *slave) { struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - cancel_work_sync(&rt711->calibration_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); + cancel_work_sync(&rt711->calibration_work); pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index f2f91dd087bf72..0f0447d1a05ba1 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -386,10 +386,8 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) { struct rt712_sdca_priv *rt712 = dev_get_drvdata(&slave->dev); - if (rt712->hw_init) { - cancel_delayed_work_sync(&rt712->jack_detect_work); - cancel_delayed_work_sync(&rt712->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt712->jack_detect_work); + cancel_delayed_work_sync(&rt712->jack_btn_check_work); pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index a8c7b42898dbbf..fc34a0537d5fb4 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -76,9 +76,16 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re { switch (reg) { case 0x0900004 ... 0x0900009: + case 0x0910000: + case 0x0910001: + case 0x0910002: + case 0x0910003: + case 0x0910202: case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: + case 0x0f00005: case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: @@ -87,18 +94,25 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x200003c: case 0x2000046: case 0x5810000: + case 0x5810033: case 0x5810036: case 0x5810037: case 0x5810038: case 0x5810039: + case 0x581003a: + case 0x581003b: case 0x5810100: - case 0x5b10018: - case 0x5b10019: + case 0x5b10000 ... 0x5b10026: + case 0x5b10100 ... 0x5b10103: + case 0x5f00010: + case 0x5f00043: case 0x5f00045: case 0x5f00048: case 0x6100000: + case 0x6100002: case 0x6100005: case 0x6100006: + case 0x6100007: case 0x610000d: case 0x6100010: case 0x6100011: @@ -152,6 +166,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: case 0x2000000: case 0x200000d: case 0x2000019: @@ -168,9 +183,11 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x581003a: + case 0x581003b: case 0x5810100: - case 0x5b10018: - case 0x5b10019: + case 0x5b10000 ... 0x5b10016: + case 0x5b10018 ... 0x5b10026: case 0x6100006: return true; default: @@ -318,6 +335,11 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, int count = 0, retry = 3; unsigned int sdca_cascade, scp_sdca_stat1, scp_sdca_stat2 = 0; + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + dev_info(&slave->dev, + "%s: IRQ arrived DURING impedance sensing\n", + __func__); + if (cancel_delayed_work_sync(&rt721->jack_detect_work)) { dev_warn(&slave->dev, "%s the pending delayed_work was cancelled", __func__); /* avoid the HID owner doesn't change to device */ @@ -395,9 +417,15 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT1); ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT2); - if (status->sdca_cascade && !rt721->disable_irq) + if (status->sdca_cascade && !rt721->disable_irq) { + unsigned int delay_ms = 280; + + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + delay_ms = 800; + mod_delayed_work(system_power_efficient_wq, - &rt721->jack_detect_work, msecs_to_jiffies(280)); + &rt721->jack_detect_work, msecs_to_jiffies(delay_ms)); + } mutex_unlock(&rt721->disable_irq_lock); @@ -436,10 +464,8 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(&slave->dev); - if (rt721->hw_init) { - cancel_delayed_work_sync(&rt721->jack_detect_work); - cancel_delayed_work_sync(&rt721->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt721->jack_detect_work); + cancel_delayed_work_sync(&rt721->jack_btn_check_work); if (rt721->first_hw_init) pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt721-sdca-sdw.h b/sound/soc/codecs/rt721-sdca-sdw.h index 13d6227f3392aa..fe04a37263344b 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.h +++ b/sound/soc/codecs/rt721-sdca-sdw.h @@ -80,18 +80,25 @@ static const struct reg_default rt721_sdca_reg_defaults[] = { }; static const struct reg_default rt721_sdca_mbq_defaults[] = { + { 0x0900006, 0xe000 }, { 0x0900007, 0xc004 }, + { 0x0910000, 0x00a1 }, + { 0x0910001, 0x1256 }, + { 0x0910002, 0x0000 }, + { 0x0910003, 0x0000 }, { 0x2000001, 0x0000 }, { 0x2000002, 0x0000 }, { 0x2000003, 0x0000 }, { 0x2000013, 0x8001 }, { 0x200003c, 0x0000 }, { 0x2000046, 0x3400 }, + { 0x5b10017, 0x1b07 }, { 0x5f00044, 0x6040 }, { 0x5f00045, 0x3333 }, { 0x5f00048, 0x0000 }, { 0x6100005, 0x0005 }, { 0x6100006, 0x0000 }, + { 0x6100007, 0x0007 }, { 0x610000d, 0x0051 }, { 0x6100010, 0x0180 }, { 0x6100011, 0x0000 }, diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index ef003c93f533d0..5fc0aa991914d7 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -27,11 +27,86 @@ #include "rt721-sdca.h" #include "rt-sdw-common.h" +static void rt721_uaj_power_on(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL10, 0xfcb5, 0xfcb5); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON12, 0xc000, 0x4000); +} + +static void rt721_impedance_sensing(struct rt721_sdca_priv *rt721) +{ + unsigned int read_imp; + int i, loop_cnt = 25; + unsigned long cal_T; + bool spk = false; + + rt721->imp_sensing_ongoing = true; + + rt721_uaj_power_on(rt721); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0x0003, 0x0003); + + cal_T = jiffies + msecs_to_jiffies(200); + + for (i = 0; i < loop_cnt; i++) { + regmap_read(rt721->mbq_regmap, RT721_IMP_SEN_CTRL13, &read_imp); + read_imp = (read_imp >> 12) & 0xf; + dev_dbg(&rt721->slave->dev, "%s, loop:%d, read_imp=0x%x\n", + __func__, i, read_imp); + + if (read_imp == 0x6 || read_imp == 0x7 || + read_imp == 0x8 || read_imp == 0x9) { + spk = true; + break; + } + + if (read_imp != 0 || i == loop_cnt - 1 || + time_after(jiffies, cal_T)) + break; + + usleep_range(20000, 21000); + } + + if (spk) { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8c00); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } else { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8000); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0048); + rt721->int_cnt = 1; +} + +static void rt721_sdca_jack_reset_state(struct rt721_sdca_priv *rt721) +{ + rt721->jack_type = 0; + rt721->imp_sensing_done = false; + rt721->last_real_JD = 0; + rt721->int_cnt = 0; +} + static void rt721_sdca_jack_detect_handler(struct work_struct *work) { struct rt721_sdca_priv *rt721 = container_of(work, struct rt721_sdca_priv, jack_detect_work.work); + unsigned int sta_mode = 0; int btn_type = 0; + int retry, retry_loop = 40; + int real_JD; if (!rt721->hs_jack) return; @@ -39,12 +114,132 @@ static void rt721_sdca_jack_detect_handler(struct work_struct *work) if (!rt721->component->card || !rt721->component->card->instantiated) return; - /* SDW_SCP_SDCA_INT_SDCA_6 is used for jack detection */ - if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { - rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, - RT721_SDCA_ENT_GE49); - if (rt721->jack_type < 0) + if (rt721->wf_id == RT721_S) { + if (rt721->imp_sensing_ongoing) { + dev_dbg(&rt721->slave->dev, + "SKIP, imp_sensing_ongoing\n"); + return; + } + + rt_sdca_index_read(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_STA1, &real_JD); + real_JD &= 0x1; + if (real_JD == 0x0) + rt721_sdca_jack_reset_state(rt721); + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == 0x1 && rt721->int_cnt == 0 && + !rt721->imp_sensing_done && rt721->jack_type != 0) { + unsigned int cur_mode = 0; + int cur_type = -1; + + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &cur_mode); + if (cur_mode) + break; + usleep_range(20000, 21000); + } + + switch (cur_mode) { + case 0x03: + cur_type = SND_JACK_HEADPHONE; + break; + case 0x05: + cur_type = SND_JACK_HEADSET; + break; + default: + cur_type = -1; + break; + } + + dev_info(&rt721->slave->dev, + "%s: resume check, cur_mode=0x%x cur_type=%d retained jack_type=%d\n", + __func__, cur_mode, cur_type, rt721->jack_type); + + if (cur_mode != 0 && cur_type == rt721->jack_type) { + dev_info(&rt721->slave->dev, + "%s: resume with same jack, reuse jack_type=%d\n", + __func__, rt721->jack_type); + rt721->imp_sensing_done = true; + rt721->last_real_JD = real_JD; + snd_soc_jack_report(rt721->hs_jack, rt721->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + return; + } + + dev_info(&rt721->slave->dev, + "%s: resume jack changed/unsettled, redetect\n", __func__); + rt721->jack_type = 0; + } + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == rt721->last_real_JD && real_JD == 0x1 && + rt721->int_cnt == 0 && rt721->imp_sensing_done && + rt721->jack_type != 0) { + dev_info(&rt721->slave->dev, + "%s: state settled (JD=%d), skip redetect\n", + __func__, real_JD); + rt721->last_real_JD = real_JD; return; + } + rt721->last_real_JD = real_JD; + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + rt721->int_cnt == 0 && real_JD == 0x1 && + !rt721->imp_sensing_done) { + dev_info(&rt721->slave->dev, + "%s: BRANCH impedance_sensing (SDCA_0 set, fresh pass)\n", + __func__); + rt721->imp_sensing_done = true; + rt721_impedance_sensing(rt721); + rt721->imp_sensing_ongoing = false; + return; + } + + /* SDW_SCP_SDCA_INT_SDCA_0 is used for jack detection */ + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + (rt721->int_cnt == 1 || real_JD == 0x0)) { + if (real_JD == 0x1) { + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &sta_mode); + if (sta_mode) + break; + usleep_range(20000, 21000); + } + dev_dbg(&rt721->slave->dev, + "%s: detected_mode settled=0x%x, %d retries\n", + __func__, sta_mode, retry); + } + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + rt721->int_cnt = 0; + if (rt721->jack_type < 0) + return; + } + dev_info(&rt721->slave->dev, + "%s: handler run stat1=0x%x stat2=0x%x (SDCA_8=%d)\n", + __func__, rt721->scp_sdca_stat1, rt721->scp_sdca_stat2, + !!(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8)); + } else { + if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + if (rt721->jack_type < 0) + return; + } } /* SDW_SCP_SDCA_INT_SDCA_8 is used for button detection */ @@ -114,8 +309,12 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) /* Report ID for HID1 */ if (buf[0] == 0x11) btn_type = rt_sdca_btn_type(&buf[1]); - } else - rt721->jack_type = 0; + } else { + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_reset_state(rt721); + else + rt721->jack_type = 0; + } dev_dbg(&rt721->slave->dev, "%s, btn_type=0x%x\n", __func__, btn_type); snd_soc_jack_report(rt721->hs_jack, rt721->jack_type | btn_type, @@ -217,11 +416,6 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); - if (rt721->wf_id == RT721_S) { - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); - } rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL6, 0x5561); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, @@ -253,6 +447,9 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) { + unsigned int calib_val, calib_val2, calib_tmp = 0, calib_tmp2 = 0; + unsigned int i, calib_loop = 10; + unsigned int wait_calib_loop = 10; unsigned int jack_func_status; struct device *dev = &rt721->slave->dev; @@ -268,6 +465,10 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0xae05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_GE_REL_CTRL1, 0x8011); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -279,37 +480,30 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_UMP_HID_CTRL5, 0x0c12); rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_CTRL2, 0xc002); + RT721_JD_1PIN_GAT_CTRL2, 0xc004); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b40); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3333); - regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); - regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL1, 0x0140); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + RT721_UAJ_TOP_TCON14, 0x333b); rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, RT721_HP_AMP_2CH_CAL18, 0x5522); - regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); - regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, - RT721_CBJ_A0_GAT_CTRL1, 0x2205); rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL4, 0xa105); + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa107); + regmap_write(rt721->mbq_regmap, 0x5B10017, 0x180f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL27, 0x4400); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3b33); + RT721_UAJ_TOP_TCON14, 0x3b3b); regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3f33); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON13, 0x6048); + RT721_UAJ_TOP_TCON14, 0x3f3b); regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); - regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000c); regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, @@ -322,6 +516,12 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_MBIAS_LV_CTRL2, 0x6677); rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, RT721_VREF2_LV_CTRL1, 0x7600); + regmap_write(rt721->mbq_regmap, 0x0910000, 0x00a1); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, + RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL2, 0x1234); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -330,16 +530,99 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_ENT_FLOAT_CTL1, 0x4040); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL4, 0x1201); - rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, - RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); - regmap_write(rt721->regmap, 0x2f58, 0x07); - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); - /* clear flag */ + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x0910001, 0x1256); + regmap_write(rt721->mbq_regmap, 0x0910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x0910003, 0x0000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_TOP_CTRL3, 0x7788); + regmap_update_bits(rt721->mbq_regmap, RT721_ADC_BIAS_CTRL3, + 0x0004, 0x0004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_MIC_BIAS_CTRL, + RT721_MIC_BIAS_TOP_CTRL2, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CHARGE_PUMP_CTRL, + RT721_CHGPMP_GAT_CTRL1, 0x0000); + for (i = 0; i < wait_calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL2, 0xd000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON_ANY, 0xc820); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x0540); + regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); + regmap_write(rt721->mbq_regmap, 0x5810033, 0x6000); + regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0020); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + for (i = 0; i < calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + regmap_write(rt721->regmap, 0x40400188, 0x0); + regmap_write(rt721->regmap, 0x40400688, 0x0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(6), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(5), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(1), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(0), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(4), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(3), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(2), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(1), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(0), 0); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x1c00, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL1, 0xc000, 0xc000); + regmap_update_bits(rt721->mbq_regmap, RT721_IMP_SEN_CTRL11, + 0x001c, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0x0007, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x000f, 0x0003); regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + msleep(500); } } @@ -1704,6 +1987,8 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) int val; rt721->disable_irq = false; + rt721->imp_sensing_done = false; + rt721->last_real_JD = -1; if (rt721->hw_init) return 0; diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index 16cf40cd88de3f..cc3bce0ba51786 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -29,6 +29,10 @@ struct rt721_sdca_priv { /* For Headset jack & Headphone */ unsigned int scp_sdca_stat1; unsigned int scp_sdca_stat2; + unsigned int int_cnt; + bool imp_sensing_ongoing; + bool imp_sensing_done; + int last_real_JD; struct snd_soc_jack *hs_jack; struct delayed_work jack_detect_work; struct delayed_work jack_btn_check_work; @@ -54,8 +58,10 @@ struct rt721_sdca_dmic_kctrl_priv { /* NID */ #define RT721_ANA_POW_PART 0x01 #define RT721_DAC_CTRL 0x04 +#define RT721_MIC_BIAS_CTRL 0x08 #define RT721_JD_CTRL 0x09 #define RT721_CBJ_CTRL 0x0a +#define RT721_CHARGE_PUMP_CTRL 0x0b #define RT721_CAP_PORT_CTRL 0x0c #define RT721_CLASD_AMP_CTRL 0x0d #define RT721_BOOST_CTRL 0x0f @@ -81,17 +87,28 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_DAC_2CH_CTRL3 0x02 #define RT721_DAC_2CH_CTRL4 0x03 +/* Index (NID:08h) */ +#define RT721_MIC_BIAS_TOP_CTRL2 0x02 + /* Index (NID:09h) */ +#define RT721_JD_1PIN_TOP_CTRL3 0x04 +#define RT721_JD_1PIN_GAT_CTRL1 0x06 #define RT721_JD_1PIN_GAT_CTRL2 0x07 +#define RT721_JD_1PIN_GAT_STA1 0x09 /* Index (NID:0ah) */ #define RT721_CBJ_A0_GAT_CTRL1 0x04 #define RT721_CBJ_A0_GAT_CTRL2 0x05 +/* Index (NID:0bh) */ +#define RT721_CHGPMP_GAT_CTRL1 0x01 + /* Index (NID:0Ch) */ #define RT721_HP_AMP_2CH_CAL1 0x05 +#define RT721_HP_AMP_2CH_CAL2 0x06 #define RT721_HP_AMP_2CH_CAL4 0x08 #define RT721_HP_AMP_2CH_CAL18 0x1b +#define RT721_HP_AMP_2CH_CAL27 0x24 /* Index (NID:0dh) */ #define RT721_CLASD_AMP_2CH_CAL 0x14 @@ -129,10 +146,13 @@ struct rt721_sdca_dmic_kctrl_priv { /* Index (NID:5fh) */ #define RT721_MISC_POWER_CTL0 0x00 +#define RT721_MISC_POWER_CTL10 0x10 #define RT721_MISC_POWER_CTL31 0x31 +#define RT721_UAJ_TOP_TCON12 0x43 #define RT721_UAJ_TOP_TCON13 0x44 #define RT721_UAJ_TOP_TCON14 0x45 #define RT721_UAJ_TOP_TCON17 0x48 +#define RT721_UAJ_TOP_TCON_ANY 0x70 /* Index (NID:61h) */ #define RT721_HDA_LEGACY_MUX_CTL0 0x00 @@ -181,6 +201,13 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_BUF_ADDR_HID1 0x44030000 #define RT721_BUF_ADDR_HID2 0x44030020 +/* Impedance Sensing Control */ +#define RT721_IMP_SEN_CTRL11 0x5b10017 +#define RT721_IMP_SEN_CTRL13 0x5b1001b + +/* ADC Bias Control 3 */ +#define RT721_ADC_BIAS_CTRL3 0x0910202 + /* RT721 SDCA Control - function number */ #define FUNC_NUM_JACK_CODEC 0x01 #define FUNC_NUM_MIC_ARRAY 0x02 diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c index 5bc9aed58d14df..078255fbe1fc3c 100644 --- a/sound/soc/codecs/rt722-sdca-sdw.c +++ b/sound/soc/codecs/rt722-sdca-sdw.c @@ -471,10 +471,8 @@ static void rt722_sdca_sdw_remove(struct sdw_slave *slave) { struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); - if (rt722->hw_init) { - cancel_delayed_work_sync(&rt722->jack_detect_work); - cancel_delayed_work_sync(&rt722->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt722->jack_detect_work); + cancel_delayed_work_sync(&rt722->jack_btn_check_work); if (rt722->first_hw_init) pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 28b4118e05c95d..2e891b99dc2721 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -176,8 +176,11 @@ static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) if (!rt766->hs_jack) return IRQ_HANDLED; - if (!rt766->component->card || !rt766->component->card->instantiated) + if (!rt766->component->card || !rt766->component->card->instantiated) { + /* trigger again if card is not instantiated */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); return IRQ_HANDLED; + } mutex_lock(&rt766->disable_irq_lock); if (!rt766->disable_irq) @@ -304,6 +307,9 @@ static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, if (rt766->hid_func_data) rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, rt766->component, rt766->irq_info, false); + } else { + /* trigger GE interrupt */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); } pm_runtime_put_autosuspend(component->dev); diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h index de4064007eb9b0..61af5e9c077afe 100644 --- a/sound/soc/codecs/rt766-sdca.h +++ b/sound/soc/codecs/rt766-sdca.h @@ -50,6 +50,7 @@ struct rt766_sdca_priv { #define RT766_DEV_ID1 0xc405 #define RT766_DEV_ID0 0xc406 #define RT766_BOND_LATCH_ID 0xc407 +#define RT766_GE_DET_IRQ_MNL 0xd203 #define RT766_HP_POWER_STATE 0x1000004 #define RT766_HP_FSM_CTL2_1 0x100000d diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index e8ceeb027e1baf..f309a57337b004 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -648,7 +648,7 @@ static int fw_parse_configuration_data_kernel( */ if (strnstr(config->name, "calib", 64)) { tas_fmw->calibration_config_id = i; - dev_dbg(tas_priv->dev, "%s: calib cofig = %d\n", + dev_dbg(tas_priv->dev, "%s: calib config = %d\n", __func__, tas_fmw->calibration_config_id); } /*skip extra 16 bytes*/ @@ -1438,7 +1438,7 @@ static int fw_parse_configuration_data( */ if (strnstr(config->name, "calib", 64)) { tas_fmw->calibration_config_id = i; - dev_dbg(tas_priv->dev, "%s: calib cofig id = %d\n", + dev_dbg(tas_priv->dev, "%s: calib config id = %d\n", __func__, tas_fmw->calibration_config_id); } @@ -1992,7 +1992,8 @@ static void dspbin_type_check(struct tasdevice_priv *tas_priv, if (tas_priv->chip_id == TAS2573) return; if ((tas_priv->dspbin_typ != TASDEV_BASIC) && - (ppcver < PPC3_VERSION_TAS5825_BASE)) + (ppcver < PPC3_VERSION_TAS5825_BASE || + tas_priv->chip_id == TAS2781)) tas_priv->fw_parse_fct_param_address = fw_parse_fct_param_address; } @@ -2003,7 +2004,8 @@ static int dspfw_default_callback(struct tasdevice_priv *tas_priv, int rc = 0; if (drv_ver == 0x100) { - if (ppcver >= PPC3_VERSION_TAS5825_BASE) { + if (ppcver >= PPC3_VERSION_TAS5825_BASE && + tas_priv->chip_id == TAS5825) { tas_priv->fw_parse_variable_header = fw_parse_variable_header_kernel; tas_priv->fw_parse_program_data = diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 442624cf2a317d..7117af5a1a569e 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -275,7 +275,7 @@ static int twl4030_get_hw_params(struct device *dev, return 0; } -static int twl4030_init_chip(struct snd_soc_component *component) +static int twl4030_soc_probe(struct snd_soc_component *component) { struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); struct twl4030_board_params *board_params = twl4030->board_params; @@ -2172,15 +2172,6 @@ static struct snd_soc_dai_driver twl4030_dai[] = { }, }; -static int twl4030_soc_probe(struct snd_soc_component *component) -{ - struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); - - snd_soc_component_set_drvdata(component, twl4030); - - return twl4030_init_chip(component); -} - static const struct snd_soc_component_driver soc_component_dev_twl4030 = { .probe = twl4030_soc_probe, .read = twl4030_read, diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c index 0dd05604f5b887..7ffbb445f8ac4b 100644 --- a/sound/soc/codecs/wcd937x.c +++ b/sound/soc/codecs/wcd937x.c @@ -2536,23 +2536,23 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) WCD937X_IRQ_AUX_PDM_WD_INT); /* Request for watchdog interrupt */ - ret = devm_request_threaded_irq(dev, wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHR PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHR PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHR watchdog interrupt (%d)\n", ret); + goto err_free_clsh; - ret = devm_request_threaded_irq(dev, wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHL PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHL PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHL watchdog interrupt (%d)\n", ret); + goto err_free_hphr_irq; - ret = devm_request_threaded_irq(dev, wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "AUX PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "AUX PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request Aux watchdog interrupt (%d)\n", ret); + goto err_free_hphl_irq; /* Disable watchdog interrupt for HPH and AUX */ disable_irq_nosync(wcd937x->hphr_pdm_wd_int); @@ -2564,23 +2564,33 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) ARRAY_SIZE(wcd9375_dapm_widgets)); if (ret < 0) { dev_err(component->dev, "Failed to add snd_ctls\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } ret = snd_soc_dapm_add_routes(dapm, wcd9375_audio_map, ARRAY_SIZE(wcd9375_audio_map)); if (ret < 0) { dev_err(component->dev, "Failed to add routes\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } } ret = wcd937x_mbhc_init(component); - if (ret) + if (ret) { dev_err(component->dev, "mbhc initialization failed\n"); + goto err_free_irqs; + } + + return ret; +err_free_irqs: + free_irq(wcd937x->aux_pdm_wd_int, wcd937x); +err_free_hphl_irq: + free_irq(wcd937x->hphl_pdm_wd_int, wcd937x); +err_free_hphr_irq: + free_irq(wcd937x->hphr_pdm_wd_int, wcd937x); +err_free_clsh: + wcd_clsh_ctrl_free(wcd937x->clsh_info); return ret; } diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c index 26fe0b6313cdb4..2d463f062e6d71 100644 --- a/sound/soc/codecs/wcd939x.c +++ b/sound/soc/codecs/wcd939x.c @@ -1794,8 +1794,9 @@ static int wcd939x_get_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; - ucontrol->value.integer.value[0] = wcd->port_enable[portidx] ? 1 : 0; + ucontrol->value.integer.value[0] = !!(wcd->port_config[portidx - 1].ch_mask & ch_mask); return 0; } @@ -1821,10 +1822,13 @@ static int wcd939x_set_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; + bool enable = !!ucontrol->value.integer.value[0]; - wcd->port_enable[portidx] = !!ucontrol->value.integer.value[0]; + if (enable == !!(wcd->port_config[portidx - 1].ch_mask & ch_mask)) + return 0; - wcd939x_connect_port(wcd, portidx, mixer->reg, wcd->port_enable[portidx]); + wcd939x_connect_port(wcd, portidx, mixer->reg, enable); return 1; } diff --git a/sound/soc/codecs/wcd939x.h b/sound/soc/codecs/wcd939x.h index 6bd2366587a8f1..dab7ef108b4858 100644 --- a/sound/soc/codecs/wcd939x.h +++ b/sound/soc/codecs/wcd939x.h @@ -899,7 +899,6 @@ struct wcd939x_sdw_priv { struct sdw_stream_runtime *sruntime; struct sdw_port_config port_config[WCD939X_MAX_SWR_PORTS]; const struct wcd_sdw_ch_info *ch_info; - bool port_enable[WCD939X_MAX_SWR_CH_IDS]; int active_ports; bool is_tx; struct wcd939x_priv *wcd939x; diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index f7ecbac386e5ee..cdee1561d5968b 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -658,7 +658,7 @@ SOC_SINGLE_TLV("EQ2 Band 4 Volume", WM5100_EQ2_2, WM5100_EQ2_B4_GAIN_SHIFT, SOC_SINGLE_TLV("EQ2 Band 5 Volume", WM5100_EQ2_2, WM5100_EQ2_B5_GAIN_SHIFT, 24, 0, eq_tlv), -SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ1_1, WM5100_EQ3_B1_GAIN_SHIFT, +SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ3_1, WM5100_EQ3_B1_GAIN_SHIFT, 24, 0, eq_tlv), SOC_SINGLE_TLV("EQ3 Band 2 Volume", WM5100_EQ3_1, WM5100_EQ3_B2_GAIN_SHIFT, 24, 0, eq_tlv), diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 7d40ed21f569ee..2d48bfffcebe86 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -134,7 +134,7 @@ SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), -SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_ADC, 0, 1, 0), SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 9bc5d1e795dc6c..5343d445c3224f 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -852,9 +852,15 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + int ret; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, 1, wm8580->drvdata->num_dacs * 2); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, + wm8580->drvdata->num_dacs * 2); + if (ret < 0) + return ret; + + return 0; } #define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 72b7d09503a9ce..7f59dc6c74c0a4 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -148,13 +148,13 @@ SOC_DOUBLE_R_TLV("Mic Boost Volume", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 6, 3, 0, micboost_tlv), SOC_DOUBLE_R("Mic Boost Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 4, 1, 0), -SOC_DOUBLE("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 3, 1, 0), +SOC_DOUBLE_R("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 3, 1, 0), SOC_DOUBLE_R_TLV("Capture Volume", WM8737_LEFT_PGA_VOLUME, WM8737_RIGHT_PGA_VOLUME, 0, 255, 0, pga_tlv), -SOC_DOUBLE("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 2, 1, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 2, 1, 0), SOC_DOUBLE("INPUT1 DC Bias Switch", WM8737_MISC_BIAS_CONTROL, 0, 1, 1, 0), diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index b0b44479383039..e87b7d5e94e201 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -171,7 +171,7 @@ SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0), SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0), SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0), SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0), -SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]), +SOC_ENUM("ALC Capture NG Type", wm8750_enum[7]), SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0), SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0), diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c index 3a2acdfa9b858f..767251ac4f89d5 100644 --- a/sound/soc/codecs/wm8782.c +++ b/sound/soc/codecs/wm8782.c @@ -39,9 +39,14 @@ static int wm8782_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_dai struct snd_pcm_runtime *runtime = sub->runtime; struct wm8782_priv *priv = snd_soc_component_get_drvdata(dai->component); + int ret; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 8000, priv->max_rate); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_dapm_widget wm8782_dapm_widgets[] = { diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 017ec2e37d1e44..70daf9e6433b2e 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -426,7 +426,7 @@ static const char *treble_cutoff_text[] = { "High", "Low" }; -static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 2, +static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 6, treble_cutoff_text); static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 25cc433c7653dd..258bc9f88f520e 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -255,9 +255,9 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { /* DAC / ADC oversampling */ SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, - 5, 1, 0), + 3, 1, 0), SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, - 5, 1, 0), + 3, 1, 0), }; /* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ diff --git a/sound/soc/codecs/wm8983.c b/sound/soc/codecs/wm8983.c index 3366166a2b771c..d4b0f8e141cda9 100644 --- a/sound/soc/codecs/wm8983.c +++ b/sound/soc/codecs/wm8983.c @@ -234,7 +234,7 @@ static const struct snd_kcontrol_new wm8983_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8983_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8983_LEFT_DAC_DIGITAL_VOL, WM8983_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index b5ca4af715c0d7..9264374bb8e4fb 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -327,7 +327,7 @@ static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL, WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index c8fef187f5401d..a5e032a3f9f651 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -97,8 +97,8 @@ static const struct snd_kcontrol_new wm8990_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), /* INMIXR */ -SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), -SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, @@ -112,7 +112,7 @@ SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, - WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8990_LLBLOVOL_SHIFT, WM8990_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, @@ -377,9 +377,9 @@ SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), -SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER4, WM8990_R12MNB_BIT, 1, 0), -SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER4, WM8990_R34MNB_BIT, 1, 0), }; diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c index 2cb7fb9962af5d..553022bcd66662 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -169,8 +169,8 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), /* INMIXR */ - SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), - SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, @@ -184,7 +184,7 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, - WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8991_LLBLOVOL_SHIFT, WM8991_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, @@ -446,9 +446,9 @@ static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), - SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER4, WM8991_R12MNB_BIT, 1, 0), - SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER4, WM8991_R34MNB_BIT, 1, 0), }; diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index b8881b260a4bea..c65a7769a9d25b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -701,17 +701,17 @@ static const char *wm8958_ng_text[] = { static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac1_ng_hold, WM8958_AIF1_DAC1_NOISE_GATE, - WM8958_AIF1DAC1_NG_THR_SHIFT, + WM8958_AIF1DAC1_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac2_ng_hold, WM8958_AIF1_DAC2_NOISE_GATE, - WM8958_AIF1DAC2_NG_THR_SHIFT, + WM8958_AIF1DAC2_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif2dac_ng_hold, WM8958_AIF2_DAC_NOISE_GATE, - WM8958_AIF2DAC_NG_THR_SHIFT, + WM8958_AIF2DAC_NG_HLD_SHIFT, wm8958_ng_text); static const struct snd_kcontrol_new wm8958_snd_controls[] = { diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 019940589e514a..7b77e97253856c 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -427,7 +427,7 @@ SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", - WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), + WM8993_OUTPUT_MIXER6, 0, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer DAC Volume", WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c index 1faa8541a872eb..5ffbf9e89fd935 100644 --- a/sound/soc/codecs/wsa885x.c +++ b/sound/soc/codecs/wsa885x.c @@ -896,7 +896,6 @@ static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); - regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); return 0; } @@ -1006,6 +1005,16 @@ static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 }, + { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, }; struct wsa885x_priv *wsa885x; diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index ade89c196a7988..dbda943cfaf35a 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -922,13 +923,16 @@ static int jh7110_i2stx0_clk_cfg(struct i2s_clk_config_data *config) static int dw_i2s_probe(struct platform_device *pdev) { - const struct i2s_platform_data *pdata = pdev->dev.platform_data; + const struct i2s_platform_data *pdata = device_get_match_data(&pdev->dev); struct dw_i2s_dev *dev; struct resource *res; int ret, irq; struct snd_soc_dai_driver *dw_i2s_dai; const char *clk_id; + if (!pdata) + pdata = dev_get_platdata(&pdev->dev); + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index a5e3670349f968..58101ce07c9f9f 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -476,6 +476,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->input = dma_buf_export(&exp_info_in); if (IS_ERR(task->input)) { ret = PTR_ERR(task->input); + task->input = NULL; return ret; } @@ -486,6 +487,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->output = dma_buf_export(&exp_info_out); if (IS_ERR(task->output)) { ret = PTR_ERR(task->output); + task->output = NULL; return ret; } diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c index 7086fb7fc1eba1..30e75fee51757e 100644 --- a/sound/soc/fsl/imx-pcm-rpmsg.c +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -269,7 +269,7 @@ static int imx_rpmsg_pcm_open(struct snd_soc_component *component, info->stream_timer[substream->stream].substream = substream; timer_setup(&info->stream_timer[substream->stream].timer, imx_rpmsg_timer_callback, 0); - return ret; + return 0; } static int imx_rpmsg_pcm_close(struct snd_soc_component *component, diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index a4c8cbfba0963f..db1fb3b26909f5 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -341,7 +341,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, stream = NULL; - return ret_val; + return 0; } static void sst_media_close(struct snd_pcm_substream *substream, diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 2d7ced953fde57..0d0881cb46991a 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -756,7 +756,7 @@ static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_so data = snd_soc_dai_get_dma_data(dai, substream); host_stream = data->host_stream; - if (runtime->state == SNDRV_PCM_STATE_XRUN) + if (runtime->state != SNDRV_PCM_STATE_PREPARED) hdac_stream(host_stream)->prepared = false; if (hdac_stream(host_stream)->prepared) return 0; @@ -1399,10 +1399,6 @@ int avs_register_component(struct device *dev, const char *name, if (!acomp) return -ENOMEM; - acomp->base = snd_soc_component_alloc(dev); - if (!acomp->base) - return -ENOMEM; - comp_name = devm_kstrdup(dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; @@ -1411,10 +1407,13 @@ int avs_register_component(struct device *dev, const char *name, drv->use_dai_pcm_id = !obsolete_card_names; - snd_soc_component_set_name(acomp->base, comp_name); + acomp->base = snd_soc_component_new(dev, comp_name, drv); + if (IS_ERR(acomp->base)) + return PTR_ERR(acomp->base); + snd_soc_component_set_priv(acomp->base, acomp); - return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + return snd_soc_component_register(acomp->base, cpu_dais, num_cpu_dais); } static struct snd_soc_dai_driver dmic_cpu_dais[] = { diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c index 2e4063b743ba76..483e151dfd66f3 100644 --- a/sound/soc/intel/avs/probes.c +++ b/sound/soc/intel/avs/probes.c @@ -298,17 +298,13 @@ int avs_register_probe_component(struct avs_dev *adev, const char *name) struct snd_soc_component *component; const char *comp_name; - component = snd_soc_component_alloc(adev->dev); - if (!component) - return -ENOMEM; - comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; - snd_soc_component_set_name(component, comp_name); + component = snd_soc_component_new(adev->dev, comp_name, &avs_probe_component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component(component, - &avs_probe_component_driver, - probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + return snd_soc_component_register(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); } diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 919d3f5f532881..553ad123d6e6ae 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -161,6 +161,20 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". +config SND_SOC_INTEL_CHT_RT5677_MACH + tristate "Cherrytrail with RT5677 codec" + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_ACPI + select SND_SOC_RT5677 + select SND_SOC_TS3A227E + help + This adds support for Cherry Trail platforms with an RT5677 codec + and TS3A227E headset-jack detector. It currently supports the + Lenovo Yoga Book YB1-X91F/L. + Say Y or m if you have such a device. If unsure select "N". + config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "Cherrytrail & Braswell with RT5645/5650 codec" depends on I2C && ACPI diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 25a1a9066cbfa5..28940ca02aa7a4 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -9,6 +9,7 @@ snd-soc-sst-bytcr-rt5640-y := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-y := bytcr_rt5651.o snd-soc-sst-bytcr-wm5102-y := bytcr_wm5102.o snd-soc-sst-cht-bsw-rt5672-y := cht_bsw_rt5672.o +snd-soc-sst-cht-rt5677-y := cht_rt5677.o snd-soc-sst-cht-bsw-rt5645-y := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-y := cht_bsw_max98090_ti.o snd-soc-sst-cht-bsw-nau8824-y := cht_bsw_nau8824.o @@ -41,6 +42,7 @@ obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_WM5102_MACH) += snd-soc-sst-bytcr-wm5102.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_RT5677_MACH) += snd-soc-sst-cht-rt5677.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH) += snd-soc-sst-cht-bsw-nau8824.o diff --git a/sound/soc/intel/boards/cht_rt5677.c b/sound/soc/intel/boards/cht_rt5677.c new file mode 100644 index 00000000000000..0f5e169fe96fa7 --- /dev/null +++ b/sound/soc/intel/boards/cht_rt5677.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cht_rt5677.c - ASoC machine driver for Cherry Trail with RT5677 + * + * Copyright (C) 2026 Yauhen Kharuzhy + * + * Based on the mainline cht_bsw_rt5672.c driver and Lenovo's + * cht_bl_dpcm_rt5677.c Android driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5677.h" +#include "../../codecs/ts3a227e.h" +#include "../atom/sst-atom-controls.h" + +#define RT5677_I2C "i2c-rt5677" +/* x86-android-tablets creates this client with dev_name "ts3a227e". */ +#define TS3A227E_I2C "i2c-ts3a227e" + +/* Platform clock 3 provides the codec's 19.2 MHz I2S master clock. */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5677-aif1" + +struct cht_rt5677_private { + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct clk *mclk; + struct gpio_desc *gpio_spk_en1; + struct gpio_desc *gpio_spk_en2; + struct gpio_desc *gpio_hp_en; +}; + +static int cht_rt5677_platform_clock_enable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(card->dev, "enabling MCLK failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret) { + dev_err(card->dev, "setting codec PLL failed: %d\n", ret); + goto disable_mclk; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "setting codec sysclk failed: %d\n", ret); + goto disable_mclk; + } + + return 0; + +disable_mclk: + clk_disable_unprepare(ctx->mclk); + return ret; +} + +static int cht_rt5677_platform_clock_disable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) + dev_warn(card->dev, "setting codec idle sysclk failed: %d\n", ret); + + clk_disable_unprepare(ctx->mclk); + + return ret; +} + +static int cht_rt5677_platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, + int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "codec DAI not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) + return cht_rt5677_platform_clock_enable(card, codec_dai); + + return cht_rt5677_platform_clock_disable(card, codec_dai); +} + +static int cht_rt5677_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + + return gpiod_set_value_cansleep(ctx->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); +} + +static int cht_rt5677_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + bool enable = SND_SOC_DAPM_EVENT_ON(event); + int rollback_ret; + int ret; + int i; + + /* Program the external boost amplifier for mode 3 before enabling it. */ + if (enable) { + for (i = 0; i < 4; i++) { + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !(i & 1)); + if (ret) + goto disable_boost; + + udelay(2); + } + } + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, enable); + if (ret) + return ret; + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en2, enable); + if (ret) { + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !enable); + if (rollback_ret) + dev_warn(card->dev, "rolling back speaker GPIO failed: %d\n", + rollback_ret); + return ret; + } + + if (enable) + msleep(50); + + return 0; + +disable_boost: + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, 0); + if (rollback_ret) + dev_warn(card->dev, "disabling speaker boost GPIO failed: %d\n", + rollback_ret); + + return ret; +} + +static const struct snd_soc_dapm_widget cht_rt5677_widgets[] = { + SND_SOC_DAPM_HP("Headphone", cht_rt5677_hp_event), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", cht_rt5677_spk_event), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + cht_rt5677_platform_clock_control, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_rt5677_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "LOUT1"}, + {"Headphone", NULL, "LOUT2"}, + {"Speaker", NULL, "LOUT1"}, + {"Speaker", NULL, "LOUT2"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Speaker", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int cht_rt5677_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret) { + dev_err(rtd->dev, "setting codec PLL failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "setting codec sysclk failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBC_CFC); + if (ret) { + dev_err(codec_dai->dev, "setting TDM format failed: %d\n", ret); + return ret; + } + + /* Four 25-bit DSP_B slots carry 24-bit samples; the codec uses slots 0 and 1. */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 25); + if (ret) { + dev_err(rtd->dev, "setting codec TDM slots failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(runtime->card); + int ret; + + /* + * The codec derives its asynchronous sample-rate conversion clocks from + * I2S1 while the SSP link runs from the Cherry Trail platform clock. + */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER | + RT5677_I2S1_SOURCE, + RT5677_CLK_SEL_I2S1_ASRC); + if (ret) { + dev_err(runtime->dev, "setting stereo ASRC clock failed: %d\n", ret); + return ret; + } + + /* Mono ADC L uses the codec system clock rather than the I2S1 clock. */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_AD_MONO_L_FILTER, RT5677_CLK_SEL_SYS2); + if (ret) { + dev_err(runtime->dev, "setting mono ASRC clock failed: %d\n", ret); + return ret; + } + + /* Firmware may leave MCLK enabled without updating the CCF count. */ + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(runtime->dev, "preparing MCLK failed: %d\n", ret); + return ret; + } + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + if (ret) { + dev_err(runtime->dev, "setting MCLK rate failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = 48000; + rate->max = 48000; + channels->min = 2; + channels->max = 2; + + /* + * Configure SSP2 for the 24-bit format expected by the codec. The + * ssp2-port front end still advertises S16_LE and converts the stream. + */ + snd_mask_none(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT)); + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static struct snd_soc_jack_pin cht_rt5677_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int cht_rt5677_headset_init(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct snd_soc_jack *jack; + int jack_type; + int ret; + + jack = devm_kzalloc(card->dev, sizeof(*jack), GFP_KERNEL); + if (!jack) + return -ENOMEM; + + /* + * TI supports four headset buttons: + * KEY_MEDIA + * KEY_VOICECOMMAND + * KEY_VOLUMEUP + * KEY_VOLUMEDOWN + */ + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", jack_type, + jack, cht_rt5677_jack_pins, + ARRAY_SIZE(cht_rt5677_jack_pins)); + if (ret) { + dev_err(card->dev, "creating headset jack failed: %d\n", ret); + return ret; + } + + return ts3a227e_enable_jack_detect(component, jack); +} + +static int cht_rt5677_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_rt5677_aif1_ops = { + .startup = cht_rt5677_aif1_startup, +}; + +static const struct snd_soc_ops cht_rt5677_be_ssp2_ops = { + .hw_params = cht_rt5677_aif1_hw_params, +}; + +static const struct snd_soc_aux_dev cht_rt5677_headset_dev = { + .dlc = COMP_AUX(TS3A227E_I2C), + .init = cht_rt5677_headset_init, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); +SND_SOC_DAILINK_DEF(media, DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai"))); +SND_SOC_DAILINK_DEF(deepbuffer, DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); +SND_SOC_DAILINK_DEF(ssp2_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port"))); +SND_SOC_DAILINK_DEF(ssp2_codec, DAILINK_COMP_ARRAY(COMP_CODEC(RT5677_I2C, CHT_CODEC_DAI))); +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform"))); + +static const struct snd_soc_dai_link cht_rt5677_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .nonatomic = true, + .dynamic = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(media, dummy, platform), + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .nonatomic = true, + .dynamic = 1, + .playback_only = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 0, + .no_pcm = 1, + .nonatomic = true, + .init = cht_rt5677_codec_init, + .be_hw_params_fixup = cht_rt5677_codec_fixup, + .ops = &cht_rt5677_be_ssp2_ops, + SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform), + }, +}; + +static const struct snd_soc_card cht_rt5677_card = { + .owner = THIS_MODULE, + .num_links = ARRAY_SIZE(cht_rt5677_dailink), + .num_aux_devs = 1, + .dapm_widgets = cht_rt5677_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_rt5677_widgets), + .dapm_routes = cht_rt5677_map, + .num_dapm_routes = ARRAY_SIZE(cht_rt5677_map), + .controls = cht_rt5677_controls, + .num_controls = ARRAY_SIZE(cht_rt5677_controls), +}; + +static const struct acpi_gpio_params speaker_enable_gpio = { 2, 0, false }; +static const struct acpi_gpio_mapping cht_rt5677_acpi_gpios[] = { + { "speaker-enable-gpios", &speaker_enable_gpio, 1 }, + { } +}; + +#define SOF_CARD_NAME "cht rt5677" +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "cht-rt5677" +#define DRIVER_NAME NULL + +static void cht_rt5677_gpiod_put(void *data) +{ + gpiod_put(data); +} + +static void cht_rt5677_remove_driver_gpios(void *data) +{ + acpi_dev_remove_driver_gpios(data); +} + +static int cht_rt5677_get_gpio(struct device *dev, struct device *codec_dev, + const char *con_id, struct gpio_desc **gpio) +{ + int ret; + + *gpio = gpiod_get(codec_dev, con_id, GPIOD_OUT_LOW); + if (IS_ERR(*gpio)) { + ret = PTR_ERR(*gpio); + return dev_err_probe(dev, ret, "getting %s GPIO failed\n", con_id); + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_gpiod_put, *gpio); + if (ret) + return dev_err_probe(dev, ret, "registering %s GPIO cleanup failed\n", + con_id); + + return 0; +} + +static int cht_rt5677_clone_dai_links(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_dai_link_component *components; + struct snd_soc_dai_link *links; + size_t size; + int i; + + links = devm_kmemdup(dev, cht_rt5677_dailink, + sizeof(cht_rt5677_dailink), GFP_KERNEL); + if (!links) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cht_rt5677_dailink); i++) { + if (links[i].num_cpus) { + size = sizeof(*components) * links[i].num_cpus; + components = devm_kmemdup(dev, links[i].cpus, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].cpus = components; + } + + if (links[i].num_codecs) { + size = sizeof(*components) * links[i].num_codecs; + components = devm_kmemdup(dev, links[i].codecs, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].codecs = components; + } + + if (links[i].num_platforms) { + size = sizeof(*components) * links[i].num_platforms; + components = devm_kmemdup(dev, links[i].platforms, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].platforms = components; + } + } + + card->dai_link = links; + return 0; +} + +static int snd_cht_rt5677_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cht_rt5677_private *ctx; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_aux_dev *aux_dev; + struct snd_soc_dai_link *dai_links; + struct snd_soc_card *card; + struct acpi_device *adev; + struct device *codec_dev; + bool sof_parent; + int ret; + int i; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + card = devm_kmemdup(dev, &cht_rt5677_card, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = cht_rt5677_clone_dai_links(dev, card); + if (ret) + return ret; + dai_links = card->dai_link; + + aux_dev = devm_kmemdup(dev, &cht_rt5677_headset_dev, + sizeof(cht_rt5677_headset_dev), GFP_KERNEL); + if (!aux_dev) + return -ENOMEM; + + card->aux_dev = aux_dev; + + strscpy(ctx->codec_name, RT5677_I2C, sizeof(ctx->codec_name)); + + /* Use the ACPI-enumerated codec name when firmware describes the codec. */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(ctx->codec_name, sizeof(ctx->codec_name), + "i2c-%s", acpi_dev_name(adev)); + + acpi_dev_put(adev); + for (i = 0; i < card->num_links; i++) { + if (dai_links[i].codecs->name && + !strcmp(dai_links[i].codecs->name, + RT5677_I2C)) { + dai_links[i].codecs->name = ctx->codec_name; + break; + } + } + } + + codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, + ctx->codec_name); + if (!codec_dev) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for codec %s\n", ctx->codec_name); + + adev = ACPI_COMPANION(codec_dev); + if (adev) { + /* + * Firmware describes the external speaker enable in _CRS. The + * x86-android-tablets quirk supplies software-node properties for + * the RT5677 GPIO2 and GPIO4 speaker/headphone enables. + */ + ret = acpi_dev_add_driver_gpios(adev, cht_rt5677_acpi_gpios); + if (ret) { + dev_err_probe(dev, ret, "adding codec GPIO mappings failed\n"); + goto out_put_codec; + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_remove_driver_gpios, adev); + if (ret) { + dev_err_probe(dev, ret, "registering GPIO mapping cleanup failed\n"); + goto out_put_codec; + } + } + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable", &ctx->gpio_spk_en1); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable2", &ctx->gpio_spk_en2); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "headphone-enable", &ctx->gpio_hp_en); + if (ret) + goto out_put_codec; + + put_device(codec_dev); + + card->dev = dev; + + ctx->mclk = devm_clk_get(dev, "pmc_plt_clk_3"); + if (IS_ERR(ctx->mclk)) + return dev_err_probe(dev, PTR_ERR(ctx->mclk), "getting MCLK failed\n"); + + snd_soc_card_set_drvdata(card, ctx); + + sof_parent = snd_soc_acpi_sof_parent(dev); + + if (sof_parent) { + card->name = SOF_CARD_NAME; + card->driver_name = SOF_DRIVER_NAME; + } else { + card->name = CARD_NAME; + card->driver_name = DRIVER_NAME; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) + return dev_err_probe(dev, ret, "registering sound card failed\n"); + + return 0; + +out_put_codec: + put_device(codec_dev); + return ret; +} + +static struct platform_driver snd_cht_rt5677_driver = { + .driver = { + .name = "cht-rt5677", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_cht_rt5677_probe, +}; + +module_platform_driver(snd_cht_rt5677_driver); + +MODULE_DESCRIPTION("Cherry Trail RT5677 machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cht-rt5677"); diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index bbba0d959ea215..ca42bc531c6b8f 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -1073,13 +1073,9 @@ int catpt_register_plat_component(struct catpt_dev *cdev) { struct snd_soc_component *component; - component = snd_soc_component_alloc(cdev->dev); - if (!component) - return -ENOMEM; - - snd_soc_component_set_name(component, catpt_comp_driver.name); + component = snd_soc_component_new(cdev->dev, catpt_comp_driver.name, &catpt_comp_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component(component, - &catpt_comp_driver, - dai_drivers, ARRAY_SIZE(dai_drivers)); + return snd_soc_component_register(component, dai_drivers, ARRAY_SIZE(dai_drivers)); } diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index e4c3492a0c2824..442ef30ffcc78e 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -137,6 +137,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .board = "cht-bsw", .sof_tplg_filename = "sof-cht-rt5670.tplg", }, + { + /* Lenovo Yoga Book YB1-X91F/L */ + .id = "10EC5677", + .drv_name = "cht-rt5677", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht_rt5677", + .sof_tplg_filename = "sof-cht-rt5677.tplg", + }, { .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 948f606cbaf208..ec99bc4a5d5ee0 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -75,8 +75,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -88,10 +90,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); diff --git a/sound/soc/mediatek/common/mtk-soundcard-driver.c b/sound/soc/mediatek/common/mtk-soundcard-driver.c index 2d39ff23f8540f..a84f4ab4488b77 100644 --- a/sound/soc/mediatek/common/mtk-soundcard-driver.c +++ b/sound/soc/mediatek/common/mtk-soundcard-driver.c @@ -129,6 +129,11 @@ void clean_card_reference(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(clean_card_reference); +static void mtk_soundcard_put_card_reference(void *data) +{ + clean_card_reference(data); +} + int mtk_soundcard_startup(struct snd_pcm_substream *substream, enum mtk_pcm_constraint_type ctype) { @@ -180,6 +185,43 @@ const struct snd_soc_ops mtk_soundcard_common_capture_ops = { }; EXPORT_SYMBOL_GPL(mtk_soundcard_common_capture_ops); +struct mtk_soundcard_name { + struct snd_soc_card *card; + const char *name; +}; + +static void mtk_soundcard_restore_name(void *data) +{ + struct mtk_soundcard_name *saved = data; + + saved->card->name = saved->name; + saved->card->topology_shortname = NULL; +} + +/* + * The card is static and outlives this probe, but the topology name is + * devm-allocated. Restore the original name when the probe's resources are + * released, so that a deferred or failed probe does not leave the card name + * pointing to freed memory for the next one. + */ +static int mtk_soundcard_set_topology_name(struct device *dev, + struct snd_soc_card *card) +{ + struct mtk_soundcard_name *saved; + + saved = devm_kzalloc(dev, sizeof(*saved), GFP_KERNEL); + if (!saved) + return -ENOMEM; + + saved->card = card; + saved->name = card->name; + card->topology_shortname = NULL; + + snd_soc_card_set_topology_name(card, "sof"); + + return devm_add_action_or_reset(dev, mtk_soundcard_restore_name, saved); +} + int mtk_soundcard_common_probe(struct platform_device *pdev) { struct device_node *platform_node, *adsp_node, *accdet_node; @@ -287,7 +329,12 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) card->probe = mtk_sof_card_probe; card->late_probe = mtk_sof_card_late_probe; - snd_soc_card_set_topology_name(card, "sof"); + ret = mtk_soundcard_set_topology_name(&pdev->dev, card); + if (ret) { + of_node_put(adsp_node); + of_node_put(platform_node); + return ret; + } } /* @@ -325,8 +372,22 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) ret = devm_snd_soc_register_card(&pdev->dev, card); - if (!needs_legacy_probe) - clean_card_reference(card); + /* + * When a component is still missing, snd_soc_bind_card() queues the + * card for a later rebind and returns success, so the card is not + * instantiated yet. That rebind reuses this same dai_link array, so + * the codec references parsed from the devicetree must be kept until + * the device is unbound: dropping them leaves both name and of_node + * unset and the rebind fails the dai link sanity check instead. + */ + if (!needs_legacy_probe) { + if (ret || snd_soc_card_is_instantiated(card)) + clean_card_reference(card); + else + ret = devm_add_action_or_reset(&pdev->dev, + mtk_soundcard_put_card_reference, + card); + } if (ret) { dev_err_probe(&pdev->dev, ret, "Cannot register card\n"); diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 4010879ad1d31e..87d6098caf9983 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -318,8 +318,11 @@ static int mt8188_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 1, MT8188_MEMIF_DL7_MAX_PERIOD_SIZE); - if (ret < 0) + if (ret >= 0) + ret = 0; + else dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + out: return ret; } diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 75c90d1d165f77..f07aeb6371083b 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -1142,6 +1142,9 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { }, .dynamic = 1, .capture_only = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_format = 1, SND_SOC_DAILINK_REG(capture8), }, [DAI_LINK_UL9_FE] = { diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index c0f340a6cc09e9..eefa96b701a5e9 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -155,8 +155,10 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_warn(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -168,11 +170,11 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } static void mt8189_fe_shutdown(struct snd_pcm_substream *substream, diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 286e39f53ae050..91a05ea6b8b6a1 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -220,33 +220,39 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], MT8196_AUD_ENG1_CLK); if (ret) - return ret; + goto err_disable_eng1; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng1; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng1: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); +err_disable_eng1: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); + + return ret; } static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) @@ -268,33 +274,39 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], MT8196_AUD_ENG2_CLK); if (ret) - return ret; + goto err_disable_eng2; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng2; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng2: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); +err_disable_eng2: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); + + return ret; } int mt8196_apll1_enable(struct mtk_base_afe *afe) @@ -302,15 +314,17 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL1_CK); if (ret) - goto err_clk_apll1; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER1); if (ret) - goto err_clk_apll1_tuner; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -324,14 +338,22 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll1 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + if (ret) + goto err_disable_tuner; return 0; -err_clk_apll1_tuner: +err_disable_tuner: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER1); -err_clk_apll1: +err_disable_apll1_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL1_CK); +err_disable_mux: + apll1_mux_setting(afe, false); + return ret; } @@ -355,15 +377,17 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL2_CK); if (ret) - goto err_clk_apll2; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER2); if (ret) - goto err_clk_apll2_tuner; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -377,14 +401,23 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll2 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + if (ret) + goto err_disable_tuner2; + return 0; -err_clk_apll2_tuner: +err_disable_tuner2: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER2); -err_clk_apll2: +err_disable_apll2_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL2_CK); - return 0; +err_disable_mux: + apll2_mux_setting(afe, false); + + return ret; } void mt8196_apll2_disable(struct mtk_base_afe *afe) @@ -469,8 +502,10 @@ int mt8196_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) return ret; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); - if (ret) + if (ret) { + mt8196_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); return ret; + } return 0; } @@ -511,26 +546,41 @@ int mt8196_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) int ret; /* bus clock for AFE external access, like DRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + if (ret) + return ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + if (ret) + goto err_disable_adsp_sel; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audiointbus; /* enable audio h clock */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audio_h; /* AFE hw clock */ /* IPM2.0: USE HOPPING & 26M */ /* set in the regmap_register_patch */ return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_disable_audiointbus: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); +err_disable_adsp_sel: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + + return ret; } int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -548,14 +598,12 @@ int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) int mt8196_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_afe_disable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_init_clock(struct mtk_base_afe *afe) @@ -570,10 +618,9 @@ int mt8196_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8196_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index dcee037991aadb..fbeba01b086c72 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2211,7 +2211,7 @@ static int mt8196_afe_runtime_suspend(struct device *dev) static int mt8196_afe_runtime_resume(struct device *dev) { struct mtk_base_afe *afe = dev_get_drvdata(dev); - int ret = 0; + int ret; ret = mt8196_afe_enable_reg_rw_clk(afe); if (ret) @@ -2222,7 +2222,9 @@ static int mt8196_afe_runtime_resume(struct device *dev) goto skip_regmap; } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); @@ -2233,10 +2235,20 @@ static int mt8196_afe_runtime_resume(struct device *dev) CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); /* enable AFE */ - mt8196_afe_enable_main_clock(afe); + ret = mt8196_afe_enable_main_clock(afe); + if (ret) + goto err_reset_26m_req; skip_regmap: return 0; + +err_reset_26m_req: + regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8196_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8196_afe_component_probe(struct snd_soc_component *component) @@ -2348,7 +2360,7 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8196_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2382,13 +2394,13 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8196_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2457,10 +2469,8 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) &mt8196_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err\n"); + if (ret) return ret; - } return 0; diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c index ef5cde0ba829e0..adebf75629beda 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c @@ -985,27 +985,30 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8196_apll1_enable(afe); + ret = mt8196_apll1_enable(afe); else - mt8196_apll2_enable(afe); + ret = mt8196_apll2_enable(afe); break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) mt8196_apll1_disable(afe); else mt8196_apll2_disable(afe); + ret = 0; break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, @@ -1015,6 +1018,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); @@ -1025,17 +1029,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8196_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8196_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c index b7aeee939d8886..44bd08f5c1fbe5 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c @@ -256,22 +256,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8196_mck_disable(afe, tdm_priv->bck_id); + ret = mt8196_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -283,23 +285,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8196_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8196_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c index e9497246f37c05..314235557fdf80 100644 --- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c @@ -514,11 +514,14 @@ static int mt8365_afe_fe_startup(struct snd_pcm_substream *substream, snd_soc_set_runtime_hwparams(substream, afe->mtk_afe_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } mt8365_afe_enable_main_clk(afe); - return ret; + + return 0; } static void mt8365_afe_fe_shutdown(struct snd_pcm_substream *substream, diff --git a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c index a058973662b3ae..2e678f4aa4b1fb 100644 --- a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c +++ b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c @@ -267,7 +267,7 @@ static const u32 *get_iir_coef(unsigned int input_fs, if (k >= IIR_NO_NEED) { } else if (k == IIR_RATIOVER) { } else if (k == IIR_INV_COEF) { - } else { + } else if (k < ARRAY_SIZE(iir_coef_tbl_list)) { coef = iir_coef_tbl_list[k].coef; cnt = iir_coef_tbl_list[k].cnt; } diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index e6e24f3b99222d..991feb3179402f 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -102,6 +102,7 @@ config SND_SOC_QDSP6_ASM_DAI config SND_SOC_QDSP6_APM_DAI tristate select SND_SOC_COMPRESS + select QCOM_SCM config SND_SOC_QDSP6_APM_LPASS_DAI tristate diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index cce0ad31ff0c03..d2dc5d37bccf89 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -593,10 +593,10 @@ EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode) + struct gpr_pkt *pkt, uint32_t rsp_opcode) { - const struct gpr_hdr *hdr = &pkt->hdr; + struct gpr_hdr *hdr = &pkt->hdr; int rc; mutex_lock(cmd_lock); @@ -636,10 +636,12 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, } EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, graph->port, &graph->cmd_wait, pkt, rsp_opcode); } @@ -994,6 +996,8 @@ int audioreach_compr_set_param(struct q6apm_graph *graph, if (rc) return rc; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_compr_set_param); @@ -1609,6 +1613,8 @@ int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 35541b2d8c9984..587f14ce71d234 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -1002,14 +1002,19 @@ struct audioreach_module_config { }; /* Packet Allocation routines */ -void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t - token); +static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, + uint32_t token); void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels); void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, - uint32_t src_port); + uint32_t src_port); void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); @@ -1020,10 +1025,13 @@ int audioreach_tplg_init(struct snd_soc_component *component); /* Module specific */ void audioreach_graph_free_buf(struct q6apm_graph *graph); -int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, - struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, + wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, + struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module, diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 033197bdd8705e..b0b10ba51c99d1 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -1,20 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2021, Linaro Limited -#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 "q6apm.h" #define DRV_NAME "q6apm-dai" @@ -36,6 +41,16 @@ #define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) #define SID_MASK_DEFAULT 0xF +#define Q6APM_MAX_SCM_REGIONS 16 +#define Q6APM_POOL_MAX_STREAMS 8 + +struct q6apm_scm_region { + phys_addr_t addr; + size_t size; + u64 src_perms; + bool assigned; +}; + static const struct snd_compr_codec_caps q6apm_compr_caps = { .num_descriptors = 1, .descriptor[0].max_ch = 2, @@ -84,9 +99,103 @@ struct q6apm_dai_rtd { }; struct q6apm_dai_data { + struct device *dev; long long sid; + bool use_scm_assign; + bool has_reserved_mem; + size_t reserved_buf_size; + struct mutex scm_lock; /* protects scm_regions and num_scm_regions */ + struct q6apm_scm_region scm_regions[Q6APM_MAX_SCM_REGIONS]; + int num_scm_regions; }; +static int q6apm_dai_scm_assign(struct q6apm_dai_data *pdata, + phys_addr_t addr, size_t size) +{ + struct qcom_scm_vmperm dst[] = { + { .vmid = QCOM_SCM_VMID_HLOS, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_MSS_MSA, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_LPASS, .perm = QCOM_SCM_PERM_RW }, + }; + struct q6apm_scm_region *r; + u64 src = BIT(QCOM_SCM_VMID_HLOS); + int ret; + + mutex_lock(&pdata->scm_lock); + + if (pdata->num_scm_regions >= Q6APM_MAX_SCM_REGIONS) { + mutex_unlock(&pdata->scm_lock); + return -ENOSPC; + } + + ret = qcom_scm_assign_mem(addr, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + mutex_unlock(&pdata->scm_lock); + return ret; + } + + r = &pdata->scm_regions[pdata->num_scm_regions++]; + r->addr = addr; + r->size = size; + r->src_perms = src; + r->assigned = true; + + mutex_unlock(&pdata->scm_lock); + + return 0; +} + +static void q6apm_dai_scm_unassign(struct q6apm_dai_data *pdata, + phys_addr_t addr) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int i; + + mutex_lock(&pdata->scm_lock); + + for (i = 0; i < pdata->num_scm_regions; i++) { + if (pdata->scm_regions[i].addr != addr || + !pdata->scm_regions[i].assigned) + continue; + + if (qcom_scm_assign_mem(addr, pdata->scm_regions[i].size, + &pdata->scm_regions[i].src_perms, + &hlos, 1)) { + dev_err(pdata->dev, "SCM unassign %pa failed\n", &addr); + mutex_unlock(&pdata->scm_lock); + return; + } + + pdata->scm_regions[i].assigned = false; + pdata->num_scm_regions--; + pdata->scm_regions[i] = pdata->scm_regions[pdata->num_scm_regions]; + mutex_unlock(&pdata->scm_lock); + return; + } + + mutex_unlock(&pdata->scm_lock); +} + +static void q6apm_dai_scm_cleanup(void *data) +{ + struct q6apm_dai_data *pdata = data; + int i; + + for (i = pdata->num_scm_regions - 1; i >= 0; i--) { + if (pdata->scm_regions[i].assigned) + q6apm_dai_scm_unassign(pdata, + pdata->scm_regions[i].addr); + } +} + +static void q6apm_dai_reserved_mem_release(void *data) +{ + of_reserved_mem_device_release(data); +} + static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | @@ -409,8 +518,11 @@ static int q6apm_dai_open(struct snd_soc_component *component, } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - BUFFER_BYTES_MIN, BUFFER_BYTES_MAX); + BUFFER_BYTES_MIN, buf_max); if (ret < 0) { dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret); goto err; @@ -431,17 +543,20 @@ static int q6apm_dai_open(struct snd_soc_component *component, } runtime->private_data = prtd; - runtime->dma_bytes = BUFFER_BYTES_MAX; + runtime->dma_bytes = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; if (pdata->sid < 0) prtd->phys = substream->dma_buffer.addr; else prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; void *pos_buffer; - prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX; - pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX); + prtd->pos_phys = prtd->phys + buf_max; + pos_buffer = (void *)(substream->dma_buffer.area + buf_max); prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer); } @@ -535,6 +650,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, { struct q6apm_dai_data *pdata; struct device *dev = component->dev; + size_t buf_max; phys_addr_t phys; int ret; @@ -544,20 +660,23 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, return -EINVAL; } + buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + if (pdata->sid < 0) phys = substream->dma_buffer.addr; else phys = substream->dma_buffer.addr | (pdata->sid << 32); - ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX); + ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, buf_max); if (ret < 0) dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); if (is_push_pull) { if (pdata->sid < 0) - phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX; + phys = substream->dma_buffer.addr + buf_max; else - phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32); + phys = (substream->dma_buffer.addr + buf_max) | (pdata->sid << 32); ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES); if (ret < 0) @@ -572,20 +691,22 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct q6apm_dai_data *pdata; struct snd_pcm *pcm = rtd->pcm; - /* - * Allocate one extra page as a workaround for a DSP bug where 32-bit - * address arithmetic can overflow when the buffer is placed near the - * end of the addressable range. - */ int size = BUFFER_BYTES_MAX + PAGE_SIZE; int graph_id, ret; bool is_push_pull; struct snd_pcm_substream *substream = NULL; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (pdata->has_reserved_mem) + size = pdata->reserved_buf_size + PAGE_SIZE; + graph_id = cpu_dai->driver->id; - /* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) @@ -603,9 +724,24 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, + substream->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(component->dev, + "SCM assign buffer failed: %d\n", ret); + return ret; + } + } + ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull); - if (ret) + if (ret) { + if (pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); return ret; + } } return 0; @@ -635,15 +771,26 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component, static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) { + struct q6apm_dai_data *pdata; struct snd_pcm_substream *substream; + pdata = snd_soc_component_get_drvdata(component); + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -683,6 +830,17 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(dev, "SCM assign compr buffer failed: %d\n", + ret); + snd_dma_free_pages(&prtd->dma_buffer); + return ret; + } + } + if (pdata->sid < 0) prtd->phys = prtd->dma_buffer.addr; else @@ -700,11 +858,16 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component, { struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; + + pdata = snd_soc_component_get_drvdata(component); q6apm_graph_stop(prtd->graph); q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id); q6apm_graph_close(prtd->graph); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, prtd->dma_buffer.addr); snd_dma_free_pages(&prtd->dma_buffer); prtd->graph = NULL; kfree(prtd); @@ -1021,6 +1184,7 @@ static int q6apm_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; + struct q6apm *apm = dev_get_drvdata(dev->parent); struct q6apm_dai_data *pdata; struct of_phandle_args args; int rc; @@ -1029,12 +1193,123 @@ static int q6apm_dai_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; + pdata->dev = dev; + mutex_init(&pdata->scm_lock); + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); if (rc < 0) pdata->sid = -1; else pdata->sid = args.args[0] & SID_MASK_DEFAULT; + if (apm && apm->gdev && + apm->gdev->domain_id == GPR_DOMAIN_ID_MODEM) { + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (pdata->sid >= 0) { + dev_err(dev, + "iommus and mDSP SCM path are mutually exclusive\n"); + return -EINVAL; + } + + pdata->use_scm_assign = true; + + rc = devm_add_action_or_reset(dev, q6apm_dai_scm_cleanup, + pdata); + if (rc) + return rc; + } + + if (pdata->use_scm_assign) { + int mem_count; + + mem_count = of_count_phandle_with_args(node, "memory-region", + NULL); + if (mem_count >= 1) { + struct device_node *mem_node; + struct reserved_mem *rmem; + + mem_node = of_parse_phandle(node, "memory-region", 0); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[0]: lookup failed\n"); + return -ENODEV; + } + + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[0] failed: %d\n", + rc); + return rc; + } + } + + if (mem_count >= 2) { + struct device_node *mem_node; + struct reserved_mem *rmem; + size_t per_stream; + + mem_node = of_parse_phandle(node, "memory-region", 1); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[1]: lookup failed\n"); + return -ENODEV; + } + + per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS; + if (per_stream <= POS_BUFFER_BYTES + PAGE_SIZE) { + dev_err(dev, + "reserved-memory pool too small: %pa bytes\n", + &rmem->size); + return -EINVAL; + } + + /* + * Assign the whole pool to the consumer VMIDs once here, + * instead of per-stream in pcm_new()/compr_open(). Each + * qcom_scm_assign_mem() call consumes an entry in a small + * fixed-size TZ memory-protection table shared platform-wide; + * assigning per-stream-slice (up to Q6APM_POOL_MAX_STREAMS + * times) exhausts that table and hangs the SMC call. + */ + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[1] failed: %d\n", + rc); + return rc; + } + + rc = of_reserved_mem_device_init_by_idx(dev, node, 1); + if (rc) { + dev_err(dev, + "reserved-memory pool init failed: %d\n", + rc); + return rc; + } + + rc = devm_add_action_or_reset(dev, + q6apm_dai_reserved_mem_release, + dev); + if (rc) + return rc; + + pdata->reserved_buf_size = + min_t(size_t, + per_stream - POS_BUFFER_BYTES - PAGE_SIZE, + BUFFER_BYTES_MAX); + pdata->has_reserved_mem = true; + } + } + dev_set_drvdata(dev, pdata); return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0); diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index f167b9dae3fa14..ea6680925f21af 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -29,11 +29,13 @@ struct apm_graph_mgmt_cmd { static struct q6apm *g_apm; -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { gpr_device_t *gdev = apm->gdev; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(gdev); + return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, NULL, &apm->wait, pkt, rsp_opcode); } @@ -502,6 +504,8 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_write_async); @@ -536,6 +540,8 @@ int q6apm_read(struct q6apm_graph *graph) mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_read); @@ -900,6 +906,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + g_apm = NULL; of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cb51ca491dc4f..9092359ccf909f 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -147,7 +147,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir); int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode); /* Callback for graph specific */ diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c index 1f3ce4cc08370c..2042ff1e8ff1b2 100644 --- a/sound/soc/qcom/qdsp6/q6prm.c +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -57,6 +57,8 @@ struct prm_cmd_release_rsc { static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(prm->gdev); + return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, NULL, &prm->wait, pkt, rsp_opcode); } diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index b5d367486d56ad..b97cafeb8e785f 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -552,6 +552,15 @@ static const struct qcom_snd_soc_common kaanapali_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common maili_priv_data = { + .driver_name = "maili", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .wcd_jack = true, +}; + static const struct qcom_snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", .dapm_widgets = sc8280xp_dapm_widgets, @@ -676,7 +685,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, - { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, + { .compatible = "qcom,maili-sndcard", .data = &maili_priv_data }, { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 23b7e63596d3a4..88618e4b052d82 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1811,14 +1811,16 @@ static int fsi_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - int ret = 0; + int ret; snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; - return ret; + return 0; } static snd_pcm_uframes_t fsi_pointer(struct snd_soc_component *component, diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c index 128543fc4fc971..9fead9a22cdc45 100644 --- a/sound/soc/renesas/rcar/msiof.c +++ b/sound/soc/renesas/rcar/msiof.c @@ -427,10 +427,12 @@ static int msiof_open(struct snd_soc_component *component, ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); open_err_dma: - if (ret < 0) + if (ret < 0) { dma_release_channel(chan); + return ret; + } - return ret; + return 0; } static int msiof_close(struct snd_soc_component *component, diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index b1587f499cb893..2e297f721bb675 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -937,17 +937,29 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, struct snd_pcm_substream *substream) { + int ret; + snd_soc_set_runtime_hwparams(substream, &rz_ssi_pcm_hardware); - return snd_pcm_hw_constraint_integer(substream->runtime, + ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; } static int rz_ssi_pcm_open_dma(struct snd_soc_component *component, struct snd_pcm_substream *substream) { - return snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + int ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; } static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component, diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index 04e7bed8275d01..0ec80a4b8c59a5 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -258,10 +258,15 @@ static int rockchip_sound_dmic_hw_params(struct snd_pcm_substream *substream, static int rockchip_sound_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + int ret; runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, - 8000, 96000); + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + 8000, 96000); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_ops rockchip_sound_max98357a_ops = { diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index ef3f6586af7729..0a67632e68ede8 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -244,7 +244,9 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int mask = 0, val = 0; int ret = 0; - pm_runtime_get_sync(cpu_dai->dev); + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; mask = I2S_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BP_FP: diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 426506a8e18e9d..d54c36682415b6 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -191,12 +191,18 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream, static int rk_aif1_startup(struct snd_pcm_substream *substream) { + int ret; /* * Set period size to 240 because pl330 has issue * dealing with larger period in stress testing. */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 240, 240); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 240, 240); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_ops rk_aif1_ops = { diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 9dfe46ffe0ff02..dab96df8bf9635 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -487,7 +487,9 @@ static int rockchip_sai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) unsigned long flags; int ret = 0; - pm_runtime_get_sync(dai->dev); + ret = pm_runtime_resume_and_get(dai->dev); + if (ret) + return ret; mask = SAI_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 3e76cb81462f02..442e70b107dac5 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1534,9 +1534,16 @@ static int samsung_i2s_probe(struct platform_device *pdev) goto err_disable_pm; priv->op_clk = clk_get_parent(priv->clk_table[CLK_I2S_RCLK_SRC]); + ret = clk_prepare_enable(priv->op_clk); + if (ret < 0) { + priv->op_clk = NULL; + goto err_unregister_clk; + } return 0; +err_unregister_clk: + i2s_unregister_clock_provider(priv); err_disable_pm: pm_runtime_disable(&pdev->dev); err_del_sec: @@ -1559,6 +1566,7 @@ static void samsung_i2s_remove(struct platform_device *pdev) i2s_unregister_clock_provider(priv); i2s_delete_secondary_device(priv); + clk_disable_unprepare(priv->op_clk); clk_disable_unprepare(priv->clk); pm_runtime_put_noidle(&pdev->dev); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 14aee10b40ba7c..7a19de75f32b53 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1035,7 +1035,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -1063,7 +1063,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -1091,7 +1091,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -1119,7 +1119,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 463ea227cac819..34efb270ce2989 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -29,25 +29,6 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, func, component->name, reg); } -struct snd_soc_component *snd_soc_component_alloc(struct device *dev) -{ - struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); - - if (!component) - return NULL; - - component->dev = dev; - - return component; -} -EXPORT_SYMBOL_GPL(snd_soc_component_alloc); - -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) -{ - component->name = name; -} -EXPORT_SYMBOL_GPL(snd_soc_component_set_name); - const char *snd_soc_component_name(struct snd_soc_component *component) { return component->name; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a23fd07abf8a85..c2fda3238f2214 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2551,14 +2551,30 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) list_del(&component->list); } -static int soc_component_initialize(struct snd_soc_component *component, - const struct snd_soc_component_driver *driver) +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver) { - struct device *dev = component->dev; + struct snd_soc_component *component; + + component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + if (!component) + return ERR_PTR(-ENOMEM); component->dapm = snd_soc_dapm_alloc(dev); if (!component->dapm) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + if (!name) { + name = snd_soc_fmt_single_name(dev, NULL); + if (!name) { + dev_err(dev, "ASoC: Failed to allocate name\n"); + return ERR_PTR(-ENOMEM); + } + } + + component->dev = dev; + component->name = name; + component->driver = driver; INIT_LIST_HEAD(&component->dai_list); INIT_LIST_HEAD(&component->dobj_list); @@ -2567,22 +2583,13 @@ static int soc_component_initialize(struct snd_soc_component *component, INIT_LIST_HEAD(&component->card_aux_list); mutex_init(&component->io_mutex); - if (!component->name) { - component->name = snd_soc_fmt_single_name(dev, NULL); - if (!component->name) { - dev_err(dev, "ASoC: Failed to allocate name\n"); - return -ENOMEM; - } - } - - component->driver = driver; - - return 0; + return component; } +EXPORT_SYMBOL_GPL(snd_soc_component_new); -static int soc_component_add(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +int snd_soc_component_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + int num_dai) { struct snd_soc_card *card, *c; int ret; @@ -2621,36 +2628,22 @@ static int soc_component_add(struct snd_soc_component *component, return ret; } +EXPORT_SYMBOL_GPL(snd_soc_component_register); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) -{ - int ret; - - ret = soc_component_initialize(component, component_driver); - if (ret < 0) - return ret; - - return soc_component_add(component, dai_drv, num_dai); -} -EXPORT_SYMBOL_GPL(snd_soc_register_component_c); - -int snd_soc_register_component_d(struct device *dev, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +int snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) { struct snd_soc_component *component; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; + component = snd_soc_component_new(dev, NULL, component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); + return snd_soc_component_register(component, dai_drv, num_dai); } -EXPORT_SYMBOL_GPL(snd_soc_register_component_d); +EXPORT_SYMBOL_GPL(snd_soc_register_component); /** * snd_soc_unregister_component_by_driver - Unregister component using a given driver diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index f6206ecaf26fec..d750dc44981d9d 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -454,10 +454,6 @@ int snd_dmaengine_pcm_register(struct device *dev, struct dmaengine_pcm *pcm; int ret; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; - pcm = kzalloc_obj(*pcm); if (!pcm) return -ENOMEM; @@ -467,10 +463,6 @@ int snd_dmaengine_pcm_register(struct device *dev, pcm->config = config; pcm->flags = flags; - if (config->name) - snd_soc_component_set_name(component, config->name); - snd_soc_component_set_priv(component, pcm); - ret = dmaengine_pcm_request_chan_of(pcm, dev, config); if (ret) goto err_free_dma; @@ -480,7 +472,15 @@ int snd_dmaengine_pcm_register(struct device *dev, else driver = &dmaengine_pcm_component; - ret = snd_soc_register_component(component, driver, NULL, 0); + component = snd_soc_component_new(dev, config->name, driver); + if (IS_ERR(component)) { + ret = PTR_ERR(component); + goto err_free_dma; + } + + snd_soc_component_set_priv(component, pcm); + + ret = snd_soc_component_register(component, NULL, 0); if (ret) goto err_free_dma; diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c index 346a52d3006a64..bcfb52061133aa 100644 --- a/sound/soc/soc-topology-test.c +++ b/sound/soc/soc-topology-test.c @@ -246,8 +246,8 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_comp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -256,7 +256,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -291,8 +291,8 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -301,7 +301,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -355,8 +355,8 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_fw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -365,7 +365,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -406,8 +406,8 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -416,7 +416,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -464,8 +464,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -474,7 +474,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -522,8 +522,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -532,7 +532,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -580,8 +580,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -590,7 +590,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -639,8 +639,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -649,7 +649,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -690,8 +690,8 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -700,7 +700,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -743,8 +743,8 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -755,7 +755,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) for (i = 0; i < 100; i++) { - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -799,13 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); /* run test */ - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); for (i = 0; i < 100; i++) { diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile index bb1c0ddef69d77..3a7d230a4c4612 100644 --- a/sound/soc/sof/amd/Makefile +++ b/sound/soc/sof/amd/Makefile @@ -4,7 +4,7 @@ # # Copyright(c) 2021, 2023, 2024 Advanced Micro Devices, Inc. All rights reserved. -snd-sof-amd-acp-y := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o +snd-sof-amd-acp-y := acp.o acp-loader.o acp_asp_mailbox.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o snd-sof-amd-acp-$(CONFIG_SND_SOC_SOF_ACP_PROBES) += acp-probes.o snd-sof-amd-renoir-y := pci-rn.o renoir.o snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index 33540f7c421b19..3bb1de8c03762f 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -132,6 +132,7 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * struct snd_soc_acpi_mach *mach; const struct snd_soc_acpi_link_adr *link; struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct amd_pdm_pdata *pdm_pdata; int ret, i; if (acp_data->info.count) { @@ -155,9 +156,25 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * } if (mach && mach->link_mask) { mach->mach_params.subsystem_rev = acp_data->pci_rev; + mach->mach_params.subsystem_vendor = acp_data->subsystem_vendor; + mach->mach_params.subsystem_device = acp_data->subsystem_device; + mach->mach_params.subsystem_id_set = true; mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, + sizeof(*pdm_pdata), + GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); return mach; } } diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index 3984cd627db6df..0bcccf27d13364 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -37,6 +37,30 @@ /* Registers from ACP_DSP_0 block */ #define ACP_DSP0_RUNSTALL 0x414 +/* + * ATU group registers for groups 9 to 16. + * ACPAXI2AXI_ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, + * so groups 9 to 16 are offset by 4 bytes relative to a simple stride. + */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0xC44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0xC48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0xC4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0xC50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0xC54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0xC58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0xC5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0xC60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0xC64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0xC68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0xC6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0xC70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0xC74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0xC78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0xC7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0xC80 + +/* ATU page size value for 2 MB pages - used for carveout groups */ +#define PAGE_SIZE_2M_ENABLE 0x0 /* Registers from ACP_AXI2AXIATU block */ #define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00 @@ -132,6 +156,40 @@ #define ACP_SHA_DMA_INCLUDE_HDR 0x1CCC #define ACP_SHA_PSP_ACK 0x1C74 +/* ACP7.B/7.F MALL/Carveout configuration registers (ACP MMIO offsets) */ +#define ACP7X_MALL_ADDR_VALID 0x114C +#define ACP7X_MALL_BASE_ADDR_LOW 0x1150 +#define ACP7X_MALL_BASE_ADDR_HIGH 0x1154 +#define ACP7X_MALL_SIZE 0x1158 +#define ACP7X_MALL_VALID 0x115C + +/* ASP MPASP mailbox SMN addresses for ACP7.B/7.F carveout firmware load */ +#define MPASP_C2PMSG_173_REG 0x03810BB4 +#define MPASP_C2PMSG_174_REG 0x03810BB8 +#define MPASP_C2PMSG_175_REG 0x03810BBC +#define MPASP_C2PMSG_73_REG 0x03810A24 + +/* ASP mailbox command IDs */ +#define ASP_MBOX_CMD_VALIDATE_IMAGE 0x0001 +#define ASP_MBOX_CMD_CALC_HMAC 0x0002 +#define ASP_MBOX_CMD_VALIDATE_HMAC 0x0003 +#define ASP_MBOX_CMD_GET_CARVEOUT_ADDR 0x0004 + +/* MPASP_C2PMSG_173 control register bit definitions */ +#define ASP_MBOX_READY_BIT BIT(31) +#define ASP_MBOX_CMD_ID_MASK 0xFFFF + +/* ASP mailbox timeout: 5 seconds per ASP firmware load specification */ +#define ASP_MBOX_TIMEOUT_US 5000000 +/* Short timeout for the initial GET_CARVEOUT_ADDR probe: fail fast when + * ASP does not support carveout rather than blocking probe for 5 seconds. + */ +#define ASP_MBOX_PROBE_TIMEOUT_US 50000 +#define ASP_MBOX_POLL_INTERVAL_US 1000 + +/* ASP mailbox payload cookie "ACPB" */ +#define ASP_MBOX_COOKIE 0x41435042 + #define ACP_SCRATCH_REG_0 0x10000 #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c index 1dc6fec6ce57d4..d912f7c4f0e26a 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -168,12 +168,76 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) struct pci_dev *pci = to_pci_dev(sdev->dev); const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *adata; + u64 carveout_offset; unsigned int src_addr, size_fw, dest_addr; u32 page_count, dma_size; + u32 acp_logical_addr; int ret; adata = sdev->pdata->hw_pdata; + if (adata->asp_carveout_base) { + size_fw = adata->fw_code_raw_size; + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; + + if (adata->fw_code_carveout_addr < adata->asp_carveout_base || + adata->fw_code_carveout_addr - adata->asp_carveout_base + + size_fw > adata->asp_carveout_size) { + dev_err(sdev->dev, + "carveout addr 0x%llx out of window [0x%llx+0x%x]\n", + adata->fw_code_carveout_addr, + adata->asp_carveout_base, + adata->asp_carveout_size); + return -EINVAL; + } + carveout_offset = adata->fw_code_carveout_addr - adata->asp_carveout_base; + acp_logical_addr = ACP_SYSTEM_MEMORY_WINDOW + + (adata->asp_carveout_group_start * 8 * SZ_1M) + + (u32)carveout_offset; + + ret = configure_and_run_sha_dma(adata, NULL, + acp_logical_addr, + ACP_IRAM_BASE_ADDRESS, size_fw); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA from carveout failed: %d\n", ret); + return ret; + } + + if (adata->is_sram_in_use) { + configure_pte_for_fw_loading(FW_SRAM_DATA_BIN, + ACP_SRAM_PAGE_COUNT, adata); + src_addr = ACP_SYSTEM_MEMORY_WINDOW + ACP_DEFAULT_SRAM_LENGTH + + (page_count * ACP_PAGE_SIZE); + dest_addr = ACP7X_SRAM_BASE_ADDRESS; + ret = configure_and_run_dma(adata, src_addr, dest_addr, + adata->fw_sram_data_bin_size); + if (ret < 0) { + dev_err(sdev->dev, "SRAM DMA failed: %d\n", ret); + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + return ret; + } + ret = acp_dma_status(adata, 0); + if (ret < 0) + dev_err(sdev->dev, "SRAM DMA status error: %d\n", ret); + } + + /* Enable cache window */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_OFFSET0, + desc->sram_pte_offset); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_SIZE0, + SRAM1_SIZE | BIT(31)); + + if (adata->is_sram_in_use) { + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + } + return ret; + } + if (adata->pci_rev >= ACP7B_PCI_ID) { if (adata->acp_sof_signed_firmware_image) { if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c index 9212a3137cfd2b..a751ffdb0adf3c 100644 --- a/sound/soc/sof/amd/acp-stream.c +++ b/sound/soc/sof/amd/acp-stream.c @@ -15,15 +15,6 @@ #include "acp-dsp-offset.h" #include "acp.h" -#define PTE_GRP1_OFFSET 0x00000000 -#define PTE_GRP2_OFFSET 0x00800000 -#define PTE_GRP3_OFFSET 0x01000000 -#define PTE_GRP4_OFFSET 0x01800000 -#define PTE_GRP5_OFFSET 0x02000000 -#define PTE_GRP6_OFFSET 0x02800000 -#define PTE_GRP7_OFFSET 0x03000000 -#define PTE_GRP8_OFFSET 0x03800000 - int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream) { const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index b059039c9e0d1e..86e2402b8ffd26 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -279,11 +279,18 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, unsigned int tx_count, fw_qualifier, val; int ret; - if (!image_addr) { + if (!image_addr && + !adata->asp_carveout_base) { dev_err(sdev->dev, "SHA DMA image address is NULL\n"); return -EINVAL; } + /* + * Flush the ATU cache before programming SHA DMA registers + * to guarantee coherency at SHA DMA start time. + */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD); if (val & ACP_SHA_RUN) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET); @@ -297,8 +304,13 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, } } - if ((adata->quirks && adata->quirks->signed_fw_image) || - adata->acp_sof_signed_firmware_image) + /* + * The signing header must NOT be included for the carveout path: ASP + * stripped it when depositing the raw binary during VALIDATE_IMAGE. + */ + if (((adata->quirks && adata->quirks->signed_fw_image) || + adata->acp_sof_signed_firmware_image) && + !adata->asp_carveout_base) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); @@ -316,7 +328,17 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, if (ret) return ret; } - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + /* + * SHA_IOC_En triggers an interrupt-on-completion to ASP for source + * address validation. Only set it on the carveout path; enabling it + * on the legacy ATU-window path breaks PSP validation. + */ + if (adata->asp_carveout_base) { + reinit_completion(&adata->sha_dma_complete); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN_WITH_IOC); + } else { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + } ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT, tx_count, tx_count == image_length, @@ -333,6 +355,23 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, return ret; } + if (adata->asp_carveout_base) { + /* + * Wait for the SHA IOC interrupt which fires after ASP validates + * the source address. Use ASP_MBOX_TIMEOUT_US to match the + * budget used for all other ASP mailbox operations. + * usecs_to_jiffies() can round to 0 at low HZ so clamp to at + * least 1 jiffy. + */ + unsigned long timeout_jiffies = + max(1UL, usecs_to_jiffies(ASP_MBOX_TIMEOUT_US)); + + if (!wait_for_completion_timeout(&adata->sha_dma_complete, + timeout_jiffies)) + dev_warn(sdev->dev, + "SHA DMA interrupt not received within timeout, continuing\n"); + } + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER, fw_qualifier, fw_qualifier & DSP_FW_RUN_ENABLE, ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US); @@ -674,9 +713,8 @@ static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); - unsigned int ext_intr_stat1; - unsigned int sdw_pme_stat, sdw_wake_en; - unsigned int i; + u32 ext_intr_stat1, sdw_pme_stat, sdw_wake_en; + u32 i; bool sdw_wake_irq = false; ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); @@ -769,6 +807,20 @@ static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) if (adata->sdw) wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + /* + * SHA DMA completion interrupt (ACP_SHA_STAT, bit 15 of ext_intr_stat). + * ACP_SHA_STAT has no dedicated mask bit in ACP_EXTERNAL_INTR_CNTL; + * it is always enabled by default and fires only when SHA DMA completes + * with SHA_IOC_En set. SHA DMA is triggered exclusively during firmware + * loading, so this interrupt is only asserted in that context. + * Acknowledge and signal sha_dma_complete so configure_and_run_sha_dma() + * can proceed to poll ACP_SHA_DSP_FW_QUALIFIER. + */ + if (ext_intr_stat & ACP_SHA_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_SHA_STAT); + complete(&adata->sha_dma_complete); + irq_flag = 1; + } if (ext_intr_stat & ACP7X_ERROR_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); @@ -1198,7 +1250,10 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1335,7 +1390,10 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1408,6 +1466,20 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) acp_memory_init(sdev); acp_dsp_stream_init(sdev); + /* + * Carveout is the only supported signed firmware load path on + * ACP7.B/7.F. Query ASP now when signed firmware is enabled so + * probe fails cleanly if carveout is unavailable. + */ + if (adata->acp_sof_signed_firmware_image) { + ret = acp7x_query_asp_carveout(sdev); + if (ret) { + dev_err(sdev->dev, + "ASP carveout unavailable, cannot load firmware: %d\n", ret); + goto free_ipc_irq; + } + } + return 0; free_ipc_irq: @@ -1441,10 +1513,7 @@ static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) { struct acp_dev_data *adata; struct amd_sdw_manager *amd_manager; - u32 sdw_pme_stat; - u32 sdw_wake_en; - u32 pme_reg; - u32 wake_mask; + u32 sdw_pme_stat, sdw_wake_en, pme_reg, wake_mask; unsigned int instance; adata = sdev->pdata->hw_pdata; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 5a887959461a26..8c8a565f893efd 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -49,10 +49,14 @@ #define ACP_PAGE_SIZE 0x1000 #define ACP_DMA_CH_RUN 0x02 #define ACP_MAX_DESC_CNT 0x02 +#define ACP_DSP_RUN 0x00 #define DSP_FW_RUN_ENABLE 0x01 #define ACP_SHA_RUN 0x01 #define ACP_SHA_RESET 0x02 #define ACP_SHA_HEADER 0x01 +/* SHA_IOC_En (bit 2): interrupt-on-completion to ASP; must be set alongside sha_run */ +#define ACP_SHA_IOC_EN 0x04 +#define ACP_SHA_RUN_WITH_IOC (ACP_SHA_RUN | ACP_SHA_IOC_EN) #define ACP_DMA_CH_RST 0x01 #define ACP_DMA_CH_GRACEFUL_RST_EN 0x10 #define ACP_ATU_CACHE_INVALID 0x01 @@ -115,6 +119,28 @@ #define ACP_FIRMWARE_SIGNATURE 0x100 #define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE #define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 +#define ACP_ASP_SIGNATURE_LENGTH 512 + +/* + * ACP7.B/7.F carveout: 32 MB region mapped at 2 MB page granularity. + * Use AXI2AXIATU_PAGE_SIZE_Mask2MB (value 0x0) for carveout ATU + * groups giving 32MB / 2MB = 16 PTEs. 4KB pages would require 8192 PTEs and + * is not how the hardware carveout path is configured. + */ +#define ACP7X_CARVEOUT_MAX_SIZE (32 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_SIZE (2 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_COUNT (ACP7X_CARVEOUT_MAX_SIZE / ACP7X_CARVEOUT_PAGE_SIZE) +/* + * PTE high-word flags for ATU entries: + * bit[31]: PAGE_Enable - entry is valid + * bit[30]: MALL_Enable - route accesses through carveout (MALL) memory, + * not standard DRAM. Required for ASP to recognise the SHA DMA + * source as within the carveout and grant ACP_SHA_DSP_FW_QUALIFIER. + * (PAGE_Enable | MALL_Enable ATU entry flags) + */ +#define ACP_ATU_PTE_PAGE_ENABLE BIT(31) +#define ACP_ATU_PTE_MALL_ENABLE BIT(30) +#define ACP_ATU_PTE_CARVEOUT_FLAGS (ACP_ATU_PTE_PAGE_ENABLE | ACP_ATU_PTE_MALL_ENABLE) #define ACP_ERROR_IRQ_MASK BIT(29) #define ACP_SDW0_IRQ_MASK BIT(21) @@ -139,6 +165,30 @@ #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 +/* + * ATU PTE group ACP logical address offsets. + * Each group covers 8 MB (0x00800000). Groups 1-8 are used for PCM + * streams; groups 9-16 are extended groups. + * + * GRP_N_OFFSET = (N - 1) * 8 MB + */ +#define PTE_GRP1_OFFSET 0x00000000 +#define PTE_GRP2_OFFSET 0x00800000 +#define PTE_GRP3_OFFSET 0x01000000 +#define PTE_GRP4_OFFSET 0x01800000 +#define PTE_GRP5_OFFSET 0x02000000 +#define PTE_GRP6_OFFSET 0x02800000 +#define PTE_GRP7_OFFSET 0x03000000 +#define PTE_GRP8_OFFSET 0x03800000 +#define PTE_GRP9_OFFSET 0x04000000 +#define PTE_GRP10_OFFSET 0x04800000 +#define PTE_GRP11_OFFSET 0x05000000 +#define PTE_GRP12_OFFSET 0x05800000 +#define PTE_GRP13_OFFSET 0x06000000 +#define PTE_GRP14_OFFSET 0x06800000 +#define PTE_GRP15_OFFSET 0x07000000 +#define PTE_GRP16_OFFSET 0x07800000 + enum clock_source { ACP_CLOCK_96M = 0, ACP_CLOCK_48M, @@ -152,6 +202,29 @@ struct acp_atu_grp_pte { u32 high; }; +/* ASP mailbox payload for GET_CARVEOUT_ADDR command (0x04) */ +struct asp_get_carveout_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_addr; /* [0x08] ASP: carveout physical base address */ + u32 carveout_size; /* [0x10] ASP: ACP_MALL_SIZE register value */ + u32 mall_addr_valid; /* [0x14] ASP: ACP_MALL_ADDR_VALID register value */ + u32 mall_valid; /* [0x18] ASP: ACP_MALL_VALID register value */ +}; + +/* + * ASP mailbox payload for VALIDATE_IMAGE command (0x01). + * Driver fills src/dest fields; ASP fills carveout_dest_addr and status. + */ +struct asp_validate_image_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_dest_addr; /* [0x08] ASP: physical addr of raw binary in carveout */ + u64 dest_offset; /* [0x10] Driver: destination offset within carveout */ + u64 src_phys_addr; /* [0x18] Driver: signed FW source physical addr in DDR */ + u32 fw_image_size; /* [0x20] Driver: total signed image size in bytes */ +}; + union dma_tx_cnt { struct { unsigned int count : 19; @@ -296,7 +369,36 @@ struct acp_dev_data { unsigned int pdm_sel; bool is_sdw_dev; unsigned int pci_rev; + u32 subsystem_vendor; + u32 subsystem_device; int acp_sof_signed_firmware_image; + /* ACP7.F ASP carveout firmware load state */ + u64 asp_carveout_base; + u32 asp_carveout_size; + u32 asp_carveout_group_start; /* first ATU group index for carveout (from ASP) */ + u32 asp_carveout_group_count; /* number of consecutive ATU groups for carveout */ + /* + * Physical address and raw size of each validated DSP code binary + * within the carveout region (returned by ASP VALIDATE_IMAGE). + * Preserved across suspend/resume so that SHA DMA can reload DSP IRAM + * from the carveout without re-authenticating from disk. + */ + u64 fw_code_carveout_addr; + u32 fw_code_raw_size; + /* Real CPU physical address of the ASP MPASP mailbox payload page */ + phys_addr_t asp_mbox_buf_phys; + void *asp_mbox_buf; + /* + * SHA DMA completion for the carveout path. + * ACP_SHA_STAT (bit 15 of ACP_EXTERNAL_INTR_STAT) has no dedicated + * mask bit in ACP_EXTERNAL_INTR_CNTL - it is always enabled by default. + * SHA DMA is triggered exclusively during firmware loading, so no + * spurious ACP_SHA_STAT interrupts are expected outside that window. + * acp7x_irq_handler signals sha_dma_complete when ACP_SHA_STAT fires; + * configure_and_run_sha_dma() waits on it before polling + * ACP_SHA_DSP_FW_QUALIFIER to ensure ASP has validated the source. + */ + struct completion sha_dma_complete; }; void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); @@ -319,6 +421,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev, const char *fw_filename); int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); +/* ACP7.B/7.F ASP carveout firmware load helper functions */ +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev); +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev); +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev); + /* Block IO callbacks */ int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *src, size_t size); diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c index b6722d11168cbb..521b7c00c76128 100644 --- a/sound/soc/sof/amd/acp7x.c +++ b/sound/soc/sof/amd/acp7x.c @@ -170,8 +170,15 @@ int sof_acp7x_ops_init(struct snd_sof_dev *sdev) acp_sof_post_fw_run_delay = obj->integer.value; } - if (acp_sof_signed_firmware_image) - sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; + if (acp_sof_signed_firmware_image) { + /* + * This driver handles only ACP7.B/7.F which both use the ASP + * carveout firmware load path. Assign unconditionally rather + * than rechecking the revision so the correct loader is always + * selected when signed firmware is enabled. + */ + sof_acp7x_ops.load_firmware = acp7x_load_firmware_carveout; + } if (acp_sof_post_fw_run_delay) sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; diff --git a/sound/soc/sof/amd/acp_asp_mailbox.c b/sound/soc/sof/amd/acp_asp_mailbox.c new file mode 100644 index 00000000000000..a9c0edfe0c3fde --- /dev/null +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vijendar Mukunda + +/* + * ASP (AMD Security Processor) mailbox communication and ACP7.B/7.F carveout + * memory firmware load/restore for the ACP7.B/7.F platform. + */ + +#include +#include +#include +#include +#include +#include + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +/* + * acp7x_asp_send_cmd - send one ASP mailbox command via the ACPI AMSG method. + * + * The ACPI method "AMSG" (stored as 'GSMA' LE) is used to + * proxy commands to ASP. Direct SMN writes to MPASP_C2PMSG_* do not work + * because the BIOS gates host access to those registers and requires all + * ASP mailbox traffic to go through this ACPI method. + * + * The caller must fill adata->asp_mbox_buf with the payload before calling. + * On return the same buffer contains ASP's response fields. + * + * ACPI method signature: AMSG(CommandId, PayloadPhysAddrHi, PayloadPhysAddrLo) + * GET_CARVEOUT_ADDR uses a short 50 ms probe timeout; other commands use 5 s. + */ +static int acp7x_asp_send_cmd(struct snd_sof_dev *sdev, u16 cmd_id) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acpi_device *adev = ACPI_COMPANION(&pci->dev); + union acpi_object args[3]; + struct acpi_object_list arg_list = { ARRAY_SIZE(args), args }; + unsigned long timeout; + acpi_handle handle; + acpi_status status; + unsigned int ready_timeout; + u32 ctrl; + int ret, smn_ret; + + if (!adev) { + dev_err(sdev->dev, "ASP cmd 0x%04x: no ACPI companion device\n", cmd_id); + return -ENODEV; + } + + handle = adev->handle; + + /* Short timeout for GET_CARVEOUT_ADDR to fail fast if ASP lacks carveout support. */ + ready_timeout = (cmd_id == ASP_MBOX_CMD_GET_CARVEOUT_ADDR) ? + ASP_MBOX_PROBE_TIMEOUT_US : ASP_MBOX_TIMEOUT_US; + + timeout = jiffies + usecs_to_jiffies(ready_timeout); + ret = -ETIMEDOUT; + do { + smn_ret = amd_smn_read(0, MPASP_C2PMSG_173_REG, &ctrl); + if (smn_ret) { + dev_err(sdev->dev, "ASP mailbox SMN read failed: %d\n", smn_ret); + return smn_ret; + } + if (ctrl & ASP_MBOX_READY_BIT) { + ret = 0; + break; + } + usleep_range(ASP_MBOX_POLL_INTERVAL_US, ASP_MBOX_POLL_INTERVAL_US + 100); + } while (!time_after(jiffies, timeout)); + + if (ret) { + dev_err(sdev->dev, "ASP mailbox not ready before cmd 0x%04x\n", cmd_id); + return -ETIMEDOUT; + } + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = cmd_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = upper_32_bits(adata->asp_mbox_buf_phys); + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = lower_32_bits(adata->asp_mbox_buf_phys); + + status = acpi_evaluate_object(handle, "AMSG", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + dev_err(sdev->dev, "ASP AMSG cmd 0x%04x: ACPI method failed: %s\n", + cmd_id, acpi_format_exception(status)); + return -EIO; + } + return 0; +} + +/* + * acp7x_configure_carveout_pte - program ATU groups for ACP7.B/7.F carveout. + * + * Programs PAGE_SIZE, BASE_ADDR and PTE entries for each ATU group covering + * the carveout region using 2 MB pages with PAGE_Enable | MALL_Enable flags. + * Must be called after GET_CARVEOUT_ADDR and on every D0 resume since + * acp_init() resets the scratch SRAM and ATU registers. + */ +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u64 phys_addr; + u32 grp_start = adata->asp_carveout_group_start; + u32 grp_count = adata->asp_carveout_group_count; + u32 total_pages, pages_per_group; + u32 grp, page_in_grp, page_idx; + u32 pte_scratch_offset, page_size_reg, base_addr_reg, reg_val; + u32 pte_lo_addr, pte_hi_addr; + u32 low, high; + u32 abs_grp; + + if (!adata->asp_carveout_base) { + dev_err(sdev->dev, "carveout PTE: no valid carveout address\n"); + return -EINVAL; + } + + if (!grp_count || grp_start < 8 || grp_start + grp_count > 16) { + dev_err(sdev->dev, "carveout PTE: invalid ATU group range %u+%u\n", + grp_start, grp_count); + return -EINVAL; + } + + /* + * ASP is expected to allocate exactly four contiguous 8 MB ATU groups + * for the fixed 32 MB carveout. Warn if the response differs so that + * unexpected firmware behaviour is clearly visible in the log. + */ + if (grp_count != 4) + dev_warn(sdev->dev, "carveout PTE: expected 4 ATU groups, got %u\n", + grp_count); + + total_pages = adata->asp_carveout_size / ACP7X_CARVEOUT_PAGE_SIZE; + pages_per_group = total_pages / grp_count; + page_idx = 0; + + for (grp = 0; grp < grp_count; grp++) { + abs_grp = grp_start + grp; + + /* + * asp_carveout_group_start is the 0-based index from + * __ffs(mall_addr_valid): bit N in MallGroupEntries represents + * hardware GRP_(N+1), so abs_grp is 0-based and GRP number is + * (abs_grp + 1). Example: bits 11-14 set -> abs_grp 11..14 -> + * programs GRP_12..GRP_15 (registers 0xC5C..0xC74). + * ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, adding a + * 4-byte gap for abs_grp >= 8 (i.e. GRP_9 and above). + */ + if (abs_grp < 8) + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8; + else + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8 + 4; + + base_addr_reg = page_size_reg + 4; + + pte_scratch_offset = abs_grp * 0x20; + reg_val = desc->sram_pte_offset + pte_scratch_offset; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, page_size_reg, PAGE_SIZE_2M_ENABLE); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base_addr_reg, reg_val | BIT(31)); + + dev_dbg(sdev->dev, + "carveout ATU: abs_grp=%u PAGE_SIZE_reg=0x%x BASE_ADDR_reg=0x%x val=0x%x\n", + abs_grp, page_size_reg, base_addr_reg, + (u32)(reg_val | BIT(31))); + + for (page_in_grp = 0; page_in_grp < pages_per_group; + page_in_grp++, page_idx++) { + pte_lo_addr = ACP_SCRATCH_REG_0 + pte_scratch_offset + (page_in_grp * 8); + pte_hi_addr = pte_lo_addr + 4; + phys_addr = adata->asp_carveout_base + + ((u64)page_idx * ACP7X_CARVEOUT_PAGE_SIZE); + low = lower_32_bits(phys_addr); + high = upper_32_bits(phys_addr) | ACP_ATU_PTE_CARVEOUT_FLAGS; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_lo_addr, low); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_hi_addr, high); + } + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + + dev_dbg(sdev->dev, + "carveout PTE done: %u groups [%u..%u] covering 0x%llx..0x%llx\n", + grp_count, grp_start, grp_start + grp_count - 1, + adata->asp_carveout_base, + adata->asp_carveout_base + (u64)adata->asp_carveout_size); + return 0; +} +EXPORT_SYMBOL_NS(acp7x_configure_carveout_pte, "SND_SOC_SOF_AMD_COMMON"); + +/* + * acp7x_alloc_asp_payload - allocate a physically contiguous buffer below 4 GB. + * + * ASP accesses memory via the SoC fabric, bypassing the IOMMU, so the buffer + * must be below 4 GB and page_to_phys() must return the real CPU physical + * address. Use __GFP_DMA32/__GFP_DMA as hard zone constraints (not hints) + * to guarantee the physical address fits in 32 bits. + */ +static void *acp7x_alloc_asp_payload(struct snd_sof_dev *sdev, phys_addr_t *phys_out) +{ + struct page *page; + void *vaddr; + + page = alloc_page(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO); + if (!page) + page = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO); + if (!page) { + dev_err(sdev->dev, "ASP mbox: failed to allocate below-4GB payload page\n"); + return NULL; + } + + vaddr = page_address(page); + *phys_out = page_to_phys(page); + + if (*phys_out > 0xFFFFFFFFULL) { + dev_err(sdev->dev, "ASP mbox: page at phys=0x%llx still exceeds 4 GB\n", + (u64)*phys_out); + __free_page(page); + return NULL; + } + + return vaddr; +} + +static void acp7x_free_asp_payload(void *vaddr) +{ + if (vaddr) + __free_page(virt_to_page(vaddr)); +} + +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct asp_get_carveout_payload *co_payload; + phys_addr_t phys; + u32 group_bits, first_bit, count; + int ret = 0; + + co_payload = acp7x_alloc_asp_payload(sdev, &phys); + if (!co_payload) + return -ENOMEM; + + co_payload->cookie = ASP_MBOX_COOKIE; + /* Sentinel: ASP overwrites on success; 0xFFFFFFFF after the call means no carveout */ + co_payload->carveout_size = 0xFFFFFFFF; + co_payload->mall_addr_valid = 0xFFFFFFFF; + co_payload->mall_valid = 0xFFFFFFFF; + + adata->asp_mbox_buf = co_payload; + adata->asp_mbox_buf_phys = phys; + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_GET_CARVEOUT_ADDR); + + dev_dbg(sdev->dev, + "GET_CARVEOUT response: status=0x%x addr=0x%llx size=0x%x mall_valid=0x%x mall_addr_valid=0x%x\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size, co_payload->mall_valid, + co_payload->mall_addr_valid); + + if (ret) + goto out_free; + + if (co_payload->status || !co_payload->carveout_addr || + !co_payload->carveout_size || co_payload->carveout_size == 0xFFFFFFFF) { + dev_warn(sdev->dev, + "ASP GET_CARVEOUT_ADDR: status=0x%x addr=0x%llx size=0x%x - no carveout\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size); + ret = -ENODATA; + goto out_free; + } + + if (!IS_ALIGNED(co_payload->carveout_addr, ACP7X_CARVEOUT_PAGE_SIZE) || + co_payload->carveout_size != ACP7X_CARVEOUT_MAX_SIZE) { + dev_warn(sdev->dev, + "ASP carveout: unexpected base alignment or size (addr=0x%llx size=0x%x, expected 0x%x)\n", + co_payload->carveout_addr, co_payload->carveout_size, + ACP7X_CARVEOUT_MAX_SIZE); + ret = -ENODATA; + goto out_free; + } + /* + * Extract ATU group start and count from MallGroupEntries bits[15:0]. + * Scan to find first set bit (start), then count + * contiguous set bits. Example: 0x7800 -> start=11, count=4. + * Defer committing asp_carveout_base/size until all validation passes + * so adata is never left with a non-zero base but zero group_count. + */ + group_bits = co_payload->mall_addr_valid & 0xFFFF; + first_bit = 0; + count = 0; + + if (group_bits) { + first_bit = __ffs(group_bits); + while ((group_bits >> (first_bit + count)) & 1) + count++; + /* Validate contiguous run - reject non-contiguous masks */ + if (group_bits != (((1u << count) - 1) << first_bit)) { + dev_warn(sdev->dev, + "ASP carveout: non-contiguous mall_addr_valid 0x%x unsupported\n", + group_bits); + ret = -ENODATA; + goto out_free; + } + } + if (!count) { + dev_warn(sdev->dev, "ASP carveout: no valid ATU groups in mall_addr_valid\n"); + ret = -ENODATA; + goto out_free; + } + adata->asp_carveout_base = co_payload->carveout_addr; + adata->asp_carveout_size = co_payload->carveout_size; + adata->asp_carveout_group_start = first_bit; + adata->asp_carveout_group_count = count; + + dev_dbg(sdev->dev, "ASP carveout: base=0x%llx size=0x%x grp_start=%u grp_count=%u\n", + adata->asp_carveout_base, adata->asp_carveout_size, + adata->asp_carveout_group_start, adata->asp_carveout_group_count); + +out_free: + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(co_payload); + return ret; +} +EXPORT_SYMBOL_NS(acp7x_query_asp_carveout, "SND_SOC_SOF_AMD_COMMON"); + +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata = plat_data->hw_pdata; + struct asp_validate_image_payload *vi_payload; + const char *fw_filename; + struct page *fw_page; + phys_addr_t vi_phys; + u64 dest; + u32 page_count, dma_size; + u32 size_fw_signed; + unsigned int order; + int ret; + + /* + * Carveout query was already done in amd_sof_acp7x_probe() via + * acp7x_query_asp_carveout(). If ASP did not return a valid carveout + * address, fail immediately - no fallback to legacy path. + */ + + /* Program PTEs for the entire carveout region before any DMA into it. + * acp_init() in resume clears the scratch SRAM so PTEs must be reprogrammed + * on every boot including D3->D0 resume. + */ + ret = acp7x_configure_carveout_pte(sdev); + if (ret) + return ret; + + /* + * On D3->D0 resume the raw binary is already in the carveout region + * (placed by ASP during the previous VALIDATE_IMAGE call). Skip the + * filesystem load and VALIDATE_IMAGE - go directly to the data binary + * path. acp_dsp_pre_fw_run() will SHA-DMA from the preserved carveout + * address into DSP IRAM. + * + * On first boot fw_code_carveout_addr is 0 so the condition below is + * false and the full firmware load path executes. + */ + if (adata->fw_code_carveout_addr) + goto load_data_binary; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_code_bin); + if (!fw_filename) + return -ENOMEM; + + /* Release any previously loaded firmware before requesting again */ + if (sdev->basefw.fw) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + } + + ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_code_bin, ret); + return ret; + } + + page_count = PAGE_ALIGN(sdev->basefw.fw->size) >> PAGE_SHIFT; + dma_size = page_count * ACP_PAGE_SIZE; + + /* + * Allocate the firmware staging buffer below 4 GB using alloc_pages so + * virt_to_phys() gives the real CPU physical address that ASP can read + * via the SoC fabric (bypassing the IOMMU). dma_alloc_coherent() gives + * an IOMMU-remapped DMA address which ASP cannot use. + * + * get_order() gives the smallest power-of-2 number of pages covering + * dma_size; alloc_pages() allocates that contiguous range. + */ + order = get_order(dma_size); + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO, order); + if (!fw_page) + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA | __GFP_ZERO, order); + if (!fw_page) { + dev_err(sdev->dev, "failed to allocate FW staging buffer\n"); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + adata->bin_buf = page_address(fw_page); + adata->sha_dma_addr = page_to_phys(fw_page); + + if (adata->sha_dma_addr > 0xFFFFFFFFULL) { + dev_err(sdev->dev, + "FW staging buffer at phys=0x%llx exceeds 4 GB\n", + (u64)adata->sha_dma_addr); + __free_pages(fw_page, order); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + memcpy(adata->bin_buf, sdev->basefw.fw->data, sdev->basefw.fw->size); + adata->fw_bin_size = sdev->basefw.fw->size; + + if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, "signed FW too small: %u\n", adata->fw_bin_size); + ret = -EINVAL; + goto free_bin; + } + /* + * SizeFWSigned (header[0x14]) is the size of the signed payload + * including the ASP signature trailer. ASP strips the ACP image + * header (0x100 bytes) but preserves the signature, so the raw + * binary deposited in carveout has size = SizeFWSigned and the + * SHA DMA length must equal SizeFWSigned. + */ + size_fw_signed = get_unaligned_le32(adata->bin_buf + ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); + + if (size_fw_signed <= ACP_ASP_SIGNATURE_LENGTH || + size_fw_signed > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, + "invalid SizeFWSigned 0x%x in carveout firmware header\n", + size_fw_signed); + ret = -EINVAL; + goto free_bin; + } + adata->fw_code_raw_size = size_fw_signed; + vi_payload = acp7x_alloc_asp_payload(sdev, &vi_phys); + if (!vi_payload) { + ret = -ENOMEM; + goto free_bin; + } + + vi_payload->cookie = ASP_MBOX_COOKIE; + vi_payload->dest_offset = 0; + /* + * ASP bypasses the CPU IOMMU and accesses memory via the SoC + * fabric directly, so it must be given a real CPU physical + * address for the staging buffer (not an IOMMU/DMA-translated + * address). Use virt_to_phys(bin_buf) which returns the real + * CPU physical address, same as for mailbox payload buffers. + */ + vi_payload->src_phys_addr = (u64)virt_to_phys(adata->bin_buf); + vi_payload->fw_image_size = adata->fw_bin_size; + + adata->asp_mbox_buf = vi_payload; + adata->asp_mbox_buf_phys = vi_phys; + + dev_dbg(sdev->dev, "sending VALIDATE_IMAGE(0x01): src=0x%llx size=0x%x dest_offset=0x%llx phys=0x%llx\n", + vi_payload->src_phys_addr, vi_payload->fw_image_size, + vi_payload->dest_offset, (u64)adata->asp_mbox_buf_phys); + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_VALIDATE_IMAGE); + + /* Read result before freeing payload */ + if (!ret && !vi_payload->status) { + dest = vi_payload->carveout_dest_addr; + + if (dest < adata->asp_carveout_base || + dest - adata->asp_carveout_base + adata->fw_code_raw_size > + adata->asp_carveout_size) { + dev_err(sdev->dev, + "VALIDATE_IMAGE: dest 0x%llx outside carveout window\n", + dest); + ret = -EIO; + } else { + adata->fw_code_carveout_addr = dest; + } + } else if (!ret && vi_payload->status) { + ret = -EIO; + } + + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(vi_payload); + + if (ret) { + if (ret == -EIO) + dev_err(sdev->dev, "ASP VALIDATE_IMAGE failed\n"); + goto free_bin; + } + + dev_dbg(sdev->dev, "ASP VALIDATE_IMAGE: raw binary at carveout 0x%llx size 0x%x\n", + adata->fw_code_carveout_addr, adata->fw_code_raw_size); + + /* DDR staging buffer is no longer needed - raw binary is in carveout */ + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + +load_data_binary: + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_data_bin); + if (!fw_filename) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + ret = request_firmware(&adata->fw_dbin, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_data_bin, ret); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + } + + ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, + (void *)adata->fw_dbin->data, + adata->fw_dbin->size); + release_firmware(adata->fw_dbin); + adata->fw_dbin = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + +free_bin: + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; +} +EXPORT_SYMBOL_NS(acp7x_load_firmware_carveout, "SND_SOC_SOF_AMD_COMMON"); + diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 0594c1f3deb6bb..5a21ab8ebb4b9b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,8 +26,8 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 -static const struct sof_amd_acp_desc acp7x_chip_info = { - .name = "acp7x", +static const struct sof_amd_acp_desc acp7b_chip_info = { + .name = "acp7b", .pgfsm_base = ACP7X_PGFSM_BASE, .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, @@ -45,13 +45,13 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .reg_end_addr = ACP7X_REG_END, }; -static const struct sof_dev_desc acp7x_desc = { - .machines = snd_soc_acpi_amd_acp7x_sof_machines, +static const struct sof_dev_desc acp7b_desc = { + .machines = snd_soc_acpi_amd_acp7b_sof_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .chip_info = &acp7x_chip_info, + .chip_info = &acp7b_chip_info, .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), .ipc_default = SOF_IPC_TYPE_3, .default_fw_path = { @@ -61,30 +61,81 @@ static const struct sof_dev_desc acp7x_desc = { [SOF_IPC_TYPE_3] = "amd/sof-tplg", }, .default_fw_filename = { - [SOF_IPC_TYPE_3] = "sof-acp7x.ri", + [SOF_IPC_TYPE_3] = "sof-acp7b.ri", }, .nocodec_tplg_filename = "sof-acp.tplg", .ops = &sof_acp7x_ops, .ops_init = sof_acp7x_ops_init, }; +static const struct sof_amd_acp_desc acp7f_chip_info = { + .name = "acp7f", + .pgfsm_base = ACP7X_PGFSM_BASE, + .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, + .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, + .ext_intr_stat = ACP7X_EXT_INTR_STAT, + .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, + .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, + .acp_error_stat = ACP7X_ERROR_STATUS, + .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, + .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, + .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, + .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, + .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, + .reg_end_addr = ACP7X_REG_END, +}; + +static const struct sof_dev_desc acp7f_desc = { + .machines = snd_soc_acpi_amd_acp7f_sof_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &acp7f_chip_info, + .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), + .ipc_default = SOF_IPC_TYPE_3, + .default_fw_path = { + [SOF_IPC_TYPE_3] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC_TYPE_3] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC_TYPE_3] = "sof-acp7f.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_acp7x_ops, + .ops_init = sof_acp7x_ops_init, +}; + +static const struct pci_device_id acp7b_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7b_desc, +}; + +static const struct pci_device_id acp7f_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7f_desc, +}; + static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { unsigned int flag; + flag = snd_amd_acp_find_config(pci); + if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) + return -ENODEV; + switch (pci->revision) { case ACP7B_PCI_ID: + return sof_pci_probe(pci, &acp7b_pci_id); case ACP7F_PCI_ID: - break; + return sof_pci_probe(pci, &acp7f_pci_id); default: return -ENODEV; } - - flag = snd_amd_acp_find_config(pci); - if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) - return -ENODEV; - - return sof_pci_probe(pci, pci_id); } static void acp7x_pci_remove(struct pci_dev *pci) @@ -94,8 +145,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7x_desc}, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID) }, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index fd371850b0d691..91f7ca22a4a7c8 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -89,7 +89,7 @@ void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable) list_for_each_codec(codec, hbus) { /* only set WAKEEN when needed for HDaudio codecs */ mask |= BIT(codec->core.addr); - if (codec->jacktbl.used) + if (codec->jacktbl_used) val |= BIT(codec->core.addr); } } else { @@ -118,7 +118,7 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev) * Wake up all jack-detecting codecs regardless whether an event * has been recorded in STATESTS */ - if (codec->jacktbl.used) + if (codec->jacktbl_used) pm_request_resume(&codec->core.dev); } EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC"); diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index a59ad944237264..22196f789ae4a4 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -1197,7 +1197,7 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, struct device_node *np = pdev->dev.of_node; struct reset_control *rst; struct resource *res; - int irq, ret; + int ret; if (!np) return -ENODEV; @@ -1243,16 +1243,6 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, return ret; } - /* Get irqs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, - dev_name(&pdev->dev), i2s); - if (ret) - return ret; - /* Reset */ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); if (IS_ERR(rst)) @@ -1277,7 +1267,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) { struct stm32_i2s_data *i2s; u32 val; - int ret; + int irq, ret; i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); if (!i2s) @@ -1303,6 +1293,15 @@ static int stm32_i2s_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "Regmap init error\n"); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); + if (ret) + return ret; + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); if (ret) return ret; diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 0ece547407739e..88863dbd05d9bd 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -720,7 +720,7 @@ static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) if (err) { regmap_read(spdifrx->regmap, STM32_SPDIFRX_CR, &cr); - sync_state = FIELD_GET(SPDIFRX_CR_SPDIFEN_MASK, cr) && + sync_state = FIELD_GET(SPDIFRX_CR_SPDIFEN_MASK, cr) == SPDIFRX_SPDIFEN_SYNC; /* SPDIFRX is in STATE_STOP. Disable SPDIFRX to clear errors */ diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index fdc954028d622b..dd3b2d3d6c4c95 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -239,6 +239,16 @@ config SND_SOC_TEGRA_WM8962 boards using the WM8962 codec. Currently, the supported boards are Microsoft Surface RT. +config SND_SOC_TEGRA_WM8994 + tristate "SoC Audio support for Tegra boards using a WM8994 codec" + depends on I2C && GPIOLIB && MFD_WM8994 + select SND_SOC_TEGRA_MACHINE_DRV + select SND_SOC_WM8994 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8994 codec. Currently, the supported boards are + LG Optimus X2 P990. + config SND_SOC_TEGRA_WM9712 tristate "SoC Audio support for Tegra boards using a WM9712 codec" depends on GPIOLIB diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 1c18ef6971c092..b8f90149bfa63d 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -43,10 +43,12 @@ obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o # Tegra machine Support snd-soc-tegra-wm8903-y := tegra_wm8903.o snd-soc-tegra-wm8962-y := tegra_wm8962.o +snd-soc-tegra-wm8994-y := tegra_wm8994.o snd-soc-tegra-machine-y := tegra_asoc_machine.o snd-soc-tegra-audio-graph-card-y := tegra_audio_graph_card.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o obj-$(CONFIG_SND_SOC_TEGRA_WM8962) += snd-soc-tegra-wm8962.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8994) += snd-soc-tegra-wm8994.o obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o obj-$(CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD) += snd-soc-tegra-audio-graph-card.o diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 50b23bbb637bdc..e2d0e5947339da 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -122,6 +122,8 @@ static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = { SND_SOC_DAPM_LINE("Line In Jack", NULL), SND_SOC_DAPM_LINE("Line In", NULL), SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_INPUT("FM Radio"), }; static const struct snd_kcontrol_new tegra_machine_controls[] = { @@ -550,6 +552,10 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) if (asoc->add_common_snd_ops) card->dai_link->ops = &tegra_machine_snd_ops; + err = snd_soc_of_parse_aux_devs(card, "nvidia,aux-devs"); + if (err) + return err; + if (!card->owner) card->owner = THIS_MODULE; if (!card->driver_name) diff --git a/sound/soc/tegra/tegra_wm8994.c b/sound/soc/tegra/tegra_wm8994.c new file mode 100644 index 00000000000000..1550faeba7256f --- /dev/null +++ b/sound/soc/tegra/tegra_wm8994.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra_wm8994.c - Tegra machine ASoC driver for boards using WM8994 codec. +// +// Copyright (C) 2026 Svyatoslav Ryhel +// +// Based on tegra_wm8994 code copyright/by: +// +// Author: Sumit Bhattacharya +// Copyright (C) 2010-2011 - NVIDIA, Inc. +// +// Based on code copyright/by: +// +// (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. +// +// Copyright 2007 Wolfson Microelectronics PLC. +// Author: Graeme Gregory +// graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8994.h" + +#include "tegra_asoc_machine.h" + +static struct snd_soc_jack_pin tegra_wm8994_mic_jack_pins[] = { + { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static unsigned int tegra_wm8994_mclk_rate(unsigned int srate) +{ + unsigned int mclk; + + switch (srate) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12000000; + break; + } + + return mclk; +} + +static const struct snd_kcontrol_new tegra_wm8994_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Int Spk"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic 1"), + SOC_DAPM_PIN_SWITCH("Internal Mic 2"), + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Line Out"), + SOC_DAPM_PIN_SWITCH("FM Radio"), +}; + +static int tegra_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + int err; + + err = tegra_asoc_machine_init(rtd); + if (err) + return err; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) { + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + machine->mic_jack, + tegra_wm8994_mic_jack_pins, + ARRAY_SIZE(tegra_wm8994_mic_jack_pins)); + if (err) { + dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); + return err; + } + + wm8994_mic_detect(component, machine->mic_jack, 1); + } + + return 0; +} + +static int tegra_wm8994_remove(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link = &card->dai_link[0]; + struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link); + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) + wm8994_mic_detect(component, NULL, 1); + + return 0; +} + +SND_SOC_DAILINK_DEFS(wm8994_hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8994_dai = { + .name = "WM8994", + .stream_name = "WM8994 PCM", + .init = tegra_wm8994_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(wm8994_hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm8994 = { + .components = "codec:wm8994", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8994_dai, + .num_links = 1, + .controls = tegra_wm8994_controls, + .num_controls = ARRAY_SIZE(tegra_wm8994_controls), + .remove = tegra_wm8994_remove, + .fully_routed = true, +}; + +static const struct tegra_asoc_data tegra_wm8994_data = { + .mclk_rate = tegra_wm8994_mclk_rate, + .mclk_id = WM8994_SYSCLK_MCLK1, + .card = &snd_soc_tegra_wm8994, + .add_common_dapm_widgets = true, + .add_common_snd_ops = true, + .add_mic_jack = true, + .add_hp_jack = true, +}; + +static const struct of_device_id tegra_wm8994_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8994", .data = &tegra_wm8994_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_wm8994_of_match); + +static struct platform_driver tegra_wm8994_driver = { + .driver = { + .name = "tegra-wm8994", + .of_match_table = tegra_wm8994_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = tegra_asoc_machine_probe, +}; +module_platform_driver(tegra_wm8994_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Tegra+WM8994 machine ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c index d9e5cca94c7324..0489eac165c922 100644 --- a/sound/sparc/cs4231.c +++ b/sound/sparc/cs4231.c @@ -1497,8 +1497,8 @@ static int cs4231_attach_begin(struct platform_device *op, return -ENOENT; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_cs4231), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cs4231), &card); if (err < 0) return err; @@ -1519,28 +1519,24 @@ static int cs4231_attach_finish(struct snd_card *card) err = snd_cs4231_pcm(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_mixer(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_timer(card); if (err < 0) - goto out_err; + return err; err = snd_card_register(card); if (err < 0) - goto out_err; + return err; dev_set_drvdata(&chip->op->dev, chip); dev++; return 0; - -out_err: - snd_card_free(card); - return err; } #ifdef SBUS_SUPPORT @@ -1771,7 +1767,7 @@ static int snd_cs4231_sbus_create(struct snd_card *card, static int cs4231_sbus_probe(struct platform_device *op) { struct resource *rp = &op->resource[0]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1785,12 +1781,14 @@ static int cs4231_sbus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_sbus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -1968,7 +1966,7 @@ static int snd_cs4231_ebus_create(struct snd_card *card, static int cs4231_ebus_probe(struct platform_device *op) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1981,12 +1979,15 @@ static int cs4231_ebus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_ebus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) + return err; + + err = cs4231_attach_finish(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -2004,13 +2005,6 @@ static int cs4231_probe(struct platform_device *op) return -ENODEV; } -static void cs4231_remove(struct platform_device *op) -{ - struct snd_cs4231 *chip = dev_get_drvdata(&op->dev); - - snd_card_free(chip->card); -} - static const struct of_device_id cs4231_match[] = { { .name = "SUNW,CS4231", @@ -2030,7 +2024,6 @@ static struct platform_driver cs4231_driver = { .of_match_table = cs4231_match, }, .probe = cs4231_probe, - .remove = cs4231_remove, }; module_platform_driver(cs4231_driver); diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index ccaa36525f8d63..9fad4102dc7077 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2576,11 +2576,16 @@ static void snd_dbri_free(struct snd_dbri *dbri) (void *)dbri->dma, dbri->dma_dvma); } +static void dbri_card_free(struct snd_card *card) +{ + snd_dbri_free(card->private_data); +} + static int dbri_probe(struct platform_device *op) { struct snd_dbri *dbri; struct resource *rp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; static int dev; int irq; int err; @@ -2598,8 +2603,8 @@ static int dbri_probe(struct platform_device *op) return -ENODEV; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_dbri), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dbri), &card); if (err < 0) return err; @@ -2609,21 +2614,20 @@ static int dbri_probe(struct platform_device *op) sprintf(card->longname, "%s at 0x%02lx:0x%016llx, irq %d", card->shortname, rp->flags & 0xffL, (unsigned long long)rp->start, irq); + card->private_free = dbri_card_free; err = snd_dbri_create(card, op, irq, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } dbri = card->private_data; err = snd_dbri_pcm(card); if (err < 0) - goto _err; + return err; err = snd_dbri_mixer(card); if (err < 0) - goto _err; + return err; /* /proc file handling */ snd_dbri_proc(card); @@ -2631,27 +2635,15 @@ static int dbri_probe(struct platform_device *op) err = snd_card_register(card); if (err < 0) - goto _err; + return err; printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", dev, dbri->regs, dbri->irq, op->dev.of_node->name[9], dbri->mm.version); dev++; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -_err: - snd_dbri_free(dbri); - snd_card_free(card); - return err; -} - -static void dbri_remove(struct platform_device *op) -{ - struct snd_card *card = dev_get_drvdata(&op->dev); - - snd_dbri_free(card->private_data); - snd_card_free(card); } static const struct of_device_id dbri_match[] = { @@ -2672,7 +2664,6 @@ static struct platform_driver dbri_sbus_driver = { .of_match_table = dbri_match, }, .probe = dbri_probe, - .remove = dbri_remove, }; module_platform_driver(dbri_sbus_driver); diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index 1145c7956afde1..30e353e7323d11 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -124,7 +124,7 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) arg->private_data = p; arg->addr.client = p->chset.client; arg->addr.port = p->chset.port; - p->oss_arg = arg; + WRITE_ONCE(p->oss_arg, arg); reset_port_mode(p, arg->seq_mode); @@ -141,16 +141,24 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) static void reset_port_mode(struct snd_emux_port *port, int midi_mode) { + struct snd_seq_oss_arg *arg; + + /* OSS events can arrive before the open path has set oss_arg. */ + arg = READ_ONCE(port->oss_arg); + if (midi_mode) { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; port->drum_flags = DEFAULT_DRUM_FLAGS; port->volume_atten = 0; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + if (arg) + arg->event_passing = + SNDRV_SEQ_OSS_PROCESS_KEYPRESS; } else { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; port->drum_flags = 0; port->volume_atten = 32; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + if (arg) + arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; } } diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index dea3a68ca896ee..634dc36f2eade6 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -143,7 +143,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) return; } if (urb->actual_length < 1) - return; + goto resubmit; payload_len = urb->actual_length - 1; @@ -193,6 +193,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } + resubmit: cdev->ep1_in_urb->actual_length = 0; ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) @@ -394,16 +395,16 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) return ret; } - /* finally, register the card and all its sub-instances */ - ret = snd_card_register(cdev->chip.card); + ret = snd_usb_caiaq_control_init(cdev); if (ret < 0) { - dev_err(dev, "snd_card_register() returned %d\n", ret); + dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); return ret; } - ret = snd_usb_caiaq_control_init(cdev); + /* finally, register the card and all its sub-instances */ + ret = snd_card_register(cdev->chip.card); if (ret < 0) { - dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); + dev_err(dev, "snd_card_register() returned %d\n", ret); return ret; } @@ -454,6 +455,7 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + spin_lock_init(&cdev->midi_lock); mutex_init(&cdev->ep1_out_mutex); *cardp = card; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index dd726ca68e804b..16f65e7867d1e3 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -77,7 +77,8 @@ struct snd_usb_caiaqdev { unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; - spinlock_t spinlock; + spinlock_t spinlock; /* for PCM audio */ + spinlock_t midi_lock; /* midi_x_stream, midi_out_active */ wait_queue_head_t ep1_wait_queue; wait_queue_head_t prepare_wait_queue; int spec_received, audio_parm_answer; diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index 18529484c8dcca..b7d7d24937a743 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -79,6 +79,7 @@ static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *subs { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + guard(spinlock_irqsave)(&cdev->midi_lock); if (up) { cdev->midi_out_substream = substream; if (!cdev->midi_out_active) @@ -151,6 +152,7 @@ void snd_usb_caiaq_midi_output_done(struct urb* urb) { struct snd_usb_caiaqdev *cdev = urb->context; + guard(spinlock_irqsave)(&cdev->midi_lock); cdev->midi_out_active = 0; if (urb->status != 0) return; diff --git a/sound/usb/card.h b/sound/usb/card.h index 8299ac241c6031..f956bcf00f77b4 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -123,6 +123,7 @@ struct snd_usb_endpoint { unsigned int syncmaxsize; /* sync endpoint packet size */ unsigned int fill_max:1; /* fill max packet size always */ unsigned int tenor_fb_quirk:1; /* corrupted feedback data */ + unsigned int snap_fb_quirk:1; /* ignore implausible feedback */ unsigned int datainterval; /* log_2 of data packet interval */ unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ unsigned char silence_value; diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 879d0451536e46..9c844c55ad3f5b 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1908,6 +1908,22 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, else f >>= -ep->freqshift; + if (unlikely(sender->snap_fb_quirk) && + (f < ep->freqn - ep->freqn / 128 || + f > ep->freqn + ep->freqn / 128)) { + /* + * Feedback deviates from nominal far beyond what real + * clock differences can explain. This firmware can get + * stuck at nominal+1 sample per frame, causing audible + * pops. Trust the nominal rate instead. + */ + dev_warn_ratelimited(&ep->chip->dev->dev, + "EP 0x%x: ignoring implausible feedback 0x%x.%04x (nominal 0x%x.%04x)\n", + ep->ep_num, f >> 16, f & 0xffff, + ep->freqn >> 16, ep->freqn & 0xffff); + f = ep->freqn; + } + if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { /* * If the frequency looks valid, set it. diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 6dbaf30232f97b..3e1f40d6b481a9 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -117,7 +117,8 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) } else { /* copy single chunk */ memcpy(runtime->dma_area + - line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize); + line6pcm->in.pos_done * bytes_per_frame, fbuf, + frames * bytes_per_frame); } line6pcm->in.pos_done += frames; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 2932eaf157f47c..d17baf7761c67d 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -155,7 +155,7 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = - kmalloc(array3_size(line6pcm->line6->iso_buffers, + kzalloc(array3_size(line6pcm->line6->iso_buffers, LINE6_ISO_PACKETS, pkt_size), GFP_KERNEL); if (!pstr->buffer) @@ -173,6 +173,14 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); + if (pstr == &line6pcm->in) { + /* prev_fbuf points into the capture buffer we are about + * to free; drop it while no capture URB is in flight. + */ + guard(spinlock_irqsave)(&line6pcm->in.lock); + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } kfree(pstr->buffer); pstr->buffer = NULL; } @@ -554,7 +562,11 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->max_packet_size_out = usb_maxpacket(line6->usbdev, usb_sndisocpipe(line6->usbdev, ep_write)); - if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + /* reject max packet sizes smaller than bytes-per-frame; + * (the magic number 6 is taken from the playback case) + */ + if (line6pcm->max_packet_size_in < 6 || + line6pcm->max_packet_size_out < 6) { dev_err(line6pcm->line6->ifcdev, "cannot get proper max packet size\n"); return -EINVAL; diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index b6e22244e03a66..a95f26a3cfa7b0 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -423,7 +423,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, { int err; - if (cval->cached & BIT(channel)) { + if (test_bit(channel, cval->cached)) { *value = cval->cache_val[index]; return 0; } @@ -440,7 +440,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, cval->control, channel, err); return err; } - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = *value; return 0; } @@ -604,7 +604,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, value); if (err < 0) return err; - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = value; return 0; } @@ -649,20 +649,25 @@ static int check_matrix_bitmap(unsigned char *bmap, /* * add an alsa control element - * search and increment the index until an empty slot is found. + * when @look_for_empty is set, search and increment the index until an empty + * slot is found. * * if failed, give up and free the control instance. */ int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info) + bool is_std_info, + bool look_for_empty) { struct usb_mixer_interface *mixer = list->mixer; int err; - while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) - kctl->id.index++; + if (look_for_empty) { + while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) + kctl->id.index++; + } + err = snd_ctl_add(mixer->chip->card, kctl); if (err < 0) { usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", @@ -1449,7 +1454,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, * properly. */ if (ret) - cval->cached = 0; + bitmap_zero(cval->cached, MAX_CHANNELS + 1); cval->initialized = 1; } @@ -1531,6 +1536,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int ret; + guard(mutex)(&cval->head.mixer->lock); if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1565,6 +1571,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err; + guard(mutex)(&cval->head.mixer->lock); ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) { cnt = 0; @@ -1595,10 +1602,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); - int max_val = get_max_exposed(cval); + int max_val; int c, cnt, val, oval, err; int changed = 0; + guard(mutex)(&cval->head.mixer->lock); + max_val = get_max_exposed(cval); if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { @@ -1646,6 +1655,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return filter_error(cval, err); @@ -2377,7 +2387,7 @@ static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, */ static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, - int in_pin, int in_ch, int num_outs, + int in_ch, int num_outs, int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; @@ -2472,34 +2482,13 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, - void *raw_desc) +/* UAC1 mixer unit */ +static int parse_audio_mixer_unit_v1(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) { - struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - err = uac_mixer_unit_get_channels(state, desc); - if (err < 0) { - usb_audio_err(state->chip, - "invalid MIXER UNIT descriptor %d\n", - unitid); - return err; - } - - num_outs = err; - input_pins = desc->bNrInPins; - - if (state->mixer->protocol == UAC_VERSION_2 || - state->mixer->protocol == UAC_VERSION_3) { - if (input_pins * num_outs > 256) { - usb_audio_err(state->chip, - "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", - unitid, input_pins, num_outs); - return -EINVAL; - } - } + int num_ins, pin, ich, och, err; num_ins = 0; ich = 0; @@ -2518,25 +2507,90 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_ins, num_outs)) break; for (; ich < num_ins; ich++) { - int och, ich_has_controls = 0; - for (och = 0; och < num_outs; och++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); - if (check_matrix_bitmap(c, ich, och, num_outs)) { - ich_has_controls = 1; + if (check_matrix_bitmap(c, ich, och, num_outs)) break; - } } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, num_outs, + if (och < num_outs) + build_mixer_unit_ctl(state, desc, ich, num_outs, unitid, &iterm); } } return 0; } +/* UAC2/UAC3 mixer unit */ +static int parse_audio_mixer_unit_v2(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) +{ + struct usb_audio_term iterm; + int pin, och, err; + + if (input_pins * num_outs > 256 || + mixer_bitmap_overflow(desc, state->mixer->protocol, + input_pins, num_outs)) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + continue; + if (!num_outs) + continue; + err = check_input_term(state, desc->baSourceID[pin], &iterm); + if (err < 0) + return err; + + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, pin, och, num_outs)) + break; + } + if (och < num_outs) + build_mixer_unit_ctl(state, desc, pin, num_outs, + unitid, &iterm); + } + return 0; +} + +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_mixer_unit_descriptor *desc = raw_desc; + int num_outs; + + num_outs = uac_mixer_unit_get_channels(state, desc); + if (num_outs < 0) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return num_outs; + } + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + default: + return parse_audio_mixer_unit_v1(state, unitid, + desc->bNrInPins, num_outs, + desc); + case UAC_VERSION_2: + case UAC_VERSION_3: + return parse_audio_mixer_unit_v2(state, unitid, + desc->bNrInPins, num_outs, + desc); + } +} + /* * Processing Unit / Extension Unit */ @@ -2548,6 +2602,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int err, val; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.integer.value[0] = cval->min; @@ -2565,6 +2620,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -2916,6 +2972,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.enumerated.item[0] = 0; @@ -2933,6 +2990,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -3205,6 +3263,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) } usb_free_urb(mixer->rc_urb); kfree(mixer->rc_setup_packet); + mutex_destroy(&mixer->lock); kfree(mixer); } @@ -3615,7 +3674,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &list->kctl->id); } @@ -3682,7 +3741,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, __u8 channel = value & 0xff; unsigned int count = 0; - if (channel >= MAX_CHANNELS) { + if (channel > MAX_CHANNELS) { usb_audio_dbg(mixer->chip, "%s(): bogus channel number %d\n", __func__, channel); @@ -3714,9 +3773,9 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) { if (channel) - info->cached &= ~BIT(channel); + clear_bit(channel, info->cached); else /* master channel */ - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); } snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -3849,6 +3908,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) return -ENOMEM; } + mutex_init(&mixer->lock); + mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) { case UAC_VERSION_1: @@ -3952,7 +4013,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue; - if (cval->cached & BIT(c + 1)) { + if (test_bit(c + 1, cval->cached)) { err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) @@ -3962,7 +4023,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) } } else { /* master */ - if (cval->cached) + if (test_bit(0, cval->cached)) snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index cf45c39cbccc59..c40f832852be1b 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -18,6 +18,7 @@ struct usb_mixer_interface { struct usb_host_interface *hostif; struct list_head list; unsigned int ignore_ctl_error; + struct mutex lock; /* lock for feature unit callbacks */ /* UAC2 status interrupt endpoint; owned by mixer.c */ struct urb *urb; /* array[MAX_ID_ELEMS], indexed by unit id */ @@ -92,7 +93,7 @@ struct usb_mixer_elem_info { int min, max, res; int max_exposed; /* control API exposes the value in 0..max_exposed */ int dBmin, dBmax; - int cached; + DECLARE_BITMAP(cached, MAX_CHANNELS + 1); int cache_val[MAX_CHANNELS]; u8 initialized; u8 min_mute; @@ -112,10 +113,11 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info); + bool is_std_info, + bool look_for_empty); #define snd_usb_mixer_add_control(list, kctl) \ - snd_usb_mixer_add_list(list, kctl, true) + snd_usb_mixer_add_list(list, kctl, true, true) void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 1e266fdbbb8e77..01210cc3fae729 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -167,7 +167,7 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, } kctl->private_free = snd_usb_mixer_elem_free; /* don't use snd_usb_mixer_add_control() here, this is a special list element */ - return snd_usb_mixer_add_list(list, kctl, false); + return snd_usb_mixer_add_list(list, kctl, false, true); } /* diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 369968565c19d5..6808730ef047ed 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -292,7 +292,7 @@ static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *value) /* Device may not support reading input controls. * Return cached value or default to avoid blocking module load. */ - if (elem->cached) + if (test_bit(0, elem->cached)) *value = elem->cache_val[0]; else *value = 0; /* Default: first option */ @@ -353,7 +353,7 @@ static int forte_input_gain_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -364,7 +364,7 @@ static int forte_input_gain_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -405,7 +405,7 @@ static int forte_ctl_enum_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -416,7 +416,7 @@ static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -454,7 +454,7 @@ static int forte_ctl_switch_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -465,7 +465,7 @@ static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -532,7 +532,7 @@ static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) int i; for (i = 0; i < elem->channels; i++) - if (elem->cached & (1 << i)) + if (test_bit(i, elem->cached)) snd_usb_set_cur_mix_value(elem, i, i, elem->cache_val[i]); return 0; @@ -692,7 +692,7 @@ static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); return 0; } diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 14fb1ad764a7a8..18e8c2bd267832 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -189,6 +189,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* route has no bias */ ucontrol->value.enumerated.item[0] = elem->cache_val[index]; @@ -204,6 +205,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, char buf[sizeof(route_msg)]; int val, val_org, err; + guard(mutex)(&elem->head.mixer->lock); /* get the new value (no bias for routes) */ val = ucontrol->value.enumerated.item[0]; @@ -236,7 +238,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -258,6 +260,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -272,6 +275,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0]; @@ -295,7 +299,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -310,6 +314,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, val = ucontrol->value.integer.value[0]; + guard(mutex)(&elem->head.mixer->lock); /* prepare the message buffer from template */ switch (elem->head.id) { case SND_US16X08_ID_BYPASS: @@ -336,7 +341,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -346,6 +351,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); + guard(mutex)(&elem->head.mixer->lock); switch (elem->head.id) { case SND_US16X08_ID_BUSS_OUT: ucontrol->value.integer.value[0] = elem->cache_val[0]; @@ -368,6 +374,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -382,6 +389,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -404,7 +412,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -428,6 +436,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, int index = ucontrol->id.index; int val_idx = COMP_STORE_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = store->val[val_idx][index]; return 0; @@ -445,6 +454,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, int threshold, ratio, attack, release, gain, switch_on; int err; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -508,7 +518,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, } store->val[val_idx][index] = val; - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -521,6 +531,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* get low switch from cache is enough, cause all bands are together */ val = store->val[EQ_STORE_BAND_IDX(elem->head.id)] [EQ_STORE_PARAM_IDX(elem->head.id)][index]; @@ -540,6 +551,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, int val, err = 0; int b_idx; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol); @@ -567,7 +579,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -582,6 +594,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = store->val[b_idx][p_idx][index]; ucontrol->value.integer.value[0] = val; @@ -601,6 +614,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -631,7 +645,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, store->val[b_idx][p_idx][index] = val; /* store new value in EQ band cache */ - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -729,6 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; u8 meter_urb[64] = {0}; + guard(mutex)(&elem->head.mixer->lock); switch (kcontrol->private_value) { case 0: { char tmp[sizeof(mix_init_msg1)]; @@ -788,6 +803,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; int val; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -1118,7 +1134,8 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, strscpy(kctl->id.name, name, sizeof(kctl->id.name)); - err = snd_usb_mixer_add_control(&elem->head, kctl); + /* look_for_empty=false, as we want the fixed id.index as channel */ + err = snd_usb_mixer_add_list(&elem->head, kctl, true, false); if (err < 0) return err; @@ -1352,7 +1369,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) } for (i = 0; i < 8; i++) elem->cache_val[i] = i < 2 ? i : i + 2; - elem->cached = 0xff; + bitmap_set(elem->cached, 0, 8); /* create compressor mixer elements */ comp_store = snd_us16x08_create_comp_store(); @@ -1374,7 +1391,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) if (err < 0) return err; elem->cache_val[0] = master_controls[i].default_val; - elem->cached = 1; + set_bit(0, elem->cached); } /* add channel controls */ @@ -1394,7 +1411,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) elem->cache_val[j] = channel_controls[i].default_val; } - elem->cached = 0xffff; + bitmap_set(elem->cached, 0, SND_US16X08_MAX_CHANNELS); } /* create eq store */ diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 38d78b8345f177..8393a0af7b40c5 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2198,6 +2198,16 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */ ep->syncmaxsize == 4) ep->tenor_fb_quirk = 1; + + /* + * Mayflower ARC AMP DAC (MAG Technology USB front-end): the + * feedback can get stuck at nominal+1 sample per frame, causing + * pops every 1-2 sec until stream restart. Snap implausible + * feedback to nominal. + */ + if (ep->chip->usb_id == USB_ID(0x0493, 0x4170) && + ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + ep->snap_fb_quirk = 1; } /* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ @@ -2332,6 +2342,21 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, int stream) { switch (chip->usb_id) { + case USB_ID(0x0499, 0x5500): /* Yamaha 01V96i */ + /* + * The 01V96i declares its audio streaming interfaces as + * vendor-specific (class 0xff) and provides no class-specific + * endpoint descriptor. parse_uac_endpoint_attributes() therefore + * returns no attributes, and the driver never issues the SET_CUR + * sampling-frequency request the hardware needs to engage its USB + * stream: playback and capture PCMs report RUNNING, but the desk + * passes no audio. + * + * Force the attribute on so the stream is initialised the same way + * the Windows driver does it. + */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ /* Optoplay sets the sample rate attribute although * it seems not supporting it in fact. @@ -2366,6 +2391,14 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, */ fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; break; + case USB_ID(0x0a12, 0x1244): /* Shanling UP4 */ + /* + * MaxPacketsOnly is set, but the device expects nominal-size + * packets. Filling 48 frames per packet at 44.1 kHz results + * in silence. No effect at 48 kHz, where both sizes match. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; + break; case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */ /* mic works only when ep packet size is set to wMaxPacketSize */ fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX; @@ -2471,6 +2504,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0802, /* Logitech Webcam C200 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */ @@ -2543,6 +2578,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | QUIRK_FLAG_FORCE_IFACE_RESET), + DEVICE_FLG(0x0644, 0x8047, /* TASCAM US-16x08 */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_FORCE_IFACE_RESET), DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), @@ -2624,6 +2662,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1237, 0x17b8, /* rongyuan GS20 */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2730,6 +2770,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x21b4, 0x0232, /* Ayre QX-5 Twenty */ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x22e8, 0xca10, /* Cambridge Audio streamers */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x2522, 0x0007, /* LH Labs Geek Out HD Audio 1V5 */ QUIRK_FLAG_SET_IFACE_FIRST), DEVICE_FLG(0x25aa, 0x600b, /* TAE1159 */ @@ -2774,6 +2816,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x3443, 0x930d, /* NexiGo N930W 60fps Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x35d8, 0x011d, /* MOONDROP DAWN PRO2 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 368dafa5df3f32..8837dfc1282891 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -118,6 +118,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); + int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset, unsigned long bytes); diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c index a5c4e70277173a..72103f60671692 100644 --- a/sound/virtio/virtio_pcm_msg.c +++ b/sound/virtio/virtio_pcm_msg.c @@ -182,6 +182,25 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss) vss->nmsgs = 0; } +/** + * virtsnd_pcm_msg_reset() - Reset the fill level of all I/O messages. + * @vss: VirtIO PCM substream. + * + * Messages that were only partially filled by the application when the + * substream was stopped are never sent to the device, so their length must be + * cleared before the substream is started again. + * + * Context: Any context. Takes and releases the VirtIO substream spinlock. + */ +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss) +{ + unsigned int i; + + guard(spinlock_irqsave)(&vss->lock); + for (i = 0; vss->msgs && i < vss->nmsgs; ++i) + vss->msgs[i]->length = 0; +} + /** * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. * @vss: VirtIO PCM substream. diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index 1105e7ff3523a0..1091100a4c982b 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -299,6 +299,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) vss->xfer_xrun = false; vss->suspended = false; vss->msg_count = 0; + virtsnd_pcm_msg_reset(vss); memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); vss->pcm_indirect.sw_buffer_size = diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 91dfcf47e979b4..542fc35ec9b733 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -475,7 +475,8 @@ static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) kfree(intelhaddata->chmap->chmap); intelhaddata->chmap->chmap = NULL; - chmap = kzalloc_obj(*chmap); + /* info->chmap must be zero-terminated; allocate a spare terminator entry */ + chmap = kzalloc_objs(*chmap, 2); if (!chmap) return; @@ -575,6 +576,12 @@ static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, intelhaddata->chmap->kctl->info = had_chmap_ctl_info; intelhaddata->chmap->kctl->get = had_chmap_ctl_get; intelhaddata->chmap->chmap = NULL; + + /* drop the inherited TLV read: it walks chmap without our mutex */ + intelhaddata->chmap->kctl->tlv.c = NULL; + intelhaddata->chmap->kctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return 0; } @@ -1634,9 +1641,9 @@ static void hdmi_lpe_audio_free(struct snd_card *card) * This function is called when the i915 driver creates the * hdmi-lpe-audio platform device. */ -static int __hdmi_lpe_audio_probe(struct platform_device *pdev) +static int hdmi_lpe_audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_intelhad_card *card_ctx; struct snd_intelhad *ctx; struct snd_pcm *pcm; @@ -1797,14 +1804,10 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev) schedule_work(&ctx->hdmi_audio_wq); } + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int hdmi_lpe_audio_probe(struct platform_device *pdev) -{ - return snd_card_free_on_error(&pdev->dev, __hdmi_lpe_audio_probe(pdev)); -} - static const struct dev_pm_ops hdmi_lpe_audio_pm = { SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume) }; diff --git a/tools/spi/Makefile b/tools/spi/Makefile index 7fccd245a53515..8cf21ff8cd038e 100644 --- a/tools/spi/Makefile +++ b/tools/spi/Makefile @@ -12,7 +12,7 @@ endif # (this improves performance and avoids hard-to-debug behaviour); MAKEFLAGS += -r -CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include +CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include ALL_TARGETS := spidev_test spidev_fdx ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index f2135d619a0bb4..5bd3159f61a737 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -4,8 +4,6 @@ * * Copyright (c) 2007 MontaVista Software, Inc. * Copyright (c) 2007 Anton Vorontsov - * - * Cross-compile with cross-gcc -I/path/to/cross-kernel/include */ #include @@ -13,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -22,8 +21,7 @@ #include #include #include - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#include static void pabort(const char *s) { @@ -44,9 +42,16 @@ static uint32_t speed = 500000; static uint16_t delay; static uint16_t word_delay; static int verbose; -static int transfer_size; +static int transfer_size = -1; +static int transfers = 1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int compare; +static int nonzero; +static int do_tx = 1, do_rx = 1; +static int random_input; +static int predictable; +static int input_choices; static uint8_t default_tx[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -69,6 +74,10 @@ static void hex_dump(const void *src, size_t length, size_t line_size, unsigned char c; printf("%s | ", prefix); + + if (length == 0) + printf("__ ||\n"); + while (length-- > 0) { printf("%02X ", *address++); if (!(++i % line_size) || (length == 0 && i % line_size)) { @@ -92,68 +101,120 @@ static void hex_dump(const void *src, size_t length, size_t line_size, * Unescape - process hexadecimal escape character * converts shell input "\x23" -> 0x23 */ -static int unescape(char *_dst, char *_src, size_t len) +static int unescape(char *src, char *dst, size_t size) { - int ret = 0; - int match; - char *src = _src; - char *dst = _dst; + char *out = dst; unsigned int ch; - while (*src) { - if (*src == '\\' && *(src+1) == 'x') { - match = sscanf(src + 2, "%2x", &ch); - if (!match) - pabort("malformed input string"); + while (*src && size--) { + if (src[0] == '\\' && src[1] != '\0') { + src++; + + if (src[0] == 'x' && + isxdigit((unsigned char)src[1]) && + isxdigit((unsigned char)src[2]) && + sscanf(&src[1], "%2x", &ch)) { + *out++ = (unsigned char)ch; + src += 3; + continue; + } - src += 4; - *dst++ = (unsigned char)ch; - } else { - *dst++ = *src++; + *out++ = '\\'; + if (!size--) + break; } - ret++; + *out++ = *src++; } - return ret; + return out - dst; } -static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) +static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len) { int ret; int out_fd; - struct spi_ioc_transfer tr = { - .tx_buf = (unsigned long)tx, - .rx_buf = (unsigned long)rx, - .len = len, - .delay_usecs = delay, - .word_delay_usecs = word_delay, - .speed_hz = speed, - .bits_per_word = bits, - }; - - if (mode & SPI_TX_OCTAL) - tr.tx_nbits = 8; - else if (mode & SPI_TX_QUAD) - tr.tx_nbits = 4; - else if (mode & SPI_TX_DUAL) - tr.tx_nbits = 2; - if (mode & SPI_RX_OCTAL) - tr.rx_nbits = 8; - else if (mode & SPI_RX_QUAD) - tr.rx_nbits = 4; - else if (mode & SPI_RX_DUAL) - tr.rx_nbits = 2; - if (!(mode & SPI_LOOP)) { - if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) - tr.rx_buf = 0; - else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) - tr.tx_buf = 0; + size_t bytes_per_word = DIV_ROUND_UP(bits, 8); + int effective_transfers; + struct spi_ioc_transfer *tr; + size_t len_per_transfer; + const uint8_t *tx_buf = tx; + const uint8_t *rx_buf = rx; + size_t rem; + + if (len) { + effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word)); + len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word); + } else { + /* zero-length transfers may be handled by the driver, deliver as ordered */ + effective_transfers = transfers; + len_per_transfer = 0; + } + + if (SPI_MSGSIZE(effective_transfers) == 0) + pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded"); + + tr = calloc(effective_transfers, sizeof(*tr)); + if (!tr) + pabort("can't allocate transfer array"); + + rem = len; + + for (int i = 0; i < effective_transfers; i++) { + size_t n = min(rem, len_per_transfer); + + tr[i] = (struct spi_ioc_transfer) { + .tx_buf = (unsigned long)tx_buf, + .rx_buf = (unsigned long)rx_buf, + .len = n, + .delay_usecs = delay, + .word_delay_usecs = word_delay, + .speed_hz = speed, + .bits_per_word = bits, + }; + + if (tx_buf) + tx_buf += n; + + if (rx_buf) + rx_buf += n; + + rem -= n; + + if (mode & SPI_TX_OCTAL) + tr[i].tx_nbits = 8; + else if (mode & SPI_TX_QUAD) + tr[i].tx_nbits = 4; + else if (mode & SPI_TX_DUAL) + tr[i].tx_nbits = 2; + if (mode & SPI_RX_OCTAL) + tr[i].rx_nbits = 8; + else if (mode & SPI_RX_QUAD) + tr[i].rx_nbits = 4; + else if (mode & SPI_RX_DUAL) + tr[i].rx_nbits = 2; + if (!(mode & SPI_LOOP)) { + if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + tr[i].rx_buf = 0; + else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) + tr[i].tx_buf = 0; + } } - ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); - if (ret < 1) + ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr); + free(tr); + if (ret < 0) { + const size_t dump_len = min_t(size_t, len, 256); + int saved_errno = errno; + + if (tx) { + hex_dump(tx, dump_len, 32, "TX"); + if (len > dump_len) + printf("... (%zu more bytes)\n", len - dump_len); + } + errno = saved_errno; pabort("can't send spi message"); + } - if (verbose) + if (verbose && tx) hex_dump(tx, len, 32, "TX"); if (output_file) { @@ -168,19 +229,31 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) close(out_fd); } - if (verbose) + if (verbose && rx) hex_dump(rx, len, 32, "RX"); + + if (tx && rx && (compare || mode & SPI_LOOP)) { + if (memcmp(tx, rx, len)) { + fprintf(stderr, "transfer error !\n"); + hex_dump(tx, len, 32, "TX"); + hex_dump(rx, len, 32, "RX"); + exit(1); + } + } } static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNOPRSTZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" " -d --delay delay (usec)\n" " -w --word-delay word delay (usec)\n" " -l --loop loopback\n" + " -c --compare compare RX'ed and TX'ed data\n" + " -t --no-tx don't send data\n" + " -r --no-rx don't receive data\n" "spi mode:\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" @@ -194,8 +267,10 @@ static void print_usage(const char *prog) "data:\n" " -i --input input data from a file (e.g. \"test.bin\")\n" " -o --output output data to a file (e.g. \"results.bin\")\n" - " -p Send data (e.g. \"1234\\xde\\xad\")\n" - " -S --size transfer size\n" + " -p send data (e.g. \"1234\\xde\\xad\")\n" + " -z --nonzero don't send 0x00 or 0xff bytes\n" + " -P --predictable transfer the given number of sequential bytes\n" + " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" " -b --bpw bits per word\n" @@ -204,6 +279,7 @@ static void print_usage(const char *prog) " -N --no-cs no chip select\n" " -R --ready slave pulls low to pause\n" " -M --mosi-idle-low leave mosi line low when idle\n" + " -T --transfers number of transfers\n" "misc:\n" " -v --verbose Verbose (show tx buffer)\n"); exit(1); @@ -218,6 +294,9 @@ static void parse_opts(int argc, char *argv[]) { "delay", 1, 0, 'd' }, { "word-delay", 1, 0, 'w' }, { "loop", 0, 0, 'l' }, + { "compare", 0, 0, 'c' }, + { "no-tx", 0, 0, 't' }, + { "no-rx", 0, 0, 'r' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "rx-cpha-flip", 0, 0, 'F' }, @@ -229,6 +308,7 @@ static void parse_opts(int argc, char *argv[]) { "input", 1, 0, 'i' }, { "output", 1, 0, 'o' }, { "size", 1, 0, 'S' }, + { "nonzero", 0, 0, 'z' }, { "iter", 1, 0, 'I' }, { "bpw", 1, 0, 'b' }, { "lsb", 0, 0, 'L' }, @@ -236,12 +316,14 @@ static void parse_opts(int argc, char *argv[]) { "no-cs", 0, 0, 'N' }, { "ready", 0, 0, 'R' }, { "mosi-idle-low", 0, 0, 'M' }, + { "predictable", 1, 0, 'P' }, + { "transfers", 1, 0, 'T' }, { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:", lopts, NULL); if (c == -1) @@ -265,6 +347,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'i': input_file = optarg; + input_choices++; break; case 'o': output_file = optarg; @@ -272,6 +355,15 @@ static void parse_opts(int argc, char *argv[]) case 'l': mode |= SPI_LOOP; break; + case 'c': + compare = 1; + break; + case 't': + do_tx = 0; + break; + case 'r': + do_rx = 0; + break; case 'H': mode |= SPI_CPHA; break; @@ -296,6 +388,9 @@ static void parse_opts(int argc, char *argv[]) case 'M': mode |= SPI_MOSI_IDLE_LOW; break; + case 'T': + transfers = atoi(optarg); + break; case 'N': mode |= SPI_NO_CS; break; @@ -307,6 +402,12 @@ static void parse_opts(int argc, char *argv[]) break; case 'p': input_tx = optarg; + input_choices++; + break; + case 'P': + transfer_size = atoi(optarg); + predictable = 1; + input_choices++; break; case '2': mode |= SPI_TX_DUAL; @@ -319,6 +420,11 @@ static void parse_opts(int argc, char *argv[]) break; case 'S': transfer_size = atoi(optarg); + random_input = 1; + input_choices++; + break; + case 'z': + nonzero = 1; break; case 'I': iterations = atoi(optarg); @@ -341,17 +447,19 @@ static void transfer_escaped_string(int fd, char *str) { size_t size = strlen(str); uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; tx = malloc(size); if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(size); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(size); + if (!rx) + pabort("can't allocate rx buffer"); + } - size = unescape((char *)tx, str, size); + size = unescape(str, (char *)tx, size); transfer(fd, tx, rx, size); free(rx); free(tx); @@ -363,7 +471,7 @@ static void transfer_file(int fd, char *filename) struct stat sb; int tx_fd; uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; if (stat(filename, &sb) == -1) pabort("can't stat input file"); @@ -376,9 +484,12 @@ static void transfer_file(int fd, char *filename) if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(sb.st_size); - if (!rx) - pabort("can't allocate rx buffer"); + + if (do_rx) { + rx = malloc(sb.st_size); + if (!rx) + pabort("can't allocate rx buffer"); + } bytes = read(tx_fd, tx, sb.st_size); if (bytes != sb.st_size) @@ -409,33 +520,40 @@ static void show_transfer_rate(void) static void transfer_buf(int fd, int len) { - uint8_t *tx; - uint8_t *rx; + uint8_t *tx = NULL; + uint8_t *rx = NULL; int i; - tx = malloc(len); - if (!tx) - pabort("can't allocate tx buffer"); - for (i = 0; i < len; i++) - tx[i] = random(); + if (do_tx) { + tx = malloc(len); + if (!tx) + pabort("can't allocate tx buffer"); + for (i = 0; i < len; i++) { + if (predictable) { + int v = i - iterations; + + if (nonzero) + v = ((v % 254) + 254) % 254 + 1; + tx[i] = (char)v; + } else { + do { + tx[i] = random(); + } while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + } + } + } - rx = malloc(len); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(len); + if (!rx) + pabort("can't allocate rx buffer"); + } transfer(fd, tx, rx, len); - - _write_count += len; - _read_count += len; - - if (mode & SPI_LOOP) { - if (memcmp(tx, rx, len)) { - fprintf(stderr, "transfer error !\n"); - hex_dump(tx, len, 32, "TX"); - hex_dump(rx, len, 32, "RX"); - exit(1); - } - } + if (do_tx) + _write_count += len; + if (do_rx) + _read_count += len; free(rx); free(tx); @@ -449,8 +567,36 @@ int main(int argc, char *argv[]) parse_opts(argc, argv); - if (input_tx && input_file) - pabort("only one of -p and --input may be selected"); + if (input_choices > 1) + pabort("at most one of -S (--size), -p, -i (--input), -P (--predictable) may be selected, " + "and each may be specified only once"); + + if ((random_input || predictable) && transfer_size < 0) + pabort("a size argument is mandatory for -S (--size) and -P (--predictable)"); + + if (iterations && transfer_size < 0) + pabort("-I (--iter) is only implemented for -S (--size) and -P (--predictable)"); + + if (nonzero && transfer_size < 0) + pabort("-z (--nonzero) is only implemented for -S (--size) and -P (--predictable)"); + + if (compare && (!do_tx || !do_rx)) + pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); + + if (!do_rx && output_file) + pabort("-r (--no-rx) conflicts with -o (--output)"); + + if (!do_tx && transfer_size < 0) + pabort("-t (--no-tx) is only implemented for -S (--size) and -P (--predictable)"); + + if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); + + if (transfers < 1) + pabort("-T (--transfers) must be 1 or above"); + + if (bits < 1) + pabort("-b (--bpw) must be 1 or above"); fd = open(device, O_RDWR); if (fd < 0) @@ -507,7 +653,7 @@ int main(int argc, char *argv[]) transfer_escaped_string(fd, input_tx); else if (input_file) transfer_file(fd, input_file); - else if (transfer_size) { + else if (transfer_size >= 0) { struct timespec last_stat; clock_gettime(CLOCK_MONOTONIC, &last_stat); @@ -526,7 +672,7 @@ int main(int argc, char *argv[]) printf("total: tx %.1fKB, rx %.1fKB\n", _write_count/1024.0, _read_count/1024.0); } else - transfer(fd, default_tx, default_rx, sizeof(default_tx)); + transfer(fd, default_tx, do_rx ? default_rx : NULL, sizeof(default_tx)); close(fd); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 273853937c2546..bc4fdb6f533759 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -43,6 +43,7 @@ TARGETS += filesystems/open_tree_ns TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify +TARGETS += filesystems/mount_cycle TARGETS += filesystems/nsfs TARGETS += filesystems/fuse TARGETS += filesystems/move_mount @@ -50,6 +51,8 @@ TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns TARGETS += filesystems/xattr +TARGETS += filesystems/mntns_unbindable +TARGETS += filesystems/umount_propagation TARGETS += firmware TARGETS += fpu TARGETS += ftrace diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 3dd8e1176b899f..7b0e1e9ebf1b1e 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -1,3 +1,4 @@ +aloop-test global-timer mixer-test pcm-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 8dab90ad22bb27..afd64a679dc1f6 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c new file mode 100644 index 00000000000000..a58b9fdbdc1751 --- /dev/null +++ b/tools/testing/selftests/alsa/aloop-test.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the hw constraints between the two ends of an snd-aloop cable, + * with and without the "PCM Notify" control, and for the "PCM Slave" + * controls that report the playback side's parameters. + * + * Needs snd-aloop loaded with the default card id "Loopback". The tests use + * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture). + */ +#include +#include +#include +#include +#include +#include "kselftest_harness.h" + +#define FRAMES 1024 +#define MAX_CHANNELS 4 + +struct stream_params { + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; +}; + +/* The playback params differ from the capture params in every field. */ +static const struct stream_params capture_params = { + SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100 +}; + +static const struct stream_params playback_params = { + SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000 +}; + +struct probe_result { + unsigned int rate_min, rate_max; + unsigned int channels_min, channels_max; + bool format_ok; +}; + +/* Value events seen on the cable's controls */ +enum { + EV_ACTIVE = 1 << 0, + EV_FORMAT = 1 << 1, + EV_RATE = 1 << 2, + EV_CHANNELS = 1 << 3, + EV_ACCESS = 1 << 4, +}; + +FIXTURE(aloop) { + char play_name[32]; + char capt_name[32]; + snd_ctl_t *ctl; + bool saved_notify; + bool notify_saved; +}; + +/* The cable's controls are on the capture side device. */ +static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name) +{ + snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM); + snd_ctl_elem_value_set_name(value, name); + snd_ctl_elem_value_set_device(value, 1); + snd_ctl_elem_value_set_subdevice(value, 0); +} + +static long cable_ctl_get(snd_ctl_t *ctl, const char *name) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, name); + if (snd_ctl_elem_read(ctl, value) < 0) + return -1; + if (!strcmp(name, "PCM Slave Access Mode")) + return snd_ctl_elem_value_get_enumerated(value, 0); + return snd_ctl_elem_value_get_integer(value, 0); +} + +static int set_notify(snd_ctl_t *ctl, bool on) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, "PCM Notify"); + snd_ctl_elem_value_set_boolean(value, 0, on); + return snd_ctl_elem_write(ctl, value); +} + +/* Read all pending control events and return the EV_* bits for the cable's controls. */ +static unsigned int read_events(snd_ctl_t *ctl) +{ + static const struct { + const char *name; + unsigned int bit; + } names[] = { + { "PCM Slave Active", EV_ACTIVE }, + { "PCM Slave Format", EV_FORMAT }, + { "PCM Slave Rate", EV_RATE }, + { "PCM Slave Channels", EV_CHANNELS }, + { "PCM Slave Access Mode", EV_ACCESS }, + }; + snd_ctl_event_t *event; + unsigned int seen = 0; + int i; + + snd_ctl_event_alloca(&event); + while (snd_ctl_read(ctl, event) > 0) { + if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM || + !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) || + snd_ctl_event_elem_get_device(event) != 1 || + snd_ctl_event_elem_get_subdevice(event) != 0) + continue; + for (i = 0; i < ARRAY_SIZE(names); i++) + if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name)) + seen |= names[i].bit; + } + return seen; +} + +/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */ +static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, + const struct stream_params *p) +{ + unsigned int buffer_time = 100000; + snd_pcm_hw_params_t *hw; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(*pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_set_access(*pcm, hw, p->access); + if (err >= 0) + err = snd_pcm_hw_params_set_format(*pcm, hw, p->format); + if (err >= 0) + err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels); + if (err >= 0) + err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0); + if (err >= 0) + err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL); + if (err >= 0) + err = snd_pcm_hw_params(*pcm, hw); + if (err < 0) { + snd_pcm_close(*pcm); + *pcm = NULL; + } + return err; +} + +/* What a client probing the device sees, and whether it may use the given format */ +static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format, + struct probe_result *res) +{ + snd_pcm_hw_params_t *hw; + snd_pcm_t *pcm; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(&pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max); + if (err >= 0) + res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format); + snd_pcm_close(pcm); + return err; +} + +static int start_playback(snd_pcm_t *pcm, const struct stream_params *p) +{ + static char silence[FRAMES * MAX_CHANNELS * 4]; + void *bufs[MAX_CHANNELS]; + snd_pcm_sframes_t written; + unsigned int i; + + if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + for (i = 0; i < p->channels; i++) + bufs[i] = silence + i * FRAMES * 4; + written = snd_pcm_writen(pcm, bufs, FRAMES); + } else { + written = snd_pcm_writei(pcm, silence, FRAMES); + } + if (written < 0) + return written; + if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) + return snd_pcm_start(pcm); + return 0; +} + +FIXTURE_SETUP(aloop) { + char ctl_name[32]; + snd_pcm_t *pcm; + int card, err; + + card = snd_card_get_index("Loopback"); + if (card < 0) + SKIP(return, "No Loopback card, snd-aloop is probably not loaded"); + + sprintf(ctl_name, "hw:%d", card); + sprintf(self->play_name, "hw:%d,0,0", card); + sprintf(self->capt_name, "hw:%d,1,0", card); + + err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->capt_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->play_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + + ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0); + ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0); + self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify"); + self->notify_saved = true; +} + +FIXTURE_TEARDOWN(aloop) { + if (self->notify_saved) + set_notify(self->ctl, self->saved_notify); + if (self->ctl) + snd_ctl_close(self->ctl); +} + +/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */ +TEST_F(aloop, playback_constrained_without_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, capture_params.rate); + EXPECT_EQ(res.rate_max, capture_params.rate); + EXPECT_EQ(res.channels_min, capture_params.channels); + EXPECT_EQ(res.channels_max, capture_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(capt); +} + +/* With notify, the playback side is free to pick other parameters. */ +TEST_F(aloop, playback_unconstrained_with_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_LE(res.rate_min, capture_params.rate); + EXPECT_GE(res.rate_max, playback_params.rate); + EXPECT_LE(res.channels_min, capture_params.channels); + EXPECT_GE(res.channels_max, playback_params.channels); + EXPECT_TRUE(res.format_ok); + + snd_pcm_close(capt); +} + +/* + * With notify, starting a playback with different parameters stops the + * running capture, and the "PCM Slave" controls report the new parameters + * with a value event for each one that changed. + */ +TEST_F(aloop, params_change_stops_capture_with_notify) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* + * The controls keep the last playback's parameters, and only notify on + * a change. Start a playback with the capture's parameters while no + * capture is open, so that every control changes below. + */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0) + TH_LOG("Playback refused other parameters while the capture is running"); + ASSERT_EQ(start_playback(play, &playback_params), 0); + + /* loopback_check_format() stops the capture from the playback's start trigger. */ + EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_ACTIVE); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + +/* With notify, a capture opened second is still pinned to the playback's parameters. */ +TEST_F(aloop, capture_constrained_with_notify) { + struct probe_result res; + snd_pcm_t *play; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + + ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, playback_params.rate); + EXPECT_EQ(res.rate_max, playback_params.rate); + EXPECT_EQ(res.channels_min, playback_params.channels); + EXPECT_EQ(res.channels_max, playback_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(play); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 53a72753bb08d7..966ef83354b979 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -28,7 +28,7 @@ #include "kselftest.h" #include "alsa-local.h" -#define TESTS_PER_CONTROL 7 +#define TESTS_PER_CONTROL 8 /* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */ #define IEC958_DEFAULT "Default" @@ -52,9 +52,14 @@ struct ctl_data { snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *def_val; + snd_ctl_elem_value_t *snapshot; + bool snapshot_valid; + bool moved; int elem; int event_missing; int event_spurious; + int side_effects; + unsigned int ev_mask; struct card_data *card; struct ctl_data *next; }; @@ -159,6 +164,10 @@ static void find_controls(void) if (err < 0) ksft_exit_fail_msg("Out of memory\n"); + err = snd_ctl_elem_value_malloc(&ctl_data->snapshot); + if (err < 0) + ksft_exit_fail_msg("Out of memory\n"); + snd_ctl_elem_list_get_id(card_data->ctls, ctl, ctl_data->id); snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id); @@ -207,6 +216,20 @@ static void find_controls(void) snd_config_delete(config); } +/* The control on the same card that an event's numid refers to */ +static struct ctl_data *find_ctl_by_numid(struct card_data *card, + unsigned int numid) +{ + struct ctl_data *ctl; + + for (ctl = ctl_list; ctl != NULL; ctl = ctl->next) + if (ctl->card == card && + snd_ctl_elem_info_get_numid(ctl->info) == numid) + return ctl; + + return NULL; +} + /* * Block for up to timeout ms for an event, returns a negative value * on error, 0 for no event and 1 for an event. @@ -265,8 +288,21 @@ static int wait_for_event(struct ctl_data *ctl, int timeout) mask = snd_ctl_event_elem_get_mask(event); ev_id = snd_ctl_event_elem_get_numid(event); if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) { + struct ctl_data *other = find_ctl_by_numid(ctl->card, + ev_id); + + /* + * Remember that the driver announced this one. + * test_ctl_write_side_effects() uses that to tell a + * deliberate link from a silent register collision. + */ + if (other) + other->ev_mask |= mask; + ksft_print_msg("Event for unexpected ctl %s\n", snd_ctl_event_elem_get_name(event)); + /* The loop condition must not see the other control's mask */ + mask = 0; continue; } @@ -1147,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl) ctl->card->card_name, ctl->elem); } +/* + * Build the smallest or the largest value the control offers. The smallest + * clears the control's register field and the largest sets its top bit, which + * is the bit a mask one bit too wide puts in its neighbour. + */ +static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val, + bool max) +{ + int i, count = snd_ctl_elem_info_get_count(ctl->info); + + snd_ctl_elem_value_set_id(val, ctl->id); + + switch (snd_ctl_elem_info_get_type(ctl->info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_boolean(val, i, max); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer(val, i, max ? + snd_ctl_elem_info_get_max(ctl->info) : + snd_ctl_elem_info_get_min(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer64(val, i, max ? + snd_ctl_elem_info_get_max64(ctl->info) : + snd_ctl_elem_info_get_min64(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_enumerated(val, i, max ? + snd_ctl_elem_info_get_items(ctl->info) - 1 : 0); + return true; + + default: + /* Nothing sensible to write for the rest */ + return false; + } +} + +/* Note every control on the card that no longer reads as it did */ +static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val) +{ + struct ctl_data *other; + int err; + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid) + continue; + + /* + * The buffer is shared and compare() looks at all of it, so + * clear what the last control left in the slots this one + * does not use. + */ + snd_ctl_elem_value_clear(val); + snd_ctl_elem_value_set_id(val, other->id); + err = snd_ctl_elem_read(ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + if (snd_ctl_elem_value_compare(other->snapshot, val)) + other->moved = true; + } +} + +/* + * Write one control and look for others on the same card that moved with it. + * A driver that links two controls on purpose tells userspace about both, so + * only an unannounced change is counted. That is what a control whose + * register mask covers bits belonging to its neighbour looks like from here. + */ +static void test_ctl_write_side_effects(struct ctl_data *ctl) +{ + struct ctl_data *other; + snd_ctl_elem_value_t *min_val, *max_val, *read_val; + int err; + + snd_ctl_elem_value_alloca(&min_val); + snd_ctl_elem_value_alloca(&max_val); + snd_ctl_elem_value_alloca(&read_val); + + /* Without a readable default there is nothing to put back */ + if (snd_ctl_elem_info_is_inactive(ctl->info) || + !snd_ctl_elem_info_is_writable(ctl->info) || + !snd_ctl_elem_info_is_readable(ctl->info) || + !set_limit_value(ctl, min_val, false) || + !set_limit_value(ctl, max_val, true)) { + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + return; + } + + /* Drain first, a stale event would look like the driver announced it */ + drop_events(ctl); + + /* + * Record what the rest of the card reads as. A volatile control can + * move on its own so there is nothing to compare it against. + */ + for (other = ctl_list; other != NULL; other = other->next) { + other->snapshot_valid = false; + other->moved = false; + other->ev_mask = 0; + + if (other == ctl || other->card != ctl->card) + continue; + if (!snd_ctl_elem_info_is_readable(other->info) || + snd_ctl_elem_info_is_volatile(other->info)) + continue; + + snd_ctl_elem_value_clear(other->snapshot); + snd_ctl_elem_value_set_id(other->snapshot, other->id); + err = snd_ctl_elem_read(ctl->card->handle, other->snapshot); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + other->snapshot_valid = true; + } + + /* + * Compare against the snapshot after each write, before anything is + * put back. Restoring the control we wrote goes through the same + * mask, so doing it first would hide the change we are looking for. + */ + err = snd_ctl_elem_write(ctl->card->handle, min_val); + if (err >= 0) { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + err = snd_ctl_elem_write(ctl->card->handle, max_val); + } + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n", + ctl->name, snd_strerror(err)); + } else { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + } + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->moved) + continue; + + if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) { + ksft_print_msg("Writing %s changed %s, the driver said so\n", + ctl->name, other->name); + } else { + ksft_print_msg("Writing %s silently changed %s\n", + ctl->name, other->name); + ctl->side_effects++; + } + } + + /* + * The control we wrote goes back first so its mask stops moving the + * rest. A plain write keeps this out of the event counters, they + * belong to the tests that check them. + */ + snd_ctl_elem_write(ctl->card->handle, ctl->def_val); + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid || + !snd_ctl_elem_info_is_writable(other->info)) + continue; + + snd_ctl_elem_value_clear(read_val); + snd_ctl_elem_value_set_id(read_val, other->id); + if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0) + continue; + + if (snd_ctl_elem_value_compare(other->snapshot, read_val)) + snd_ctl_elem_write(ctl->card->handle, other->snapshot); + } + + /* Our own restores queue events, the next test must not see them */ + drop_events(ctl); + + if (err < 0) + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + else + ksft_test_result(!ctl->side_effects, + "write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); +} + static bool test_ctl_write_invalid_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val) { @@ -1390,6 +1622,7 @@ int main(void) test_ctl_name(ctl); test_ctl_write_default(ctl); test_ctl_write_valid(ctl); + test_ctl_write_side_effects(ctl); test_ctl_write_invalid(ctl); test_ctl_event_missing(ctl); test_ctl_event_spurious(ctl); diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c new file mode 100644 index 00000000000000..a1d41b1099401c --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "bpf_ma_ttrace.skel.h" + +#define NR_ELEMS 4096 + +/* + * The first free_bulk() starts RCU tasks trace GP. The rest of the elements are + * deleted while it's in flight. They should be freed without further alloc or + * free from this map. + */ +void test_bpf_ma_ttrace(void) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct bpf_ma_ttrace *skel; + __u32 cnt = NR_ELEMS; + long *vals = NULL; + int *keys = NULL; + int i, err, fd, nr_cpus; + + skel = bpf_ma_ttrace__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "nr_cpus")) + goto out; + skel->bss->nr_cpus = nr_cpus; + + keys = calloc(NR_ELEMS, sizeof(*keys)); + vals = calloc(NR_ELEMS, sizeof(*vals)); + if (!ASSERT_OK_PTR(keys, "keys") || !ASSERT_OK_PTR(vals, "vals")) + goto out; + for (i = 0; i < NR_ELEMS; i++) + keys[i] = i; + + fd = bpf_map__fd(skel->maps.htab); + err = bpf_map_update_batch(fd, keys, vals, &cnt, NULL); + if (!ASSERT_OK(err, "update_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "update_cnt")) + goto out; + err = bpf_map_delete_batch(fd, keys, &cnt, NULL); + if (!ASSERT_OK(err, "delete_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "delete_cnt")) + goto out; + + /* Wait for all __free_rcu() callbacks to finish */ + for (i = 0; i < 300; i++) { + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.check_ttrace), &opts); + if (!ASSERT_OK(err, "test_run") || !ASSERT_OK(opts.retval, "retval")) + goto out; + if (!skel->bss->in_progress) + break; + usleep(100000); + } + ASSERT_EQ(skel->bss->nr_caches, nr_cpus, "nr_caches"); + ASSERT_EQ(skel->bss->in_progress, 0, "in_progress"); + ASSERT_EQ(skel->bss->not_freed, 0, "not_freed"); +out: + free(keys); + free(vals); + bpf_ma_ttrace__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c index ecc3d47919ad62..f8497e764beb52 100644 --- a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c +++ b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c @@ -55,38 +55,6 @@ static void *load_module_thread(void *p) return p; } -static int sys_userfaultfd(int flags) -{ - return syscall(__NR_userfaultfd, flags); -} - -static int test_setup_uffd(void *fault_addr) -{ - struct uffdio_register uffd_register = {}; - struct uffdio_api uffd_api = {}; - int uffd; - - uffd = sys_userfaultfd(O_CLOEXEC); - if (uffd < 0) - return -errno; - - uffd_api.api = UFFD_API; - uffd_api.features = 0; - if (ioctl(uffd, UFFDIO_API, &uffd_api)) { - close(uffd); - return -1; - } - - uffd_register.range.start = (unsigned long)fault_addr; - uffd_register.range.len = getpagesize(); - uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) { - close(uffd); - return -1; - } - return uffd; -} - static void test_bpf_mod_race_config(const struct test_config *config) { void *fault_addr, *skel_fail; @@ -117,7 +85,7 @@ static void test_bpf_mod_race_config(const struct test_config *config) if (!ASSERT_OK(bpf_mod_race__attach(skel), "bpf_mod_kfunc_race__attach")) goto end_destroy; - uffd = test_setup_uffd(fault_addr); + uffd = uffd_block_page(fault_addr); if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address")) goto end_destroy; diff --git a/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c new file mode 100644 index 00000000000000..1eb0d723760559 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include "tramp_prog_detach.skel.h" +#include "testing_helpers.h" + +/* + * Detach and free progs while a task sleeps in the prog that runs before them + * in the same trampoline image, then let that task continue through the + * image. It must not call into the freed progs. + * + * The task is held in a sleepable prog with userfaultfd, like bpf_mod_race + * does. + */ + +static struct bpf_program *pick_prog(struct tramp_prog_detach *skel, + bool fexit, bool sleepable) +{ + if (fexit) + return sleepable ? skel->progs.fexit_sleepable : + skel->progs.fexit_victim; + return sleepable ? skel->progs.fentry_sleepable : + skel->progs.fentry_victim; +} + +static struct bpf_program *sleepable_prog; + +static void *run_sleepable(void *arg) +{ + LIBBPF_OPTS(bpf_test_run_opts, topts); + + /* calls bpf_fentry_test1() */ + return (void *)(long)bpf_prog_test_run_opts(bpf_program__fd(sleepable_prog), + &topts); +} + +static struct tramp_prog_detach *load_one(bool fexit, bool sleepable) +{ + struct tramp_prog_detach *skel; + int err; + + skel = tramp_prog_detach__open(); + if (!ASSERT_OK_PTR(skel, "open")) + return NULL; + + bpf_program__set_autoload(pick_prog(skel, fexit, sleepable), true); + err = tramp_prog_detach__load(skel); + if (!ASSERT_OK(err, "load")) + goto err; + skel->bss->pid = getpid(); + err = tramp_prog_detach__attach(skel); + if (!ASSERT_OK(err, "attach")) + goto err; + return skel; +err: + tramp_prog_detach__destroy(skel); + return NULL; +} + +/* The .bss map of a destroyed skeleton goes away when its prog is freed */ +static bool wait_for_map_free(__u32 map_id) +{ + int i, fd; + + for (i = 0; i < 100; i++) { + fd = bpf_map_get_fd_by_id(map_id); + if (fd < 0) + return true; + close(fd); + usleep(100 * 1000); + } + return false; +} + +static void test_detach(bool sleepable_fexit, bool victim_fexit) +{ + struct tramp_prog_detach *sleepable = NULL, *victims[2] = {}; + struct pollfd pfd = { .events = POLLIN }; + struct uffdio_copy uffd_copy = {}; + struct bpf_map_info map_info = {}; + __u32 map_info_len = sizeof(map_info); + struct uffd_msg uffd_msg; + void *fault_page, *src_page = MAP_FAILED; + long page_size = getpagesize(); + bool started = false; + void *thread_ret; + pthread_t thread; + int i, uffd = -1; + + fault_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!ASSERT_NEQ(fault_page, MAP_FAILED, "mmap fault_page")) + return; + src_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!ASSERT_NEQ(src_page, MAP_FAILED, "mmap src_page")) + goto out; + + /* The most recently attached prog runs first */ + for (i = 0; i < ARRAY_SIZE(victims); i++) { + victims[i] = load_one(victim_fexit, false); + if (!victims[i]) + goto out; + } + sleepable = load_one(sleepable_fexit, true); + if (!sleepable) + goto out; + sleepable_prog = pick_prog(sleepable, sleepable_fexit, true); + + /* Not armed yet so this doesn't block, make sure sleepable runs first */ + if (!ASSERT_OK((long)run_sleepable(NULL), "dry run")) + goto out; + for (i = 0; i < ARRAY_SIZE(victims); i++) + if (!ASSERT_LT(sleepable->bss->ts, victims[i]->bss->ts, "prog order")) + goto out; + + uffd = uffd_block_page(fault_page); + if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address")) + goto out; + sleepable->bss->fault_addr = fault_page; + + if (!ASSERT_OK(pthread_create(&thread, NULL, run_sleepable, NULL), + "pthread_create")) + goto out; + started = true; + + /* Wait for the thread to sleep in bpf_copy_from_user() */ + pfd.fd = uffd; + if (!ASSERT_EQ(poll(&pfd, 1, 10000), 1, "poll uffd")) + goto out; + if (!ASSERT_EQ(read(uffd, &uffd_msg, sizeof(uffd_msg)), sizeof(uffd_msg), + "read uffd")) + goto out; + if (!ASSERT_EQ(uffd_msg.event, UFFD_EVENT_PAGEFAULT, "uffd pagefault")) + goto out; + + /* + * Detach and unload the victim progs and wait for them to be freed. + * After the first one, the task is in an image that isn't the + * trampoline's current one anymore. + */ + for (i = 0; i < ARRAY_SIZE(victims); i++) { + if (!ASSERT_OK(bpf_map_get_info_by_fd(bpf_map__fd(victims[i]->maps.bss), + &map_info, &map_info_len), + "victim bss info")) + goto out; + tramp_prog_detach__destroy(victims[i]); + victims[i] = NULL; + if (!ASSERT_TRUE(wait_for_map_free(map_info.id), "victim freed")) + goto out; + } + +out: + /* Let the thread proceed with the rest of the trampoline */ + if (uffd >= 0) { + uffd_copy.dst = (unsigned long)fault_page; + uffd_copy.src = (unsigned long)src_page; + uffd_copy.len = page_size; + ASSERT_OK(ioctl(uffd, UFFDIO_COPY, &uffd_copy), "uffd copy"); + close(uffd); + } + if (started && + ASSERT_OK(pthread_join(thread, &thread_ret), "pthread_join")) + ASSERT_NULL(thread_ret, "blocking run"); + + for (i = 0; i < ARRAY_SIZE(victims); i++) + tramp_prog_detach__destroy(victims[i]); + tramp_prog_detach__destroy(sleepable); + if (src_page != MAP_FAILED) + munmap(src_page, page_size); + munmap(fault_page, page_size); +} + +void serial_test_tramp_prog_detach(void) +{ + /* a task sleeping before the original function is called */ + if (test__start_subtest("fentry")) + test_detach(false, false); + /* a task sleeping after the original function returned */ + if (test__start_subtest("fexit")) + test_detach(true, true); + /* the original function runs in between and must still be called */ + if (test__start_subtest("fentry_fexit")) + test_detach(false, true); +} diff --git a/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c new file mode 100644 index 00000000000000..31b2a962e339db --- /dev/null +++ b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include "bpf_kfuncs.h" + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(max_entries, 4096); + __type(key, int); + __type(value, long); +} htab SEC(".maps"); + +extern const void __per_cpu_offset __ksym; + +int nr_cpus; +int nr_caches; +int in_progress; +int not_freed; + +/* Look at bpf_mem_cache of every cpu that htab allocates its elements from */ +SEC("syscall") +int check_ttrace(void *ctx) +{ + struct bpf_htab *h = bpf_core_cast(&htab, struct bpf_htab); + unsigned long cache = (unsigned long)BPF_CORE_READ(h, ma.cache); + const unsigned long *offsets = &__per_cpu_offset; + struct bpf_mem_cache *c; + unsigned long off; + int cpu; + + nr_caches = 0; + in_progress = 0; + not_freed = 0; + bpf_for(cpu, 0, nr_cpus) { + if (bpf_probe_read_kernel(&off, sizeof(off), offsets + cpu)) + return -1; + c = bpf_core_cast((void *)(cache + off), struct bpf_mem_cache); + if (c->unit_size) + nr_caches++; + if (c->call_rcu_ttrace_in_progress.counter) + in_progress++; + if (c->free_by_rcu_ttrace.first || c->waiting_for_gp_ttrace.first) + not_freed++; + } + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/tramp_prog_detach.c b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c new file mode 100644 index 00000000000000..507d372167ecbc --- /dev/null +++ b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include +#include + +char _license[] SEC("license") = "GPL"; + +int pid; +void *fault_addr; +__u64 ts; + +static int do_sleepable(void) +{ + char dst; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + ts = bpf_ktime_get_ns(); + /* blocks for as long as user space wants when fault_addr is armed */ + bpf_copy_from_user(&dst, sizeof(dst), fault_addr); + return 0; +} + +static int do_victim(void) +{ + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + ts = bpf_ktime_get_ns(); + return 0; +} + +SEC("?fentry.s/bpf_fentry_test1") +int BPF_PROG(fentry_sleepable, int a) +{ + return do_sleepable(); +} + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(fentry_victim, int a) +{ + return do_victim(); +} + +SEC("?fexit.s/bpf_fentry_test1") +int BPF_PROG(fexit_sleepable, int a, int ret) +{ + return do_sleepable(); +} + +SEC("?fexit/bpf_fentry_test1") +int BPF_PROG(fexit_victim, int a, int ret) +{ + return do_victim(); +} diff --git a/tools/testing/selftests/bpf/progs/verifier_align.c b/tools/testing/selftests/bpf/progs/verifier_align.c index 3e52686515cac6..0083ef8b8ba7fd 100644 --- a/tools/testing/selftests/bpf/progs/verifier_align.c +++ b/tools/testing/selftests/bpf/progs/verifier_align.c @@ -284,7 +284,7 @@ __msg("26: {{.*}} R5=pkt(r=8,imm=14)") */ __msg("28: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)") /* Constant is added to R5 again, setting reg->off to 18. */ -__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0x7fc)") +__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0xffc)") /* And once more we add a variable; resulting {{[^)]*}}var_off * is still (4n), fixed offset is not changed. * Also, we create a new reg->id. @@ -359,7 +359,7 @@ __msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)") __msg("8: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)") /* Packet pointer has (4n+2) offset */ __msg("11: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)") -__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc)") +__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)") /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) * which is 2. Then the variable offset is (4n+2), so @@ -375,7 +375,7 @@ __msg("17: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)") * another (4n+2). */ __msg("19: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)") -__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)") +__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x1ffc)") /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) * which is 2. Then the variable offset is (4n+2), so diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c index 915a9707298b0d..139ff019d87d1e 100644 --- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c @@ -920,4 +920,182 @@ l1_%=: r0 = *(u8*)(r9 + 0); \ : __clobber_all); } +SEC("tc") +__description("direct packet access: 8-aligned offset, check p + 8, load 8 bytes at p") +__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT) +__naked void pkt_same_id_check_copy_load_base(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r6 > r3 goto l0_%=; \ + /* [r5, r5 + 8) is in the packet */ \ + r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: 8-aligned offset, check p, load past it via p + 8") +__failure __msg("invalid access to packet, off=51 size=1") +__naked void pkt_same_id_check_base_load_via_copy(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r5 > r3 goto l0_%=; \ + /* r6 - 7 is r5 + 1 */ \ + r0 = *(u8*)(r6 - 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: 8-aligned offset, check p + 4, 4-byte load at p + 2") +__failure __msg("invalid access to packet, off=52 size=4") +__naked void pkt_same_id_check_base_4_load_via_copy(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + r7 = r5; \ + r7 += 4; \ + if r7 > r3 goto l0_%=; \ + /* [r5, r5 + 4) is in the packet, [r5 + 2, r5 + 6) may not be */ \ + r0 = *(u32*)(r6 - 6); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: checked pointer minus non-negative unknown keeps range") +__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT) +__naked void pkt_sub_unknown_keeps_range(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x1f; \ + r4 += 8; \ + r5 = r2; \ + r5 += 40; \ + if r5 > r3 goto l0_%=; \ + /* r5 is 8 to 39 bytes below the checked pointer */ \ + r5 -= r4; \ + r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: no pruning of a path whose checks prove fewer bytes") +__failure __msg("invalid access to packet, off=255 size=8") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_same_id_pruning(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r0 = *(u32*)(r1 + %[__sk_buff_priority]); \ + r4 &= 0xff; \ + r5 = r2; \ + r5 += r4; \ + if r0 != 0 goto l1_%=; \ + /* this path proves [r5, r5 + 8) */ \ + r6 = r5; \ + r6 += 8; \ + if r6 > r3 goto l0_%=; \ + goto l2_%=; \ +l1_%=: /* this path proves [r5, r5 + 7) */ \ + if r5 > r3 goto l0_%=; \ + r6 = r5; \ + r6 += 7; \ + if r6 > r3 goto l0_%=; \ +l2_%=: r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)), + __imm_const(__sk_buff_priority, offsetof(struct __sk_buff, priority)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: spilled copy of checked pointer, load past range") +__failure __msg("invalid access to packet, off=262 size=1, R7(id={{[0-9]+}},off=262,r=262)") +__naked void pkt_spilled_copy_gets_range(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0xff; \ + r5 = r2; \ + r5 += r4; \ + *(u64*)(r10 - 8) = r5; \ + /* proves only bytes before r5 */ \ + if r5 > r3 goto l0_%=; \ + r6 = r5; \ + r6 += 7; \ + if r6 > r3 goto l0_%=; \ + /* [r5, r5 + 7) is in the packet */ \ + r7 = *(u64*)(r10 - 8); \ + r0 = *(u8*)(r7 + 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_meta_access.c b/tools/testing/selftests/bpf/progs/verifier_meta_access.c index 62235f032ffe7b..c87e0be4b2ff0e 100644 --- a/tools/testing/selftests/bpf/progs/verifier_meta_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_meta_access.c @@ -281,4 +281,34 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("meta access, 8-aligned offset, check p, load a byte at p + 1 via p + 8") +__failure __msg("invalid access to packet, off=51 size=1") +__naked void meta_access_check_base_load_via_copy(void) +{ + asm volatile (" \ + r9 = r1; \ + call %[bpf_get_prandom_u32]; \ + r4 = r0; \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r2 = *(u32*)(r9 + %[xdp_md_data_meta]); \ + r3 = *(u32*)(r9 + %[xdp_md_data]); \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r5 > r3 goto l0_%=; \ + /* r6 - 7 is r5 + 1 */ \ + r0 = *(u8*)(r6 - 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_meta, offsetof(struct xdp_md, data_meta)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c index c970e7793dfcbc..9f672d0c9dda22 100644 --- a/tools/testing/selftests/bpf/testing_helpers.c +++ b/tools/testing/selftests/bpf/testing_helpers.c @@ -13,6 +13,7 @@ #include "test_progs.h" #include "testing_helpers.h" #include +#include int parse_num_list(const char *s, bool **num_set, int *num_set_len) { @@ -459,6 +460,33 @@ int kern_sync_rcu(void) return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0); } +int uffd_block_page(void *fault_addr) +{ + struct uffdio_register uffd_register = {}; + struct uffdio_api uffd_api = {}; + int uffd; + + uffd = syscall(__NR_userfaultfd, O_CLOEXEC); + if (uffd < 0) + return -errno; + + uffd_api.api = UFFD_API; + uffd_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffd_api)) { + close(uffd); + return -1; + } + + uffd_register.range.start = (unsigned long)fault_addr; + uffd_register.range.len = getpagesize(); + uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) { + close(uffd); + return -1; + } + return uffd; +} + int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt) { __u32 buf_element_size = sizeof(struct bpf_insn); diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h index 2edc6fb7fc5212..1f83b6080716fe 100644 --- a/tools/testing/selftests/bpf/testing_helpers.h +++ b/tools/testing/selftests/bpf/testing_helpers.h @@ -36,6 +36,8 @@ __u64 read_perf_max_sample_freq(void); int load_bpf_testmod(bool verbose); int unload_bpf_testmod(bool verbose); int kern_sync_rcu(void); +/* returns a userfaultfd that makes accesses to fault_addr's page block */ +int uffd_block_page(void *fault_addr); int finit_module(int fd, const char *param_values, int flags); int delete_module(const char *name, int flags); int load_module(const char *path, bool verbose); diff --git a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh index b1f0781f6b25f1..c7eba3f15066cb 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh @@ -9,6 +9,7 @@ lib_dir=$(dirname $0)/../../../net/forwarding ALL_TESTS=" port_range_occ_test + port_range_occ_tmplt_test " NUM_NETIFS=2 source $lib_dir/lib.sh @@ -101,6 +102,31 @@ port_range_occ_test() log_test "port range occupancy" } +port_range_occ_tmplt_test() +{ + local occ + + RET=0 + occ=$(port_range_occ_get) + + # Addition of a chain template should not impact occupancy. + tc chain add dev "$swp1" ingress chain 1 protocol ip \ + flower ip_proto udp src_port 501-600 dst_port 701-800 + check_err $? "Could not add chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + # And just for completeness sake, neither should deletion. + tc chain del dev "$swp1" ingress chain 1 + check_err $? "Could not delete chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + log_test "port range occupancy after template create/destroy" +} + trap cleanup EXIT setup_prepare diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 9eb185fb2f9ddb..62f7b1c4664970 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -7,3 +7,4 @@ anon_inode_test kernfs_test idmapped_tmpfile ustat_test +rw_hint_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 03be337c1f351a..93e2cc9123b041 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test rw_hint_test TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/empty_mntns/.gitignore b/tools/testing/selftests/filesystems/empty_mntns/.gitignore index 99f89d329db240..27bcbcaeb1d191 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/.gitignore +++ b/tools/testing/selftests/filesystems/empty_mntns/.gitignore @@ -2,3 +2,6 @@ clone3_empty_mntns_test empty_mntns_test overmount_chroot_test +internal_sb_reconfigure_test +nullfs_atime_test +root_readdir_test diff --git a/tools/testing/selftests/filesystems/empty_mntns/Makefile b/tools/testing/selftests/filesystems/empty_mntns/Makefile index 22e3fb915e812d..22af2164c54e9d 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/Makefile +++ b/tools/testing/selftests/filesystems/empty_mntns/Makefile @@ -4,9 +4,14 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS += -lcap TEST_GEN_PROGS := empty_mntns_test overmount_chroot_test clone3_empty_mntns_test +TEST_GEN_PROGS += internal_sb_reconfigure_test nullfs_atime_test root_readdir_test + +LOCAL_HDRS += ../readdir_hold.h include ../../lib.mk $(OUTPUT)/empty_mntns_test: ../utils.c $(OUTPUT)/overmount_chroot_test: ../utils.c $(OUTPUT)/clone3_empty_mntns_test: ../utils.c +$(OUTPUT)/internal_sb_reconfigure_test: ../utils.c +$(OUTPUT)/root_readdir_test: LDLIBS += -pthread diff --git a/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c new file mode 100644 index 00000000000000..cb645d1e5a9a0a --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The root of an empty mount namespace is a nullfs mount. Its superblock is + * kernel-internal and shared by every mount namespace. It can't be + * reconfigured, neither through fspick() nor through mount(MS_REMOUNT) nor + * through umount() of the root which remounts it read-only. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../utils.h" +#include "../wrappers.h" +#include "empty_mntns.h" +#include "kselftest_harness.h" + +#ifndef __NR_fspick +#define __NR_fspick 433 +#endif + +static int sys_fspick(int dfd, const char *path, unsigned int flags) +{ + return syscall(__NR_fspick, dfd, path, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_USERNS, /* could not create the user namespace */ + CHILD_UNSHARE, /* could not create the empty mount namespace */ + CHILD_FSPICK, /* fspick() of the root was not refused with EINVAL */ + CHILD_REMOUNT, /* mount(MS_REMOUNT) was not refused with EINVAL */ + CHILD_UMOUNT, /* umount() of the root succeeded */ + CHILD_STATFS, /* statfs() of the root failed */ + CHILD_RDONLY, /* the root ended up read-only */ +}; + +static int empty_mntns_child(void) +{ + struct statfs st; + + if (enter_userns()) + return CHILD_USERNS; + if (unshare(UNSHARE_EMPTY_MNTNS)) + return CHILD_UNSHARE; + + if (sys_fspick(AT_FDCWD, "/", 0) >= 0 || errno != EINVAL) + return CHILD_FSPICK; + if (!mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL) || + errno != EINVAL) + return CHILD_REMOUNT; + if (!umount2("/", 0)) + return CHILD_UMOUNT; + if (statfs("/", &st)) + return CHILD_STATFS; + if (st.f_flags & ST_RDONLY) + return CHILD_RDONLY; + return CHILD_OK; +} + +FIXTURE(internal_sb_reconfigure) {}; + +FIXTURE_SETUP(internal_sb_reconfigure) +{ + pid_t pid; + int status; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + if (enter_userns()) + _exit(1); + if (unshare(UNSHARE_EMPTY_MNTNS)) + _exit(1); + _exit(0); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + if (!WIFEXITED(status) || WEXITSTATUS(status)) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); +} + +FIXTURE_TEARDOWN(internal_sb_reconfigure) {} + +TEST_F(internal_sb_reconfigure, nullfs_root) +{ + pid_t pid; + int status; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(empty_mntns_child()); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c new file mode 100644 index 00000000000000..51e34f3c4f5418 --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The root of every empty mount namespace is the same nullfs inode. A read + * of it by one user must not change the access time another user sees. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef UNSHARE_EMPTY_MNTNS +#define UNSHARE_EMPTY_MNTNS 0x00100000 +#endif + +enum { + CHILD_OK, + CHILD_UNSUPPORTED, + CHILD_SETUP, + CHILD_CHANGED, +}; + +static int wait_byte(int fd) +{ + char c; + + return read(fd, &c, 1) == 1 ? 0 : -1; +} + +static int send_byte(int fd) +{ + return write(fd, "x", 1) == 1 ? 0 : -1; +} + +static int empty_mntns(void) +{ + if (!unshare(UNSHARE_EMPTY_MNTNS)) + return 0; + return errno == EINVAL ? CHILD_UNSUPPORTED : CHILD_SETUP; +} + +/* the watcher: stats its root before and after the reader read its own */ +static int watcher(int to_reader, int from_reader) +{ + struct stat before, after; + int ret; + + ret = empty_mntns(); + if (ret) + return ret; + if (stat("/", &before)) + return CHILD_SETUP; + if (send_byte(to_reader) || wait_byte(from_reader)) + return CHILD_SETUP; + if (stat("/", &after)) + return CHILD_SETUP; + if (before.st_atim.tv_sec != after.st_atim.tv_sec || + before.st_atim.tv_nsec != after.st_atim.tv_nsec) + return CHILD_CHANGED; + return CHILD_OK; +} + +/* the reader: lists its own root, which is the same inode */ +static int reader(int to_watcher, int from_watcher) +{ + struct dirent *de; + DIR *d; + int ret; + + ret = empty_mntns(); + if (ret) + return ret; + if (wait_byte(from_watcher)) + return CHILD_SETUP; + d = opendir("/"); + if (!d) + return CHILD_SETUP; + while ((de = readdir(d))) + ; + closedir(d); + return send_byte(to_watcher) ? CHILD_SETUP : CHILD_OK; +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST(empty_mntns_root_atime) +{ + int to_reader[2], to_watcher[2], w, r; + pid_t watcher_pid, reader_pid; + + if (geteuid()) + SKIP(return, "test requires root"); + ASSERT_EQ(pipe(to_reader), 0); + ASSERT_EQ(pipe(to_watcher), 0); + + watcher_pid = fork(); + ASSERT_GE(watcher_pid, 0); + if (watcher_pid == 0) + _exit(watcher(to_reader[1], to_watcher[0])); + reader_pid = fork(); + ASSERT_GE(reader_pid, 0); + if (reader_pid == 0) + _exit(reader(to_watcher[1], to_reader[0])); + + r = wait_child(reader_pid); + w = wait_child(watcher_pid); + if (r == CHILD_UNSUPPORTED || w == CHILD_UNSUPPORTED) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + EXPECT_EQ(r, CHILD_OK); + EXPECT_EQ(w, CHILD_OK) + TH_LOG("the access time of the root changed while this namespace did nothing"); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c new file mode 100644 index 00000000000000..0ff54424702701 --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Reading the root of an empty mount namespace holds nothing that anybody + * else waits for: the directory never has an entry, so a readdir stuck in + * the page fault of its buffer stalls neither a create nor a lookup in it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" +#include "../readdir_hold.h" +#include "empty_mntns.h" + +TEST(readdir_blocks_nobody) +{ + struct readdir_hold hold; + bool stalled; + int dfd; + + if (geteuid()) + SKIP(return, "test requires root"); + if (readdir_hold_init(&hold)) + SKIP(return, "test requires userfaultfd"); + if (unshare(UNSHARE_EMPTY_MNTNS)) { + readdir_hold_destroy(&hold); + if (errno == EINVAL) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + ASSERT_TRUE(false) + TH_LOG("unshare(UNSHARE_EMPTY_MNTNS): %m"); + } + dfd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(dfd, 0); + ASSERT_EQ(readdir_hold_check(&hold, dfd, &stalled), 0); + /* the lookup came back while the readdir was stuck in its fault */ + EXPECT_FALSE(stalled); + close(dfd); + readdir_hold_destroy(&hold); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/Makefile b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile new file mode 100644 index 00000000000000..33a311c5bd724f --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := mntns_unbindable_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c new file mode 100644 index 00000000000000..9aebc37cf74d8b --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unbindable mount stays unbindable in a cloned mount namespace. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif + +static int sys_open_tree(int dfd, const char *filename, unsigned int flags) +{ + return syscall(__NR_open_tree, dfd, filename, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, /* the operation failed with EINVAL as it must */ + CHILD_ALLOWED, /* the operation succeeded: the flag was lost */ + CHILD_UNSHARE, /* unshare(CLONE_NEWNS) failed */ + CHILD_ERRNO, /* the operation failed with some other errno */ + CHILD_MOUNTINFO, /* the mount was not found in mountinfo */ +}; + +FIXTURE(mntns_unbindable) +{ + char base[64]; + char src[80]; + char dst[80]; + bool mounted; +}; + +FIXTURE_SETUP(mntns_unbindable) +{ + self->mounted = false; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/mntns_unbindable.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + + snprintf(self->src, sizeof(self->src), "%s/src", self->base); + snprintf(self->dst, sizeof(self->dst), "%s/dst", self->base); + ASSERT_EQ(mkdir(self->src, 0755), 0); + ASSERT_EQ(mkdir(self->dst, 0755), 0); + + ASSERT_EQ(mount("tmpfs", self->src, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, self->src, NULL, MS_UNBINDABLE, NULL), 0); +} + +FIXTURE_TEARDOWN(mntns_unbindable) +{ + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int classify(int ret, int err) +{ + if (ret >= 0) + return CHILD_ALLOWED; + return err == EINVAL ? CHILD_OK : CHILD_ERRNO; +} + +/* Is the mount on @mountpoint marked unbindable in /proc/self/mountinfo? */ +static int mountinfo_unbindable(const char *mountpoint) +{ + char line[4096]; + FILE *f; + int ret = CHILD_MOUNTINFO; + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return CHILD_ERRNO; + + while (fgets(line, sizeof(line), f)) { + char *fields[6], *p = line, *opt; + int i; + + for (i = 0; i < 6; i++) { + fields[i] = strsep(&p, " "); + if (!fields[i]) + break; + } + if (i < 6 || strcmp(fields[4], mountpoint)) + continue; + + /* the optional fields, up to the "-" separator */ + ret = CHILD_ALLOWED; + while ((opt = strsep(&p, " ")) && strcmp(opt, "-")) { + if (!strcmp(opt, "unbindable")) + ret = CHILD_OK; + } + break; + } + fclose(f); + return ret; +} + +static int run_in_child(int (*fn)(const char *src, const char *dst), + const char *src, const char *dst) +{ + int status; + pid_t pid; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) + _exit(fn(src, dst)); + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +static int bind_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND, NULL); + return classify(ret, errno); +} + +static int rbind_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND | MS_REC, NULL); + return classify(ret, errno); +} + +static int open_tree_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = sys_open_tree(AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + return classify(ret, errno); +} + +static int mountinfo_after_clone(const char *src, const char *dst) +{ + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + return mountinfo_unbindable(src); +} + +static int bind_after_two_clones(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND, NULL); + return classify(ret, errno); +} + +/* The namespace the mount was made unbindable in. */ +TEST_F(mntns_unbindable, refuses_bind) +{ + int ret = mount(self->src, self->dst, NULL, MS_BIND, NULL); + + ASSERT_EQ(classify(ret, errno), CHILD_OK); + ASSERT_EQ(mountinfo_unbindable(self->src), CHILD_OK); +} + +/* A copy of the namespace must not turn the mount bindable. */ +TEST_F(mntns_unbindable, refuses_bind_after_clone) +{ + ASSERT_EQ(run_in_child(bind_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("bind of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_rbind_after_clone) +{ + ASSERT_EQ(run_in_child(rbind_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("rbind of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_open_tree_after_clone) +{ + ASSERT_EQ(run_in_child(open_tree_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("OPEN_TREE_CLONE of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, mountinfo_after_clone) +{ + ASSERT_EQ(run_in_child(mountinfo_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("mountinfo does not show the mount as unbindable in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_bind_after_two_clones) +{ + ASSERT_EQ(run_in_child(bind_after_two_clones, self->src, self->dst), CHILD_OK) + TH_LOG("bind of an unbindable mount allowed two mount namespace copies down"); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore new file mode 100644 index 00000000000000..30f7dd071bcbbd --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +loop_cycle_test +unmounted_tree_test +overmount_reparent_test +mount_cover_test +locked_handle_test +nsfs_rbind_loop_test +overmount_ns_file_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile new file mode 100644 index 00000000000000..fbe8c5e19d424a --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := loop_cycle_test unmounted_tree_test overmount_reparent_test mount_cover_test locked_handle_test +TEST_GEN_PROGS += nsfs_rbind_loop_test overmount_ns_file_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +LOCAL_HDRS += ../readdir_hold.h + +include ../../lib.mk + +$(OUTPUT)/locked_handle_test: LDLIBS += -pthread +$(OUTPUT)/mount_cover_test: LDLIBS += -pthread diff --git a/tools/testing/selftests/filesystems/mount_cycle/config b/tools/testing/selftests/filesystems/mount_cycle/config new file mode 100644 index 00000000000000..3bd5ce46af7410 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/config @@ -0,0 +1,39 @@ +CONFIG_USER_NS=y +CONFIG_TMPFS=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_VFAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_MINIX_FS=y +CONFIG_AUTOFS_FS=y +CONFIG_ZRAM=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_KEYS=y +CONFIG_ECRYPT_FS=y +CONFIG_BINFMT_MISC=y +CONFIG_FUSE_FS=y +CONFIG_FUSE_PASSTHROUGH=y +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_ZONED_LOOP=y +CONFIG_CONFIGFS_FS=y +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_USB_STORAGE=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_USB_GADGET=y +CONFIG_USB_DUMMY_HCD=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_MD=y +CONFIG_BLK_DEV_MD=y +CONFIG_MD_RAID1=y +CONFIG_MD_BITMAP=y +CONFIG_MD_BITMAP_FILE=y +CONFIG_INOTIFY_USER=y +CONFIG_DNOTIFY=y +CONFIG_FANOTIFY=y +CONFIG_FILE_LOCKING=y +CONFIG_CRYPTO_AES=y +CONFIG_USERFAULTFD=y diff --git a/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c new file mode 100644 index 00000000000000..b21f43471e4959 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * may_decode_fh() refuses a file handle below a mount with locked children + * to root in a user namespace. That has to hold while the mount is lazily + * unmounted and its children, the locked ones too, are taken off it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +#define ROUNDS 100 /* lazy umounts raced per test */ +#define RACERS 3 /* threads in open_by_handle_at() */ +#define EXTRA_MOUNTS 64 /* make umount_tree() hold mount_lock longer */ +#define MAX_DELAY_US 3000 /* before the umount */ +#define TAIL_US 2000 /* after it */ + +struct handle { + struct file_handle fh; + unsigned char buf[MAX_HANDLE_SZ]; +}; + +/* exit codes of the child */ +enum { + CHILD_OK, + CHILD_SETUP, + CHILD_DECODED, /* decoded past a locked child */ + CHILD_MOUNTED, /* not refused while mounted */ + CHILD_UNMOUNTED, /* not refused once unmounted */ + CHILD_ALLOWED, /* refused where nothing is locked */ + CHILD_NOUSERNS, /* no user namespace to be had */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + /* EINVAL: no USER_NS, ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM */ + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return errno == EINVAL || errno == ENOSPC || errno == EPERM ? + CHILD_NOUSERNS : CHILD_SETUP; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return CHILD_SETUP; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return CHILD_SETUP; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return CHILD_SETUP; + if (setgid(0) || setuid(0)) + return CHILD_SETUP; + if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_SETUP; + return CHILD_OK; +} + +static int get_handle(const char *path, struct handle *h) +{ + int mntid; + + h->fh.handle_bytes = MAX_HANDLE_SZ; + return name_to_handle_at(AT_FDCWD, path, &h->fh, &mntid, 0); +} + +static int decode(int dfd, struct handle *h) +{ + return open_by_handle_at(dfd, &h->fh, O_RDONLY | O_DIRECTORY | O_CLOEXEC); +} + +struct race { + int dfd; + struct handle *h; + atomic_int go; + atomic_int stop; + atomic_int won; /* the first decoded descriptor */ +}; + +static void *racer(void *arg) +{ + struct race *r = arg; + + while (!atomic_load(&r->go)) + ; + while (!atomic_load(&r->stop)) { + int none = -1; + int fd; + + fd = decode(r->dfd, r->h); + if (fd < 0) + continue; + if (!atomic_compare_exchange_strong(&r->won, &none, fd)) + close(fd); + } + return NULL; +} + +/* stop the @n racers started so far and wait for them, they spin on @r */ +static void stop_racers(struct race *r, pthread_t *th, int n) +{ + int i; + + atomic_store(&r->stop, 1); + atomic_store(&r->go, 1); /* one still waiting for the start sees stop next */ + for (i = 0; i < n; i++) + pthread_join(th[i], NULL); +} + +/* + * Bind @h recursively at @w, or take a detached copy of it, and race + * open_by_handle_at() of @inner against the lazy umount. The decoded + * descriptor, if there was one, is left in @won. + */ +static int race_round(const char *h, const char *w, struct handle *inner, + bool dissolve, int *won) +{ + struct race r = { .h = inner, .won = -1 }; + pthread_t th[RACERS]; + int treefd = -1, fd, i; + + if (dissolve) { + treefd = syscall(__NR_open_tree, AT_FDCWD, h, + OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC); + if (treefd < 0) + return CHILD_SETUP; + /* an ordinary descriptor on the copy, treefd's close dissolves it */ + r.dfd = openat(treefd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + } else { + if (mount(h, w, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + r.dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + } + if (r.dfd < 0) + return CHILD_SETUP; + + fd = decode(r.dfd, inner); + if (fd >= 0 || errno != EPERM) { + if (fd >= 0) + close(fd); + return CHILD_MOUNTED; + } + + for (i = 0; i < RACERS; i++) { + if (pthread_create(&th[i], NULL, racer, &r)) { + stop_racers(&r, th, i); + return CHILD_SETUP; + } + } + atomic_store(&r.go, 1); + usleep(rand() % MAX_DELAY_US); + if (dissolve) + close(treefd); + else if (umount2(w, MNT_DETACH)) { + stop_racers(&r, th, RACERS); + return CHILD_SETUP; + } + usleep(TAIL_US); + stop_racers(&r, th, RACERS); + + fd = decode(r.dfd, inner); + if (fd >= 0) { + close(fd); + return CHILD_UNMOUNTED; + } + close(r.dfd); + *won = atomic_load(&r.won); + return CHILD_OK; +} + +static int race_child(const char *h, const char *w, struct handle *inner, + bool dissolve) +{ + int ret, won, i; + + srand(getpid()); + ret = enter_userns(); + if (ret) + return ret; + for (i = 0; i < ROUNDS; i++) { + ret = race_round(h, w, inner, dissolve, &won); + if (ret) + return ret; + if (won >= 0) + return CHILD_DECODED; + } + return CHILD_OK; +} + +/* a bind without locked children decodes while mounted and not after */ +static int allowed_child(const char *plain, const char *w, struct handle *h) +{ + int dfd, fd, ret; + + ret = enter_userns(); + if (ret) + return ret; + if (mount(plain, w, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dfd < 0) + return CHILD_SETUP; + fd = decode(dfd, h); + if (fd < 0) + return CHILD_ALLOWED; + close(fd); + if (umount2(w, MNT_DETACH)) + return CHILD_SETUP; + fd = decode(dfd, h); + if (fd >= 0) { + close(fd); + return CHILD_UNMOUNTED; + } + return errno == EPERM ? CHILD_OK : CHILD_UNMOUNTED; +} + +FIXTURE(locked_handle) { + char base[DIR_LEN]; + char h[PATH_LEN]; /* the tree with the covered directory */ + char plain[PATH_LEN]; /* a tree with no mount in it */ + char w[PATH_LEN]; /* where the child binds either */ + struct handle inner; /* h/top/secret/inner, covered */ + struct handle uncovered; /* plain/inner */ +}; + +FIXTURE_SETUP(locked_handle) +{ + char p[PATH_LEN], e[PATH_LEN]; + int i; + + if (geteuid()) + SKIP(return, "test requires root"); + + snprintf(self->base, sizeof(self->base), "/tmp/locked_handle.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + + snprintf(self->h, sizeof(self->h), "%s/h", self->base); + snprintf(self->plain, sizeof(self->plain), "%s/plain", self->base); + snprintf(self->w, sizeof(self->w), "%s/w", self->base); + ASSERT_EQ(mkdir(self->h, 0755), 0); + ASSERT_EQ(mkdir(self->plain, 0755), 0); + ASSERT_EQ(mkdir(self->w, 0755), 0); + snprintf(p, sizeof(p), "%s/plain/inner", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(get_handle(p, &self->uncovered), 0); + + /* the handle is for this filesystem */ + ASSERT_EQ(mount("tmpfs", self->h, "tmpfs", 0, NULL), 0); + snprintf(p, sizeof(p), "%s/h/top", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/h/top/secret", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/h/top/secret/inner", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(get_handle(p, &self->inner), 0); + snprintf(e, sizeof(e), "%s/h/empty", self->base); + ASSERT_EQ(mkdir(e, 0755), 0); + + /* cover it, and some more so the umount takes longer */ + snprintf(p, sizeof(p), "%s/h/top/secret", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), 0); + for (i = 0; i < EXTRA_MOUNTS; i++) { + snprintf(p, sizeof(p), "%s/h/c%d", self->base, i); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount(e, p, NULL, MS_BIND, NULL), 0); + } +} + +FIXTURE_TEARDOWN(locked_handle) +{ + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST_F(locked_handle, lazy_umount) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(race_child(self->h, self->w, &self->inner, false)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_F(locked_handle, dissolve) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(race_child(self->h, self->w, &self->inner, true)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_F(locked_handle, allowed_use) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(allowed_child(self->plain, self->w, &self->uncovered)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c new file mode 100644 index 00000000000000..6b4f5304c2e4ff --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c @@ -0,0 +1,1542 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A mount that another namespace's rmdir detached, or that went down with + * the detached tree it was in when the tree's last fd was closed, keeps + * its submounts connected, and a connected submount is put by its + * parent's final mntput(). A submount whose filesystem keeps a file open + * on the parent then holds the parent's count above zero for good: + * nothing in userspace refers to either mount any more and nothing can + * release them. A loop device is the simplest such filesystem, its + * backing file sits on the parent. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#define IMAGE_SIZE (1440 * 1024) +#define SECTOR 512 + +/* + * The tmpfs of the fixture. A directory of its own from mkdtemp(), so that + * the test needs no writable root directory and two instances don't take + * each other's mounts down. + */ +static char base[64]; + +/* The configfs directory of the gadget, named after the test process. */ +static char gadget[96]; + +/* A path below the base directory. Eight of them can be in use at a time. */ +static const char *at(const char *rel) +{ + static char buf[8][PATH_MAX]; + static unsigned int next; + char *p = buf[next++ % 8]; + + snprintf(p, PATH_MAX, "%s/%s", base, rel); + return p; +} + +/* A path below the gadget's configfs directory. */ +static const char *gat(const char *rel) +{ + static char buf[4][PATH_MAX]; + static unsigned int next; + char *p = buf[next++ % 4]; + + snprintf(p, PATH_MAX, "%s%s", gadget, rel); + return p; +} + +/* The loop devices this process has bound, to release them on every path. */ +#define MAX_LOOPS 4 +static int bound[MAX_LOOPS]; +static int nr_bound; + +/* What a child tells its parent: the loop devices it has bound, -1 for none. */ +struct report { + int n[2]; +}; + +/* A blank FAT12 floppy image: boot sector, two FATs, an empty root directory. */ +static int write_fat12(int fd) +{ + struct stat st; + unsigned char sector[SECTOR] = { + 0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1', + [11] = 0x00, 0x02, /* bytes per sector: 512 */ + [13] = 1, /* sectors per cluster */ + [14] = 1, 0, /* reserved sectors */ + [16] = 2, /* FATs */ + [17] = 0xe0, 0x00, /* root directory entries: 224 */ + [19] = 0x40, 0x0b, /* total sectors: 2880 */ + [21] = 0xf0, /* media descriptor */ + [22] = 9, 0, /* sectors per FAT */ + [24] = 18, 0, /* sectors per track */ + [26] = 2, 0, /* heads */ + [38] = 0x29, /* extended boot signature */ + [39] = 0x12, 0x34, 0x56, 0x78, + [43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ', + [54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ', + [510] = 0x55, 0xaa, + }; + unsigned char fat[SECTOR] = { 0xf0, 0xff, 0xff }; + + if (pwrite(fd, sector, SECTOR, 0) != SECTOR) + return -1; + /* the first FAT and the second one, one sector each is enough */ + if (pwrite(fd, fat, SECTOR, 1 * SECTOR) != SECTOR || + pwrite(fd, fat, SECTOR, 10 * SECTOR) != SECTOR) + return -1; + if (fstat(fd, &st) || S_ISBLK(st.st_mode)) + return 0; + return ftruncate(fd, IMAGE_SIZE); +} + +/* + * A blank FAT12 image with 4 KiB sectors and @sectors of them, for a device + * with 4 KiB logical blocks (zram) or for a bigger image than a floppy. + */ +static int write_fat12_4k(int fd, unsigned int sectors) +{ + unsigned int fat_sectors = (sectors * 3 / 2 + 4095) / 4096; + unsigned char sector[4096] = { + 0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1', + [11] = 0x00, 0x10, /* bytes per sector: 4096 */ + [13] = 1, /* sectors per cluster */ + [14] = 1, 0, /* reserved sectors */ + [16] = 2, /* FATs */ + [17] = 128, 0, /* root directory entries: one sector */ + [19] = sectors & 0xff, sectors >> 8, + [21] = 0xf8, /* media descriptor */ + [22] = fat_sectors, 0, + [24] = 63, 0, /* sectors per track */ + [26] = 255, 0, /* heads */ + [38] = 0x29, /* extended boot signature */ + [39] = 0x12, 0x34, 0x56, 0x78, + [43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ', + [54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ', + [510] = 0x55, 0xaa, + }; + unsigned char fat[4096] = { 0xf8, 0xff, 0xff }; + struct stat st; + + if (pwrite(fd, sector, sizeof(sector), 0) != sizeof(sector)) + return -1; + if (pwrite(fd, fat, sizeof(fat), 1 * 4096) != sizeof(fat) || + pwrite(fd, fat, sizeof(fat), (1 + fat_sectors) * 4096) != sizeof(fat)) + return -1; + if (fstat(fd, &st) || S_ISBLK(st.st_mode)) + return 0; + return ftruncate(fd, (off_t)sectors * 4096); +} + +#define MINIX_BLOCK 1024 +#define MINIX_BLOCKS 4096 /* a 4 MiB image */ +#define MINIX_INODES 512 +#define MINIX_ITABLE (MINIX_INODES * 32 / MINIX_BLOCK) +#define MINIX_FIRSTDATA (2 + 1 + 1 + MINIX_ITABLE) /* boot, super, imap, zmap, inodes */ + +/* + * A blank minix v1 image, for the holders that need a FIFO or a device + * node on the dying mount, which vfat can't hold. Superblock in block 1, + * one block each for the inode and zone bitmaps, the inode table, and + * the root directory in the first data zone. + */ +static int write_minix(int fd) +{ + struct { + __u16 s_ninodes, s_nzones, s_imap_blocks, s_zmap_blocks; + __u16 s_firstdatazone, s_log_zone_size; + __u32 s_max_size; + __u16 s_magic, s_state; + } sb = { + .s_ninodes = MINIX_INODES, + .s_nzones = MINIX_BLOCKS, + .s_imap_blocks = 1, + .s_zmap_blocks = 1, + .s_firstdatazone = MINIX_FIRSTDATA, + .s_max_size = (7 + 512 + 512 * 512) * MINIX_BLOCK, + .s_magic = MINIX_SUPER_MAGIC, + .s_state = 1, /* MINIX_VALID_FS */ + }; + struct { + __u16 i_mode, i_uid; + __u32 i_size, i_time; + __u8 i_gid, i_nlinks; + __u16 i_zone[9]; + } root = { + .i_mode = S_IFDIR | 0755, + .i_size = 2 * 16, + .i_nlinks = 2, + .i_zone = { MINIX_FIRSTDATA }, + }; + unsigned char imap[MINIX_BLOCK], zmap[MINIX_BLOCK], dir[MINIX_BLOCK] = {}; + int i; + + /* bit 0 is reserved in both maps, the root inode and its zone are in use */ + memset(imap, 0xff, sizeof(imap)); + for (i = 2; i <= MINIX_INODES; i++) + imap[i / 8] &= ~(1 << (i % 8)); + memset(zmap, 0xff, sizeof(zmap)); + for (i = 2; i <= MINIX_BLOCKS - MINIX_FIRSTDATA; i++) + zmap[i / 8] &= ~(1 << (i % 8)); + dir[0] = 1; + dir[2] = '.'; + dir[16] = 1; + dir[18] = '.'; + dir[19] = '.'; + + if (pwrite(fd, &sb, sizeof(sb), 1 * MINIX_BLOCK) != sizeof(sb) || + pwrite(fd, imap, sizeof(imap), 2 * MINIX_BLOCK) != sizeof(imap) || + pwrite(fd, zmap, sizeof(zmap), 3 * MINIX_BLOCK) != sizeof(zmap) || + pwrite(fd, &root, sizeof(root), 4 * MINIX_BLOCK) != sizeof(root) || + pwrite(fd, dir, sizeof(dir), MINIX_FIRSTDATA * MINIX_BLOCK) != sizeof(dir)) + return -1; + return ftruncate(fd, (off_t)MINIX_BLOCKS * MINIX_BLOCK); +} + +static int read_sysfs(const char *path, char *buf, size_t size) +{ + ssize_t n; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, size - 1); + close(fd); + if (n < 0) + return -1; + buf[n] = '\0'; + return 0; +} + +/* Is @dev the mount source of this line of mountinfo? loop1 is not loop10. */ +static bool line_has_source(const char *line, const char *dev) +{ + const char *sep, *src, *end; + + sep = strstr(line, " - "); + if (!sep) + return false; + src = strchr(sep + 3, ' '); /* skip the filesystem type */ + if (!src) + return false; + src++; + end = strchr(src, ' '); + if (!end) + return false; + return (size_t)(end - src) == strlen(dev) && !strncmp(src, dev, end - src); +} + +/* + * Does a mount namespace of a process that /proc shows have a mount of @dev? + * The mounts under test are in no namespace at this point on any kernel, so + * this only checks that the test got as far as it thinks. + */ +static bool mounted_anywhere(const char *dev) +{ + char path[PATH_MAX], line[4096]; + struct dirent *de; + bool found = false; + DIR *proc; + FILE *f; + + proc = opendir("/proc"); + if (!proc) + return false; + while (!found && (de = readdir(proc))) { + if (de->d_name[0] < '0' || de->d_name[0] > '9') + continue; + snprintf(path, sizeof(path), "/proc/%s/mountinfo", de->d_name); + f = fopen(path, "re"); + if (!f) + continue; + while (fgets(line, sizeof(line), f)) { + if (line_has_source(line, dev)) { + found = true; + break; + } + } + fclose(f); + } + closedir(proc); + return found; +} + +/* Wait up to @ms milliseconds for the loop device to give up its backing file. */ +static bool loop_released(const char *sysfs, int ms) +{ + char buf[PATH_MAX]; + + for (; ms > 0; ms -= 100) { + if (read_sysfs(sysfs, buf, sizeof(buf)) < 0) + return errno == ENOENT; + usleep(100000); + } + return read_sysfs(sysfs, buf, sizeof(buf)) < 0 && errno == ENOENT; +} + +/* Tell the loop device to give its file up, now or when its last user is gone. */ +static void loop_clear(int n) +{ + char dev[32]; + int lfd; + + if (n < 0) + return; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + lfd = open(dev, O_RDWR); + if (lfd < 0) + return; + ioctl(lfd, LOOP_CLR_FD); + close(lfd); +} + +FIXTURE(loop_cycle) { + char dev[32]; /* the loop device the child set up */ + char sysfs[64]; /* its backing_file attribute */ + int loops[MAX_LOOPS]; /* every loop device the case has bound */ + int nr_loops; + bool gadget; /* the gadget's configfs directory exists */ +}; + +static void remember_loop(FIXTURE_DATA(loop_cycle) *self, int n) +{ + if (n >= 0 && self->nr_loops < MAX_LOOPS) + self->loops[self->nr_loops++] = n; +} + +FIXTURE_SETUP(loop_cycle) +{ + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + if (access("/dev/loop-control", R_OK | W_OK)) + SKIP(return, "test requires loop devices"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(base, sizeof(base), "/tmp/loop_cycle.XXXXXX"); + ASSERT_NE(mkdtemp(base), NULL); + /* a failed assertion in the setup does not run the teardown */ + ASSERT_EQ(mount("tmpfs", base, "tmpfs", 0, NULL), 0) + rmdir(base); + snprintf(gadget, sizeof(gadget), + "/sys/kernel/config/usb_gadget/kselftest_cycle_%d", getpid()); + self->dev[0] = '\0'; + self->nr_loops = 0; + self->gadget = false; +} + +static int mount_configfs(void); +static void gadget_remove(void); + +FIXTURE_TEARDOWN(loop_cycle) +{ + if (self->gadget) + gadget_remove(); + /* whatever a failed assertion has left bound */ + for (int i = 0; i < self->nr_loops; i++) + loop_clear(self->loops[i]); + umount2(base, MNT_DETACH); + rmdir(base); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_NS, /* could not set up the namespace or the tmpfs */ + CHILD_IMAGE, /* could not write the image */ + CHILD_LOOP, /* could not set up the loop device */ + CHILD_MOUNT, /* could not mount it (vfat and msdos both refused) */ + CHILD_PIPE, /* the parent went away */ + CHILD_HOLDER, /* could not set the holder up below the mount */ + CHILD_SKIP, /* the kernel lacks what the holder needs */ + CHILD_NOFS, /* the kernel lacks the filesystem of the image */ +}; + +/* Bind the loop device that LOOP_CTL_GET_FREE names to @ifd; the device number. */ +static int loop_bind_free(int ifd) +{ + int cfd, lfd, n; + char dev[32]; + + cfd = open("/dev/loop-control", O_RDWR); + if (cfd < 0) + return -1; + n = ioctl(cfd, LOOP_CTL_GET_FREE); + close(cfd); + if (n < 0) + return -1; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + lfd = open(dev, O_RDWR); + if (lfd < 0) + return -1; + if (ioctl(lfd, LOOP_SET_FD, ifd)) + n = -1; + close(lfd); + return n; +} + +/* + * Bind a free loop device to the open image @ifd; the device number. The + * device is free when LOOP_CTL_GET_FREE names it and may be somebody else's + * a moment later, so try again when it is busy. + */ +static int loop_bind(int ifd) +{ + int n = -1; + + for (int i = 0; i < 64 && n < 0; i++) { + n = loop_bind_free(ifd); + if (n < 0 && errno != EBUSY) + return -1; + } + if (n >= 0 && nr_bound < MAX_LOOPS) + bound[nr_bound++] = n; + return n; +} + +/* Mount a FAT image, as vfat or as msdos; -1 with ENODEV if the kernel has neither. */ +static int mount_fat(const char *dev, const char *mp) +{ + int err; + + if (!mount(dev, mp, "vfat", 0, NULL)) + return 0; + err = errno; + if (!mount(dev, mp, "msdos", 0, NULL)) + return 0; + if (err != ENODEV) + errno = err; + return -1; +} + +/* + * A child that gives up has to take down what it has set up: nobody else + * knows about it. Unmount, then tell the loop devices to let go. The mounts + * are released after this process is gone and the devices follow them. + */ +static int child_fails(int ret) +{ + umount2(at("vol"), MNT_DETACH); + umount2(at("vol2"), MNT_DETACH); + umount2(at("p"), MNT_DETACH); + umount2(at("img"), MNT_DETACH); + for (int i = 0; i < nr_bound; i++) + loop_clear(bound[i]); + return ret; +} + +/* Write an image to @img, bind a loop device to it and mount that at @mp; the device number. */ +static int loop_mount(const char *img, const char *mp) +{ + char dev[32]; + int ifd, n; + + ifd = open(img, O_RDWR | O_CREAT | O_EXCL, 0600); + if (ifd < 0 || write_fat12(ifd)) + return -CHILD_IMAGE; + n = loop_bind(ifd); + close(ifd); /* the loop device holds the file from now on */ + if (n < 0) + return -CHILD_LOOP; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (mkdir(mp, 0755)) + return -CHILD_MOUNT; + if (mount_fat(dev, mp)) + return errno == ENODEV ? -CHILD_NOFS : -CHILD_MOUNT; + return n; +} + +/* + * Two tmpfs mounts, each carrying the image of the loop mount below the + * other: the loop mount below vol has its image on vol2 and the other way + * round. + */ +static int crossed_child(int to_parent, int from_parent) +{ + struct report r; + char c; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL) || + mkdir(at("vol2"), 0755) || mount("tmpfs", at("vol2"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + r.n[0] = loop_mount(at("vol2/img"), at("vol/mnt")); + if (r.n[0] < 0) + return child_fails(-r.n[0]); + r.n[1] = loop_mount(at("vol/img"), at("vol2/mnt")); + if (r.n[1] < 0) + return child_fails(-r.n[1]); + if (write(to_parent, &r, sizeof(r)) != sizeof(r)) + return child_fails(CHILD_PIPE); + if (read(from_parent, &c, 1) != 1) + return child_fails(CHILD_PIPE); + return CHILD_OK; +} + +/* + * In its own mount namespace the child mounts a tmpfs on vol, + * puts a filesystem image on it, binds a loop device to the image and + * mounts that loop device below. The loop device's backing file is a + * reference on the mount the image is on, held by the loop device, held + * by the mounted filesystem, held by the mount below. + */ +static int loop_child(int to_parent, int from_parent) +{ + struct report r = { { -1, -1 } }; + char c; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + r.n[0] = loop_mount(at("vol/img"), at("vol/mnt")); + if (r.n[0] < 0) + return child_fails(-r.n[0]); + + if (write(to_parent, &r, sizeof(r)) != sizeof(r)) + return child_fails(CHILD_PIPE); + /* keep the namespace alive while the parent removes the directory */ + if (read(from_parent, &c, 1) != 1) + return child_fails(CHILD_PIPE); + return CHILD_OK; +} + +/* + * The child did not report. Skip if the kernel lacks something, fail with + * what the child said otherwise. A child that a signal killed has failed. + */ +#define CHILD_GAVE_UP(pid) do { \ + int __status; \ + \ + ASSERT_EQ(waitpid(pid, &__status, 0), pid); \ + if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_NOFS) \ + SKIP(return, "test requires the filesystem of the image (FAT or minix)"); \ + if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_SKIP) \ + SKIP(return, "the kernel lacks what this holder needs"); \ + ASSERT_TRUE(false) \ + TH_LOG("child failed to set up: %s %d", \ + WIFEXITED(__status) ? "exit status" : "signal", \ + WIFEXITED(__status) ? WEXITSTATUS(__status) : WTERMSIG(__status)); \ +} while (0) + +/* The child has to leave by itself and with nothing to complain about. */ +#define CHILD_LEFT(pid) do { \ + int __status; \ + \ + ASSERT_EQ(waitpid(pid, &__status, 0), pid); \ + ASSERT_TRUE(WIFEXITED(__status)); \ + ASSERT_EQ(WEXITSTATUS(__status), CHILD_OK); \ +} while (0) + +/* + * An exclusive open of the device fails while a filesystem holds it, and + * the only filesystem that ever did is the one mounted below the dead + * mount. Give a release in flight a moment. Then the device must clear + * right away rather than only be marked for autoclear. + */ +static void assert_loop_released(struct __test_metadata *_metadata, + FIXTURE_DATA(loop_cycle) *self) +{ + int lfd; + + for (int i = 0; i < 20; i++) { + lfd = open(self->dev, O_RDONLY | O_EXCL); + if (lfd >= 0) + break; + usleep(100000); + } + EXPECT_GE(lfd, 0) + TH_LOG("%s is still held by the loop mount below the dead mount: nothing refers to either mount and nothing can release them", + self->dev); + if (lfd >= 0) + close(lfd); + + lfd = open(self->dev, O_RDWR); + ASSERT_GE(lfd, 0); + ASSERT_EQ(ioctl(lfd, LOOP_CLR_FD), 0); + close(lfd); + ASSERT_TRUE(loop_released(self->sysfs, 5000)) + TH_LOG("%s kept its backing file after LOOP_CLR_FD: the filesystem on it is still mounted somewhere nobody can reach", + self->dev); + /* somebody else may bind it from now on, so the teardown leaves it alone */ + for (int i = 0; i < self->nr_loops; i++) { + char dev[32]; + + snprintf(dev, sizeof(dev), "/dev/loop%d", self->loops[i]); + if (!strcmp(dev, self->dev)) + self->loops[i] = -1; + } +} + +/* + * rmdir of vol from here, where it is not a mountpoint, detaches + * the child's tmpfs with the loop mount connected below it. Once the child + * is gone nothing refers to either mount. The loop device must then be + * free to give up its backing file, which only happens when the mounted + * filesystem below the detached tmpfs has been released. + */ +TEST_F(loop_cycle, detached_loop_mount_released) +{ + int to_parent[2], to_child[2]; + struct report r = { { -1, -1 } }; + char buf[PATH_MAX]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(to_child), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(loop_child(to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + remember_loop(self, r.n[0]); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]); + ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0); + ASSERT_NE(strstr(buf, "/vol/img"), NULL); + + /* not a mountpoint in this namespace, so the directory can go */ + ASSERT_EQ(rmdir(at("vol")), 0); + + /* the child leaves: its namespace and every reference it held are gone */ + ASSERT_EQ(write(to_child[1], "", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(to_child[1]); + + /* nothing can reach the two mounts any more */ + ASSERT_EQ(access(at("vol"), F_OK), -1); + ASSERT_FALSE(mounted_anywhere(self->dev)); + + assert_loop_released(_metadata, self); +} + +/* + * The same two mounts in a detached tree: a clone of vol from + * open_tree(), the image opened through the clone so that the loop device + * holds the clone, and the loop mount moved below the clone. The last + * close of the tree's fd dissolves the tree with the loop mount left + * connected below the dead clone, and nothing refers to either afterwards. + */ +TEST_F(loop_cycle, dissolved_tree_loop_mount_released) +{ + int tfd, ifd, fsfd, mfd, n; + char buf[PATH_MAX]; + + fsfd = sys_fsopen("vfat", 0); + if (fsfd < 0) + fsfd = sys_fsopen("msdos", 0); + if (fsfd < 0) + SKIP(return, "test requires a FAT filesystem"); + + ASSERT_EQ(mkdir(at("vol"), 0755), 0); + ASSERT_EQ(mount("tmpfs", at("vol"), "tmpfs", 0, NULL), 0); + tfd = sys_open_tree(AT_FDCWD, at("vol"), OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + + /* the image, opened through the clone: the loop device holds the clone */ + ifd = openat(tfd, "img", O_RDWR | O_CREAT | O_EXCL, 0600); + ASSERT_GE(ifd, 0); + ASSERT_EQ(write_fat12(ifd), 0); + n = loop_bind(ifd); + close(ifd); + ASSERT_GE(n, 0); + remember_loop(self, n); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", n); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", n); + + /* the loop mount, moved below the clone */ + ASSERT_EQ(mkdirat(tfd, "mnt", 0755), 0); + ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", self->dev, 0), 0); + ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0); + mfd = sys_fsmount(fsfd, 0, 0); + ASSERT_GE(mfd, 0); + close(fsfd); + ASSERT_EQ(sys_move_mount(mfd, "", tfd, "mnt", MOVE_MOUNT_F_EMPTY_PATH), 0); + close(mfd); + + ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0); + ASSERT_NE(strstr(buf, "img"), NULL); + + /* the last fd of the tree: both mounts die, the loop mount connected */ + close(tfd); + + /* nothing can reach the two mounts any more */ + ASSERT_FALSE(mounted_anywhere(self->dev)); + + assert_loop_released(_metadata, self); +} + +/* + * The cycle in two steps: rmdir of vol leaves the loop mount below it + * connected while its image's mount, vol2, is alive; then rmdir of vol2 + * takes that one with the loop mount whose image is on the dead vol. Each + * dead mount now owns a loop mount whose filesystem pins the other. + */ +TEST_F(loop_cycle, crossed_images_released) +{ + int to_parent[2], to_child[2]; + struct report r = { { -1, -1 } }; + char sysfs[2][64], dev[2][32]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(to_child), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(crossed_child(to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + for (int i = 0; i < 2; i++) { + remember_loop(self, r.n[i]); + snprintf(dev[i], sizeof(dev[i]), "/dev/loop%d", r.n[i]); + snprintf(sysfs[i], sizeof(sysfs[i]), "/sys/block/loop%d/loop/backing_file", r.n[i]); + } + + /* step one: the mount with the first loop mount below it goes */ + ASSERT_EQ(rmdir(at("vol")), 0); + + /* step two: the other one, with the loop mount whose image is on the first */ + ASSERT_EQ(rmdir(at("vol2")), 0); + + /* the child leaves: its namespace and every reference it held are gone */ + ASSERT_EQ(write(to_child[1], "", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(to_child[1]); + + /* nothing can reach the four mounts any more */ + for (int i = 0; i < 2; i++) { + ASSERT_FALSE(mounted_anywhere(dev[i])); + strcpy(self->dev, dev[i]); + strcpy(self->sysfs, sysfs[i]); + assert_loop_released(_metadata, self); + } +} + +/* + * The holders. Each keeps a file or a path on a mount P for as long as + * its own filesystem or device lives, and each has that filesystem or + * device mounted at C below P. Once another namespace's rmdir has + * detached P with C connected below it and the child is gone, P is owned + * by nobody, C by P, and P is kept by whatever the holder still holds. + * + * P is a loop mount so that its death can be observed: the loop device + * gives its backing file up when P's superblock goes. The image sits on + * a tmpfs next to P, not above it, so the loop device's own reference is + * not part of the picture. + */ +enum holder { + HOLDER_AUTOFS, /* a FIFO on P as the daemon's pipe */ + HOLDER_ZRAM, /* a device node on P as the writeback device */ + HOLDER_ECRYPTFS, /* a directory on P as the lower directory */ + HOLDER_BINFMT_MISC, /* an executable on P as an 'F' interpreter */ + HOLDER_FUSE, /* a file on P as a passthrough backing file */ + HOLDER_ZLOOP, /* a directory on P for the zone files */ + HOLDER_GADGET, /* a file on P as a mass storage LUN, over dummy_hcd */ + HOLDER_MD, /* a file on P as an array's bitmap file */ +}; + +#define HOLDER_IMG at("img/p.img") +#define HOLDER_MNT at("p") +#define HOLDER_BELOW at("p/c") + +static int write_file(const char *path, const char *s) +{ + int fd = open(path, O_WRONLY); + ssize_t n; + + if (fd < 0) + return -1; + n = write(fd, s, strlen(s)); + close(fd); + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Bind a free loop device to @img; the device number. */ +static int loop_attach(const char *img) +{ + int ifd, n; + + ifd = open(img, O_RDWR); + if (ifd < 0) + return -1; + n = loop_bind(ifd); + close(ifd); + return n; +} + +static int holder_autofs(void) +{ + char opts[64]; + int pfd; + + if (mkfifo(at("p/pipe"), 0600)) + return CHILD_HOLDER; + pfd = open(at("p/pipe"), O_RDWR); + if (pfd < 0) + return CHILD_HOLDER; + snprintf(opts, sizeof(opts), "fd=%d,minproto=5,maxproto=5", pfd); + if (mount("autofs", HOLDER_BELOW, "autofs", 0, opts)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + close(pfd); /* the mount keeps its own */ + return CHILD_OK; +} + +/* + * zram's writeback device has to be a block device node, and that node has + * to be on P. Point it at a second loop device. + */ +static void zram_reset(void); + +static int holder_zram(void) +{ + char dev[32], buf[64]; + struct stat st; + int fd, n; + + if (access("/sys/block/zram0/backing_dev", W_OK)) + return CHILD_SKIP; + /* somebody else's device, leave it alone */ + if (read_sysfs("/sys/block/zram0/initstate", buf, sizeof(buf)) || buf[0] != '0') + return CHILD_SKIP; + fd = open(at("img/wb.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || ftruncate(fd, IMAGE_SIZE)) + return CHILD_HOLDER; + close(fd); + n = loop_attach(at("img/wb.img")); + if (n < 0) + return CHILD_HOLDER; + /* the minor is not the number of the device when loop has partitions */ + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (stat(dev, &st) || mknod(at("p/wbdev"), S_IFBLK | 0600, st.st_rdev)) + return CHILD_HOLDER; + if (write_file("/sys/block/zram0/backing_dev", at("p/wbdev"))) + return CHILD_HOLDER; + snprintf(buf, sizeof(buf), "%d", 4 * 1024 * 1024); + if (write_file("/sys/block/zram0/disksize", buf)) + goto undo; + fd = open("/dev/zram0", O_RDWR); + if (fd < 0) + goto undo; + n = write_fat12_4k(fd, 1024); /* zram has 4 KiB blocks */ + close(fd); /* the reset in undo is refused while the device is open */ + if (n) + goto undo; + if (mount_fat("/dev/zram0", HOLDER_BELOW)) { + n = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + zram_reset(); + return n; + } + return CHILD_OK; +undo: + zram_reset(); + return CHILD_HOLDER; +} + +/* Is @alg in /proc/crypto? A module is listed once a request has loaded it. */ +static bool crypto_has(const char *alg) +{ + char line[256], name[64]; + bool found = false; + FILE *f; + + f = fopen("/proc/crypto", "r"); + if (!f) + return true; /* no way to tell, assume it is */ + while (!found && fgets(line, sizeof(line), f)) + found = sscanf(line, "name : %63s", name) == 1 && !strcmp(name, alg); + fclose(f); + return found; +} + +/* The kernel's auth token layout, which userspace has to match byte for byte. */ +struct ecryptfs_auth_tok { + __u16 version; + __u16 token_type; + __u32 flags; + struct { + __u32 flags, encrypted_key_size, decrypted_key_size; + __u8 encrypted_key[512], decrypted_key[64]; + } session_key; + __u8 reserved[32]; + struct { + __u32 password_bytes; + __s32 hash_algo; + __u32 hash_iterations, session_key_encryption_key_bytes, flags; + __u8 session_key_encryption_key[64]; + __u8 signature[17]; + __u8 salt[8]; + } password; +} __attribute__((packed)); + +#define ECRYPTFS_SIG "0123456789abcdef" + +static int holder_ecryptfs(void) +{ + struct ecryptfs_auth_tok tok = { + .version = 0x0004, + .token_type = 0, /* ECRYPTFS_PASSWORD */ + .password.session_key_encryption_key_bytes = 16, + .password.flags = 0x02, /* ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET */ + .password.signature = ECRYPTFS_SIG, + }; + + /* a session keyring of this child's own, so that the key goes with it */ + if (syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0) + return errno == ENOSYS ? CHILD_SKIP : CHILD_HOLDER; + if (syscall(__NR_add_key, "user", ECRYPTFS_SIG, &tok, sizeof(tok), + KEY_SPEC_SESSION_KEYRING) < 0) + return CHILD_HOLDER; + if (mkdir(at("p/lower"), 0755)) + return CHILD_HOLDER; + if (mount(at("p/lower"), HOLDER_BELOW, "ecryptfs", 0, + "ecryptfs_sig=" ECRYPTFS_SIG ",ecryptfs_cipher=aes,ecryptfs_key_bytes=16")) { + /* EINVAL without the cipher; a module is loaded by the attempt */ + if (errno == ENODEV || (errno == EINVAL && !crypto_has("aes"))) + return CHILD_SKIP; + return CHILD_HOLDER; + } + return CHILD_OK; +} + +/* + * binfmt_misc instances are per user namespace, so the mount below P is + * made from a new one, which gets a copy of P. + */ +static int holder_binfmt_misc(void) +{ + static const char interp[] = "#!/bin/true\n"; + char reg[PATH_MAX]; + int out; + + /* 'F' opens the interpreter at registration, nothing runs it here */ + out = open(at("p/interp"), O_WRONLY | O_CREAT | O_EXCL, 0755); + if (out < 0 || write(out, interp, sizeof(interp) - 1) != sizeof(interp) - 1) + return CHILD_HOLDER; + close(out); + + /* ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM says no */ + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) { + if (errno == EINVAL || errno == ENOSPC || errno == EPERM) + return CHILD_SKIP; + return CHILD_HOLDER; + } + if (write_file("/proc/self/setgroups", "deny") || + write_file("/proc/self/uid_map", "0 0 1") || + write_file("/proc/self/gid_map", "0 0 1")) + return CHILD_HOLDER; + if (mount("binfmt_misc", HOLDER_BELOW, "binfmt_misc", 0, NULL)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + snprintf(reg, sizeof(reg), ":cycle:E::cyc::%s:F", at("p/interp")); + if (write_file(at("p/c/register"), reg)) + return CHILD_HOLDER; + return CHILD_OK; +} + +/* + * A fuse server that only ever answers FUSE_INIT, with passthrough on, and + * then registers a file on P as a backing file. The registration alone + * makes the fuse superblock hold the file. + */ +static int holder_fuse(void) +{ + struct fuse_backing_map map = {}; + struct fuse_in_header *ih; + struct fuse_init_out init = { + .major = FUSE_KERNEL_VERSION, + .minor = FUSE_KERNEL_MINOR_VERSION, + .flags = FUSE_INIT_EXT, + .flags2 = FUSE_PASSTHROUGH >> 32, + .max_write = 4096, + .max_stack_depth = 1, + }; + struct fuse_out_header oh = { .len = sizeof(oh) + sizeof(init) }; + struct iovec iov[2] = { { &oh, sizeof(oh) }, { &init, sizeof(init) } }; + char opts[64], buf[8192]; + int ffd, bfd; + ssize_t n; + + bfd = open(at("p/backing"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (bfd < 0) + return CHILD_HOLDER; + ffd = open("/dev/fuse", O_RDWR); + if (ffd < 0) + return CHILD_SKIP; + snprintf(opts, sizeof(opts), "fd=%d,rootmode=40000,user_id=0,group_id=0", ffd); + if (mount("fuse", HOLDER_BELOW, "fuse", 0, opts)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + + n = read(ffd, buf, sizeof(buf)); + ih = (void *)buf; + if (n < (ssize_t)sizeof(*ih) || ih->opcode != FUSE_INIT) + return CHILD_HOLDER; + oh.unique = ih->unique; + if (writev(ffd, iov, 2) != (ssize_t)oh.len) + return CHILD_HOLDER; + + map.fd = bfd; + if (ioctl(ffd, FUSE_DEV_IOC_BACKING_OPEN, &map) < 0) { + /* EOPNOTSUPP: no FUSE_PASSTHROUGH, ENOTTY: no such ioctl */ + if (errno == EPERM || errno == EOPNOTSUPP || errno == ENOTTY) + return CHILD_SKIP; + return CHILD_HOLDER; + } + close(bfd); /* the connection keeps its own */ + return CHILD_OK; /* ffd stays open until the child exits */ +} + +/* Wait for a device node the kernel is about to create. */ +static int open_when_there(const char *dev, int flags, int ms) +{ + int fd; + + for (; ms > 0; ms -= 100) { + fd = open(dev, flags); + if (fd >= 0) + return fd; + usleep(100000); + } + return -1; +} + +/* + * zloop keeps every zone file open. One conventional zone is enough for a + * FAT image, the sequential one stays empty. + */ +static int holder_zloop(void) +{ + char cmd[PATH_MAX]; + int fd, ret = CHILD_HOLDER; + + if (access("/dev/zloop-control", W_OK)) + return CHILD_SKIP; + /* somebody else's device, leave it alone */ + if (!access("/sys/block/zloop0", F_OK)) + return CHILD_SKIP; + if (mkdir(at("p/zl"), 0755) || mkdir(at("p/zl/0"), 0755)) + return CHILD_HOLDER; + snprintf(cmd, sizeof(cmd), + "add id=0,capacity_mb=8,zone_size_mb=4,conv_zones=1,base_dir=%s", + at("p/zl")); + if (write_file("/dev/zloop-control", cmd)) + return CHILD_HOLDER; + fd = open_when_there("/dev/zloop0", O_RDWR, 5000); + if (fd < 0 || write_fat12_4k(fd, 1024)) /* 4 KiB blocks, like P */ + goto undo; + close(fd); + fd = -1; + if (mount_fat("/dev/zloop0", HOLDER_BELOW)) { + ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + goto undo; + } + return CHILD_OK; +undo: + if (fd >= 0) + close(fd); + write_file("/dev/zloop-control", "remove id=0"); + return ret; +} + +static int mount_configfs(void) +{ + if (access("/sys/kernel/config", F_OK)) + return -1; + if (mount("configfs", "/sys/kernel/config", "configfs", 0, NULL) && errno != EBUSY) + return -1; + return 0; +} + +/* Take the gadget out of configfs again, in the reverse order of its creation. */ +static void gadget_remove(void) +{ + if (mount_configfs()) + return; + write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n"); + write_file(gat("/UDC"), "\n"); + unlink(gat("/configs/c.1/mass_storage.0")); + rmdir(gat("/configs/c.1/strings/0x409")); + rmdir(gat("/configs/c.1")); + rmdir(gat("/functions/mass_storage.0")); + rmdir(gat("/strings/0x409")); + rmdir(gat("")); +} + +/* The disk usb-storage created for the gadget, by the LUN's inquiry string. */ +static int find_gadget_disk(char *dev, size_t len, int ms) +{ + char path[PATH_MAX], model[64]; + struct dirent *de; + DIR *d; + + for (; ms > 0; ms -= 100, usleep(100000)) { + d = opendir("/sys/block"); + if (!d) + return -1; + while ((de = readdir(d))) { + if (strncmp(de->d_name, "sd", 2)) + continue; + snprintf(path, sizeof(path), "/sys/block/%s/device/model", de->d_name); + if (read_sysfs(path, model, sizeof(model)) || + strncmp(model, "File-Stor Gadget", 16)) + continue; + snprintf(dev, len, "/dev/%s", de->d_name); + closedir(d); + return 0; + } + closedir(d); + } + return -1; +} + +/* + * A mass storage gadget bound to the dummy UDC, so that this kernel is + * also the USB host that sees the LUN as a SCSI disk. sd locks the + * medium on open, which is what keeps the LUN's file from being ejected. + */ +#define GADGET_STEP(x) do { \ + if (x) { \ + fprintf(stderr, "gadget: %s failed: %s\n", #x, strerror(errno)); \ + gadget_remove(); \ + return CHILD_HOLDER; \ + } \ +} while (0) + +static int holder_gadget(void) +{ + char dev[PATH_MAX]; + int fd; + + if (mount_configfs()) + return CHILD_SKIP; + if (access("/sys/kernel/config/usb_gadget", F_OK) || + access("/sys/class/udc/dummy_udc.0", F_OK)) + return CHILD_SKIP; + + fd = open(at("p/lun.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || write_fat12(fd)) + return CHILD_HOLDER; + close(fd); + + GADGET_STEP(mkdir(gat(""), 0755)); + GADGET_STEP(write_file(gat("/idVendor"), "0x1d6b")); + GADGET_STEP(write_file(gat("/idProduct"), "0x0104")); + GADGET_STEP(mkdir(gat("/strings/0x409"), 0755)); + GADGET_STEP(write_file(gat("/strings/0x409/serialnumber"), "1")); + GADGET_STEP(write_file(gat("/strings/0x409/manufacturer"), "kselftest")); + GADGET_STEP(write_file(gat("/strings/0x409/product"), "cycle")); + GADGET_STEP(mkdir(gat("/configs/c.1"), 0755)); + GADGET_STEP(mkdir(gat("/configs/c.1/strings/0x409"), 0755)); + GADGET_STEP(write_file(gat("/configs/c.1/strings/0x409/configuration"), "c")); + GADGET_STEP(mkdir(gat("/functions/mass_storage.0"), 0755)); + GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/removable"), "1")); + GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/file"), at("p/lun.img"))); + GADGET_STEP(symlink(gat("/functions/mass_storage.0"), gat("/configs/c.1/mass_storage.0"))); + /* EBUSY: the controller is somebody else's, leave it alone */ + if (write_file(gat("/UDC"), "dummy_udc.0")) { + fd = errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER; + gadget_remove(); + return fd; + } + + /* usb-storage waits a second before it scans the device */ + if (find_gadget_disk(dev, sizeof(dev), 15000)) { + /* without usb-storage and sd the LUN never shows up as a disk */ + fd = (access("/sys/bus/usb/drivers/usb-storage", F_OK) || + access("/sys/bus/scsi/drivers/sd", F_OK)) ? CHILD_SKIP : CHILD_HOLDER; + gadget_remove(); + return fd; + } + fd = open_when_there(dev, O_RDONLY, 5000); + GADGET_STEP(fd < 0); + close(fd); + if (mount_fat(dev, HOLDER_BELOW)) { + fd = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + gadget_remove(); + return fd; + } + return CHILD_OK; +} + +#define BITMAP_MAGIC 0x6d746962 + +/* + * A RAID1 of one loop device, not persistent, with its bitmap in a file + * on P. The bitmap file needs a superblock the kernel accepts; sync_size, + * uuid and events are not looked at for a non-persistent array. + */ +static int holder_md(void) +{ + struct { + __u32 magic, version; + __u8 uuid[16]; + __u64 events, events_cleared, sync_size; + __u32 state, chunksize, daemon_sleep, write_behind; + } bsb = { + .magic = BITMAP_MAGIC, + .version = 4, + .chunksize = 64 * 1024, + .daemon_sleep = 5, + }; + mdu_array_info_t info = { + .level = 1, + .raid_disks = 1, + .size = 8 * 1024, /* KiB */ + .not_persistent = 1, + }; + mdu_disk_info_t disk = { + .major = 7, + .state = (1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC), + }; + int fd, mdfd, bfd, n, ret = CHILD_HOLDER; + char dev[32], buf[4096]; + struct stat st; + + fd = open(at("img/md.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || ftruncate(fd, 8 * 1024 * 1024)) + return CHILD_HOLDER; + close(fd); + n = loop_attach(at("img/md.img")); + if (n < 0) + return CHILD_HOLDER; + /* the minor is not the number of the device when loop has partitions */ + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (stat(dev, &st)) + return CHILD_HOLDER; + disk.major = major(st.st_rdev); + disk.minor = minor(st.st_rdev); + + bfd = open(at("p/bitmap"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (bfd < 0 || write(bfd, &bsb, sizeof(bsb)) != sizeof(bsb) || ftruncate(bfd, 4096)) + return CHILD_HOLDER; + + if (read_sysfs("/proc/mdstat", buf, sizeof(buf))) { + fprintf(stderr, "md: no /proc/mdstat\n"); + return CHILD_SKIP; + } + if (!strstr(buf, "[raid1]")) { + fprintf(stderr, "md: no raid1 personality: %s\n", buf); + return CHILD_SKIP; + } + /* a node of our own: /dev may not have one and is not ours to change */ + if (mknod(at("img/md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0))) + return CHILD_HOLDER; + mdfd = open(at("img/md0"), O_RDWR); + if (mdfd < 0) + return CHILD_SKIP; + /* somebody else's array: SET_ARRAY_INFO would say EINVAL, not EBUSY */ + if (!read_sysfs("/sys/block/md0/md/array_state", buf, sizeof(buf)) && + strncmp(buf, "clear", 5)) { + fprintf(stderr, "md: md0 is in use: %s", buf); + close(mdfd); + return CHILD_SKIP; + } + /* the bitmap ops are only installed once a bitmap type is chosen */ + write_file("/sys/block/md0/md/bitmap_type", "bitmap"); + /* EBUSY: somebody else's array, leave it alone */ + if (ioctl(mdfd, SET_ARRAY_INFO, &info)) + return errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER; + /* the array is ours from here on and has to be stopped on every path */ + if (ioctl(mdfd, ADD_NEW_DISK, &disk)) + goto undo; + /* attach the bitmap file before the array runs, the way mdadm does */ + if (ioctl(mdfd, SET_BITMAP_FILE, bfd)) { + fprintf(stderr, "md: SET_BITMAP_FILE: %s\n", strerror(errno)); + if (errno == EINVAL) + ret = CHILD_SKIP; + goto undo; + } + close(bfd); /* the array keeps its own */ + if (ioctl(mdfd, RUN_ARRAY, NULL)) { + fprintf(stderr, "md: RUN_ARRAY: %s\n", strerror(errno)); + goto undo; + } + if (write_fat12(mdfd)) + goto undo; + close(mdfd); + if (mount_fat(at("img/md0"), HOLDER_BELOW)) { + ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + mdfd = open(at("img/md0"), O_RDWR); + goto undo; + } + return CHILD_OK; +undo: + if (mdfd >= 0) { + ioctl(mdfd, STOP_ARRAY); + close(mdfd); + } + return ret; +} + +/* + * A device keeps its file for as long as it is configured, so once nothing + * below the dead mount is left the device has to be told to let go. With + * the cycle unbroken C's filesystem still holds the device and every one + * of these refuses. + */ +static int holder_let_go_once(enum holder holder) +{ + int fd, ret; + + switch (holder) { + case HOLDER_ZRAM: + return write_file("/sys/block/zram0/reset", "1"); + case HOLDER_ZLOOP: + return write_file("/dev/zloop-control", "remove id=0"); + case HOLDER_GADGET: + if (mount_configfs()) + return -1; + /* a zero-length write is a no-op for configfs; a newline ejects */ + return write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n"); + case HOLDER_MD: + fd = open(at("img/md0"), O_RDONLY); + if (fd < 0) { + /* the child's node went with its tmpfs */ + mknod(at("md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0)); + fd = open(at("md0"), O_RDONLY); + } + if (fd < 0) + return -1; + ret = ioctl(fd, STOP_ARRAY); + close(fd); + return ret; + default: + return 0; + } +} + +static void zram_reset(void) +{ + holder_let_go_once(HOLDER_ZRAM); +} + +/* + * In its own mount namespace the child puts the image on a tmpfs next to + * P, mounts P from a loop device, and sets the holder up with its own + * filesystem or device mounted at C below P. + */ +static int holder_child(int to_parent, int from_parent, enum holder holder) +{ + bool fifo = holder == HOLDER_AUTOFS || holder == HOLDER_ZRAM; + /* room for a 4 MiB zone file or a floppy image on P */ + bool big = holder == HOLDER_ZLOOP || holder == HOLDER_GADGET; + struct report r = { { -1, -1 } }; + char dev[32], c; + int ifd, n, ret; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("img"), 0755) || mount("tmpfs", at("img"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + + ifd = open(HOLDER_IMG, O_RDWR | O_CREAT | O_EXCL, 0600); + if (ifd < 0 || (fifo ? write_minix(ifd) : + big ? write_fat12_4k(ifd, 3072) : write_fat12(ifd))) + return child_fails(CHILD_IMAGE); + close(ifd); + n = loop_attach(HOLDER_IMG); + if (n < 0) + return child_fails(CHILD_LOOP); + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (mkdir(HOLDER_MNT, 0755)) + return child_fails(CHILD_MOUNT); + if (fifo ? mount(dev, HOLDER_MNT, "minix", 0, NULL) : mount_fat(dev, HOLDER_MNT)) + return child_fails(errno == ENODEV ? CHILD_NOFS : CHILD_MOUNT); + if (mkdir(HOLDER_BELOW, 0755)) + return child_fails(CHILD_MOUNT); + + switch (holder) { + case HOLDER_AUTOFS: + ret = holder_autofs(); + break; + case HOLDER_ZRAM: + ret = holder_zram(); + break; + case HOLDER_ECRYPTFS: + ret = holder_ecryptfs(); + break; + case HOLDER_BINFMT_MISC: + ret = holder_binfmt_misc(); + break; + case HOLDER_FUSE: + ret = holder_fuse(); + break; + case HOLDER_ZLOOP: + ret = holder_zloop(); + break; + case HOLDER_GADGET: + ret = holder_gadget(); + break; + case HOLDER_MD: + ret = holder_md(); + break; + default: + ret = CHILD_HOLDER; + } + if (ret != CHILD_OK) + return child_fails(ret); + + /* P's device first, then the one the holder has bound for itself */ + r.n[0] = n; + if (nr_bound > 1) + r.n[1] = bound[1]; + if (write(to_parent, &r, sizeof(r)) != sizeof(r) || + read(from_parent, &c, 1) != 1) { + /* the parent went away and will not tell the devices to let go */ + umount2(HOLDER_BELOW, MNT_DETACH); + for (int i = 0; i < 50 && holder_let_go_once(holder); i++) + usleep(100000); + if (holder == HOLDER_GADGET) + gadget_remove(); + return child_fails(CHILD_PIPE); + } + return CHILD_OK; +} + +/* + * The release of C's filesystem may still be in flight when the child is + * gone, and a device that is still held refuses to let go, so try for a + * while. With the cycle unbroken it refuses for good. + */ +static void holder_let_go(enum holder holder) +{ + for (int i = 0; i < 50; i++) { + if (!holder_let_go_once(holder)) + return; + usleep(100000); + } +} + +/* + * rmdir of p from here, where it is a plain directory, detaches + * P in the child's namespace with C connected below it. Once the child is + * gone the holder's file on P is the only thing left that refers to P, + * and it is dropped only when C's filesystem dies, which waits for P. + * The loop device backing P tells whether that resolved. + */ +static void holder_cycle(struct __test_metadata *_metadata, + FIXTURE_DATA(loop_cycle) *self, enum holder holder) +{ + struct report r = { { -1, -1 } }; + int to_parent[2], from_parent[2]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(from_parent), 0); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(from_parent[1]); + _exit(holder_child(to_parent[1], from_parent[0], holder)); + } + close(to_parent[1]); + close(from_parent[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + self->gadget = holder == HOLDER_GADGET; + remember_loop(self, r.n[0]); + remember_loop(self, r.n[1]); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]); + + ASSERT_EQ(rmdir(HOLDER_MNT), 0); + ASSERT_EQ(write(from_parent[1], "x", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(from_parent[1]); + + holder_let_go(holder); + assert_loop_released(_metadata, self); + /* the teardown takes the gadget and the holder's own loop device away */ +} + +TEST_F(loop_cycle, autofs_pipe_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_AUTOFS); +} + +TEST_F(loop_cycle, zram_writeback_node_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ZRAM); +} + +TEST_F(loop_cycle, ecryptfs_lower_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ECRYPTFS); +} + +TEST_F(loop_cycle, binfmt_misc_interpreter_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_BINFMT_MISC); +} + +TEST_F(loop_cycle, fuse_backing_file_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_FUSE); +} + +TEST_F(loop_cycle, zloop_zone_files_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ZLOOP); +} + +/* up to 15 s for the disk to show up and 12 s for a device that is held */ +TEST_F_TIMEOUT(loop_cycle, mass_storage_lun_on_dead_mount_released, 120) +{ + holder_cycle(_metadata, self, HOLDER_GADGET); +} + +TEST_F(loop_cycle, md_bitmap_file_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_MD); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c new file mode 100644 index 00000000000000..0a0b0c0376919f --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unmounted mount that would have stayed attached to its unmounted parent + * leaves a cover behind instead. A lookup on the parent at the mountpoint + * finds knullfs: an empty read-only directory that is shared by every cover + * and every kernel thread, so it can't be watched or locked and nothing can + * be mounted on it. The mount itself is a root from then on. Where a file + * was mounted, the stand-in is an empty regular file of the same instance. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" +#include "../readdir_hold.h" + +#ifndef NULL_FS_MAGIC +#define NULL_FS_MAGIC 0x4E554C4C +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef F_SETDELEG +#define F_SETDELEG (F_SETLEASE + 16) +#endif + +/* struct delegation of , which doesn't mix with */ +struct delegation_req { + uint32_t d_flags; + uint16_t d_type; + uint16_t __pad; +}; + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* what the parent asks the child to do */ +#define CMD_RMDIR 'r' +#define CMD_CLOSE_T 't' +#define CMD_QUIT 'q' + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int touch(const char *path) +{ + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* + * The child mounts P on @base/p, C on P/covered and the file P/src on P/file + * in a mount namespace of its own, binds P a second time at @base/q and hands + * out descriptors on P and on C. rmdir() of @base/p from here unmounts P + * together with C and the file bind. P and C are held by the descriptors and + * the unmounted children leave their covers behind. On request the child + * removes C's mountpoint through the bind. + * + * It also mounts T on @base/t with a child on T/covered, binds T at @base/u + * with a second child on the same dentry and hands out descriptors on T and + * U. rmdir() of both from here leaves two covers on one mountpoint. On request + * the child lets go of T. + */ +static int cover_child(const char *base, int to_parent, int from_parent) +{ + char p[PATH_LEN], c[PATH_LEN], q[PATH_LEN], qc[PATH_LEN]; + char f[PATH_LEN], src[PATH_LEN], t[PATH_LEN], u[PATH_LEN], tc[PATH_LEN]; + int fds[4], ret; + char cmd; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return 1; + snprintf(p, sizeof(p), "%s/p", base); + if (mkdir(p, 0755) || mount("tmpfs", p, "tmpfs", 0, NULL)) + return 2; + snprintf(c, sizeof(c), "%s/p/covered", base); + if (mkdir(c, 0755) || mount("tmpfs", c, "tmpfs", 0, NULL)) + return 3; + snprintf(f, sizeof(f), "%s/p/file", base); + snprintf(src, sizeof(src), "%s/p/src", base); + if (touch(src) || touch(f) || mount(src, f, NULL, MS_BIND, NULL)) + return 4; + snprintf(q, sizeof(q), "%s/q", base); + if (mkdir(q, 0755) || mount(p, q, NULL, MS_BIND, NULL)) + return 5; + snprintf(t, sizeof(t), "%s/t", base); + if (mkdir(t, 0755) || mount("tmpfs", t, "tmpfs", 0, NULL)) + return 6; + snprintf(tc, sizeof(tc), "%s/t/covered", base); + if (mkdir(tc, 0755) || mount("tmpfs", tc, "tmpfs", 0, NULL)) + return 7; + snprintf(u, sizeof(u), "%s/u", base); + if (mkdir(u, 0755) || mount(t, u, NULL, MS_BIND, NULL)) + return 8; + snprintf(tc, sizeof(tc), "%s/u/covered", base); + if (mount("tmpfs", tc, "tmpfs", 0, NULL)) + return 9; + fds[0] = open(p, O_PATH | O_DIRECTORY | O_CLOEXEC); + fds[1] = open(c, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + fds[2] = open(t, O_PATH | O_DIRECTORY | O_CLOEXEC); + fds[3] = open(u, O_PATH | O_DIRECTORY | O_CLOEXEC); + if (fds[0] < 0 || fds[1] < 0 || fds[2] < 0 || fds[3] < 0) + return 10; + if (write(to_parent, fds, sizeof(fds)) != sizeof(fds)) + return 11; + + snprintf(qc, sizeof(qc), "%s/q/covered", base); + for (;;) { + if (read(from_parent, &cmd, 1) != 1) + return 12; + switch (cmd) { + case CMD_RMDIR: + ret = rmdir(qc) ? errno : 0; + if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret)) + return 13; + break; + case CMD_CLOSE_T: + ret = close(fds[2]) ? errno : 0; + if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret)) + return 13; + break; + case CMD_QUIT: + return 0; + default: + return 14; + } + } +} + +FIXTURE(mount_cover) { + char base[DIR_LEN]; + pid_t child; + int to_child; + int from_child; + int dfd; /* P, unmounted, held */ + int cfd; /* C, unmounted, held */ + int fd; /* what a lookup on P finds at C's mountpoint */ + int tfd; /* T, unmounted, held */ + int ufd; /* U, a bind of T, unmounted, held */ + int fan; /* fanotify group from before the user namespace, or -1 */ + struct readdir_hold hold; /* likewise from before, uffd -1 without */ +}; + +/* + * Everything the setup has made. The harness does not run the teardown + * when an assertion of the setup fails, so the setup calls this itself. + */ +static void cover_cleanup(FIXTURE_DATA(mount_cover) *self) +{ + char cmd = CMD_QUIT; + int status; + + if (self->fd >= 0) + close(self->fd); + if (self->cfd >= 0) + close(self->cfd); + if (self->dfd >= 0) + close(self->dfd); + if (self->tfd >= 0) + close(self->tfd); + if (self->ufd >= 0) + close(self->ufd); + if (self->fan >= 0) + close(self->fan); + readdir_hold_destroy(&self->hold); + if (self->child > 0) { + if (write(self->to_child, &cmd, 1) != 1) + kill(self->child, SIGKILL); + waitpid(self->child, &status, 0); + } + if (self->to_child >= 0) + close(self->to_child); + if (self->from_child >= 0) + close(self->from_child); + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int same_file(int fd1, int fd2) +{ + struct stat st1, st2; + + if (fstat(fd1, &st1) || fstat(fd2, &st2)) + return 0; + return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino; +} + +FIXTURE_SETUP(mount_cover) +{ + int to_parent[2], to_child[2], fds[4], pidfd, status; + char dir[PATH_LEN]; + struct statfs sf; + + self->child = 0; + self->to_child = self->from_child = -1; + self->dfd = self->cfd = self->fd = self->tfd = self->ufd = -1; + + /* + * A group for plain events takes CAP_SYS_ADMIN in the initial user + * namespace, so get one before that is gone. An inode mark can be + * added to it from anywhere. + */ + self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY); + /* same for the userfaultfd that holds a readdir in its fault */ + readdir_hold_init(&self->hold); + + snprintf(self->base, sizeof(self->base), "/tmp/mount_cover.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + if (enter_userns()) { + cover_cleanup(self); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0) + cover_cleanup(self); + + snprintf(dir, sizeof(dir), "%s/p", self->base); + ASSERT_EQ(pipe(to_parent), 0) + cover_cleanup(self); + ASSERT_EQ(pipe(to_child), 0) + cover_cleanup(self); + self->child = fork(); + ASSERT_GE(self->child, 0) + cover_cleanup(self); + if (self->child == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(cover_child(self->base, to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + self->to_child = to_child[1]; + self->from_child = to_parent[0]; + if (read(self->from_child, fds, sizeof(fds)) != sizeof(fds)) { + pid_t pid = self->child; + + waitpid(pid, &status, 0); + self->child = 0; + cover_cleanup(self); + ASSERT_TRUE(false) + TH_LOG("child failed to set up: %s %d", + WIFEXITED(status) ? "step" : "signal", + WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); + } + + pidfd = syscall(__NR_pidfd_open, self->child, 0); + ASSERT_GE(pidfd, 0) + cover_cleanup(self); + self->dfd = syscall(__NR_pidfd_getfd, pidfd, fds[0], 0); + self->cfd = syscall(__NR_pidfd_getfd, pidfd, fds[1], 0); + self->tfd = syscall(__NR_pidfd_getfd, pidfd, fds[2], 0); + self->ufd = syscall(__NR_pidfd_getfd, pidfd, fds[3], 0); + close(pidfd); + ASSERT_GE(self->dfd, 0) + cover_cleanup(self); + ASSERT_GE(self->cfd, 0) + cover_cleanup(self); + ASSERT_GE(self->tfd, 0) + cover_cleanup(self); + ASSERT_GE(self->ufd, 0) + cover_cleanup(self); + + /* unmounts P and C, C leaves its cover behind */ + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + /* unmounts T and U with their children, two covers on one mountpoint */ + snprintf(dir, sizeof(dir), "%s/t", self->base); + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + snprintf(dir, sizeof(dir), "%s/u", self->base); + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + + self->fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(self->fd, 0) + cover_cleanup(self); + ASSERT_EQ(fstatfs(self->fd, &sf), 0) + cover_cleanup(self); + ASSERT_EQ(sf.f_type, NULL_FS_MAGIC) + cover_cleanup(self); +} + +FIXTURE_TEARDOWN(mount_cover) +{ + cover_cleanup(self); +} + +TEST_F(mount_cover, not_watchable) +{ + char p[PATH_LEN]; + int ifd; + + snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd); + + ifd = inotify_init1(IN_CLOEXEC); + if (ifd < 0 && errno == ENOSYS) { + TH_LOG("no inotify in this kernel, skipping that part"); + } else { + ASSERT_GE(ifd, 0); + EXPECT_EQ(inotify_add_watch(ifd, p, IN_OPEN), -1); + EXPECT_EQ(errno, EINVAL); + close(ifd); + } + + /* dnotify ends up at the same place */ + EXPECT_EQ(fcntl(self->fd, F_NOTIFY, DN_ACCESS), -1); + EXPECT_EQ(errno, EINVAL); + + if (self->fan < 0) { + TH_LOG("no fanotify group without CAP_SYS_ADMIN in the initial user namespace, skipping the fanotify part"); + return; + } + EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD, FAN_OPEN, self->fd, NULL), -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST_F(mount_cover, not_lockable) +{ + struct flock fl = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + }; + + EXPECT_EQ(flock(self->fd, LOCK_EX | LOCK_NB), -1); + EXPECT_EQ(errno, ENOLCK); + EXPECT_EQ(fcntl(self->fd, F_SETLK, &fl), -1); + EXPECT_EQ(errno, ENOLCK); + EXPECT_EQ(fcntl(self->fd, F_GETLK, &fl), -1); + EXPECT_EQ(errno, ENOLCK); +} + +/* a lease is refused for the owner and for everybody else alike */ +TEST_F(mount_cover, not_leasable) +{ + struct delegation_req deleg = { + .d_type = F_RDLCK, + }; + + EXPECT_EQ(fcntl(self->fd, F_SETLEASE, F_RDLCK), -1); + EXPECT_TRUE(errno == EINVAL || errno == EACCES); + EXPECT_EQ(fcntl(self->fd, F_SETDELEG, &deleg), -1); + EXPECT_TRUE(errno == EINVAL || errno == EACCES); +} + +TEST_F(mount_cover, not_mountable) +{ + char p[PATH_LEN]; + + snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd); + EXPECT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), -1); + EXPECT_EQ(errno, ENOENT); +} + +TEST_F(mount_cover, read_only) +{ + struct statvfs sv; + + EXPECT_EQ(mkdirat(self->fd, "x", 0755), -1); + EXPECT_EQ(errno, ENOENT); + EXPECT_EQ(fchmod(self->fd, 0777), -1); + EXPECT_EQ(errno, EROFS); + /* the immutable inode is checked before the read-only mount */ + EXPECT_EQ(faccessat(self->fd, ".", W_OK, 0), -1); + EXPECT_EQ(errno, EPERM); + ASSERT_EQ(fstatvfs(self->fd, &sv), 0); + EXPECT_TRUE(sv.f_flag & ST_RDONLY); +} + +/* the stand-in is a root of its own, ".." stays put */ +TEST_F(mount_cover, island) +{ + int fd; + + fd = openat(self->fd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_TRUE(same_file(fd, self->fd)); + close(fd); +} + +/* + * C is alive for as long as the child holds it, but it can't be reached + * through P anymore and it's a root of its own as well. + */ +TEST_F(mount_cover, held_child_detached) +{ + struct statfs sf; + int fd; + + ASSERT_EQ(fstatfs(self->cfd, &sf), 0); + EXPECT_EQ(sf.f_type, TMPFS_MAGIC); + + fd = openat(self->cfd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_TRUE(same_file(fd, self->cfd)); + close(fd); + + fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); +} + +/* + * The cover goes with its mountpoint: once the child has removed C's + * mountpoint through the bind of P, the name is gone from P as well. + * What was opened through the cover before stays open. + */ +TEST_F(mount_cover, cover_goes_with_mountpoint) +{ + char cmd = CMD_RMDIR; + struct statfs sf; + int ret; + + ASSERT_EQ(write(self->to_child, &cmd, 1), 1); + ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret)); + ASSERT_EQ(ret, 0); + + EXPECT_EQ(openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1); + EXPECT_EQ(errno, ENOENT); + ASSERT_EQ(fstatfs(self->fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); +} + +/* + * A readdir of the stand-in holds nothing that others wait for: one holder + * sticks in the page fault of its buffer, and a create and a lookup of + * another come back meanwhile. + */ +TEST_F(mount_cover, readdir_blocks_nobody) +{ + bool stalled; + + if (self->hold.uffd < 0) + SKIP(return, "test requires userfaultfd"); + ASSERT_EQ(readdir_hold_check(&self->hold, self->fd, &stalled), 0); + EXPECT_FALSE(stalled); +} + +/* where a file was mounted, the stand-in is an empty regular file */ +TEST_F(mount_cover, file_stand_in) +{ + char src[PATH_LEN], p[PATH_LEN]; + struct statfs sf; + struct stat st; + char c; + int fd; + + fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_TRUE(S_ISREG(st.st_mode)); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + /* the two stand-ins are two inodes */ + EXPECT_FALSE(same_file(fd, self->fd)); + EXPECT_EQ(read(fd, &c, 1), 0); + EXPECT_EQ(flock(fd, LOCK_EX | LOCK_NB), -1); + EXPECT_EQ(errno, ENOLCK); + + snprintf(src, sizeof(src), "%s/src", self->base); + ASSERT_EQ(touch(src), 0); + snprintf(p, sizeof(p), "/proc/self/fd/%d", fd); + EXPECT_EQ(mount(src, p, NULL, MS_BIND, NULL), -1); + EXPECT_EQ(errno, ENOENT); + close(fd); + + /* writes and truncates go nowhere, like /dev/null */ + fd = openat(self->dfd, "file", O_WRONLY | O_TRUNC | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(write(fd, "xy", 2), 2); + EXPECT_EQ(ftruncate(fd, 4), 0); + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_size, 0); + /* the shared inode keeps its identity */ + EXPECT_EQ(fchmod(fd, 0600), -1); + EXPECT_EQ(errno, EPERM); + close(fd); + fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(read(fd, &c, 1), 0); + close(fd); + + EXPECT_EQ(openat(self->dfd, "file", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1); + EXPECT_EQ(errno, ENOTDIR); +} + +/* + * Two unmounted parents left covers on the same dentry. A lookup on + * either finds a stand-in, and U's cover stays when T goes. + */ +TEST_F(mount_cover, shared_mountpoint) +{ + char cmd = CMD_CLOSE_T; + struct statfs sf; + int fd, ret; + + fd = openat(self->tfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); + + fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); + + /* the last references to T go, with T its cover */ + ASSERT_EQ(write(self->to_child, &cmd, 1), 1); + ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret)); + ASSERT_EQ(ret, 0); + close(self->tfd); + self->tfd = -1; + + fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c new file mode 100644 index 00000000000000..0928a584eddc80 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A recursive bind mount of a namespace file that lives in another mount + * namespace copies whatever is stacked on top of it there. If that includes + * the file of the caller's own mount namespace, or of an older one, the copy + * would pin the namespace it is put in forever. The bind mount has to be + * refused, a plain bind mount of the file itself still works. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_UNSHARE, /* could not create the newer mount namespace */ + CHILD_PIPE, /* the parent went away */ + CHILD_TMPFS, /* could not mount the tmpfs in the new namespace */ + CHILD_REC_ALLOWED, /* the recursive bind mount was not refused */ + CHILD_REC_ERRNO, /* it was refused with the wrong error */ + CHILD_PLAIN_REFUSED, /* the plain bind mount of the file was refused */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +static int send_msg(int fd, char msg) +{ + return write(fd, &msg, 1) == 1 ? 0 : -1; +} + +static char recv_msg(int fd) +{ + char msg; + + if (read(fd, &msg, 1) != 1) + return 0; + return msg; +} + +FIXTURE(nsfs_rbind_loop) { + char dir[DIR_LEN]; + char x[PATH_LEN]; +}; + +FIXTURE_SETUP(nsfs_rbind_loop) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/nsfs_rbind_loop.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); + snprintf(self->x, sizeof(self->x), "%s/x", self->dir); + ASSERT_EQ(create_file(self->x), 0); +} + +FIXTURE_TEARDOWN(nsfs_rbind_loop) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* + * The child in the newer mount namespace binds the network namespace file + * mount of the parent through @fd. Recursively that would copy the mount of + * its own mount namespace file that the parent stacked on top. + */ +static int newer_ns_child(const char *dir, int fd, int to_parent, int from_parent) +{ + char src[32], y[PATH_LEN]; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (send_msg(to_parent, 'r') || recv_msg(from_parent) != 'g') + return CHILD_PIPE; + + snprintf(y, sizeof(y), "%s/y", dir); + if (mount("tmpfs", y, "tmpfs", 0, NULL)) + return CHILD_TMPFS; + snprintf(src, sizeof(src), "/proc/self/fd/%d", fd); + snprintf(y, sizeof(y), "%s/y/f", dir); + if (create_file(y)) + return CHILD_TMPFS; + + if (!mount(src, y, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_REC_ALLOWED; + if (errno != EINVAL) + return CHILD_REC_ERRNO; + if (mount(src, y, NULL, MS_BIND, NULL)) + return CHILD_PLAIN_REFUSED; + umount2(y, MNT_DETACH); + return CHILD_OK; +} + +TEST_F(nsfs_rbind_loop, own_ns_file_below_foreign_source) +{ + int to_child[2], to_parent[2], fd, status; + char p[PATH_LEN]; + pid_t pid; + + snprintf(p, sizeof(p), "%s/y", self->dir); + ASSERT_EQ(mkdir(p, 0755), 0); + + /* M, a mount of our network namespace file, held by a descriptor */ + ASSERT_EQ(mount("/proc/self/ns/net", self->x, NULL, MS_BIND, NULL), 0); + fd = open(self->x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + ASSERT_EQ(pipe(to_child), 0); + ASSERT_EQ(pipe(to_parent), 0); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_child[1]); + close(to_parent[0]); + _exit(newer_ns_child(self->dir, fd, to_parent[1], to_child[0])); + } + close(to_child[0]); + close(to_parent[1]); + ASSERT_EQ(recv_msg(to_parent[0]), 'r'); + + /* the child's mount namespace file on top of M */ + snprintf(p, sizeof(p), "/proc/%d/ns/mnt", pid); + ASSERT_EQ(mount(p, self->x, NULL, MS_BIND, NULL), 0); + + ASSERT_EQ(send_msg(to_child[1], 'g'), 0); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); + + close(fd); + ASSERT_EQ(umount2(self->x, MNT_DETACH), 0); + ASSERT_EQ(umount2(self->x, MNT_DETACH), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c new file mode 100644 index 00000000000000..ea1b33fa0bf8a0 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A mount namespace file bind-mounted on top of the mount that is moved + * onto a shared mount isn't copied to the peers and slaves. The mount that + * already sits at the destination in a slave has to end up on top of the + * propagated copy, not below the root of the mount namespace file where no + * path walk ever finds it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 128 + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* the first bytes of the file at @path, "" if it can't be read */ +static const char *read_file(const char *path, char *buf, size_t len) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + n = read(fd, buf, len - 1); + close(fd); + } + buf[n > 0 ? n : 0] = '\0'; + return buf; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +FIXTURE(overmount_ns_file) { + char dir[DIR_LEN]; + pid_t child; +}; + +FIXTURE_SETUP(overmount_ns_file) +{ + self->child = -1; + snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_ns_file.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(overmount_ns_file) +{ + if (self->child > 0) { + kill(self->child, SIGKILL); + waitpid(self->child, NULL, 0); + } + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* + * A is a shared tmpfs and B its slave with Q, a bind mount of a file, on + * B/file. S is a bind mount of a file with N, a bind mount of a newer mount + * namespace's file, on top of it. S is moved onto A/file. Its copy S' lands + * on B/file below Q, without N. B/file keeps reading Q and once Q is + * unmounted it reads S'. + */ +TEST_F(overmount_ns_file, existing_mount_stays_on_top) +{ + char a[PATH_LEN], b[PATH_LEN], s[PATH_LEN], p[PATH_LEN], buf[16]; + int fd, pfd[2]; + char c; + + snprintf(a, sizeof(a), "%s/A", self->dir); + snprintf(b, sizeof(b), "%s/B", self->dir); + snprintf(s, sizeof(s), "%s/s", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + ASSERT_EQ(mkdir(s, 0755), 0); + + /* A shared, B its slave, Q on B/file */ + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/A/file", self->dir); + ASSERT_EQ(create_file(p, "A"), 0); + ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, b, NULL, MS_SLAVE, NULL), 0); + snprintf(p, sizeof(p), "%s/Q", self->dir); + ASSERT_EQ(create_file(p, "Q"), 0); + snprintf(b, sizeof(b), "%s/B/file", self->dir); + ASSERT_EQ(mount(p, b, NULL, MS_BIND, NULL), 0); + + /* S on s/f, pinned by a file descriptor before N goes on top */ + ASSERT_EQ(mount("tmpfs", s, "tmpfs", 0, NULL), 0); + snprintf(p, sizeof(p), "%s/s/f", self->dir); + ASSERT_EQ(create_file(p, "f"), 0); + snprintf(s, sizeof(s), "%s/s/S", self->dir); + ASSERT_EQ(create_file(s, "S"), 0); + ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0); + fd = open(p, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + /* a newer mount namespace whose file can be bound */ + ASSERT_EQ(pipe(pfd), 0); + self->child = fork(); + ASSERT_GE(self->child, 0); + if (self->child == 0) { + close(pfd[0]); + if (unshare(CLONE_NEWNS) || write(pfd[1], "r", 1) != 1) + _exit(1); + pause(); + _exit(0); + } + close(pfd[1]); + ASSERT_EQ(read(pfd[0], &c, 1), 1); + close(pfd[0]); + snprintf(s, sizeof(s), "/proc/%d/ns/mnt", self->child); + ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0); + + ASSERT_STREQ(read_file(b, buf, sizeof(buf)), "Q"); + + snprintf(a, sizeof(a), "%s/A/file", self->dir); + ASSERT_EQ(sys_move_mount(fd, "", AT_FDCWD, a, MOVE_MOUNT_F_EMPTY_PATH), 0); + close(fd); + + /* Q is still on top of the copy in B and can be unmounted */ + EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "Q"); + EXPECT_EQ(umount2(b, 0), 0); + EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "S"); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c new file mode 100644 index 00000000000000..46a56b722f3ece --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * propagate_umount() slides a surviving overmount off a mount that is being + * unmounted and unmounts that mount right after. If a file descriptor keeps + * the unmounted mount alive its ->overmount is left pointing at the moved + * mount, which is freed on its own schedule. move_mount(MOVE_MOUNT_BENEATH) + * with such a file descriptor as the target walks ->overmount in + * topmost_overmount() before it checks that the target is still mounted and + * must not step into the freed mount. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif + +#ifndef FSCONFIG_CMD_CREATE +#define FSCONFIG_CMD_CREATE 6 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* A detached tmpfs mount to serve as the source of a move. */ +static int detached_tmpfs(void) +{ + int sfd, mfd; + + sfd = sys_fsopen("tmpfs", 0); + if (sfd < 0) + return -1; + if (sys_fsconfig(sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) { + close(sfd); + return -1; + } + mfd = sys_fsmount(sfd, 0, 0); + close(sfd); + return mfd; +} + +FIXTURE(overmount_reparent) { + char dir[DIR_LEN]; +}; + +FIXTURE_SETUP(overmount_reparent) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_reparent.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } +} + +FIXTURE_TEARDOWN(overmount_reparent) +{ + char p[PATH_LEN]; + + snprintf(p, sizeof(p), "%s/b", self->dir); + umount2(p, MNT_DETACH); + snprintf(p, sizeof(p), "%s/a", self->dir); + umount2(p, MNT_DETACH); +} + +/* + * A shared mount base_a is bind-mounted to base_b as its peer. A mount X on + * base_a/mp propagates a copy X_b onto base_b/mp. A file descriptor pins X_b, + * then X_b is made private and an overmount is stacked on its root. + * + * A lazy unmount of X propagates to X_b: X_b is committed to the unmount while + * its overmount survives, so propagate_umount() reparents the overmount onto + * base_b and unmounts X_b, which the file descriptor keeps alive. Unmounting + * the reparented overmount frees it. X_b->overmount now dangles unless + * mnt_change_mountpoint() reset it. + * + * move_mount(MOVE_MOUNT_BENEATH) through the file descriptor makes + * do_lock_mount() follow X_b->overmount in topmost_overmount() before it + * notices that X_b is no longer mounted. With the pointer reset the move fails + * cleanly with ENOENT; otherwise it reads the freed overmount. + */ +TEST_F(overmount_reparent, dead_overmount_holder_not_followed) +{ + char a[PATH_LEN], b[PATH_LEN], amp[PATH_LEN], bmp[PATH_LEN]; + int fd, mfd, ret; + + snprintf(a, sizeof(a), "%s/a", self->dir); + snprintf(b, sizeof(b), "%s/b", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0); + + snprintf(amp, sizeof(amp), "%s/a/mp", self->dir); + snprintf(bmp, sizeof(bmp), "%s/b/mp", self->dir); + /* base_a and base_b share one superblock, so this dir is in both. */ + ASSERT_EQ(mkdir(amp, 0755), 0); + + /* X on base_a/mp propagates a copy X_b onto base_b/mp. */ + ASSERT_EQ(mount("tmpfs", amp, "tmpfs", 0, NULL), 0); + + /* Pin X_b before anything is stacked on top of it. */ + fd = open(bmp, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + /* Keep the overmount local to X_b. */ + ASSERT_EQ(mount(NULL, bmp, NULL, MS_PRIVATE, NULL), 0); + + /* The overmount on X_b's root: X_b->overmount points at it. */ + ASSERT_EQ(mount("tmpfs", bmp, "tmpfs", 0, NULL), 0); + + /* Reparents the overmount onto base_b and unmounts X_b. */ + ASSERT_EQ(umount2(amp, MNT_DETACH), 0); + + /* Free the reparented overmount. */ + ASSERT_EQ(umount2(bmp, MNT_DETACH), 0); + usleep(100000); + + mfd = detached_tmpfs(); + ASSERT_GE(mfd, 0); + + ret = sys_move_mount(mfd, "", fd, "", + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH | + MOVE_MOUNT_BENEATH); + EXPECT_EQ(ret, -1); + EXPECT_EQ(errno, ENOENT); + + close(mfd); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/settings b/tools/testing/selftests/filesystems/mount_cycle/settings new file mode 100644 index 00000000000000..694d70710ff08a --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/settings @@ -0,0 +1 @@ +timeout=300 diff --git a/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c new file mode 100644 index 00000000000000..233f8ea92efc50 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unmounted mount tree that a file descriptor keeps alive is put and + * vacated behind the root's back, so nothing may walk it under + * namespace_sem: it can't be copied recursively and its propagation can't + * be changed. The mount at its root alone can still be copied and that copy + * is an ordinary mount. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +#ifndef __NR_pidfd_open +#define __NR_pidfd_open 434 +#endif + +#define PATH_LEN 64 + +static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static inline int sys_pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(__NR_pidfd_open, pid, flags); +} + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int touch(const char *path) +{ + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* A private mount namespace of our own, all mounts private. */ +static int own_mntns(void) +{ + if (unshare(CLONE_NEWNS)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +FIXTURE(unmounted_tree) { + char dir[PATH_LEN]; +}; + +FIXTURE_SETUP(unmounted_tree) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/unmounted_tree.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(unmounted_tree) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* A recursive copy of the mount @fd refers to must fail with EINVAL. */ +static void assert_not_walked(struct __test_metadata *_metadata, int fd, + const char *target) +{ + char link[PATH_LEN]; + int tfd; + + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + EXPECT_EQ(mount(link, target, NULL, MS_BIND | MS_REC, NULL), -1); + EXPECT_EQ(errno, EINVAL); + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + EXPECT_LT(tfd, 0); + EXPECT_EQ(errno, EINVAL); + if (tfd >= 0) + close(tfd); +} + +/* The mount @fd refers to can be copied on its own and mounted on @target. */ +static void assert_root_copied(struct __test_metadata *_metadata, int fd, + const char *target) +{ + int tfd; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + ASSERT_EQ(sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH), 0); + close(tfd); + ASSERT_EQ(umount2(target, MNT_DETACH), 0); +} + +/* + * A lazily unmounted bind mount of an nsfs file, kept alive by an fd, can't + * be copied recursively anymore. Copying the mount itself still works and + * so does copying a mounted bind mount of the same file. + */ +TEST_F(unmounted_tree, detached_nsfs_bind_not_walked) +{ + char x[PATH_LEN], y[PATH_LEN], z[PATH_LEN], link[PATH_LEN]; + int fd, tfd; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + snprintf(z, sizeof(z), "%s/z", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + ASSERT_EQ(touch(z), 0); + + ASSERT_EQ(mount("/proc/self/ns/net", x, NULL, MS_BIND, NULL), 0); + fd = open(x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(umount2(x, MNT_DETACH), 0); + + assert_not_walked(_metadata, fd, y); + + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + ASSERT_EQ(mount(link, y, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(umount2(y, MNT_DETACH), 0); + assert_root_copied(_metadata, fd, y); + /* opening it is fine too */ + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); + close(fd); + + ASSERT_EQ(mount("/proc/self/ns/net", y, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(y, z, NULL, MS_BIND | MS_REC, NULL), 0); + tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); +} + +/* Bind-mount the pidfd @pidfd on @target the way pidfd_bind_mount does. */ +static int bind_pidfd(int pidfd, const char *target) +{ + int tfd, ret; + + tfd = sys_open_tree(pidfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (tfd < 0) + return -1; + ret = sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + close(tfd); + return ret; +} + +/* The same for a bind mount of a pidfd. */ +TEST_F(unmounted_tree, detached_pidfs_bind_not_walked) +{ + char x[PATH_LEN], y[PATH_LEN]; + int pidfd, fd, tfd; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + + pidfd = sys_pidfd_open(getpid(), 0); + ASSERT_GE(pidfd, 0); + ASSERT_EQ(bind_pidfd(pidfd, x), 0); + fd = open(x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(umount2(x, MNT_DETACH), 0); + + assert_not_walked(_metadata, fd, y); + assert_root_copied(_metadata, fd, y); + close(fd); + + ASSERT_EQ(bind_pidfd(pidfd, y), 0); + tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); + close(pidfd); +} + +/* What a child in its own mount namespace reports back. */ +enum { + CHILD_OK, + CHILD_NS, /* could not set up the mount namespace */ + CHILD_MOUNT, /* could not set up the mounts */ + CHILD_PIPE, /* the parent went away */ + CHILD_WALKED, /* the detached tree was copied recursively */ + CHILD_ROOT_REFUSED, /* the mount at its root wasn't copied */ + CHILD_STILL_MOUNTED, /* the copy survived the unlink of its mountpoint */ + CHILD_ERRNO, /* refused, but not with EINVAL */ +}; + +static const char *child_reason(int code) +{ + static const char *const reasons[] = { + [CHILD_OK] = "ok", + [CHILD_NS] = "could not set up the mount namespace", + [CHILD_MOUNT] = "could not set up the mounts", + [CHILD_PIPE] = "the parent went away", + [CHILD_WALKED] = "the detached tree was copied recursively", + [CHILD_ROOT_REFUSED] = "the mount at its root wasn't copied", + [CHILD_STILL_MOUNTED] = "the copy survived the unlink of its mountpoint", + [CHILD_ERRNO] = "refused, but not with EINVAL", + }; + + if (code < 0 || code >= (int)(sizeof(reasons) / sizeof(reasons[0]))) + return "child died"; + return reasons[code]; +} + +struct child_args { + const char *x; /* the nsfs bind mount goes here */ + const char *s; /* a file to bind on top of it */ + const char *y; /* a target to copy to */ +}; + +/* + * Run @fn in a child in a mount namespace of its own. Once the child + * reports that its mounts are in place, unlink @victim here, where it is a + * plain file, and let the child carry on. Returns what the child reported. + */ +static int run_child(struct __test_metadata *_metadata, + int (*fn)(const struct child_args *, int, int), + const struct child_args *a, const char *victim) +{ + int to_parent[2], to_child[2], status; + pid_t pid; + char c; + + if (pipe(to_parent) || pipe(to_child)) + return -1; + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(fn(a, to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + if (read(to_parent[0], &c, 1) == 1) { + /* not a mountpoint in this namespace, so the file can go */ + EXPECT_EQ(unlink(victim), 0); + EXPECT_EQ(write(to_child[1], "", 1), 1); + } + close(to_parent[0]); + close(to_child[1]); + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* + * An nsfs bind mount on @x, an fd on it and a bind mount of @s on top. Once + * the parent has unlinked @x underneath, the first mount is detached and the + * second one, which stayed attached to it, has been vacated. The tree may + * not be walked, the mount at its root may still be copied. + */ +static int vacant_child(const struct child_args *a, int to_parent, int from_parent) +{ + char link[PATH_LEN]; + int fd, tfd; + char c; + + if (own_mntns()) + return CHILD_NS; + if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fd = open(a->x, O_PATH | O_CLOEXEC); + if (fd < 0 || mount(a->s, a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + + if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) + return CHILD_PIPE; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + if (tfd >= 0) { + close(tfd); + return CHILD_WALKED; + } + if (errno != EINVAL) + return CHILD_ERRNO; + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + if (!mount(link, a->y, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_WALKED; + if (errno != EINVAL) + return CHILD_ERRNO; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (tfd < 0) + return CHILD_ROOT_REFUSED; + close(tfd); + /* the last reference on the detached mount collects the vacant one */ + close(fd); + return CHILD_OK; +} + +/* + * unlink() of the child's mountpoint from here, where it is a plain file, + * detaches the child's nsfs bind mount with the mount on top left attached + * to it. The mount on top loses its last reference right there and is + * vacated. The child must not be able to walk that tree. + */ +TEST_F(unmounted_tree, vacant_child_not_copied) +{ + char x[PATH_LEN], s[PATH_LEN], y[PATH_LEN]; + struct child_args a = { .x = x, .s = s, .y = y }; + int ret; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(s, sizeof(s), "%s/s", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(s), 0); + ASSERT_EQ(touch(y), 0); + + ret = run_child(_metadata, vacant_child, &a, x); + ASSERT_EQ(ret, CHILD_OK) + TH_LOG("child: %s", child_reason(ret)); +} + +/* + * An nsfs bind mount on @x, lazily unmounted but kept alive by an fd, and a + * copy of it on @y. That copy is a mount like any other: once the parent has + * unlinked @y underneath, it is unmounted and its attributes can't be changed + * anymore. A copy that took the flag along is only unhooked, stays in the + * namespace and still takes the change. + */ +static int ordinary_copy(const struct child_args *a, int to_parent, int from_parent) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_NOSUID, + }; + char link[PATH_LEN]; + int fd, fdy; + char c; + + if (own_mntns()) + return CHILD_NS; + if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fd = open(a->x, O_PATH | O_CLOEXEC); + if (fd < 0 || umount2(a->x, MNT_DETACH)) + return CHILD_MOUNT; + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + if (mount(link, a->y, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fdy = open(a->y, O_PATH | O_CLOEXEC); + if (fdy < 0) + return CHILD_MOUNT; + + if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) + return CHILD_PIPE; + + if (!sys_mount_setattr(fdy, "", AT_EMPTY_PATH, &attr, sizeof(attr))) + return CHILD_STILL_MOUNTED; + if (errno != EINVAL) + return CHILD_ERRNO; + close(fdy); + close(fd); + return CHILD_OK; +} + +/* + * A copy of a detached nsfs bind mount must not take after its source and + * read as unmounted. unlink() of its mountpoint from here unmounts it like + * any other mount, so the child can't change its attributes anymore. + */ +TEST_F(unmounted_tree, copy_of_detached_bind_is_ordinary) +{ + char x[PATH_LEN], y[PATH_LEN]; + struct child_args a = { .x = x, .y = y }; + int ret; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + + ret = run_child(_metadata, ordinary_copy, &a, y); + ASSERT_EQ(ret, CHILD_OK) + TH_LOG("child: %s", child_reason(ret)); +} + +/* + * mount_setattr() may change the propagation of a detached tree while that + * tree is still the root of an anonymous mount namespace, but not once the + * tree has been dissolved and only a second fd keeps its root alive. + */ +TEST_F(unmounted_tree, detached_tree_setattr_refused) +{ + struct mount_attr attr = { + .propagation = MS_SHARED, + }; + char a[PATH_LEN], b[PATH_LEN]; + int tfd, fd; + + snprintf(a, sizeof(a), "%s/a", self->dir); + snprintf(b, sizeof(b), "%s/a/b", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + ASSERT_EQ(mount("tmpfs", b, "tmpfs", 0, NULL), 0); + + tfd = sys_open_tree(AT_FDCWD, self->dir, AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); + attr.propagation = MS_PRIVATE; + ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); + + /* a second reference on the root, then dissolve the tree */ + fd = sys_open_tree(tfd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); + ASSERT_GE(fd, 0); + close(tfd); + + attr.propagation = MS_SHARED; + ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), -1); + ASSERT_EQ(errno, EINVAL); + attr.propagation = MS_PRIVATE; + ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), -1); + ASSERT_EQ(errno, EINVAL); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore index fb12b93fbcaa0b..76f95c0ae5ef41 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore +++ b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore @@ -1 +1,2 @@ open_tree_ns_test +open_tree_ns_covered_test diff --git a/tools/testing/selftests/filesystems/open_tree_ns/Makefile b/tools/testing/selftests/filesystems/open_tree_ns/Makefile index 4976ed1d7d4a92..fb2aa77b6edbb6 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/Makefile +++ b/tools/testing/selftests/filesystems/open_tree_ns/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := open_tree_ns_test +TEST_GEN_PROGS := open_tree_ns_test open_tree_ns_covered_test CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS := -lcap diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c new file mode 100644 index 00000000000000..27f5cddfb9ef7a --- /dev/null +++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * open_tree(OPEN_TREE_NAMESPACE) by a caller that isn't privileged over the + * mount namespace it copies from must not reveal what the mounts below the + * copied mount cover. Without AT_RECURSIVE the copy is refused when there's + * anything mounted below the requested directory. With AT_RECURSIVE + * unbindable mounts are copied as well. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_NAMESPACE +#define OPEN_TREE_NAMESPACE (1 << 1) +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_USERNS, /* could not create the child user namespace */ + CHILD_NONREC_ALLOWED, /* the non-recursive copy was not refused */ + CHILD_NONREC_ERRNO, /* it was refused with the wrong error */ + CHILD_REC_REFUSED, /* the recursive copy failed */ + CHILD_SETNS, /* could not enter the new mount namespace */ + CHILD_NO_COVER, /* the covering mount is missing in the copy */ + CHILD_REVEALED, /* the covered file is visible in the copy */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace, the uid @uid is mapped to 0. */ +static int enter_userns(uid_t uid, gid_t gid) +{ + char map[32]; + + if (unshare(CLONE_NEWUSER)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + return setgid(0) || setuid(0) ? -1 : 0; +} + +FIXTURE(open_tree_ns_covered) { + char dir[DIR_LEN]; + char cover[PATH_LEN]; +}; + +FIXTURE_VARIANT(open_tree_ns_covered) { + int propagation; +}; + +FIXTURE_VARIANT_ADD(open_tree_ns_covered, private_cover) { + .propagation = MS_PRIVATE, +}; + +FIXTURE_VARIANT_ADD(open_tree_ns_covered, unbindable_cover) { + .propagation = MS_UNBINDABLE, +}; + +/* + * Root in a user namespace owns a private mount namespace with a tmpfs + * on @dir and a second tmpfs covering @dir/covered/under.txt. + */ +FIXTURE_SETUP(open_tree_ns_covered) +{ + char p[PATH_LEN]; + int fd; + + snprintf(self->dir, sizeof(self->dir), "/tmp/open_tree_ns_covered.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns(getuid(), getgid()) || unshare(CLONE_NEWNS)) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); + + /* the flag has to exist before its rules can be checked */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + if (fd < 0 && (errno == EINVAL || errno == ENOSYS)) { + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); + SKIP(return, "OPEN_TREE_NAMESPACE not supported"); + } + ASSERT_GE(fd, 0); + close(fd); + + snprintf(self->cover, sizeof(self->cover), "%s/covered", self->dir); + ASSERT_EQ(mkdir(self->cover, 0755), 0); + snprintf(p, sizeof(p), "%s/covered/under.txt", self->dir); + ASSERT_EQ(create_file(p, "hidden"), 0); + ASSERT_EQ(mount("tmpfs", self->cover, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, self->cover, NULL, variant->propagation, NULL), 0); +} + +FIXTURE_TEARDOWN(open_tree_ns_covered) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* A caller in a new user namespace that doesn't own the mount namespace. */ +static int foreign_child(const char *dir) +{ + struct stat st; + int fd; + + if (enter_userns(0, 0)) + return CHILD_USERNS; + + fd = sys_open_tree(AT_FDCWD, dir, OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + if (fd >= 0) + return CHILD_NONREC_ALLOWED; + if (errno != EINVAL) + return CHILD_NONREC_ERRNO; + + fd = sys_open_tree(AT_FDCWD, dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); + if (fd < 0) + return CHILD_REC_REFUSED; + if (setns(fd, CLONE_NEWNS)) + return CHILD_SETNS; + if (stat("/covered", &st)) + return CHILD_NO_COVER; + if (!access("/covered/under.txt", F_OK) || errno != ENOENT) + return CHILD_REVEALED; + return CHILD_OK; +} + +TEST_F(open_tree_ns_covered, foreign_user_namespace) +{ + int status, fd; + pid_t pid; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(foreign_child(self->dir)); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); + + /* the owner of the mount namespace keeps bind mount semantics */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + ASSERT_GE(fd, 0); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore index 077f7a1281687c..b343cc430051d2 100644 --- a/tools/testing/selftests/filesystems/overlayfs/.gitignore +++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore @@ -2,3 +2,4 @@ dev_in_maps set_layers_via_fds idmapped_mounts +automount_in_layer diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index b3185f684add60..382a59bda5e708 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -9,6 +9,7 @@ LOCAL_HDRS += ../wrappers.h log.h TEST_GEN_PROGS := dev_in_maps TEST_GEN_PROGS += set_layers_via_fds TEST_GEN_PROGS += idmapped_mounts +TEST_GEN_PROGS += automount_in_layer include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c new file mode 100644 index 00000000000000..0476c4582bdfcc --- /dev/null +++ b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A layer of an overlay is a private clone of the mount it was given and + * belongs to no mount namespace. fanotify hands out descriptors on it. An + * automount triggered through one has no namespace to go into: the open has + * to fail, not oops with namespace_sem held. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 192 + +static bool have_fs(const char *name) +{ + char line[128]; + bool found = false; + FILE *f; + + f = fopen("/proc/filesystems", "re"); + if (!f) + return false; + while (fgets(line, sizeof(line), f)) { + char *nl = strchr(line, '\n'); + char *tab = strchr(line, '\t'); + + if (nl) + *nl = 0; + if (tab && !strcmp(tab + 1, name)) + found = true; + } + fclose(f); + return found; +} + +static int mnt_id_of(int fd) +{ + char path[64], buf[4096], *p; + ssize_t n; + int info; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", fd); + info = open(path, O_RDONLY | O_CLOEXEC); + if (info < 0) + return -1; + n = read(info, buf, sizeof(buf) - 1); + close(info); + if (n <= 0) + return -1; + buf[n] = 0; + p = strstr(buf, "mnt_id:"); + return p ? atoi(p + strlen("mnt_id:")) : -1; +} + +static bool on_debugfs(int fd) +{ + struct statfs sf; + + return !fstatfs(fd, &sf) && sf.f_type == DEBUGFS_MAGIC; +} + +FIXTURE(layer) { + char base[DIR_LEN]; + int fan; + int evfd; /* on the layer clone of the lower debugfs */ +}; + +FIXTURE_SETUP(layer) +{ + char lower[PATH_LEN], other[PATH_LEN], ovl[PATH_LEN], opts[2 * PATH_LEN + 16]; + struct fanotify_event_metadata *ev; + struct pollfd pfd; + char buf[4096]; + int lfd, lower_id; + ssize_t n; + DIR *d; + + self->fan = -1; + self->evfd = -1; + if (geteuid()) + SKIP(return, "test requires root"); + if (!have_fs("debugfs") || !have_fs("tracefs")) + SKIP(return, "test requires debugfs with the tracefs automount"); + if (!have_fs("overlay")) + SKIP(return, "test requires overlayfs"); + + snprintf(self->base, sizeof(self->base), "/tmp/layer.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0); + snprintf(lower, sizeof(lower), "%s/lower", self->base); + snprintf(other, sizeof(other), "%s/other", self->base); + snprintf(ovl, sizeof(ovl), "%s/ovl", self->base); + ASSERT_EQ(mkdir(lower, 0755), 0); + ASSERT_EQ(mkdir(other, 0755), 0); + ASSERT_EQ(mkdir(ovl, 0755), 0); + ASSERT_EQ(mount("debugfs", lower, "debugfs", 0, NULL), 0); + snprintf(opts, sizeof(opts), "lowerdir=%s:%s", lower, other); + ASSERT_EQ(mount("overlay", ovl, "overlay", MS_RDONLY, opts), 0); + + self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_NONBLOCK | FAN_CLOEXEC, + O_RDONLY | O_CLOEXEC); + ASSERT_GE(self->fan, 0); + ASSERT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, + FAN_OPEN | FAN_ONDIR, AT_FDCWD, lower), 0); + lfd = open(lower, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(lfd, 0); + lower_id = mnt_id_of(lfd); + close(lfd); + + /* the overlay opens its lower directory through the layer clone */ + d = opendir(ovl); + ASSERT_NE(d, NULL); + closedir(d); + pfd.fd = self->fan; + pfd.events = POLLIN; + ASSERT_EQ(poll(&pfd, 1, 5000), 1); + n = read(self->fan, buf, sizeof(buf)); + ASSERT_GT(n, 0); + for (ev = (void *)buf; FAN_EVENT_OK(ev, n); ev = FAN_EVENT_NEXT(ev, n)) { + if (ev->fd < 0) + continue; + if (self->evfd < 0 && on_debugfs(ev->fd) && + mnt_id_of(ev->fd) != lower_id) + self->evfd = ev->fd; + else + close(ev->fd); + } + ASSERT_GE(self->evfd, 0) + TH_LOG("no event on the layer clone"); +} + +FIXTURE_TEARDOWN(layer) +{ + if (self->evfd >= 0) + close(self->evfd); + if (self->fan >= 0) + close(self->fan); + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +TEST_F(layer, automount_below_the_clone_is_refused) +{ + struct stat st; + int fd; + + /* the automount point is there */ + ASSERT_EQ(fstatat(self->evfd, "tracing", &st, AT_NO_AUTOMOUNT), 0); + /* the clone is in no namespace, so the automount has nowhere to go */ + fd = openat(self->evfd, "tracing", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + EXPECT_LT(fd, 0); + EXPECT_EQ(errno, EINVAL); + if (fd >= 0) + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/readdir_hold.h b/tools/testing/selftests/filesystems/readdir_hold.h new file mode 100644 index 00000000000000..57eee1bef4700d --- /dev/null +++ b/tools/testing/selftests/filesystems/readdir_hold.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Hold a readdir of a directory in the page fault of its buffer and see + * whether a create and a lookup in that directory wait for it. For a + * directory that is permanently empty they must not. + */ +#ifndef __SELFTESTS_READDIR_HOLD_H +#define __SELFTESTS_READDIR_HOLD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HOLD_FAULT_MS 5000 /* for the reader to reach the fault */ +#define HOLD_QUEUE_MS 2000 /* for the create to queue up behind it */ +#define HOLD_LOOKUP_MS 5000 /* for the lookup to come back */ + +struct readdir_hold { + int uffd; + int taskdir; /* /proc/self/task, from before any namespace change */ + long page_size; + char *page; /* faults until released */ + int dfd; + pid_t creator_tid; + int created; + int done[2]; /* the finder writes a byte when it is back */ +}; + +/* + * The fault happens in the kernel, so the userfaultfd needs CAP_SYS_PTRACE + * in the initial user namespace or vm.unprivileged_userfaultfd. Call this + * before entering a user namespace. + */ +static inline int readdir_hold_init(struct readdir_hold *h) +{ + struct uffdio_api api = { .api = UFFD_API }; + + h->page = MAP_FAILED; + h->page_size = sysconf(_SC_PAGESIZE); + h->taskdir = open("/proc/self/task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + h->uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + if (h->uffd < 0 || h->taskdir < 0 || ioctl(h->uffd, UFFDIO_API, &api)) { + if (h->uffd >= 0) + close(h->uffd); + if (h->taskdir >= 0) + close(h->taskdir); + h->uffd = h->taskdir = -1; + return -1; + } + return 0; +} + +static inline void readdir_hold_destroy(struct readdir_hold *h) +{ + if (h->uffd >= 0) + close(h->uffd); + if (h->taskdir >= 0) + close(h->taskdir); + h->uffd = h->taskdir = -1; +} + +static inline void *readdir_hold_reader(void *arg) +{ + struct readdir_hold *h = arg; + + /* the first byte written to the buffer faults until released */ + syscall(__NR_getdents64, h->dfd, h->page, h->page_size); + return NULL; +} + +static inline void *readdir_hold_creator(void *arg) +{ + struct readdir_hold *h = arg; + + h->creator_tid = syscall(__NR_gettid); + /* takes the directory lock exclusive before it fails */ + mkdirat(h->dfd, "x", 0755); + __atomic_store_n(&h->created, 1, __ATOMIC_RELEASE); + return NULL; +} + +static inline void *readdir_hold_finder(void *arg) +{ + struct readdir_hold *h = arg; + int fd; + + /* a lookup that misses the dcache takes the lock shared */ + fd = openat(h->dfd, "no_such_name", O_RDONLY | O_CLOEXEC); + if (fd >= 0) + close(fd); + if (write(h->done[1], "x", 1) != 1) + perror("readdir_hold: finder"); + return NULL; +} + +/* the creator is back, or waits in the kernel for the lock */ +static inline bool readdir_hold_creator_settled(struct readdir_hold *h) +{ + char path[32], buf[256], *p; + ssize_t n; + int fd; + + if (__atomic_load_n(&h->created, __ATOMIC_ACQUIRE)) + return true; + if (!h->creator_tid) + return false; + snprintf(path, sizeof(path), "%d/stat", h->creator_tid); + fd = openat(h->taskdir, path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n <= 0) + return false; + buf[n] = 0; + /* "pid (comm) state ..." */ + p = strrchr(buf, ')'); + return p && p[1] == ' ' && p[2] == 'D'; +} + +static inline bool readdir_hold_faulted(struct readdir_hold *h) +{ + struct pollfd pfd = { .fd = h->uffd, .events = POLLIN }; + struct uffd_msg msg; + + if (poll(&pfd, 1, HOLD_FAULT_MS) != 1) + return false; + if (read(h->uffd, &msg, sizeof(msg)) != sizeof(msg)) + return false; + return msg.event == UFFD_EVENT_PAGEFAULT; +} + +/* let the reader go on */ +static inline void readdir_hold_release(struct readdir_hold *h) +{ + struct uffdio_copy cp = { + .dst = (unsigned long)h->page, + .len = h->page_size, + }; + void *zero; + + zero = calloc(1, h->page_size); + if (!zero) + return; + cp.src = (unsigned long)zero; + if (ioctl(h->uffd, UFFDIO_COPY, &cp) && errno != EEXIST) + perror("readdir_hold: UFFDIO_COPY"); + free(zero); +} + +/* + * A readdir of @dfd that sticks in the fault of its buffer, then a create + * and a lookup in @dfd. Whether the lookup came back while the readdir was + * still stuck goes to @stalled. Returns -1 when that could not be found + * out. + */ +static inline int readdir_hold_check(struct readdir_hold *h, int dfd, + bool *stalled) +{ + pthread_t reader, creator, finder; + struct uffdio_register reg = {}; + struct pollfd pfd; + int ret = -1, i; + + h->dfd = dfd; + h->created = 0; + h->creator_tid = 0; + h->page = mmap(NULL, h->page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (h->page == MAP_FAILED) + return -1; + reg.range.start = (unsigned long)h->page; + reg.range.len = h->page_size; + reg.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(h->uffd, UFFDIO_REGISTER, ®) || pipe2(h->done, O_CLOEXEC)) + goto out_page; + + if (pthread_create(&reader, NULL, readdir_hold_reader, h)) + goto out_pipe; + if (!readdir_hold_faulted(h)) + goto out_reader; + if (pthread_create(&creator, NULL, readdir_hold_creator, h)) + goto out_reader; + for (i = 0; i < HOLD_QUEUE_MS / 10 && !readdir_hold_creator_settled(h); i++) + usleep(10000); + if (pthread_create(&finder, NULL, readdir_hold_finder, h)) + goto out_creator; + + pfd.fd = h->done[0]; + pfd.events = POLLIN; + *stalled = poll(&pfd, 1, HOLD_LOOKUP_MS) != 1; + ret = 0; + + readdir_hold_release(h); + pthread_join(finder, NULL); +out_creator: + if (ret) + readdir_hold_release(h); + pthread_join(creator, NULL); +out_reader: + if (ret) + readdir_hold_release(h); + pthread_join(reader, NULL); +out_pipe: + close(h->done[0]); + close(h->done[1]); +out_page: + munmap(h->page, h->page_size); + h->page = MAP_FAILED; + return ret; +} + +#endif /* __SELFTESTS_READDIR_HOLD_H */ diff --git a/tools/testing/selftests/filesystems/rw_hint_test.c b/tools/testing/selftests/filesystems/rw_hint_test.c new file mode 100644 index 00000000000000..d1930f82f63b5d --- /dev/null +++ b/tools/testing/selftests/filesystems/rw_hint_test.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * F_SET_RW_HINT is refused on an immutable inode. Nothing is ever written + * to it and it may be shared with everybody, like a namespace file or the + * root of an empty mount namespace. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +/* and don't mix with the libc headers */ +#ifndef FS_IOC_GETFLAGS +#define FS_IOC_GETFLAGS _IOR('f', 1, long) +#define FS_IOC_SETFLAGS _IOW('f', 2, long) +#endif +#ifndef FS_IMMUTABLE_FL +#define FS_IMMUTABLE_FL 0x00000010 +#endif +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_GET_RW_HINT +#define F_GET_RW_HINT (F_LINUX_SPECIFIC_BASE + 11) +#define F_SET_RW_HINT (F_LINUX_SPECIFIC_BASE + 12) +#endif +#ifndef RWH_WRITE_LIFE_SHORT +#define RWH_WRITE_LIFE_SHORT 2 +#endif +#ifndef UNSHARE_EMPTY_MNTNS +#define UNSHARE_EMPTY_MNTNS 0x00100000 +#endif + +static int set_hint(int fd, uint64_t hint) +{ + return fcntl(fd, F_SET_RW_HINT, &hint); +} + +static long get_hint(int fd) +{ + uint64_t hint; + + if (fcntl(fd, F_GET_RW_HINT, &hint)) + return -1; + return hint; +} + +TEST(immutable_file) +{ + char path[] = "/tmp/rw_hint.XXXXXX"; + int fd, flags; + + if (geteuid()) + SKIP(return, "test requires root"); + + fd = mkstemp(path); + ASSERT_GE(fd, 0); + unlink(path); + ASSERT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0); + EXPECT_EQ(get_hint(fd), RWH_WRITE_LIFE_SHORT); + + if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) { + close(fd); + SKIP(return, "no file attributes on this filesystem"); + } + flags |= FS_IMMUTABLE_FL; + ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1); + EXPECT_EQ(errno, EPERM); + flags &= ~FS_IMMUTABLE_FL; + ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0); + close(fd); +} + +TEST(namespace_file) +{ + int fd; + + if (geteuid()) + SKIP(return, "test requires root"); + + fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1); + EXPECT_EQ(errno, EPERM); + close(fd); +} + +TEST(empty_mntns_root) +{ + int status; + pid_t pid; + + if (geteuid()) + SKIP(return, "test requires root"); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + int fd; + + if (unshare(UNSHARE_EMPTY_MNTNS)) + _exit(errno == EINVAL ? 100 : 1); + fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fd < 0) + _exit(2); + if (set_hint(fd, RWH_WRITE_LIFE_SHORT) == 0) + _exit(3); + _exit(errno == EPERM ? 0 : 4); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + if (WEXITSTATUS(status) == 100) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + EXPECT_EQ(WEXITSTATUS(status), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile new file mode 100644 index 00000000000000..31f783a92b2314 --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test locked_mount_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c new file mode 100644 index 00000000000000..0799b2ba545d70 --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MNT_LOCKED keeps the owner of a user namespace from revealing what a + * mount covers. The lock has to be set on the copy in that namespace and + * only there, it has to survive the expiry of a mount placed beneath the + * locked one, and it has to survive the propagated umount of such a copy. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 192 +#define FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC) + +/* exit codes of the children, each test says what they mean */ +enum { + CHILD_OK, + CHILD_SETUP, + CHILD_STEP1, + CHILD_STEP2, + CHILD_STEP3, + CHILD_STEP4, +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Root in a new user namespace with a copy of the mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + return setgid(0) || setuid(0) ? -1 : 0; +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +static int wait_byte(int fd) +{ + char c; + + return read(fd, &c, 1) == 1 ? 0 : -1; +} + +static int send_byte(int fd) +{ + return write(fd, "x", 1) == 1 ? 0 : -1; +} + +/* A read of @path fails: the file is covered. */ +static bool covered(const char *path) +{ + int fd = open(path, O_RDONLY | O_CLOEXEC); + + if (fd >= 0) + close(fd); + return fd < 0; +} + +FIXTURE(locked_mount) { + char base[DIR_LEN]; + bool tracing; /* debugfs with the tracefs automount is there */ +}; + +FIXTURE_SETUP(locked_mount) +{ + char p[PATH_LEN]; + struct stat st; + + if (geteuid()) + SKIP(return, "test requires root"); + + snprintf(self->base, sizeof(self->base), "/tmp/locked_mount.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0); + + /* an automount that a user can name: the tracefs below debugfs */ + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + self->tracing = !mount("debugfs", p, "debugfs", FLAGS, NULL); + if (self->tracing) { + /* the cases trigger it themselves */ + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + self->tracing = !fstatat(AT_FDCWD, p, &st, AT_NO_AUTOMOUNT) && + S_ISDIR(st.st_mode); + } +} + +FIXTURE_TEARDOWN(locked_mount) +{ + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +/* + * The child keeps a directory descriptor on the host's debugfs mount, moves + * to a user namespace of its own and triggers the automount through the + * descriptor. The mount goes below the host's mount and propagates into the + * child's copy. The child's copy has to be locked, the host's not. + */ +static int automount_child(const char *base, int dfd, int to_host, int from_host) +{ + char p[PATH_LEN]; + struct stat st; + + if (enter_userns()) + return CHILD_SETUP; + if (fstatat(dfd, "tracing/.", &st, 0)) + return CHILD_STEP1; + if (send_byte(to_host) || wait_byte(from_host)) + return CHILD_SETUP; + /* the flags of the copy are locked: EPERM */ + snprintf(p, sizeof(p), "%s/dbg/tracing", base); + if (!mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL) || errno != EPERM) + return CHILD_STEP2; + return CHILD_OK; +} + +TEST_F(locked_mount, automount_locked_in_the_triggering_namespace) +{ + int to_host[2], from_host[2], dfd, ret; + char p[PATH_LEN]; + pid_t pid; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + dfd = open(p, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(dfd, 0); + ASSERT_EQ(pipe(to_host), 0); + ASSERT_EQ(pipe(from_host), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(automount_child(self->base, dfd, to_host[1], from_host[0])); + close(dfd); + ASSERT_EQ(wait_byte(to_host[0]), 0); + + /* the host's own mount isn't locked: the flags can go */ + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + EXPECT_EQ(mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL), 0); + + ASSERT_EQ(send_byte(from_host[1]), 0); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * A copy of a tree with a locked cover. The child puts a shrinkable mount + * beneath the cover, which hands the lock down, unmounts the cover and then + * asks for the umount of an unlocked ancestor. That expires the shrinkable + * mount on a kernel that doesn't look at the lock, and the covered + * directory is bare. + */ +static int expiry_child(const char *base) +{ + char srv[PATH_LEN], shr[PATH_LEN], x[PATH_LEN], c[PATH_LEN]; + char hidden[PATH_LEN], secret[PATH_LEN]; + + snprintf(srv, sizeof(srv), "%s/srv", base); + snprintf(shr, sizeof(shr), "%s/shr", base); + snprintf(x, sizeof(x), "%s/x", base); + snprintf(c, sizeof(c), "%s/c", base); + snprintf(hidden, sizeof(hidden), "%s/x/hidden", base); + snprintf(secret, sizeof(secret), "%s/x/hidden/secret", base); + + if (enter_userns()) + return CHILD_SETUP; + if (mount(srv, x, NULL, MS_BIND | MS_REC, NULL) || + mount(shr, c, NULL, MS_BIND, NULL)) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(hidden, 0) || errno != EINVAL) + return CHILD_STEP1; + if (syscall(__NR_move_mount, AT_FDCWD, c, AT_FDCWD, hidden, MOVE_MOUNT_BENEATH)) + return CHILD_STEP2; + /* the lock moved down, the cover may go */ + if (umount2(hidden, 0)) + return CHILD_STEP3; + /* the holder of the lock may not, in any way */ + if (!umount2(hidden, 0) || errno != EINVAL) + return CHILD_STEP4; + if (chdir(x)) + return CHILD_SETUP; + umount2(x, 0); + return covered(secret) ? CHILD_OK : CHILD_STEP4; +} + +TEST_F(locked_mount, expiry_leaves_a_locked_mount_alone) +{ + char p[PATH_LEN], q[PATH_LEN]; + pid_t pid; + int ret; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + snprintf(q, sizeof(q), "%s/shr", self->base); + ASSERT_EQ(mkdir(q, 0755), 0); + /* a bind of an automount is shrinkable as well */ + ASSERT_EQ(mount(p, q, NULL, MS_BIND, NULL), 0); + snprintf(p, sizeof(p), "%s/srv", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/srv/hidden", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/srv/hidden/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + snprintf(p, sizeof(p), "%s/srv/hidden", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/x", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/c", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(expiry_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * The copy of the mount tree that a user namespace gets at its creation has + * the automounts in it locked unless they are on an expiry list. A bind of + * such a tree inside the namespace copies the lock to the automount but not + * to the root of the bind, so the child may ask for the umount of that root. + * That must not expire the locked automount below it: the root is busy, the + * umount fails and the automount has to be there afterwards. + */ +static int copied_tree_child(const char *base) +{ + char dbg[PATH_LEN], x[PATH_LEN], tracing[PATH_LEN]; + struct stat root, st; + + snprintf(dbg, sizeof(dbg), "%s/dbg", base); + snprintf(x, sizeof(x), "%s/x", base); + snprintf(tracing, sizeof(tracing), "%s/x/tracing", base); + + if (enter_userns()) + return CHILD_SETUP; + if (mount(dbg, x, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + /* the copy of the automount carries the lock */ + if (!umount2(tracing, 0) || errno != EINVAL) + return CHILD_STEP1; + /* the root of the bind doesn't; keep it busy */ + if (chdir(x)) + return CHILD_SETUP; + if (!umount2(x, 0) || errno != EBUSY) + return CHILD_STEP2; + /* the automount below it must not have gone */ + if (stat(x, &root) || fstatat(AT_FDCWD, tracing, &st, AT_NO_AUTOMOUNT)) + return CHILD_SETUP; + return st.st_dev != root.st_dev ? CHILD_OK : CHILD_STEP3; +} + +TEST_F(locked_mount, umount_of_a_bind_leaves_a_locked_automount_alone) +{ + char p[PATH_LEN]; + struct stat st; + pid_t pid; + int ret; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + /* the copy the child gets has to contain the automount */ + snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base); + ASSERT_EQ(stat(p, &st), 0); + snprintf(p, sizeof(p), "%s/x", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(copied_tree_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * Z is a user namespace with a copy of a tree in which P/d is covered by a + * locked mount. The host mounts X on P/d, which propagates beneath Z's + * cover, and unmounts it again. Z's cover has to be locked afterwards as + * it was before. + */ +static int propagation_child(const char *base, int to_host, int from_host) +{ + char d[PATH_LEN], secret[PATH_LEN]; + + snprintf(d, sizeof(d), "%s/P/d", base); + snprintf(secret, sizeof(secret), "%s/P/d/secret", base); + + if (enter_userns()) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(d, 0) || errno != EINVAL) + return CHILD_STEP1; + if (send_byte(to_host) || wait_byte(from_host)) + return CHILD_SETUP; + /* X came and went beneath it: still locked */ + if (!umount2(d, 0) || errno != EINVAL) + return CHILD_STEP2; + return covered(secret) ? CHILD_OK : CHILD_STEP3; +} + +TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked) +{ + int to_host[2], from_host[2], ret; + char p[PATH_LEN]; + pid_t pid; + + snprintf(p, sizeof(p), "%s/P", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/P/d", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/P/d/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + ASSERT_EQ(pipe(to_host), 0); + ASSERT_EQ(pipe(from_host), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + /* M: a manager's namespace that covers P/d for Z */ + pid_t z; + + if (unshare(CLONE_NEWNS)) + _exit(CHILD_SETUP); + snprintf(p, sizeof(p), "%s/P", self->base); + if (mount(NULL, p, NULL, MS_SLAVE, NULL)) + _exit(CHILD_SETUP); + snprintf(p, sizeof(p), "%s/P/d", self->base); + if (mount("tmpfs", p, "tmpfs", 0, "mode=0755")) + _exit(CHILD_SETUP); + z = fork(); + if (z < 0) + _exit(CHILD_SETUP); + if (z == 0) + _exit(propagation_child(self->base, to_host[1], from_host[0])); + _exit(wait_child(z)); + } + ASSERT_EQ(wait_byte(to_host[0]), 0); + + /* the host mounts on P/d and unmounts again; both propagate */ + snprintf(p, sizeof(p), "%s/P/d", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + ASSERT_EQ(umount2(p, 0), 0); + + ASSERT_EQ(send_byte(from_host[1]), 0); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c new file mode 100644 index 00000000000000..b024ae3417ce96 --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A synchronous umount first unmounts the shrinkable submounts of the + * victim that aren't busy. Every one of them has to be checked right + * before it is unmounted: unmounting one can slide a busy mount to where + * the propagated copy of the next one is looked up. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef FAN_REPORT_MNT +#define FAN_REPORT_MNT 0x00004000 +#endif +#ifndef FAN_MARK_MNTNS +#define FAN_MARK_MNTNS 0x00000110 +#endif +#ifndef FAN_MNT_ATTACH +#define FAN_MNT_ATTACH 0x01000000 +#endif +#ifndef FAN_MNT_DETACH +#define FAN_MNT_DETACH 0x02000000 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +FIXTURE(shrink_submounts) { + char base[DIR_LEN]; + char automount[PATH_LEN]; + bool mounted; + int fan; +}; + +/* + * Shrinkable mounts come from an automount. The tracefs mount below debugfs + * is one and bind mounts inherit the flag. + */ +FIXTURE_SETUP(shrink_submounts) +{ + struct stat st; + char p[PATH_LEN]; + + self->mounted = false; + self->fan = -1; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/shrink_submounts.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + ASSERT_EQ(mount(NULL, self->base, NULL, MS_PRIVATE, NULL), 0); + + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + if (mount("debugfs", p, "debugfs", 0, NULL)) { + umount2(self->base, MNT_DETACH); + rmdir(self->base); + SKIP(return, "test requires debugfs"); + } + snprintf(self->automount, sizeof(self->automount), "%s/dbg/tracing", + self->base); + snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base); + if (stat(p, &st)) { + umount2(self->base, MNT_DETACH); + rmdir(self->base); + SKIP(return, "test requires the tracefs automount"); + } +} + +FIXTURE_TEARDOWN(shrink_submounts) +{ + if (self->fan >= 0) + close(self->fan); + chdir("/"); + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static bool mounted_tmpfs(const char *path) +{ + struct statfs st; + + return !statfs(path, &st) && st.f_type == TMPFS_MAGIC; +} + +/* + * P is a shared bind mount of the automount, P1 a slave of P that is shared + * in turn and P2 its peer. B, another bind mount of the automount, goes on + * P/options and propagates copies Bc1 and Bc2 onto P1/options and + * P2/options. R, a tmpfs and our working directory, sits on top of Bc2 which + * is made private first. P, with B on it, is moved to P1/instances. + * + * A synchronous umount of P1 unmounts the shrinkable submounts Bc1 and B + * first. Unmounting Bc1 takes Bc2 along and slides R to P2/options where + * the propagated copy of B is looked up next. R is busy, so B has to stay + * and the umount fails with EBUSY. + */ +TEST_F(shrink_submounts, busy_mount_moved_into_reach) +{ + char p[PATH_LEN], p1[PATH_LEN], p2[PATH_LEN], r[PATH_LEN], cwd[PATH_LEN]; + int nsfd; + + snprintf(p, sizeof(p), "%s/p", self->base); + snprintf(p1, sizeof(p1), "%s/p1", self->base); + snprintf(p2, sizeof(p2), "%s/p2", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mkdir(p1, 0755), 0); + ASSERT_EQ(mkdir(p2, 0755), 0); + + /* watch the mount namespace so that the detached mounts get queued */ + self->fan = fanotify_init(FAN_REPORT_MNT, O_RDONLY); + if (self->fan >= 0) { + nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + ASSERT_GE(nsfd, 0); + EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_MNTNS, + FAN_MNT_ATTACH | FAN_MNT_DETACH, nsfd, NULL), 0); + close(nsfd); + } + + ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(p, p1, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, p1, NULL, MS_SLAVE, NULL), 0); + ASSERT_EQ(mount(NULL, p1, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(p1, p2, NULL, MS_BIND, NULL), 0); + + /* B on P/options, copies on P1/options and P2/options */ + snprintf(r, sizeof(r), "%s/p/options", self->base); + ASSERT_EQ(mount(self->automount, r, NULL, MS_BIND, NULL), 0); + + /* R on top of Bc2 */ + snprintf(r, sizeof(r), "%s/p2/options", self->base); + ASSERT_EQ(mount(NULL, r, NULL, MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("R", r, "tmpfs", 0, NULL), 0); + ASSERT_EQ(chdir(r), 0); + + /* P, with B on it, below the victim */ + snprintf(cwd, sizeof(cwd), "%s/p1/instances", self->base); + ASSERT_EQ(mount(p, cwd, NULL, MS_MOVE, NULL), 0); + + ASSERT_TRUE(mounted_tmpfs(r)); + ASSERT_EQ(umount2(p1, 0), -1); + EXPECT_EQ(errno, EBUSY); + + /* R is still mounted and still our working directory */ + EXPECT_TRUE(mounted_tmpfs(r)); + ASSERT_NE(getcwd(cwd, sizeof(cwd)), NULL); + EXPECT_STREQ(cwd, r); +} + +/* + * T is shared and Q, a slave of T, has T moved into it, so Q receives + * propagation from its own child. M, another bind mount of the automount, is + * on T/options and that is the dentry T sits on in Q. Unmounting M makes T + * the propagated victim at that dentry in Q and takes T along. The shrink + * walk of V has just unmounted M and continues in the children of T. + */ +TEST_F(shrink_submounts, parent_goes_with_child) +{ + char v[PATH_LEN], t[PATH_LEN], q[PATH_LEN], p[PATH_LEN]; + struct stat before, after; + + snprintf(v, sizeof(v), "%s/v", self->base); + snprintf(t, sizeof(t), "%s/v/t", self->base); + snprintf(q, sizeof(q), "%s/v/q", self->base); + ASSERT_EQ(mkdir(v, 0755), 0); + ASSERT_EQ(mount("V", v, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, v, NULL, MS_PRIVATE, NULL), 0); + ASSERT_EQ(stat(v, &before), 0); + ASSERT_EQ(mkdir(t, 0755), 0); + ASSERT_EQ(mkdir(q, 0755), 0); + + /* T shared, M on T/options before anything receives from T */ + ASSERT_EQ(mount(self->automount, t, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, t, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/v/t/options", self->base); + ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0); + + /* Q, a slave of T, and T moved into Q at the dentry M sits on */ + ASSERT_EQ(mount(t, q, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, q, NULL, MS_SLAVE, NULL), 0); + snprintf(p, sizeof(p), "%s/v/q/options", self->base); + ASSERT_EQ(mount(t, p, NULL, MS_MOVE, NULL), 0); + + /* M, T and then Q go, V is empty and can be unmounted */ + ASSERT_EQ(umount2(v, 0), 0); + ASSERT_EQ(stat(v, &after), 0); + EXPECT_NE(before.st_dev, after.st_dev); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c new file mode 100644 index 00000000000000..9e18d54dfb3298 --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A synchronous umount fails with EBUSY when a mount it would pull out by + * propagation is still in use. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif +#ifndef STATX_MNT_ID +#define STATX_MNT_ID 0x00001000U +#endif + +static int sys_open_tree(int dfd, const char *filename, unsigned int flags) +{ + return syscall(__NR_open_tree, dfd, filename, flags); +} + +static int sys_move_mount(int from_dfd, const char *from_pathname, + int to_dfd, const char *to_pathname, + unsigned int flags) +{ + return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, + to_pathname, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_UNSHARE, /* could not set up the slave namespace */ + CHILD_OPEN_TREE, /* open_tree() failed */ + CHILD_MOVE_MOUNT, /* move_mount() failed */ + CHILD_STATX, /* statx() failed */ + CHILD_PIPE, /* the parent went away */ +}; + +/* Messages between parent and child. */ +enum { + MSG_READY = 'r', /* child: the copy is mounted and referenced */ + MSG_CHECK = 'c', /* parent: check that the copy is still attached */ + MSG_ATTACHED = 'a', /* child: it is */ + MSG_DETACHED = 'd', /* child: it is not */ + MSG_CLOSE = 'x', /* parent: drop the reference */ + MSG_CLOSED = 'y', /* child: dropped */ + MSG_EXIT = 'e', /* parent: done */ +}; + +FIXTURE(umount_propagation) +{ + char base[64]; + char victim[80]; + bool mounted; +}; + +FIXTURE_SETUP(umount_propagation) +{ + self->mounted = false; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/umount_propagation.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + ASSERT_EQ(mount(NULL, self->base, NULL, MS_SHARED, NULL), 0); + + snprintf(self->victim, sizeof(self->victim), "%s/victim", self->base); + ASSERT_EQ(mkdir(self->victim, 0755), 0); + ASSERT_EQ(mount("tmpfs", self->victim, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(umount_propagation) +{ + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int send_msg(int fd, char msg) +{ + return write(fd, &msg, 1) == 1 ? 0 : -1; +} + +static char recv_msg(int fd) +{ + char msg; + + if (read(fd, &msg, 1) != 1) + return 0; + return msg; +} + +/* Is the mount with id @mnt_id attached in this mount namespace? */ +static bool mount_attached(__u64 mnt_id) +{ + char line[4096]; + bool found = false; + FILE *f; + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return false; + + while (fgets(line, sizeof(line), f)) { + if (strtoull(line, NULL, 10) == mnt_id) { + found = true; + break; + } + } + fclose(f); + return found; +} + +/* + * The slave namespace: take a detached copy of the shared tree and move it + * beneath the propagated copy of the victim, keeping the open_tree() + * descriptor as a reference on it. + */ +static int slave_child(const char *base, const char *victim, int to_parent, + int from_parent) +{ + struct statx stx; + int fd; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (mount("", "/", NULL, MS_REC | MS_SLAVE, NULL)) + return CHILD_UNSHARE; + + fd = sys_open_tree(AT_FDCWD, base, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); + if (fd < 0) + return CHILD_OPEN_TREE; + if (sys_move_mount(fd, "", AT_FDCWD, victim, + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH)) + return CHILD_MOVE_MOUNT; + if (statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID, &stx)) + return CHILD_STATX; + + if (send_msg(to_parent, MSG_READY) || recv_msg(from_parent) != MSG_CHECK) + return CHILD_PIPE; + if (send_msg(to_parent, mount_attached(stx.stx_mnt_id) ? + MSG_ATTACHED : MSG_DETACHED)) + return CHILD_PIPE; + + if (recv_msg(from_parent) != MSG_CLOSE) + return CHILD_PIPE; + close(fd); + if (send_msg(to_parent, MSG_CLOSED) || recv_msg(from_parent) != MSG_EXIT) + return CHILD_PIPE; + return CHILD_OK; +} + +TEST_F(umount_propagation, busy_copy_pulled_out) +{ + int to_child[2], to_parent[2]; + int status; + pid_t pid; + + ASSERT_EQ(pipe(to_child), 0); + ASSERT_EQ(pipe(to_parent), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_child[1]); + close(to_parent[0]); + _exit(slave_child(self->base, self->victim, to_parent[1], + to_child[0])); + } + close(to_child[0]); + close(to_parent[1]); + + ASSERT_EQ(recv_msg(to_parent[0]), MSG_READY); + + /* the copy in the slave namespace is in use */ + ASSERT_EQ(umount2(self->victim, 0), -1); + ASSERT_EQ(errno, EBUSY); + + ASSERT_EQ(send_msg(to_child[1], MSG_CHECK), 0); + ASSERT_EQ(recv_msg(to_parent[0]), MSG_ATTACHED); + + /* and once it is not, the umount goes through */ + ASSERT_EQ(send_msg(to_child[1], MSG_CLOSE), 0); + ASSERT_EQ(recv_msg(to_parent[0]), MSG_CLOSED); + ASSERT_EQ(umount2(self->victim, 0), 0); + + ASSERT_EQ(send_msg(to_child[1], MSG_EXIT), 0); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 32d81ba15a25ea..8c41ebfb85e42f 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -546,6 +546,37 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) ASSERT_EQ(args.data[1], 2); } +/* + * Call hid_hw_raw_request against the given uhid device, + * check that the program is called and does the expected. + */ +TEST_F(hid_bpf, test_hid_user_raw_request_call_eio) +{ + struct hid_hw_request_syscall_args args = { + .retval = -1, + .type = HID_FEATURE_REPORT, + .request_type = HID_REQ_GET_PROTOCOL, + .size = MAX_BUF_SIZE, + }; + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, + .ctx_in = &args, + .ctx_size_in = sizeof(args), + ); + int err, prog_fd; + + LOAD_BPF; + + args.hid = self->hid.hid_id; + args.data[0] = 1; /* report ID */ + + prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request); + + err = bpf_prog_test_run_opts(prog_fd, &tattrs); + ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); + + ASSERT_EQ(args.retval, -EIO); +} + /* * Call hid_hw_raw_request against the given uhid device, * check that the program is called and prevents the diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 6a1482e3a286b0..f43549a56f0b2a 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -103,6 +103,7 @@ TEST_GEN_PROGS_x86 += x86/nested_tdp_fault_test TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test TEST_GEN_PROGS_x86 += x86/nested_vmsave_vmload_test +TEST_GEN_PROGS_x86 += x86/nested_x2apic_test TEST_GEN_PROGS_x86 += x86/platform_info_test TEST_GEN_PROGS_x86 += x86/pmu_counters_test TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test @@ -195,6 +196,7 @@ TEST_GEN_PROGS_arm64 += arm64/vgic_v5 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access TEST_GEN_PROGS_arm64 += arm64/no-vgic TEST_GEN_PROGS_arm64 += arm64/idreg-idst +TEST_GEN_PROGS_arm64 += arm64/hidden_features TEST_GEN_PROGS_arm64 += arm64/kvm-uuid TEST_GEN_PROGS_arm64 += access_tracking_perf_test TEST_GEN_PROGS_arm64 += arch_timer diff --git a/tools/testing/selftests/kvm/arm64/hidden_features.c b/tools/testing/selftests/kvm/arm64/hidden_features.c new file mode 100644 index 00000000000000..194d746e7605e5 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/hidden_features.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hidden_features - Check that a feature's instruction runs in the guest when + * its ID register field is advertised, and is UNDEFINED when userspace clears + * the field. + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + */ +#include "kvm_util.h" +#include "processor.h" +#include "test_util.h" + +static volatile bool undef; + +static void guest_tlbi_os(void) +{ + /* tlbi vmalle1os */ + asm volatile("sys #0, c8, c1, #0\n\tdsb ish\n\tisb" ::: "memory"); +} + +static void guest_mops(void) +{ + register u64 *d asm("x0"); + register u64 n asm("x1"); + register u64 s asm("x2"); + u64 buf[8]; + + d = buf; + n = sizeof(buf); + s = 0; + /* setp [x0]!, x1!, x2; setm; sete */ + asm volatile(".inst 0x19c20420\n\t.inst 0x19c24420\n\t.inst 0x19c28420" + : "+r"(d), "+r"(n) : "r"(s) : "cc", "memory"); +} + +static void guest_tcr2(void) +{ + read_sysreg_s(SYS_TCR2_EL1); +} + +static void guest_fpmr(void) +{ + read_sysreg_s(SYS_FPMR); +} + +struct feature { + const char *name; + u64 id_reg; + u64 mask; + u8 shift; + u64 min; + void (*insn)(void); + bool (*trappable)(struct kvm_vcpu *vcpu); +}; + +/* Without FGT, KVM traps a hidden TLBI OS only through HCR_EL2.TTLBOS (FEAT_EVT2). */ +static bool tlbi_os_trappable(struct kvm_vcpu *vcpu) +{ + u64 mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1)); + u64 mmfr2 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1)); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, FGT, mmfr0) >= ID_AA64MMFR0_EL1_FGT_IMP || + SYS_FIELD_GET(ID_AA64MMFR2_EL1, EVT, mmfr2) >= ID_AA64MMFR2_EL1_EVT_TTLBxS; +} + +#define FEATURE(n, reg, field, min_val, fn, trap) \ +{ \ + .name = n, \ + .id_reg = SYS_##reg, \ + .mask = reg##_##field##_MASK, \ + .shift = reg##_##field##_SHIFT, \ + .min = reg##_##field##_##min_val, \ + .insn = fn, \ + .trappable = trap, \ +} + +static const struct feature features[] = { + FEATURE("TLBI OS", ID_AA64ISAR0_EL1, TLB, OS, guest_tlbi_os, tlbi_os_trappable), + FEATURE("MOPS", ID_AA64ISAR2_EL1, MOPS, IMP, guest_mops, NULL), + FEATURE("TCR2_EL1", ID_AA64MMFR3_EL1, TCRX, IMP, guest_tcr2, NULL), + FEATURE("FPMR", ID_AA64PFR2_EL1, FPMR, IMP, guest_fpmr, NULL), +}; + +static void guest_code(const struct feature *feat) +{ + undef = false; + feat->insn(); + GUEST_SYNC(undef); + GUEST_DONE(); +} + +static void guest_undef_handler(struct ex_regs *regs) +{ + undef = true; + regs->pc += 4; +} + +static bool run(const struct feature *feat, bool hide) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + bool got = false; + u64 val; + + vm = vm_create_with_one_vcpu(&vcpu, (void *)guest_code); + vm_init_descriptor_tables(vm); + vcpu_init_descriptor_tables(vcpu); + vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_UNKNOWN, guest_undef_handler); + vcpu_args_set(vcpu, 1, feat); + + if (hide) { + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg)); + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg), val & ~feat->mask); + } + + for (;;) { + vcpu_run(vcpu); + switch (get_ucall(vcpu, &uc)) { + case UCALL_SYNC: + got = uc.args[1]; + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + case UCALL_DONE: + kvm_vm_free(vm); + return got; + default: + TEST_FAIL("Unknown ucall %lu", uc.cmd); + } + } +} + +static void probe_feature(const struct feature *feat, bool *present, bool *trappable) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + u64 val; + + vm = vm_create_with_one_vcpu(&vcpu, NULL); + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg)); + *present = ((val & feat->mask) >> feat->shift) >= feat->min; + *trappable = !feat->trappable || feat->trappable(vcpu); + kvm_vm_free(vm); +} + +int main(void) +{ + const struct feature *feat; + bool present, trappable; + int i; + + test_disable_default_vgic(); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(features) * 2); + + for (i = 0; i < ARRAY_SIZE(features); i++) { + feat = &features[i]; + + probe_feature(feat, &present, &trappable); + if (!present) { + ksft_test_result_skip("%s advertised, not supported\n", feat->name); + ksft_test_result_skip("%s hidden, not supported\n", feat->name); + continue; + } + + if (run(feat, false)) + ksft_test_result_fail("%s advertised, UNDEF\n", feat->name); + else + ksft_test_result_pass("%s advertised\n", feat->name); + + if (!trappable) + ksft_test_result_skip("%s hidden, not trappable\n", feat->name); + else if (run(feat, true)) + ksft_test_result_pass("%s hidden\n", feat->name); + else + ksft_test_result_fail("%s hidden, no UNDEF\n", feat->name); + } + + ksft_finished(); +} diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c new file mode 100644 index 00000000000000..ce204ce29a9c0b --- /dev/null +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "vmx.h" +#include "svm_util.h" + +/* + * Use the kernel's posted interrupt vectors to minimize the risk of crashing + * the host if KVM is buggy. Note, the vectors aren't set in stone, ideally + * these will be kept up-to-date if the kernel vectors change, but it's "fine" + * if they are stale. + */ +#define POSTED_INTR_VECTOR 0xf2 +#define POSTED_INTR_WAKEUP_VECTOR 0xf1 +#define POSTED_INTR_NESTED_VECTOR 0xf0 + +static bool inhibit_apicv; +static volatile unsigned int nr_irqs; + +static void guest_irq_handler(struct ex_regs *regs) +{ + nr_irqs++; + x2apic_write_reg(APIC_EOI, 0); +} + +static void l2_guest_code(void) +{ + if (inhibit_apicv) + wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); + + for (;;) { + x2apic_write_reg(APIC_TASKPRI, 0xf0); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); + + asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); + } +} + +static void l1_svm_code(struct svm_test_data *svm) +{ + struct vmcb_control_area *ctrl = &svm->vmcb->control; + + generic_svm_setup(svm, l2_guest_code); + ctrl->intercept |= BIT_ULL(INTERCEPT_CPUID) | BIT_ULL(INTERCEPT_MSR_PROT); + + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_CPUID); + + stgi(); + x2apic_write_reg(APIC_TASKPRI, 0); +} + +static void l1_vmx_code(struct vmx_pages *vmx, struct hyperv_test_pages *hv_pages) +{ + u64 control; + + if (hv_pages) { + wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); + enable_vp_assist(hv_pages->vp_assist_gpa, hv_pages->vp_assist); + evmcs_enable(); + } + + GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true); + + if (hv_pages) { + GUEST_ASSERT(load_evmcs(hv_pages)); + current_evmcs->hv_enlightenments_control.msr_bitmap = 1; + } else { + GUEST_ASSERT(load_vmcs(vmx)); + } + + prepare_vmcs(vmx, NULL); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, (unsigned long)l2_guest_code), 0); + + control = vmreadz(PIN_BASED_VM_EXEC_CONTROL); + control |= PIN_BASED_EXT_INTR_MASK; + vmwrite(PIN_BASED_VM_EXEC_CONTROL, control); + + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); + control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_TPR_SHADOW; + GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); + + control = vmreadz(SECONDARY_VM_EXEC_CONTROL); + control |= SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + SECONDARY_EXEC_APIC_REGISTER_VIRT | + SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY; + control &= (rdmsr(MSR_IA32_VMX_PROCBASED_CTLS2) >> 32); + GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); + + GUEST_ASSERT(!vmlaunch()); + GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, + vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); +} + +static void l1_vmx_code_part2(void) +{ + u64 control; + + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); + control &= ~CPU_BASED_TPR_SHADOW; + GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); + + control = vmreadz(SECONDARY_VM_EXEC_CONTROL); + control &= ~(SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + SECONDARY_EXEC_APIC_REGISTER_VIRT | + SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY); + GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); + + GUEST_ASSERT(!vmresume()); + GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, + vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); +} + +static void l1_test_x2apic_intercepts(void) +{ + GUEST_ASSERT_EQ(nr_irqs, 0); + + sti_nop(); + + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); + + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 0); + + x2apic_write_reg(APIC_TASKPRI, 0xff); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xff); + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_WAKEUP_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 0); + + x2apic_write_reg(APIC_TASKPRI, 0); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); + + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_NESTED_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 3); + + nr_irqs = 0; +} + +static void l1_guest_code(void *test_data, void *hv_pages) +{ + x2apic_enable(); + + if (this_cpu_has(X86_FEATURE_SVM)) + l1_svm_code(test_data); + else + l1_vmx_code(test_data, hv_pages); + + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); + x2apic_write_reg(APIC_TASKPRI, 0xf0); + + l1_test_x2apic_intercepts(); + + if (this_cpu_has(X86_FEATURE_VMX)) { + l1_vmx_code_part2(); + l1_test_x2apic_intercepts(); + } + + GUEST_DONE(); +} + +static void test_x2apic_intercepts(bool with_inhibit_apicv, bool use_evmcs) +{ + gva_t nested_test_data_gva, hv_pages_gva = 0; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + + inhibit_apicv = with_inhibit_apicv; + + vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); + vm_install_exception_handler(vm, POSTED_INTR_VECTOR, guest_irq_handler); + vm_install_exception_handler(vm, POSTED_INTR_WAKEUP_VECTOR, guest_irq_handler); + vm_install_exception_handler(vm, POSTED_INTR_NESTED_VECTOR, guest_irq_handler); + + sync_global_to_guest(vm, inhibit_apicv); + + if (kvm_cpu_has(X86_FEATURE_SVM)) + vcpu_alloc_svm(vm, &nested_test_data_gva); + else + vcpu_alloc_vmx(vm, &nested_test_data_gva); + + if (use_evmcs) { + vcpu_set_hv_cpuid(vcpu); + vcpu_enable_evmcs(vcpu); + + vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva); + } + + vcpu_args_set(vcpu, 2, nested_test_data_gva, hv_pages_gva); + + vcpu_run(vcpu); + + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_DONE: + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Expected DONE, got unexpected ucall %lu", uc.cmd); + } + + kvm_vm_free(vm); +} + +int main(int argc, char *argv[]) +{ + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); + + test_x2apic_intercepts(true, false); + test_x2apic_intercepts(false, false); + + if (kvm_has_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS)) { + test_x2apic_intercepts(true, true); + test_x2apic_intercepts(false, true); + } +} diff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c index 0f2aad1b7dbd6f..2edbc992e6bcf0 100644 --- a/tools/testing/selftests/mm/hugetlb-mmap.c +++ b/tools/testing/selftests/mm/hugetlb-mmap.c @@ -122,7 +122,7 @@ int main(int argc, char **argv) hugepage_size = default_huge_page_size(); if (!hugepage_size) ksft_exit_skip("Could not detect default hugetlb page size."); - ksft_print_msg("Default size hugepages (%lu kB)\n", hugepage_size >> 10); + ksft_print_msg("Default size hugepages (%zu kB)\n", hugepage_size >> 10); } /* munmap will fail if the length is not page aligned */ @@ -131,7 +131,7 @@ int main(int argc, char **argv) hugetlb_set_nr_pages(hugepage_size, nr); if (hugetlb_free_pages(hugepage_size) < nr) - ksft_exit_skip("Not enough %lu Kb pages\n", hugepage_size >> 10); + ksft_exit_skip("Not enough %zu Kb pages\n", hugepage_size >> 10); ksft_set_plan(2); ksft_print_msg("Mapping %lu Mbytes\n", (unsigned long)length >> 20); diff --git a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c index 12434415ec3651..9c8fc8c7f62cfd 100644 --- a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c +++ b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c @@ -146,17 +146,19 @@ static void null_endofword(char *word) *word = '\0'; } -static bool is_shared_mount(const char *path) +/* Does the mount on @path carry the optional field @field in mountinfo? */ +static bool mount_has_field(const char *path, const char *field) { size_t len = 0; char *line = NULL; FILE *f = NULL; + bool found = false; f = fopen("/proc/self/mountinfo", "re"); if (!f) return false; - while (getline(&line, &len, f) != -1) { + while (!found && getline(&line, &len, f) != -1) { char *opts, *target; target = get_field(line, 4); @@ -172,15 +174,29 @@ static bool is_shared_mount(const char *path) if (strcmp(target, path) != 0) continue; - null_endofword(opts); - if (strstr(opts, "shared:")) - return true; + /* the optional fields end at the "-" separator */ + while (opts && *opts != '-') { + char *next = strchr(opts, ' '); + + if (next) + *next++ = '\0'; + if (!strncmp(opts, field, strlen(field))) { + found = true; + break; + } + opts = next; + } } free(line); fclose(f); - return false; + return found; +} + +static bool is_shared_mount(const char *path) +{ + return mount_has_field(path, "shared:"); } /* Attempt to de-conflict with the selftests tree. */ @@ -372,4 +388,50 @@ TEST_F(move_mount_set_group, complex_sharing_copying) ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1); } +#define SET_GROUP_B "/tmp/B" +#define SET_GROUP_C "/tmp/C" + +/* + * An unbindable mount is neither shared nor a slave, so it must not be + * accepted as the target: with a slave source it would end up unbindable + * and a slave at the same time. + */ +TEST_F(move_mount_set_group, unbindable_target) +{ + bool ret; + + ret = move_mount_set_group_supported(); + ASSERT_GE(ret, 0); + if (!ret) + SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); + + ASSERT_EQ(mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0), 0); + + /* B: a slave of A's peer group */ + ASSERT_EQ(mkdir(SET_GROUP_B, 0777), 0); + ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_B, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, SET_GROUP_B, NULL, MS_SLAVE, 0), 0); + ASSERT_TRUE(mount_has_field(SET_GROUP_B, "master:")); + + /* C: unbindable */ + ASSERT_EQ(mkdir(SET_GROUP_C, 0777), 0); + ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_C, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, SET_GROUP_C, NULL, MS_UNBINDABLE, 0), 0); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); + + /* from a slave */ + ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_B, + AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_FALSE(mount_has_field(SET_GROUP_C, "master:")); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); + + /* from a shared mount */ + ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_A, + AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_FALSE(mount_has_field(SET_GROUP_C, "shared:")); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index c9f46031ac73b2..dacd36ed8455e3 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -55,6 +55,7 @@ tools tun txring_overwrite txtimestamp +udp_splice_checksum udpgso udpgso_bench_rx udpgso_bench_tx diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 3ee3378f8b26ef..590b33e16d9fb7 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -199,6 +199,7 @@ TEST_GEN_PROGS := \ tap \ tcp_port_share \ tls \ + udp_splice_checksum \ # end of TEST_GEN_PROGS TEST_FILES := \ diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh index 663744305e5219..d13b20cccba90e 100755 --- a/tools/testing/selftests/net/amt.sh +++ b/tools/testing/selftests/net/amt.sh @@ -150,6 +150,13 @@ setup_interface() ip netns exec "${RELAY}" ip a a 10.0.0.2/24 dev relay_gw ip netns exec "${RELAY}" ip link add amtr type amt mode relay \ local 10.0.0.2 dev relay_gw relay_port 2268 max_tunnels 4 + # Count the IGMP and MLD queries that leave the relay through its own + # amt device; test_query_egress expects none. + ip netns exec "${RELAY}" tc qdisc add dev amtr clsact + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 1 \ + protocol ip flower ip_proto 0x2 action pass + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 2 \ + protocol ipv6 flower ip_proto icmpv6 type 130 action pass ip netns exec "${RELAY}" ip a a 172.17.0.1/24 dev relay_src ip netns exec "${RELAY}" ip a a 2001:db8:3::1/64 dev relay_src ip netns exec "${SOURCE}" ip a a 172.17.0.2/24 dev src_relay @@ -246,6 +253,27 @@ test_ipv6_forward() fi } +# The relay sends its General Queries straight from the receive path, in +# the same context that found the tunnel. A query queued on the amt device +# instead could outlive the tunnel it was built for. The forwarding tests +# above show that the gateway got its queries. +test_query_egress() +{ + local n4 n6 + + n4=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 1 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + n6=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 2 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + if [ "$n4" -eq 0 ] && [ "$n6" -eq 0 ]; then + printf "TEST: %-60s [ OK ]\n" "amt relay queries bypass the amt device" + else + printf "TEST: %-60s [FAIL]\n" "amt relay queries bypass the amt device" + echo "IGMP queries on amtr egress: $n4, MLD queries: $n6" >&2 + ERR=1 + fi +} + send_mcast4() { sleep 5 @@ -287,6 +315,7 @@ wait $pid || err=$? if [ $err -eq 1 ]; then ERR=1 fi +test_query_egress printf "TEST: %-50s" "IPv4 amt traffic forwarding torture" send_mcast_torture4 printf " [ OK ]\n" diff --git a/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt b/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt new file mode 100644 index 00000000000000..f072e5e4c44948 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// An out-of-order segment advances SND.UNA. The gap filler still carries +// its original, older ACK. The reply must echo the gap filler's timestamp. +--tcp_ts_tick_usecs=1000 + +`./defaults.sh` + +0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 ++0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 ++0 bind(3, ..., ...) = 0 ++0 listen(3, 1) = 0 +0.100 < S 0:0(0) win 20000 ++0 > S. 0:0(0) ack 1 +0.200 < . 1:1(0) ack 1 win 20000 ++0 accept(3, ..., ...) = 4 +0.300 write(4, ..., 17) = 17 ++0 > P. 1:18(17) ack 1 +0.310 < P. 18:35(17) ack 18 win 20000 ++0 > . 18:18(0) ack 1 +0.320 < P. 1:18(17) ack 1 win 20000 ++0 > . 18:18(0) ack 35 ++0 read(4, ..., 34) = 34 diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50bb3..afaa81c9bac531 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -542,6 +543,141 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +FIXTURE(tun_vnet_gso) +{ + char ifname[IFNAMSIZ]; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_gso) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_gso) +{ + if (self->fd >= 0) + close(self->fd); +} + +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) +{ + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); + struct virtio_net_hdr vh = { 0 }; + struct tcphdr tcph = { 0 }; + uint32_t sum; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, + param_hwaddr_outer_dst); + + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + + cur += build_ipv4_header(cur, IPPROTO_TCP, + sizeof(tcph) + payload_len, + ¶m_ipaddr4_outer_src, + ¶m_ipaddr4_outer_dst); + + tcph.source = htons(12345); + tcph.dest = htons(80); + tcph.seq = htonl(1); + tcph.doff = sizeof(tcph) / 4; + tcph.ack = 1; + tcph.window = htons(65535); + memcpy(cur, &tcph, sizeof(tcph)); + memset(cur + sizeof(tcph), PKT_DATA, payload_len); + + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, + sizeof(param_ipaddr4_outer_src)); + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, + sizeof(param_ipaddr4_outer_dst)); + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); + sum += add_csum(cur, sizeof(tcph) + payload_len); + tcph.check = finish_ip_csum(sum); + memcpy(cur, &tcph, sizeof(tcph)); + cur += sizeof(tcph) + payload_len; + + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.gso_size = 1400; + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; + memcpy(buf, &vh, sizeof(vh)); + + return cur - buf; +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) +{ + struct virtio_net_hdr vh; + uint8_t pkt[4096] = { 0 }; + int len, ret; + + len = build_vlan_tcpv4_gso_packet(pkt, 2800); + memcpy(&vh, pkt, sizeof(vh)); + + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ + vh.flags = 0; + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* TCP header truncated after 10 bytes, without NEEDS_CSUM. + * Requires the transport header offset found by flow dissection: + * pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes + * from the mac header would accept this frame. + */ + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr) + 10; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) +{ + /* Ethernet frame of 65540 bytes: skb->len is above U16_MAX when the + * frame is flow-dissected, before eth_type_trans() pulls ETH_HLEN. + */ + const int payload_len = 65540 - ETH_HLEN - 4 - + sizeof(struct iphdr) - sizeof(struct tcphdr); + struct virtio_net_hdr vh; + uint8_t *pkt; + int len, ret; + + pkt = calloc(1, sizeof(vh) + 65540); + ASSERT_NE(pkt, NULL); + + len = build_vlan_tcpv4_gso_packet(pkt, payload_len); + ASSERT_EQ(len, sizeof(vh) + 65540); + memcpy(&vh, pkt, sizeof(vh)); + + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + free(pkt); + ASSERT_EQ(ret, len); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; diff --git a/tools/testing/selftests/net/udp_splice_checksum.c b/tools/testing/selftests/net/udp_splice_checksum.c new file mode 100644 index 00000000000000..dc61444e64816e --- /dev/null +++ b/tools/testing/selftests/net/udp_splice_checksum.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Exercise UDP software checksums over multiple spliced pipe fragments. + * All traffic stays on loopback; no privileges or external services are needed. + * Exit status: 0 = pass, 1 = failure, 4 = all cases skipped. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest.h" + +#define TIMEOUT_MS 200 + +struct test_case { + const char *name; + const char *body; + const char *prefix; + size_t fragments[3]; + bool ordinary; + bool page_tail; +}; + +static const struct test_case cases[] = { + { "ordinary-control", "abcDEFGH", "", { 3, 5 }, true, false }, + { "uniform-odd-fragments", "BBBBBBBB", "", { 3, 5 }, false, false }, + { "even-fragments", "abcdEFGH", "", { 4, 4 }, false, false }, + { "odd-fragments", "abcDEFGH", "", { 3, 5 }, false, false }, + { "odd-prefix-odd-fragments", "abcDEFGH", "P", { 3, 5 }, false, false }, + { "even-prefix-odd-fragments", "abcDEFGH", "PQ", { 3, 5 }, false, false }, + { "page-tail-odd-fragments", "abcDEFGH", "", { 3, 5 }, false, true }, + { "three-fragments", "abcDEFGhijkl", "", { 3, 4, 5 }, false, false }, + { "odd-prefix-three-fragments", + "abcDEFGhijkl", + "P", + { 3, 4, 5 }, + false, + false }, +}; + +static const char *const families[] = { "IPv4", "IPv6", "IPv4-mapped" }; + +static int error(const char *what) +{ + ksft_perror(what); + return 1; +} + +static bool unavailable(void) +{ + return errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT || + errno == EADDRNOTAVAIL; +} + +static long long now_ms(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + ksft_exit_fail_perror("clock_gettime"); + return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static int receive_one(int fd, const char *expected, size_t length) +{ + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + long long deadline = now_ms() + TIMEOUT_MS; + int remaining, ret; + char received[64]; + ssize_t count; + + for (;;) { + remaining = deadline - now_ms(); + if (remaining < 0) + remaining = 0; + ret = poll(&pfd, 1, remaining); + if (ret < 0) { + if (errno == EINTR) + continue; + return error("poll"); + } + if (!ret) { + ksft_print_msg("no datagram within %d ms after send\n", + TIMEOUT_MS); + return 1; + } + count = recv(fd, received, sizeof(received), + MSG_DONTWAIT | MSG_TRUNC); + if (count < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return error("recv"); + } + break; + } + if (count != (ssize_t)length || memcmp(received, expected, length)) { + ksft_print_msg("received %zd bytes; expected %zu matching\n", + count, length); + return 1; + } + count = recv(fd, received, sizeof(received), MSG_DONTWAIT | MSG_TRUNC); + if (count >= 0) { + ksft_print_msg("unexpected extra datagram (%zd bytes)\n", + count); + return 1; + } + if (errno != EAGAIN) + return error("extra-datagram check"); + return 0; +} + +/* Separate source pipes prevent adjacent small writes merging into one buffer. */ +static int append_fragment(int dest, const char *data, size_t length) +{ + int source[2], ret = 1; + + if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) + return error("source pipe"); + if (write(source[1], data, length) != (ssize_t)length) { + error("source write"); + goto out; + } + if (splice(source[0], NULL, dest, NULL, length, SPLICE_F_NONBLOCK) != + (ssize_t)length) { + error("pipe-to-pipe splice"); + goto out; + } + ret = 0; +out: + close(source[0]); + close(source[1]); + return ret; +} + +static int pipe_capacity(int fd, unsigned int slots, const char **skip_reason) +{ + long required = slots * sysconf(_SC_PAGESIZE); + int capacity = fcntl(fd, F_GETPIPE_SZ); + + if (capacity < 0) + return error("query pipe capacity"); + if (capacity >= required || + fcntl(fd, F_SETPIPE_SZ, required) >= required) + return 0; + ksft_print_msg("cannot reserve %u pipe slots: %s\n", slots, + strerror(errno)); + *skip_reason = "required pipe capacity unavailable"; + return KSFT_SKIP; +} + +/* Leave three bytes at a page's end, followed by five bytes on the next page. */ +static int append_page_tail(int dest, const char *body, + const char **skip_reason) +{ + long page = sysconf(_SC_PAGESIZE); + int source[2], ret = 1; + char *data; + + if (page < 8) { + ksft_print_msg("invalid page size %ld\n", page); + return 1; + } + data = malloc(page + 5); + if (!data) + return error("malloc"); + memset(data, 'x', page - 3); + memcpy(data + page - 3, body, 8); + if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) { + error("page-tail source pipe"); + goto free_data; + } + ret = pipe_capacity(source[1], 2, skip_reason); + if (ret) + goto out; + ret = 1; + if (write(source[1], data, page + 5) != page + 5) { + error("page-tail write"); + goto out; + } + if (read(source[0], data, page - 3) != page - 3) { + error("page-tail discard"); + goto out; + } + if (splice(source[0], NULL, dest, NULL, 8, SPLICE_F_NONBLOCK) != 8) { + error("page-tail pipe-to-pipe splice"); + goto out; + } + ret = 0; +out: + close(source[0]); + close(source[1]); +free_data: + free(data); + return ret; +} + +static int run_case(unsigned int family, bool connected, + const struct test_case *test, const char **skip_reason) +{ + size_t body_length = strlen(test->body), offset = 0; + int tx_family = family == 0 ? AF_INET : AF_INET6; + int rx_family = family == 1 ? AF_INET6 : AF_INET; + int rx = -1, tx = -1, pipefd[2] = { -1, -1 }; + size_t prefix_length = strlen(test->prefix); + struct sockaddr_storage address = { 0 }; + socklen_t address_length; + struct sockaddr_in6 *v6; + struct sockaddr_in *v4; + int ret = 1, zero = 0; + char expected[64]; + unsigned int i; + ssize_t count; + + v4 = (struct sockaddr_in *)&address; + v6 = (struct sockaddr_in6 *)&address; + *skip_reason = "address family unavailable"; + memcpy(expected, test->prefix, prefix_length); + memcpy(expected + prefix_length, test->body, body_length); + rx = socket(rx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (rx < 0) { + ret = unavailable() ? KSFT_SKIP : error("receive socket"); + goto out; + } + if (rx_family == AF_INET) { + v4->sin_family = AF_INET; + v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address_length = sizeof(*v4); + } else { + v6->sin6_family = AF_INET6; + v6->sin6_addr = in6addr_loopback; + address_length = sizeof(*v6); + } + if (bind(rx, (struct sockaddr *)&address, address_length)) { + ret = unavailable() ? KSFT_SKIP : error("bind loopback"); + goto out; + } + if (getsockname(rx, (struct sockaddr *)&address, &address_length)) { + error("getsockname"); + goto out; + } + if (family == 2) { + unsigned short port = v4->sin_port; + + memset(&address, 0, sizeof(address)); + v6->sin6_family = AF_INET6; + v6->sin6_port = port; + inet_pton(AF_INET6, "::ffff:127.0.0.1", &v6->sin6_addr); + address_length = sizeof(*v6); + } + tx = socket(tx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (tx < 0) { + ret = unavailable() ? KSFT_SKIP : error("send socket"); + goto out; + } + if (family == 2 && + setsockopt(tx, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero))) { + error("disable IPV6_V6ONLY"); + goto out; + } + if (connected && + connect(tx, (struct sockaddr *)&address, address_length)) { + error("connect"); + goto out; + } + if (test->ordinary) { + count = sendto(tx, expected, prefix_length + body_length, 0, + (struct sockaddr *)&address, address_length); + if (count != (ssize_t)(prefix_length + body_length)) { + error("ordinary sendto"); + goto out; + } + } else { + if (pipe2(pipefd, O_NONBLOCK | O_CLOEXEC)) { + error("assembled pipe"); + goto out; + } + ret = pipe_capacity(pipefd[1], test->fragments[2] ? 3 : 2, + skip_reason); + if (ret) + goto out; + ret = 1; + if (test->page_tail) { + ret = append_page_tail(pipefd[1], test->body, + skip_reason); + if (ret) + goto out; + ret = 1; + } else { + for (i = 0; i < ARRAY_SIZE(test->fragments); i++) { + if (!test->fragments[i]) + break; + if (append_fragment(pipefd[1], + test->body + offset, + test->fragments[i])) + goto out; + offset += test->fragments[i]; + } + } + /* Even an empty prefix establishes the destination and UDP cork. */ + count = sendto(tx, test->prefix, prefix_length, MSG_MORE, + (struct sockaddr *)&address, address_length); + if (count != (ssize_t)prefix_length) { + error("prime sendto(MSG_MORE)"); + goto out; + } + count = splice(pipefd[0], NULL, tx, NULL, body_length, + SPLICE_F_NONBLOCK | SPLICE_F_MORE); + if (count != (ssize_t)body_length) { + ksft_print_msg("socket splice: %zd, expected %zu: %s\n", + count, body_length, + count < 0 ? strerror(errno) : + "short transfer"); + goto out; + } + if (sendto(tx, "", 0, 0, (struct sockaddr *)&address, + address_length)) { + error("commit sendto"); + goto out; + } + } + ret = receive_one(rx, expected, prefix_length + body_length); +out: + if (pipefd[0] >= 0) { + close(pipefd[0]); + close(pipefd[1]); + } + if (tx >= 0) + close(tx); + if (rx >= 0) + close(rx); + return ret; +} + +int main(void) +{ + unsigned int family, connected, i; + const char *connection, *skip; + int ret; + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(families) * 2 * ARRAY_SIZE(cases)); + for (family = 0; family < ARRAY_SIZE(families); family++) { + for (connected = 0; connected < 2; connected++) { + connection = connected ? "connected" : "unconnected"; + for (i = 0; i < ARRAY_SIZE(cases); i++) { + ret = run_case(family, connected, &cases[i], + &skip); + if (ret == KSFT_SKIP) + ksft_test_result_skip("%s %s %s: %s\n", + families[family], + connection, + cases[i].name, + skip); + else + ksft_test_result(!ret, "%s %s %s\n", + families[family], + connection, + cases[i].name); + } + } + } + if (!ksft_get_pass_cnt() && !ksft_get_fail_cnt()) { + /* ksft_exit_skip() would add a result beyond the plan. */ + ksft_print_cnts(); + return KSFT_SKIP; + } + ksft_finished(); +} diff --git a/tools/testing/selftests/net/veth.sh b/tools/testing/selftests/net/veth.sh index 9709dd067c7283..8b867e0675a7c1 100755 --- a/tools/testing/selftests/net/veth.sh +++ b/tools/testing/selftests/net/veth.sh @@ -9,6 +9,8 @@ readonly DST=1 readonly DST_NAT=100 readonly NS_SRC=$BASE$SRC readonly NS_DST=$BASE$DST +# shellcheck disable=SC2155 # prefer RO variable over return value from cmd +readonly YNL="$(dirname "$(readlink -f "$0")")/../../../net/ynl/pyynl/cli.py" # "baremetal" network used for raw UDP traffic readonly BM_NET_V4=192.168.1. @@ -74,6 +76,37 @@ chk_tso_flag() { __chk_flag "$1" $2 $3 tcp-segmentation-offload } +chk_xdp_feature() { + local msg="$1" + local target=$2 + local expected=$3 + local feature=$4 + local ifindex + local out + local st + local flag + + ifindex=`ip netns exec $BASE$target cat /sys/class/net/veth$target/ifindex` + out=`ip netns exec $BASE$target "$YNL" --family netdev --do dev-get \ + --output-json --json "{\"ifindex\": $ifindex}" 2>&1` + st=$? + + printf "%-60s" "$msg" + if [ $st -ne 0 ]; then + echo " fail - ynl cli error: $out" + ret=1 + return + fi + + flag=`echo "$out" | grep -c "\"$feature\""` + if [ "$flag" = "$expected" ]; then + echo " ok " + else + echo " fail - expected $expected found $flag" + ret=1 + fi +} + chk_channels() { local msg="$1" local target=$2 @@ -222,6 +255,7 @@ fi [ $CPUS -lt 2 ] && echo "Only one CPU available, some tests will be skipped" [ $STRESS -gt 0 -a $CPUS -lt 3 ] && echo " stress test will be skipped, too" +[ ! -f "$YNL" ] && echo "ynl cli not found, ndo-xmit tests will be skipped" create_ns chk_gro_flag "default - gro flag" $SRC off @@ -283,6 +317,18 @@ chk_gro_flag " - peer gro flag" $SRC off chk_tso_flag " - tso flag" $SRC on chk_tso_flag " - peer tso flag" $DST on ip -n $NS_DST link set dev veth$DST up +if [ -f "$YNL" ]; then + chk_xdp_feature " - peer ndo-xmit" $SRC 1 ndo-xmit + # make sure the peer flag is set before disabling gro while down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip netns exec $NS_DST ethtool -K veth$DST gro on + chk_xdp_feature " - peer ndo-xmit re-armed" $SRC 1 ndo-xmit + ip -n $NS_DST link set dev veth$DST down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip -n $NS_DST link set dev veth$DST up + chk_xdp_feature " - peer ndo-xmit cleared" $SRC 0 ndo-xmit + ip netns exec $NS_DST ethtool -K veth$DST gro on +fi ip netns exec $NS_SRC ethtool -K veth$SRC tx-udp-segmentation off ip netns exec $NS_DST ethtool -K veth$DST rx-udp-gro-forwarding on chk_gro " - aggregation with TSO off" 1 diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 4e06d0baaeec62..af919a56a4f4f0 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -165,6 +165,7 @@ auto-test-targets := \ create_dsq \ dequeue \ dequeue_iter \ + dequeue_remote \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c new file mode 100644 index 00000000000000..983590c6d49801 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called when a task leaves the BPF scheduler's + * custody by being moved to the local DSQ of a CPU other than the one whose + * rq it's on (move_remote_task_to_local_dsq()). + * + * ops.enqueue() puts every task into custody and ops.dispatch() moves it to + * the dispatching CPU's local DSQ, so most moves cross CPUs. With + * @test_use_move_to_local, tasks are queued on a user DSQ and consumed with + * scx_bpf_dsq_move_to_local(). Otherwise, they are queued in a BPF queue and + * dispatched with SCX_DSQ_LOCAL_ON. + * + * Copyright (c) 2026 Google LLC. + */ + +#include + +#define SHARED_DSQ 0 +#define MAX_DISPATCH_POPS 8 + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +struct { + __uint(type, BPF_MAP_TYPE_QUEUE); + __uint(max_entries, 32768); + __type(value, s32); +} global_queue SEC(".maps"); + +enum task_state { + TASK_NONE = 0, + TASK_ENQUEUED, /* in BPF custody, waiting for ops.dequeue() */ + TASK_DISPATCHED, /* left custody */ +}; + +struct task_ctx { + enum task_state state; + s32 enq_cpu; /* scx_bpf_task_cpu() at ops.enqueue() */ + u64 enqueue_seq; +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* core_cookie only exists with CONFIG_SCHED_CORE */ +struct task_struct___core_sched { + unsigned long core_cookie; +} __attribute__((preserve_access_index)); + +bool test_use_move_to_local; + +u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt; +u64 remote_dispatch_cnt, remote_running_cnt, missed_dequeue_cnt; +u64 core_sched_exec_dequeue_cnt; + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + return bpf_task_storage_get(&task_ctx_stor, p, 0, 0); +} + +static bool task_has_core_cookie(struct task_struct *p) +{ + struct task_struct___core_sched *t = (void *)p; + + if (!bpf_core_field_exists(t->core_cookie)) + return false; + return BPF_CORE_READ(t, core_cookie); +} + +s32 BPF_STRUCT_OPS(dequeue_remote_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + /* no direct dispatch, always go through ops.enqueue() */ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_remote_enqueue, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + s32 pid = p->pid; + + tctx = lookup_task_ctx(p); + if (!tctx) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + return; + } + + /* the previous custody period must have ended with ops.dequeue() */ + if (tctx->state == TASK_ENQUEUED) + scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu", + p->pid, p->comm, tctx->enqueue_seq); + + /* + * Mark @p as enqueued before making it visible to ops.dispatch() on + * other CPUs, which skips queue entries of tasks not in ENQUEUED + * state as stale. + */ + tctx->state = TASK_ENQUEUED; + tctx->enq_cpu = scx_bpf_task_cpu(p); + tctx->enqueue_seq++; + + if (test_use_move_to_local) { + scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags); + } else if (bpf_map_push_elem(&global_queue, &pid, 0)) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + tctx->state = TASK_DISPATCHED; + tctx->enq_cpu = -1; + goto out; + } + + __sync_fetch_and_add(&enqueue_cnt, 1); +out: + scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct task_ctx *tctx; + + __sync_fetch_and_add(&dequeue_cnt, 1); + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* + * Only core scheduling can pick a task straight out of custody, and + * only if the task has a core cookie. Otherwise, the custody exit was + * missed when @p was inserted into a local DSQ and got deferred until + * @p was picked. + */ + if ((deq_flags & SCX_DEQ_CORE_SCHED_EXEC) && !task_has_core_cookie(p)) { + __sync_fetch_and_add(&core_sched_exec_dequeue_cnt, 1); + scx_bpf_error("%d (%s): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + } + + /* ops.dequeue() ends the custody period started by ops.enqueue() */ + if (tctx->state != TASK_ENQUEUED) + scx_bpf_error("%d (%s): dequeue outside custody deq_flags=0x%llx state=%d seq=%llu", + p->pid, p->comm, deq_flags, tctx->state, + tctx->enqueue_seq); + + if (deq_flags & SCX_DEQ_SCHED_CHANGE) { + __sync_fetch_and_add(&change_dequeue_cnt, 1); + tctx->state = TASK_NONE; + } else { + __sync_fetch_and_add(&dispatch_dequeue_cnt, 1); + tctx->state = TASK_DISPATCHED; + } +} + +void BPF_STRUCT_OPS(dequeue_remote_dispatch, s32 cpu, struct task_struct *prev) +{ + struct task_ctx *tctx; + struct task_struct *p; + s32 pid; + int i; + + if (test_use_move_to_local) { + scx_bpf_dsq_move_to_local(SHARED_DSQ, 0); + return; + } + + /* pop past stale entries so that they don't leave this CPU idle */ + bpf_for(i, 0, MAX_DISPATCH_POPS) { + if (bpf_map_pop_elem(&global_queue, &pid)) + return; + + p = bpf_task_from_pid(pid); + if (!p) + continue; + + /* + * Entries are stale if @p left custody through a property + * change dequeue or was dispatched from a duplicate entry. + */ + tctx = lookup_task_ctx(p); + if (!tctx || tctx->state != TASK_ENQUEUED) { + bpf_task_release(p); + continue; + } + + if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr)) { + if (scx_bpf_task_cpu(p) != cpu) + __sync_fetch_and_add(&remote_dispatch_cnt, 1); + scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu, + SCX_SLICE_DFL, 0); + } else { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0); + } + + bpf_task_release(p); + return; + } + + /* out of pops with entries left, retry instead of idling this CPU */ + if (!bpf_map_peek_elem(&global_queue, &pid)) + scx_bpf_kick_cpu(cpu, SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_running, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* tasks can only run from a local DSQ, i.e. after leaving custody */ + if (tctx->state == TASK_ENQUEUED) { + __sync_fetch_and_add(&missed_dequeue_cnt, 1); + scx_bpf_error("%d (%s): running without ops.dequeue() (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + return; + } + + if (tctx->enq_cpu >= 0 && tctx->enq_cpu != scx_bpf_task_cpu(p)) + __sync_fetch_and_add(&remote_running_cnt, 1); + tctx->enq_cpu = -1; +} + +s32 BPF_STRUCT_OPS(dequeue_remote_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, + BPF_LOCAL_STORAGE_GET_F_CREATE); + if (!tctx) + return -ENOMEM; + + /* task storage persists across attachments, start from scratch */ + tctx->state = TASK_NONE; + tctx->enq_cpu = -1; + tctx->enqueue_seq = 0; + + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_remote_init) +{ + return scx_bpf_create_dsq(SHARED_DSQ, -1); +} + +void BPF_STRUCT_OPS(dequeue_remote_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_remote_ops = { + .select_cpu = (void *)dequeue_remote_select_cpu, + .enqueue = (void *)dequeue_remote_enqueue, + .dequeue = (void *)dequeue_remote_dequeue, + .dispatch = (void *)dequeue_remote_dispatch, + .running = (void *)dequeue_remote_running, + .init_task = (void *)dequeue_remote_init_task, + .init = (void *)dequeue_remote_init, + .exit = (void *)dequeue_remote_exit, + .flags = SCX_OPS_ENQ_LAST, + .name = "dequeue_remote", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.c b/tools/testing/selftests/sched_ext/dequeue_remote.c new file mode 100644 index 00000000000000..f2c1901030de00 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called for tasks leaving BPF custody through + * an SCX-internal cross-CPU migration (move_remote_task_to_local_dsq()). + * + * Copyright (c) 2026 Google LLC. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scx_test.h" +#include "dequeue_remote.bpf.skel.h" + +#define MAX_WORKERS 64 +#define RUN_MS 2000 + +static int nr_cpus; + +static long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000; +} + +/* mix of short bursts and sleeps to generate lots of enqueues and wakeups */ +static void worker_fn(int id) +{ + long long end = now_ms() + RUN_MS; + volatile unsigned long sum = 0; + + while (now_ms() < end) { + unsigned long j; + + for (j = 0; j < 20000 + id * 1000; j++) + sum += j; + if (id & 1) + usleep(100); + else + sched_yield(); + } + + exit(0); +} + +static enum scx_test_status run_scenario(struct dequeue_remote *skel, + bool use_move_to_local, + const char *name) +{ + enum scx_test_status ret = SCX_TEST_PASS; + struct bpf_link *link; + pid_t pids[MAX_WORKERS]; + int nr_workers, nr_forked, i; + + nr_workers = 2 * nr_cpus; + if (nr_workers < 4) + nr_workers = 4; + if (nr_workers > MAX_WORKERS) + nr_workers = MAX_WORKERS; + + skel->bss->test_use_move_to_local = use_move_to_local; + skel->bss->enqueue_cnt = 0; + skel->bss->dequeue_cnt = 0; + skel->bss->dispatch_dequeue_cnt = 0; + skel->bss->change_dequeue_cnt = 0; + skel->bss->remote_dispatch_cnt = 0; + skel->bss->remote_running_cnt = 0; + skel->bss->missed_dequeue_cnt = 0; + skel->bss->core_sched_exec_dequeue_cnt = 0; + memset(&skel->data->uei, 0, sizeof(skel->data->uei)); + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_remote_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops for %s", name); + + fflush(stdout); + fflush(stderr); + + for (nr_forked = 0; nr_forked < nr_workers; nr_forked++) { + pid_t pid = fork(); + + if (pid < 0) { + SCX_ERR("Failed to fork worker %d", nr_forked); + ret = SCX_TEST_FAIL; + break; + } + if (pid == 0) + worker_fn(nr_forked); + pids[nr_forked] = pid; + } + + /* on failure, kill the remaining workers but still reap them */ + for (i = 0; i < nr_forked; i++) { + int status; + + if (ret != SCX_TEST_PASS) + kill(pids[i], SIGKILL); + + if (waitpid(pids[i], &status, 0) != pids[i]) { + SCX_ERR("Failed to wait for worker %d", i); + ret = SCX_TEST_FAIL; + } else if (ret == SCX_TEST_PASS && status != 0) { + SCX_ERR("Worker %d exited with status %d", i, status); + ret = SCX_TEST_FAIL; + } + } + + bpf_link__destroy(link); + + if (ret != SCX_TEST_PASS) + return ret; + + printf("%s:\n", name); + printf(" workers: %d\n", nr_workers); + printf(" enqueues: %lu\n", (unsigned long)skel->bss->enqueue_cnt); + printf(" dequeues: %lu (dispatch: %lu, property_change: %lu)\n", + (unsigned long)skel->bss->dequeue_cnt, + (unsigned long)skel->bss->dispatch_dequeue_cnt, + (unsigned long)skel->bss->change_dequeue_cnt); + if (!use_move_to_local) + printf(" remote SCX_DSQ_LOCAL_ON dispatches: %lu\n", + (unsigned long)skel->bss->remote_dispatch_cnt); + printf(" ran on a CPU other than the enqueue CPU: %lu\n", + (unsigned long)skel->bss->remote_running_cnt); + printf(" ran without ops.dequeue(): %lu\n", + (unsigned long)skel->bss->missed_dequeue_cnt); + printf(" late SCX_DEQ_CORE_SCHED_EXEC dequeues: %lu\n", + (unsigned long)skel->bss->core_sched_exec_dequeue_cnt); + + if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_UNREG)) + SCX_ERR("Scheduler exited with kind=%lld: %s", + (long long)skel->data->uei.kind, skel->data->uei.msg); + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + /* the test is meaningless if no task was moved across CPUs */ + SCX_GT(skel->bss->remote_running_cnt, 0); + SCX_EQ(skel->bss->enqueue_cnt, skel->bss->dequeue_cnt); + + return SCX_TEST_PASS; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_remote *skel; + cpu_set_t cpus; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(cpus), &cpus), + "Failed to get CPU affinity"); + nr_cpus = CPU_COUNT(&cpus); + if (nr_cpus < 2) { + fprintf(stderr, "Skipping: requires at least 2 usable CPUs\n"); + return SCX_TEST_SKIP; + } + + skel = SCX_OPS_OPEN(dequeue_remote_ops, dequeue_remote); + SCX_OPS_LOAD(skel, dequeue_remote_ops, dequeue_remote, uei); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_remote *skel = ctx; + enum scx_test_status status; + + status = run_scenario(skel, false, + "BPF queue -> SCX_DSQ_LOCAL_ON | cpu"); + if (status != SCX_TEST_PASS) + return status; + + status = run_scenario(skel, true, + "user DSQ -> scx_bpf_dsq_move_to_local()"); + if (status != SCX_TEST_PASS) + return status; + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_remote *skel = ctx; + + dequeue_remote__destroy(skel); +} + +struct scx_test dequeue_remote_test = { + .name = "dequeue_remote", + .description = "Verify ops.dequeue() on SCX-internal cross-CPU migrations", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; + +REGISTER_SCX_TEST(&dequeue_remote_test) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 2d5843aebd7296..c3b6ff157d3e04 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -412,5 +412,51 @@ "teardown": [ "$TC qdisc del dev $DEV1 ingress" ] + }, + { + "id": "4000", + "name": "Change a wildcard route filter classid only (no routing attribute)", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0xffff8000 flowid 1:9", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "907a", + "name": "Try to change a route filter with no routing attribute and a non-wildcard handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route classid 1:2", + "expExitCode": "2", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0x00010001 flowid 1:1 to 1 from 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 2400b461576645..5b19bfcfa8d745 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1589,7 +1589,7 @@ static int ublk_start_daemon(const struct dev_ctx *ctx, struct ublk_dev *dev) /* Signal listener thread to stop and wait for it */ if (linfo.stop_efd >= 0) { - write(linfo.stop_efd, &stop_val, sizeof(stop_val)); + (void)!write(linfo.stop_efd, &stop_val, sizeof(stop_val)); pthread_join(listener, NULL); close(linfo.stop_efd); ublk_shmem_sock_destroy(dinfo->dev_id, linfo.sock_fd); diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c index c8ef7b8cd46be7..e973e0a6d1a829 100644 --- a/tools/testing/vma/tests/vma.c +++ b/tools/testing/vma/tests/vma.c @@ -40,7 +40,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -53,7 +53,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks); + vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -71,7 +71,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks, false); ASSERT_NE(vma_new, vma_orig); ASSERT_EQ(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -82,7 +82,7 @@ static bool test_copy_vma(void) /* Move backwards and do not merge. */ vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags); - vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks); + vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks, false); ASSERT_NE(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0); ASSERT_EQ(vma_new->vm_end, 0x2000); @@ -95,7 +95,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags); vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags); - vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks); + vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks, false); vma_assert_attached(vma_new); ASSERT_EQ(vma_new, vma_next); diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 85f42289748dc6..5a782849521c90 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1363,6 +1363,9 @@ int kvm_trylock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); + if (WARN_ON_ONCE(kvm_is_vcpu_creation_in_progress(kvm))) + return -EBUSY; + kvm_for_each_vcpu(i, vcpu, kvm) if (!mutex_trylock_nest_lock(&vcpu->mutex, &kvm->lock)) goto out_unlock; @@ -1386,6 +1389,9 @@ int kvm_lock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); + if (WARN_ON_ONCE(kvm_is_vcpu_creation_in_progress(kvm))) + return -EBUSY; + kvm_for_each_vcpu(i, vcpu, kvm) { r = mutex_lock_killable_nest_lock(&vcpu->mutex, &kvm->lock); if (r) @@ -4182,6 +4188,8 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) struct kvm_vcpu *vcpu; struct page *page; + guard(mutex)(&kvm->lock); + /* * KVM tracks vCPU IDs as 'int', be kind to userspace and reject * too-large values instead of silently truncating. @@ -4194,26 +4202,17 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) if (id >= KVM_MAX_VCPU_IDS) return -EINVAL; - mutex_lock(&kvm->lock); - if (kvm->created_vcpus >= kvm->max_vcpus) { - mutex_unlock(&kvm->lock); + if (kvm->created_vcpus >= kvm->max_vcpus) return -EINVAL; - } - if (test_bit(id, kvm->vcpu_ids)) { - mutex_unlock(&kvm->lock); + if (kvm_get_vcpu_by_id(kvm, id)) return -EEXIST; - } r = kvm_arch_vcpu_precreate(kvm, id); - if (r) { - mutex_unlock(&kvm->lock); + if (r) return r; - } kvm->created_vcpus++; - __set_bit(id, kvm->vcpu_ids); - mutex_unlock(&kvm->lock); vcpu = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL_ACCOUNT); if (!vcpu) { @@ -4244,13 +4243,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) goto arch_vcpu_destroy; } - mutex_lock(&kvm->lock); - - if (WARN_ON_ONCE(kvm_get_vcpu_by_id(kvm, id))) { - r = -EEXIST; - goto unlock_vcpu_destroy; - } - /* * Set the vCPU's index *before* the vCPU is reachable by other tasks. * Unwind the index back to -1 on failure so that KVM can use the index @@ -4284,7 +4276,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) atomic_inc(&kvm->online_vcpus); mutex_unlock(&vcpu->mutex); - mutex_unlock(&kvm->lock); kvm_arch_vcpu_postcreate(vcpu); kvm_create_vcpu_debugfs(vcpu); return r; @@ -4295,7 +4286,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) xa_erase(&kvm->vcpu_array, vcpu->vcpu_idx); unlock_vcpu_destroy: vcpu->vcpu_idx = -1; - mutex_unlock(&kvm->lock); kvm_dirty_ring_free(&vcpu->dirty_ring); arch_vcpu_destroy: kvm_arch_vcpu_destroy(vcpu); @@ -4304,10 +4294,7 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) vcpu_free: kmem_cache_free(kvm_vcpu_cache, vcpu); vcpu_decrement: - mutex_lock(&kvm->lock); kvm->created_vcpus--; - __clear_bit(id, kvm->vcpu_ids); - mutex_unlock(&kvm->lock); return r; }