diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie b/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie new file mode 100644 index 000000000000..cceec6ac96bc --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie @@ -0,0 +1,15 @@ +What: /sys/bus/pci/devices//vendor_reset +Date: 22-Jul-2026 +KernelVersion: 6.17 +Contact: linux-bluetooth@vger.kernel.org +Description: This read-write attribute allows userspace to trigger a + Product Level Device Reset (PLDR) on Intel PCIe Bluetooth + controllers. Reading the attribute displays the supported + reset type. Writing integer 0 triggers PLDR. Any other + input is rejected with -EINVAL. + + PLDR resets the entire on-chip platform shared between + Bluetooth and WiFi. This means any driver attached to + the WiFi device that shares hardware with this Bluetooth + device will be released, the platform will be reset, and + both the Bluetooth and WiFi devices will be re-probed. diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml index fa5a8f8e390c..17983e6c3597 100644 --- a/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml +++ b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml @@ -14,15 +14,22 @@ description: provides up to 5 independent DPLL channels, up to 10 differential or single-ended inputs and 10 differential or 20 single-ended outputs. These devices support both I2C and SPI interfaces. + The ZL3064x line card timing ICs share the ZL3073x register map + and are described with a fallback to the register equivalent ZL3073x + part. properties: compatible: - enum: - - microchip,zl30731 - - microchip,zl30732 - - microchip,zl30733 - - microchip,zl30734 - - microchip,zl30735 + oneOf: + - enum: + - microchip,zl30731 + - microchip,zl30732 + - microchip,zl30733 + - microchip,zl30734 + - microchip,zl30735 + - items: + - const: microchip,zl30643 + - const: microchip,zl30733 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/net/adi,ad3306.yaml b/Documentation/devicetree/bindings/net/adi,ad3306.yaml new file mode 100644 index 000000000000..785d05c995db --- /dev/null +++ b/Documentation/devicetree/bindings/net/adi,ad3306.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/adi,ad3306.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ADI ADIN1140 10BASE-T1S MAC-PHY + +maintainers: + - Ciprian Regus + +description: | + The ADIN1140 (also called AD3306) is a low power single port + 10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC + and all the associated analog circuitry. + The device tries to implement the Open Alliance TC6 10BASE-T1x MAC-PHY + Serial Interface specification and is compliant with the + IEEE 802.3cg-2019 Ethernet standard for 10 Mbps single pair + Ethernet (SPE). The device has a 4-wire SPI interface for + communication between the MAC and host processor. + +allOf: + - $ref: /schemas/net/ethernet-controller.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + oneOf: + - items: + - const: adi,adin1140 + - const: adi,ad3306 + - const: adi,ad3306 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 25000000 + + interrupts: + maxItems: 1 + description: Interrupt from the MAC-PHY for receive data available + and error conditions + +required: + - compatible + - reg + - interrupts + - spi-max-frequency + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@0 { + compatible = "adi,ad3306"; + reg = <0>; + spi-max-frequency = <23000000>; + + interrupt-parent = <&gpio>; + interrupts = <6 IRQ_TYPE_LEVEL_LOW>; + + local-mac-address = [ 00 11 22 33 44 55 ]; + }; + }; diff --git a/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml b/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml index aafb6447b6c2..cfb53080dba2 100644 --- a/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml +++ b/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml @@ -8,7 +8,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Altera GMII to SGMII Converter maintainers: - - Matthew Gerlach + - Maxime Chevallier description: This binding describes the Altera GMII to SGMII converter. diff --git a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml index 63084f762373..db93436c96fa 100644 --- a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml +++ b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Altera SOCFPGA SoC DWMAC controller maintainers: - - Matthew Gerlach + - Maxime Chevallier description: This binding describes the Altera SOCFPGA SoC implementation of the diff --git a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml index a077c0330013..81e2b6dfeb02 100644 --- a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml +++ b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml @@ -16,7 +16,9 @@ allOf: properties: compatible: oneOf: - - const: rockchip,rk3568v2-canfd + - enum: + - rockchip,rk3568v2-canfd + - rockchip,rk3588-canfd - items: - const: rockchip,rk3568v3-canfd - const: rockchip,rk3568v2-canfd diff --git a/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml b/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml new file mode 100644 index 000000000000..7874e9e49224 --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/can/ti,am3517-hecc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments High End CAN Controller (HECC) + +maintainers: + - Eduard Bostina + +allOf: + - $ref: can-controller.yaml# + +properties: + compatible: + const: ti,am3517-hecc + + reg: + maxItems: 3 + + reg-names: + items: + - const: hecc + - const: hecc-ram + - const: mbx + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + ti,use-hecc1int: + type: boolean + description: + If provided, configures HECC to produce all interrupts on the + HECC1INT interrupt line. By default, the HECC0INT interrupt line + will be used. + default: false + + xceiver-supply: + description: Regulator that powers the CAN transceiver. + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + can@5c050000 { + compatible = "ti,am3517-hecc"; + reg = <0x5c050000 0x80>, + <0x5c053000 0x180>, + <0x5c052000 0x200>; + reg-names = "hecc", "hecc-ram", "mbx"; + interrupts = <24>; + clocks = <&hecc_ck>; + }; diff --git a/Documentation/devicetree/bindings/net/can/ti_hecc.txt b/Documentation/devicetree/bindings/net/can/ti_hecc.txt deleted file mode 100644 index e0f0a7cfe329..000000000000 --- a/Documentation/devicetree/bindings/net/can/ti_hecc.txt +++ /dev/null @@ -1,32 +0,0 @@ -Texas Instruments High End CAN Controller (HECC) -================================================ - -This file provides information, what the device node -for the hecc interface contains. - -Required properties: -- compatible: "ti,am3517-hecc" -- reg: addresses and lengths of the register spaces for 'hecc', 'hecc-ram' - and 'mbx' -- reg-names :"hecc", "hecc-ram", "mbx" -- interrupts: interrupt mapping for the hecc interrupts sources -- clocks: clock phandles (see clock bindings for details) - -Optional properties: -- ti,use-hecc1int: if provided configures HECC to produce all interrupts - on HECC1INT interrupt line. By default HECC0INT interrupt - line will be used. -- xceiver-supply: regulator that powers the CAN transceiver - -Example: - -For am3517evm board: - hecc: can@5c050000 { - compatible = "ti,am3517-hecc"; - reg = <0x5c050000 0x80>, - <0x5c053000 0x180>, - <0x5c052000 0x200>; - reg-names = "hecc", "hecc-ram", "mbx"; - interrupts = <24>; - clocks = <&hecc_ck>; - }; diff --git a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml index 40835497050a..18015e60fd6d 100644 --- a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml +++ b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml @@ -8,7 +8,7 @@ title: Xilinx CAN and CANFD controller maintainers: - - Appana Durga Kedareswara rao + - Harini T properties: compatible: @@ -53,6 +53,9 @@ properties: $ref: /schemas/types.yaml#/definitions/flag description: CAN TX_OL, TX_TL and RX FIFOs have ECC support(AXI CAN) + phys: + maxItems: 1 + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml b/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml new file mode 100644 index 000000000000..4cd0f9730d95 --- /dev/null +++ b/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/dptel,dap8211r.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY + +maintainers: + - Artem Shimko + +description: | + The DAP8211R(I) is a Gigabit Ethernet PHY with RGMII interface, + supporting IEEE 802.3az Energy Efficient Ethernet, IEEE 1588 SyncE, + and an internal packet generator for diagnostics. + + Specifications: + - 10BASE-Te, 100BASE-TX, 1000BASE-T + - RGMII with configurable TX/RX clock delays (150 ps steps, 0-2250 ps) + - IEEE 802.3az-2010 Energy Efficient Ethernet + - IEEE 1588 SyncE support + - Internal packet generator and checker for link diagnostics + +allOf: + - $ref: ethernet-phy.yaml# + +properties: + compatible: + const: ethernet-phy-id0008.011b + + reg: + maxItems: 1 + + rx-internal-delay-ps: + description: + RGMII RX clock delay in picoseconds (0 to maximum). + multipleOf: 150 + maximum: 2250 + default: 1950 + + tx-internal-delay-ps: + description: + RGMII TX clock delay in picoseconds (0 to maximum). + multipleOf: 150 + maximum: 2250 + default: 1950 + +unevaluatedProperties: false + +examples: + - | + mdio { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-phy@1 { + compatible = "ethernet-phy-id0008.011b"; + reg = <1>; + rx-internal-delay-ps = <1950>; + tx-internal-delay-ps = <1950>; + }; + }; diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml index 815a90808901..90b3582b7619 100644 --- a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml +++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml @@ -100,6 +100,10 @@ properties: Built-in switch of the Airoha AN7583 SoC const: airoha,an7583-switch + - description: + Built-in switch of the EcoNet EN7528 SoC + const: econet,en7528-switch + reg: maxItems: 1 @@ -318,6 +322,7 @@ allOf: - mediatek,mt7988-switch - airoha,en7581-switch - airoha,an7583-switch + - econet,en7528-switch then: $ref: "#/$defs/builtin-dsa-port" properties: diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml new file mode 100644 index 000000000000..e0e7ffef6648 --- /dev/null +++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/dsa/mediatek,mt7628-esw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek MT7628 Embedded Ethernet Switch + +maintainers: + - Joris Vaisvila + +description: + The MT7628 SoC's built-in Ethernet Switch has five user ports and one + internally connected CPU port. The user ports are all connected to the SoC's + integrated Fast Ethernet PHYs. The switch registers are directly mapped in + the SoC's memory. + +allOf: + - $ref: dsa.yaml#/$defs/ethernet-ports + +properties: + compatible: + const: mediatek,mt7628-esw + + reg: + maxItems: 1 + + resets: + items: + - description: internal switch block reset + - description: internal phy package reset + + reset-names: + items: + - const: esw + - const: ephy + +required: + - compatible + - reg + - resets + - reset-names + - ethernet-ports + +unevaluatedProperties: false + +examples: + - | + switch@10110000 { + compatible = "mediatek,mt7628-esw"; + reg = <0x10110000 0x8000>; + + resets = <&sysc 23>, <&sysc 24>; + reset-names = "esw", "ephy"; + + ethernet-ports { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-port@0 { + reg = <0>; + phy-mode = "internal"; + }; + + ethernet-port@1 { + reg = <1>; + phy-mode = "internal"; + }; + + ethernet-port@2 { + reg = <2>; + phy-mode = "internal"; + }; + + ethernet-port@3 { + reg = <3>; + phy-mode = "internal"; + }; + + ethernet-port@4 { + reg = <4>; + phy-mode = "internal"; + }; + + ethernet-port@6 { + reg = <6>; + phy-mode = "internal"; + ethernet = <ðernet>; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml index 65882ff79d8d..ba49fd6a086c 100644 --- a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml +++ b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml @@ -20,16 +20,37 @@ select: contains: enum: - eswin,eic7700-qos-eth + - eswin,eic7700-qos-eth-clk-inversion required: - compatible allOf: - $ref: snps,dwmac.yaml# + - if: + properties: + compatible: + contains: + const: eswin,eic7700-qos-eth + then: + properties: + tx-internal-delay-ps: + maximum: 2540 + - if: + properties: + compatible: + contains: + const: eswin,eic7700-qos-eth-clk-inversion + then: + properties: + tx-internal-delay-ps: + minimum: 2000 properties: compatible: items: - - const: eswin,eic7700-qos-eth + - enum: + - eswin,eic7700-qos-eth + - eswin,eic7700-qos-eth-clk-inversion - const: snps,dwmac-5.20 reg: @@ -63,10 +84,14 @@ properties: - const: stmmaceth rx-internal-delay-ps: - enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400] + minimum: 0 + maximum: 2540 + multipleOf: 20 tx-internal-delay-ps: - enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400] + minimum: 0 + maximum: 4540 + multipleOf: 20 eswin,hsp-sp-csr: description: @@ -105,8 +130,6 @@ required: - phy-mode - resets - reset-names - - rx-internal-delay-ps - - tx-internal-delay-ps - eswin,hsp-sp-csr unevaluatedProperties: false @@ -116,26 +139,51 @@ examples: ethernet@50400000 { compatible = "eswin,eic7700-qos-eth", "snps,dwmac-5.20"; reg = <0x50400000 0x10000>; - clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, - <&d0_clock 193>; - clock-names = "axi", "cfg", "stmmaceth", "tx"; interrupt-parent = <&plic>; interrupts = <61>; interrupt-names = "macirq"; - phy-mode = "rgmii-id"; - phy-handle = <&phy0>; + clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, + <&d0_clock 193>; + clock-names = "axi", "cfg", "stmmaceth", "tx"; resets = <&reset 95>; reset-names = "stmmaceth"; - rx-internal-delay-ps = <200>; - tx-internal-delay-ps = <200>; eswin,hsp-sp-csr = <&hsp_sp_csr 0x100 0x108 0x118 0x114 0x11c>; - snps,axi-config = <&stmmac_axi_setup>; + phy-handle = <&phy0>; + phy-mode = "rgmii-id"; snps,aal; snps,fixed-burst; snps,tso; + snps,axi-config = <&stmmac_axi_setup>; + stmmac_axi_setup: stmmac-axi-config { snps,blen = <0 0 0 0 16 8 4>; snps,rd_osr_lmt = <2>; snps,wr_osr_lmt = <2>; }; }; + + ethernet@50410000 { + compatible = "eswin,eic7700-qos-eth-clk-inversion", "snps,dwmac-5.20"; + reg = <0x50410000 0x10000>; + interrupt-parent = <&plic>; + interrupts = <70>; + interrupt-names = "macirq"; + clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, + <&d0_clock 194>; + clock-names = "axi", "cfg", "stmmaceth", "tx"; + resets = <&reset 94>; + reset-names = "stmmaceth"; + eswin,hsp-sp-csr = <&hsp_sp_csr 0x200 0x208 0x218 0x214 0x21c>; + phy-handle = <&gmac1_phy0>; + phy-mode = "rgmii-id"; + snps,aal; + snps,fixed-burst; + snps,tso; + snps,axi-config = <&stmmac_axi_setup_gmac1>; + + stmmac_axi_setup_gmac1: stmmac-axi-config { + snps,blen = <0 0 0 0 16 8 4>; + snps,rd_osr_lmt = <2>; + snps,wr_osr_lmt = <2>; + }; + }; diff --git a/Documentation/devicetree/bindings/net/fsl,gianfar.yaml b/Documentation/devicetree/bindings/net/fsl,gianfar.yaml index 0d8909770ccb..e2543b683bd4 100644 --- a/Documentation/devicetree/bindings/net/fsl,gianfar.yaml +++ b/Documentation/devicetree/bindings/net/fsl,gianfar.yaml @@ -234,7 +234,7 @@ examples: ; }; - queue-group@2d14000 { + queue-group@2d14000 { reg = <0x0 0x2d14000 0x0 0x1000>; interrupts = , , diff --git a/Documentation/devicetree/bindings/net/microchip,lan7800.yaml b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml new file mode 100644 index 000000000000..cb3927215dfc --- /dev/null +++ b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/microchip,lan7800.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip LAN7800/LAN7801/LAN7850 Gigabit Ethernet controller + +maintainers: + - Thangaraj Samynathan + - Rengarajan Sundararajan + +description: + The LAN7800/LAN7801/LAN7850 devices are usually configured by + programming their OTP or with an external EEPROM, but some + platforms (e.g. Raspberry Pi 3 B+) have neither. The Device Tree + properties, if present, override the OTP and EEPROM. + +allOf: + - $ref: /schemas/usb/usb-device.yaml# + - $ref: /schemas/net/ethernet-controller.yaml# + +properties: + compatible: + enum: + - usb424,7800 + - usb424,7801 + - usb424,7850 + + reg: + maxItems: 1 + description: USB port number + + mdio: + $ref: /schemas/net/mdio.yaml# + unevaluatedProperties: false + + patternProperties: + "^ethernet-phy(@[0-9a-f]+)?$": + $ref: /schemas/net/ethernet-phy.yaml# + unevaluatedProperties: false + type: object + + properties: + microchip,led-modes: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 4 + description: + Array of LED mode values for each of up to 4 LEDs. + Omitted LEDs are turned off. Allowed values are defined + in include/dt-bindings/net/microchip-lan78xx.h. + + required: + - reg + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + + usb { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@1 { + compatible = "usb424,7800"; + reg = <1>; + local-mac-address = [00 11 22 33 44 55]; + + mdio { + #address-cells = <1>; + #size-cells = <0>; + ethernet-phy@1 { + reg = <1>; + microchip,led-modes = < + LAN78XX_LINK_1000_ACTIVITY + LAN78XX_LINK_10_100_ACTIVITY + >; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt deleted file mode 100644 index 11a679530ae6..000000000000 --- a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt +++ /dev/null @@ -1,53 +0,0 @@ -Microchip LAN78xx Gigabit Ethernet controller - -The LAN78XX devices are usually configured by programming their OTP or with -an external EEPROM, but some platforms (e.g. Raspberry Pi 3 B+) have neither. -The Device Tree properties, if present, override the OTP and EEPROM. - -Required properties: -- compatible: Should be one of "usb424,7800", "usb424,7801" or "usb424,7850". - -The MAC address will be determined using the optional properties -defined in ethernet.txt. - -Optional properties of the embedded PHY: -- microchip,led-modes: a 0..4 element vector, with each element configuring - the operating mode of an LED. Omitted LEDs are turned off. Allowed values - are defined in "include/dt-bindings/net/microchip-lan78xx.h". - -Example: - -/* Based on the configuration for a Raspberry Pi 3 B+ */ -&usb { - usb-port@1 { - compatible = "usb424,2514"; - reg = <1>; - #address-cells = <1>; - #size-cells = <0>; - - usb-port@1 { - compatible = "usb424,2514"; - reg = <1>; - #address-cells = <1>; - #size-cells = <0>; - - ethernet: ethernet@1 { - compatible = "usb424,7800"; - reg = <1>; - local-mac-address = [ 00 11 22 33 44 55 ]; - - mdio { - #address-cells = <0x1>; - #size-cells = <0x0>; - eth_phy: ethernet-phy@1 { - reg = <1>; - microchip,led-modes = < - LAN78XX_LINK_1000_ACTIVITY - LAN78XX_LINK_10_100_ACTIVITY - >; - }; - }; - }; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml index b9c394009040..2bf6be9807a7 100644 --- a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml +++ b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml @@ -35,7 +35,7 @@ properties: - usb424,9906 # SMSC9505A USB Ethernet Device (HAL) - usb424,9907 # SMSC9500 USB Ethernet Device (Alternate ID) - usb424,9908 # SMSC9500A USB Ethernet Device (Alternate ID) - - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device ID) + - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device - usb424,9e00 # SMSC9500A USB Ethernet Device - usb424,9e01 # SMSC9505A USB Ethernet Device - usb424,9e08 # SMSC LAN89530 USB Ethernet Device diff --git a/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml b/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml index 0f0f35865ef4..b4efa2f6036a 100644 --- a/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml +++ b/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml @@ -140,8 +140,8 @@ examples: #include switch: ethernet-switch@e0000000 { compatible = "microchip,lan966x-switch"; - reg = <0xe0000000 0x0100000>, - <0xe2000000 0x0800000>; + reg = <0xe0000000 0x0100000>, + <0xe2000000 0x0800000>; reg-names = "cpu", "gcb"; interrupts = ; interrupt-names = "xtr"; diff --git a/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml b/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml index 75c7c8d1f411..9de8eeb8c6a4 100644 --- a/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml +++ b/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml @@ -210,9 +210,9 @@ examples: #include switch: switch@600000000 { compatible = "microchip,sparx5-switch"; - reg = <0 0x401000>, - <0x10004000 0x7fc000>, - <0x11010000 0xaf0000>; + reg = <0 0x401000>, + <0x10004000 0x7fc000>, + <0x11010000 0xaf0000>; reg-names = "cpu", "devices", "gcb"; interrupts = ; interrupt-names = "xtr"; diff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml new file mode 100644 index 000000000000..3bb32349c28c --- /dev/null +++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu-gen1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek PSE MCU + +maintainers: + - Jonas Jelonek + +description: | + A microcontroller (MCU) that manages the PSE (Power Sourcing Equipment) + hardware on a range of managed PoE switches. The host CPU talks only to + this MCU - over I2C/SMBus or UART - using a small message-based protocol; + the PSE silicon it drives sits behind the MCU and is never accessed + directly. For example, on the Zyxel GS1900-10HP the SoC reaches the MCU + over UART, and the MCU manages the on-board PSE chip. + + This binding describes the MCU together with its Realtek firmware: the + firmware and its host protocol, which are stable across boards. The + microcontroller silicon is a general-purpose part that varies, and the + PSE silicon behind the MCU (Realtek RTL823x/RTL8239* or Broadcom + BCM59xxx) is reported by the MCU and detected at runtime - neither is + named here. + + Two protocol generations exist, both Realtek's: + gen1 older boards, where the MCU fronts Broadcom PSE silicon + gen2 the altered protocol used with Realtek's own PSE silicon + + On an I2C attachment the framing the MCU firmware expects is part of the + compatible: '-smbus' (reads carry a leading command byte and a repeated + start) or '-i2c' (bare block writes and reads). A UART attachment carries + no framing suffix; the transport is given by the parent 'serial' node. + + Each board additionally carries a device-specific compatible that falls + back to one of the protocol compatibles above. Drivers bind on the + protocol compatible; the device-specific string identifies the board and + reserves a place for a future per-board quirk without having to retrofit + device trees already in the field. + +properties: + compatible: + oneOf: + # UART + - items: + - enum: + - zyxel,gs1900-10hp-a1-pse + - const: realtek,pse-mcu-gen1 + + # I2C, SMBus framing + - items: + - enum: + - zyxel,gs1920-24hp-v2-pse + - const: realtek,pse-mcu-gen1-smbus + + # UART + - items: + - enum: + - zyxel,gs1900-10hp-b1-pse + - zyxel,xmg1915-10ep-pse + - const: realtek,pse-mcu-gen2 + + # I2C, SMBus framing + - items: + - enum: + - zyxel,xs1930-12hp-pse + - const: realtek,pse-mcu-gen2-smbus + + # I2C, raw framing + - items: + - enum: + - linksys,lgs328mpc-v2-pse + - const: realtek,pse-mcu-gen2-i2c + + reg: + maxItems: 1 + + reset-gpios: + description: Reset line of the MCU. + maxItems: 1 + + disable-ports-gpios: + description: + Hardware gate that forces all ports into admin-disabled state while + asserted. + maxItems: 1 + +required: + - compatible + +allOf: + - $ref: pse-controller.yaml# + # A '-smbus'/'-i2c' compatible is an I2C attachment: it has 'reg' and + # cannot carry serial bus properties. A bare gen compatible is a UART + # attachment: no 'reg', the transport comes from the parent serial node. + - if: + properties: + compatible: + contains: + enum: + - realtek,pse-mcu-gen1-smbus + - realtek,pse-mcu-gen2-smbus + - realtek,pse-mcu-gen2-i2c + then: + required: + - reg + properties: + current-speed: false + max-speed: false + else: + allOf: + - $ref: /schemas/serial/serial-peripheral-props.yaml# + + properties: + reg: false + +unevaluatedProperties: false + +examples: + # SMBus-framed I2C attachment + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-pse@20 { + compatible = "zyxel,xs1930-12hp-pse", "realtek,pse-mcu-gen2-smbus"; + reg = <0x20>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; + + # Raw-I2C-framed attachment + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-pse@20 { + compatible = "linksys,lgs328mpc-v2-pse", "realtek,pse-mcu-gen2-i2c"; + reg = <0x20>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; + + # UART attachment + - | + serial { + ethernet-pse { + compatible = "zyxel,gs1900-10hp-a1-pse", "realtek,pse-mcu-gen1"; + current-speed = <19200>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml b/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml index 2125b5ddf73d..c8f76c8e7584 100644 --- a/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml +++ b/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml @@ -254,10 +254,10 @@ examples: ethernet@15c30000 { compatible = "renesas,r9a09g057-gbeth", "renesas,rzv2h-gbeth", "snps,dwmac-5.20"; reg = <0x15c30000 0x10000>; - clocks = <&cpg CPG_MOD 0xbd>, <&cpg CPG_MOD 0xbc>, - <&ptp_clock>, <&cpg CPG_MOD 0xb8>, - <&cpg CPG_MOD 0xb9>, <&cpg CPG_MOD 0xba>, - <&cpg CPG_MOD 0xbb>; + clocks = <&cpg CPG_MOD 0xbd>, <&cpg CPG_MOD 0xbc>, + <&ptp_clock>, <&cpg CPG_MOD 0xb8>, + <&cpg CPG_MOD 0xb9>, <&cpg CPG_MOD 0xba>, + <&cpg CPG_MOD 0xbb>; clock-names = "stmmaceth", "pclk", "ptp_ref", "tx", "rx", "tx-180", "rx-180"; resets = <&cpg 0xb0>; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 435dd57e8f11..d4d2ba5b0a9a 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -461,6 +461,8 @@ patternProperties: description: Dongwoon Anatech "^dptechnics,.*": description: DPTechnics + "^dptel,.*": + description: Guangdong Dapu Telecom Co., Ltd. "^dragino,.*": description: Dragino Technology Co., Limited "^dream,.*": diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index bae14766d4f7..7c117ae37cc1 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -91,6 +91,13 @@ following pin states: - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm +Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE`` +capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in +automatic mode, overriding the active input selection. This is useful +for automatic-only DPLL devices where mode cannot be switched to manual. +When such a pin is disconnected, the device returns to automatic input +selection. + The actual hardware status of a pin is reported via the operational state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent device: @@ -109,8 +116,9 @@ Shared pins A single pin object can be attached to multiple dpll devices. Then there are two groups of configuration knobs: -1) Set on a pin - the configuration affects all dpll devices pin is - registered to (i.e., ``DPLL_A_PIN_FREQUENCY``), +1) Set on a pin - the configuration is a property of the pin itself and + applies to all dpll devices the pin is registered with + (i.e., ``DPLL_A_PIN_FREQUENCY``), 2) Set on a pin-dpll tuple - the configuration affects only selected dpll device (i.e., ``DPLL_A_PIN_PRIO``, ``DPLL_A_PIN_STATE``, ``DPLL_A_PIN_DIRECTION``). @@ -500,9 +508,9 @@ as well as parameter being configured (``DPLL_A_MODE``). ``DPLL_CMD_PIN_SET`` - to target a pin user must provide a ``DPLL_A_PIN_ID``, which is unique identifier of a pin in the system. Also configured pin parameters must be added. -If ``DPLL_A_PIN_FREQUENCY`` is configured, this affects all the dpll -devices that are connected with the pin, that is why frequency attribute -shall not be enclosed in ``DPLL_A_PIN_PARENT_DEVICE``. +If ``DPLL_A_PIN_FREQUENCY`` is configured, it is a property of the pin +itself and applies to all dpll devices the pin is registered with, so the +frequency attribute shall not be enclosed in ``DPLL_A_PIN_PARENT_DEVICE``. Other attributes: ``DPLL_A_PIN_PRIO``, ``DPLL_A_PIN_STATE`` or ``DPLL_A_PIN_DIRECTION`` must be enclosed in ``DPLL_A_PIN_PARENT_DEVICE`` as their configuration relates to only one diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 52ad1e7805d1..38b1190f3d26 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -895,6 +895,16 @@ attribute-sets: resource-dump response. Bit 0 (dev) selects device-level resources; bit 1 (port) selects port-level resources. When absent all classes are returned. + - + name: parent-dev + type: nest + nested-attributes: dl-parent-dev + doc: | + Identifies the devlink instance which owns the parent rate node. + Used with rate-set and rate-new to parent a rate object to a node on + a different devlink instance, enabling cross-device rate scheduling. + When absent, the parent node is resolved on the same instance. + - name: dl-dev-stats subset-of: devlink @@ -1317,6 +1327,16 @@ attribute-sets: Specifies the bandwidth share assigned to the Traffic Class. The bandwidth for the traffic class is determined in proportion to the sum of the shares of all configured classes. + - + name: dl-parent-dev + subset-of: devlink + attributes: + - + name: bus-name + - + name: dev-name + - + name: index operations: enum-model: directional @@ -2289,8 +2309,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2303,6 +2323,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-new @@ -2311,8 +2332,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2325,6 +2346,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-del diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 2bf83f6732ab..cdc8c7b456df 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -165,6 +165,13 @@ definitions: - name: gnss doc: GNSS recovered clock + - + name: int-nco + doc: | + Device internal numerically controlled oscillator. + When connected as a DPLL input, the DPLL enters NCO mode + where the output frequency is adjusted by the host via + the PTP clock interface. render-max: true - type: enum @@ -252,6 +259,12 @@ definitions: - name: state-can-change doc: pin state can be changed + - + name: state-connected-override + doc: | + pin state can be set to connected regardless of current + DPLL device mode, overriding the active input selection. + Requires state-can-change to be set as well. - type: const name: phase-offset-divider @@ -456,6 +469,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPM (parts per million). This is a lower-precision version of fractional-frequency-offset-ppt. @@ -502,6 +518,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPT (parts per trillion, 10^-12). This is a higher-precision version of fractional-frequency-offset. diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 5f143da7458c..3e3f03bd5c29 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -6,6 +6,14 @@ doc: >- netdev configuration over generic netlink. definitions: + - + type: const + name: page-size + # Dummy value, codegen needs a number. The real value comes from + # the PAGE_SIZE macro in the header below. + value: 0 + header: asm/page.h + scope: kernel - type: flags name: xdp-act @@ -598,6 +606,16 @@ attribute-sets: type: u32 checks: min: 1 + - + name: rx-page-size + doc: | + Size in bytes of each device page the NIC writes into from the bound + dmabuf. Must be a power of two and >= PAGE_SIZE; defaults to + PAGE_SIZE. + type: u32 + checks: + min: page-size + max: u32-max operations: list: @@ -812,6 +830,7 @@ operations: - ifindex - fd - queues + - rx-page-size reply: attributes: - id diff --git a/Documentation/netlink/specs/nl80211.yaml b/Documentation/netlink/specs/nl80211.yaml index 802097128bda..d9fdd66b497e 100644 --- a/Documentation/netlink/specs/nl80211.yaml +++ b/Documentation/netlink/specs/nl80211.yaml @@ -1139,10 +1139,10 @@ attribute-sets: type: binary - name: fils-discovery - type: binary # TOOD: nest + type: binary # TODO: nest - name: unsol-bcast-probe-resp - type: binary # TOOD: nest + type: binary # TODO: nest - name: s1g-capability type: binary diff --git a/Documentation/netlink/specs/rt-addr.yaml b/Documentation/netlink/specs/rt-addr.yaml index 163a106c41bb..0ecbd24c890c 100644 --- a/Documentation/netlink/specs/rt-addr.yaml +++ b/Documentation/netlink/specs/rt-addr.yaml @@ -123,6 +123,9 @@ attribute-sets: - name: proto type: u8 + - + name: mc-users + type: u32 operations: @@ -176,6 +179,7 @@ operations: value: 58 attributes: &mcaddr-attrs - multicast + - mc-users - cacheinfo dump: request: diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 68c26a70bb64..b80c2ac3ac31 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -928,6 +928,11 @@ attribute-sets: name: vfinfo-list type: nest nested-attributes: vfinfo-list-attrs + doc: | + Per-VF details. The list holds at most 256 VFs, or 128 when + statistics are included, because it is one attribute and has to fit + in a u16 length. A device with more VFs than that reports a + truncated list; num-vf still carries the real count. - name: stats64 type: binary diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml index 33195db96746..253037ea5176 100644 --- a/Documentation/netlink/specs/rt-route.yaml +++ b/Documentation/netlink/specs/rt-route.yaml @@ -78,6 +78,27 @@ definitions: - name: rta-used type: u32 + - + name: del-reason + type: enum + name-prefix: rt-del-reason- + enum-name: rt-del-reason + doc: | + Why the kernel deleted a route. New causes may be appended. The + value space is family-agnostic, a value is never reinterpreted + per address family. + entries: + - + name: unspec + doc: The deletion path does not record a cause. + - + name: expired + doc: The RTF_EXPIRES lifetime ran out and the route was garbage + collected. + - + name: ra-withdrawn + doc: A Router Advertisement withdrew the route with a zero + lifetime. attribute-sets: - @@ -185,6 +206,18 @@ attribute-sets: type: u32 byte-order: big-endian display-hint: hex + - + name: del-reason + type: u32 + enum: del-reason + doc: | + Emitted only on RTM_DELROUTE notifications, and only when the + deletion path records a cause. An absent attribute means + either an older kernel or a deletion path that does not + record its cause, consumers must treat absent and unspec + identically. Currently only IPv6 deletion paths record a + cause. The attribute is notification-only, the kernel rejects + it in requests. - name: metrics name-prefix: rtax- @@ -299,6 +332,7 @@ operations: - dport - nh-id - flowlabel + - del-reason dump: request: value: 26 @@ -313,7 +347,35 @@ operations: do: request: value: 24 - attributes: *all-route-attrs + attributes: &route-req-attrs + - dst + - src + - iif + - oif + - gateway + - priority + - prefsrc + - metrics + - multipath + - flow + - cacheinfo + - table + - mark + - mfc-stats + - via + - newdst + - pref + - encap-type + - encap + - expires + - pad + - uid + - ttl-propagate + - ip-proto + - sport + - dport + - nh-id + - flowlabel - name: delroute doc: Delete an existing route @@ -321,4 +383,23 @@ operations: do: request: value: 25 - attributes: *all-route-attrs + attributes: *route-req-attrs + - + name: newroute-ntf + doc: Notification about a created route. + value: 24 + notify: getroute + - + name: delroute-ntf + doc: Notification about a deleted route. + value: 25 + notify: getroute + +mcast-groups: + list: + - + name: rtnlgrp-ipv4-route + value: 7 + - + name: rtnlgrp-ipv6-route + value: 11 diff --git a/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg b/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg index 381323423b4c..c8cf0bc49b69 100644 --- a/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg +++ b/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg @@ -93,9 +93,9 @@ - - + + Accessiblefor SW Inaccessiblefor SW diff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst index 9e8a16c44102..49487a35c163 100644 --- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst +++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst @@ -102,6 +102,95 @@ currently in use, and that bank will used for the next boot:: # devlink dev flash pci/0000:b5:00.0 \ file pensando/dsc_fw_1.63.0-22.tar +Firmware Management (PLDM) +========================== + +Firmware that supports PLDM can be updated using the devlink flash command +with a PLDM firmware package. The entire package can be updated at once:: + + # devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw + +Individual components can also be updated by specifying the component name:: + + # devlink dev flash pci/0000:b5:00.0 \ + file firmware.pldmfw component fw.cpld + +Per-component update uses driver-defined component names (fw, fw.cpld, +etc.). Not all components support per-component update - +devlink will reject the request if the specified component cannot +be updated. + +Gold (recovery) components can be updated by specifying the base component +name (e.g., ``fw`` for ``fw.gold``) with a goldfw package file when the +device supports per-component update. The ``.gold`` suffix in devlink info +output indicates the gold slot version, not a flash target. + +Info versions (PLDM) +==================== + +Firmware that supports PLDM reports component versions using driver-defined +names. The driver reports the following component versions: + +.. list-table:: devlink info versions for PLDM-capable firmware + :widths: 5 5 90 + + * - Name + - Type + - Description + * - ``fw`` + - running, stored + - Main firmware + * - ``fw.gold`` + - stored + - Gold (recovery) firmware + * - ``fw.bootloader`` + - running, stored + - Boot loader + * - ``fw.cpld`` + - running, stored + - CPLD + * - ``fw.secure`` + - running, stored + - Secure boot firmware + * - ``fw.fpga`` + - running, stored + - FPGA configuration + * - ``fw.suc`` + - running, stored + - System Unit Controller firmware + * - ``fw.suc.bootloader`` + - running, stored + - System Unit Controller bootloader + * - ``fw.uboot`` + - running, stored + - U-Boot bootloader + * - ``asic.id`` + - fixed + - The ASIC type for this device + * - ``asic.rev`` + - fixed + - The revision of the ASIC for this device + +Example output:: + + $ devlink dev info pci/0000:00:05.0 + pci/0000:00:05.0: + driver pds_core + serial_number FLM18420073 + versions: + fixed: + asic.id 0x0 + asic.rev 0x0 + running: + fw.bootloader 1.2.3 + fw 1.3.0 + fw.cpld 3.18 + stored: + fw.bootloader 1.2.3 + fw.gold 1.2.0 + fw 1.3.0 + fw.cpld 3.18 + Health Reporters ================ diff --git a/Documentation/networking/device_drivers/ethernet/index.rst b/Documentation/networking/device_drivers/ethernet/index.rst index 786a23c84b90..d9980c84487a 100644 --- a/Documentation/networking/device_drivers/ethernet/index.rst +++ b/Documentation/networking/device_drivers/ethernet/index.rst @@ -35,6 +35,7 @@ Contents: intel/idpf intel/igb intel/igbvf + intel/ixd intel/ixgbe intel/ixgbevf intel/i40e diff --git a/Documentation/networking/device_drivers/ethernet/intel/ixd.rst b/Documentation/networking/device_drivers/ethernet/intel/ixd.rst new file mode 100644 index 000000000000..1387626e5d20 --- /dev/null +++ b/Documentation/networking/device_drivers/ethernet/intel/ixd.rst @@ -0,0 +1,39 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +========================================================================== +iXD Linux* Base Driver for the Intel(R) Control Plane Function +========================================================================== + +Intel iXD Linux driver. +Copyright(C) 2025 Intel Corporation. + +.. contents:: + +For questions related to hardware requirements, refer to the documentation +supplied with your Intel adapter. All hardware requirements listed apply to use +with Linux. + + +Identifying Your Adapter +======================== +For information on how to identify your adapter, and for the latest Intel +network drivers, refer to the Intel Support website: +http://www.intel.com/support + + +Support +======= +For general information, go to the Intel support website at: +http://www.intel.com/support/ + +If an issue is identified with the released source code on a supported kernel +with a supported adapter, email the specific information related to the issue +to intel-wired-lan@lists.osuosl.org. + + +Trademarks +========== +Intel is a trademark or registered trademark of Intel Corporation or its +subsidiaries in the United States and/or other countries. + +* Other names and brands may be claimed as the property of others. diff --git a/Documentation/networking/devlink/devlink-params.rst b/Documentation/networking/devlink/devlink-params.rst index ca19ee3e63c8..13eb8aa84c38 100644 --- a/Documentation/networking/devlink/devlink-params.rst +++ b/Documentation/networking/devlink/devlink-params.rst @@ -165,3 +165,9 @@ own name. - u32 - Controls the maximum number of MAC address filters that can be assigned to a Virtual Function (VF). + * - ``max_sfs`` + - u32 + - The maximum number of subfunctions which can be created on the device. + Modifying this parameter may require a device restart and PCI bus + rescanning because the BAR layout may change. A value of 0 disables + subfunction creation. diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 9374ebe70f48..fe2cfee3e2a6 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -107,6 +107,15 @@ doesn't have the eswitch. Local controller (identified by controller number = 0) has the eswitch. The Devlink instance on the local controller has eswitch devlink ports for both the controllers. +A non-zero controller number may also be used for ports that are not external. +For example, a SmartNIC may have additional local PCI physical functions +that are managed by the eswitch but are not on an external host. These +ports use a non-zero controller number to distinguish them from the eswitch +manager's own functions, while the external flag remains unset. + +The ``phys_port_name`` includes the controller prefix (``c``) +whenever the controller number is non-zero, regardless of the external flag. + Function configuration ====================== @@ -420,6 +429,8 @@ API allows to configure following rate object's parameters: Parent node name. Parent node rate limits are considered as additional limits to all node children limits. ``tx_max`` is an upper limit for children. ``tx_share`` is a total bandwidth distributed among children. + If the device supports cross-function scheduling, the parent can be from a + different function of the same underlying device. ``tc_bw`` Allow users to set the bandwidth allocation per traffic class on rate diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 32f70879ddd0..d4a83fdcff7f 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -31,10 +31,10 @@ sure to respect following rules: - Lock ordering should be maintained. If driver needs to take instance lock of both nested and parent instances at the same time, devlink - instance lock of the parent instance should be taken first, only then - instance lock of the nested instance could be taken. - - Driver should use object-specific helpers to setup the nested relationship - before registering the nested devlink instance: + instance lock of the nested instance should be taken first, only then + instance lock of the parent instance could be taken. + - Driver should use object-specific helpers to setup the + nested relationship: - ``devl_nested_devlink_set()`` - called to setup devlink -> nested devlink relationship (could be used for multiple nested instances). @@ -87,6 +87,7 @@ parameters, info versions, and other features it supports. ice ionic iosm + ixd ixgbe kvaser_pciefd kvaser_usb diff --git a/Documentation/networking/devlink/ixd.rst b/Documentation/networking/devlink/ixd.rst new file mode 100644 index 000000000000..cba4f6f65013 --- /dev/null +++ b/Documentation/networking/devlink/ixd.rst @@ -0,0 +1,30 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=================== +ixd devlink support +=================== + +This document describes the devlink features implemented by the ``ixd`` +device driver. + +Info versions +============= + +The ``ixd`` driver reports the following versions + +.. list-table:: devlink info versions implemented + :widths: 5 5 5 90 + + * - Name + - Type + - Example + - Description + * - ``device.type`` + - fixed + - MEV + - The hardware type for this device + * - ``fw.mgmt.api`` + - running + - 2.0 + - 2-digit version number (major.minor) of the communication channel + (virtchnl) used by the device. diff --git a/Documentation/networking/devlink/mlx5.rst b/Documentation/networking/devlink/mlx5.rst index 4bba4d780a4a..9421c89951a0 100644 --- a/Documentation/networking/devlink/mlx5.rst +++ b/Documentation/networking/devlink/mlx5.rst @@ -45,8 +45,13 @@ Parameters - The range is between 1 and a device-specific max. - Applies to each physical function (PF) independently, if the device supports it. Otherwise, it applies symmetrically to all PFs. + * - ``max_sfs`` + - permanent + - The range is between 0 and a device-specific max. + - Applies to each physical function (PF) independently. -Note: permanent parameters such as ``enable_sriov`` and ``total_vfs`` require FW reset to take effect +Note: permanent parameters such as ``enable_sriov``, ``total_vfs`` and ``max_sfs`` + require FW reset to take effect .. code-block:: bash @@ -419,3 +424,36 @@ User commands examples: .. note:: This command can run over all interfaces such as PF/VF and representor ports. + +Rates +===== + +mlx5 devices can limit transmission of individual VFs or a group of them via +the devlink-rate API in switchdev mode. + +User commands examples: + +- Print the existing rates:: + + $ devlink port function rate show + +- Set a max tx limit on traffic from VF0:: + + $ devlink port function rate set pci/0000:82:00.0/1 tx_max 10Gbit + +- Create a rate group with a max tx limit and add two VFs to it:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Same scenario, with a min guarantee of 20% of the bandwidth for the first VF:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 tx_share 2Gbit + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Cross-device scheduling:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.1/32769 parent pci/0000:82:00.0/group1 diff --git a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst index 6dbd97d435e9..3dc03bff739e 100644 --- a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst +++ b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst @@ -22,6 +22,7 @@ struct_mutex ra_mutex struct_fib_rules_ops* rules_ops struct_fib_table fib_main struct_fib_table fib_default +spinlock_t fib_table_hash_lock unsigned_int fib_rules_require_fldissect bool fib_has_custom_rules bool fib_has_custom_local_routes diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst index d2a238f8cc8b..db71d4283032 100644 --- a/Documentation/networking/netdevices.rst +++ b/Documentation/networking/netdevices.rst @@ -421,10 +421,17 @@ running under the lock: * ``NETDEV_CHANGENAME`` * ``NETDEV_REGISTER`` * ``NETDEV_UP`` +* ``NETDEV_DOWN`` +* ``NETDEV_GOING_DOWN`` The following notifiers are running without the lock: * ``NETDEV_UNREGISTER`` +Many SW devices (uppers) catch their lower's ``NETDEV_UNREGISTER`` +events and may interact with them via ``dev_*()`` handlers, which take +the instance lock. Until we convert these devices to ``netif_*()`` variants, +``NETDEV_UNREGISTER`` stays unlocked. + There are no clear expectations for the remaining notifiers. Notifiers not on the list may run with or without the instance lock, potentially even invoking the same notifier type with and without the lock from different code paths. diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst index fe2aabde923a..013824078cea 100644 --- a/Documentation/networking/oa-tc6-framework.rst +++ b/Documentation/networking/oa-tc6-framework.rst @@ -454,7 +454,8 @@ Device drivers API The include/linux/oa_tc6.h defines the following functions: .. c:function:: struct oa_tc6 *oa_tc6_init(struct spi_device *spi, \ - struct net_device *netdev) + struct net_device *netdev, \ + struct oa_tc6_quirks *quirks) Initialize OA TC6 lib. diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst index 4d7087f727be..56c9dc7b96af 100644 --- a/Documentation/networking/tuntap.rst +++ b/Documentation/networking/tuntap.rst @@ -206,6 +206,38 @@ enable is true we enable it, otherwise we disable it:: return ioctl(fd, TUNSETQUEUE, (void *)&ifr); } +3.4 qdisc backpressure +---------------------- + +IFF_BACKPRESSURE can be set to enable qdisc backpressure. Without it, TX +drops occur when the internal ring buffer is full, so any attached qdisc +is effectively bypassed and applications only learn about congestion +through those drops. + +With it, the kernel stops the queue instead, letting the qdisc hold and +schedule packets, so its AQM, shaping and fairness actually apply. This +helps protocols like TCP, which cut throughput in reaction to packet +drops. With IFF_BACKPRESSURE, drops then only occur as a rare race. +Backpressure requires a qdisc to be attached and has no effect with +noqueue. + +The flag is a property of the TUN/TAP device rather than of the file +descriptor it was set on, so it applies to all queues of the device, +regardless of which process opened which queue. + +The flag can only be changed while the device has at most one queue. On a +multiqueue device that already has a second queue attached or detached, a +later TUNSETIFF succeeds but leaves the flag as it is. All the other +TUNSETIFF flags behave the same way. + +The txqueuelen can be reduced alongside this flag to further shift +buffering into the qdisc and reduce bufferbloat, at a possible +performance cost. + +When running multiple network streams in parallel through a single +TUN/TAP queue, the flag may reduce performance due to the extra overhead +of the backpressure mechanism. + Universal TUN/TAP device driver Frequently Asked Question ========================================================= diff --git a/Documentation/process/maintainer-netdev.rst b/Documentation/process/maintainer-netdev.rst index ec7b9aa2877f..1739d9f856c3 100644 --- a/Documentation/process/maintainer-netdev.rst +++ b/Documentation/process/maintainer-netdev.rst @@ -203,6 +203,22 @@ For RFC postings specifically, if nobody responded in a week - reviewers either missed the posting or have no strong opinions. If the code is ready, repost as a PATCH. +There are 2 services actively providing LLM-generated review on posted patches: + +- https://sashiko.dev/ +- https://netdev-ai.bots.linux.dev/sashiko/ + +both use the Sashiko infrastructure on top of different models. Reviews are +available after 24h. Patch authors are expected to proactively look into the +AI-generated reviews and handle such feedback as any other kind of review: +either debate it or address it. In both cases a reply on the mailing list is +expected. + +Authors are strongly encouraged to run LLM reviews on the posted patches in +advance of the actual post. Large series triggering a significant amount of +AI-generated feedback will likely get little attention from maintainers and +reviewers. + Emails saying just "ping" or "bump" are considered rude. If you can't figure out the status of the patch from patchwork or where the discussion has landed - describe your best guess and ask if it's correct. For example:: @@ -210,6 +226,10 @@ landed - describe your best guess and ask if it's correct. For example:: I don't understand what the next steps are. Person X seems to be unhappy with A, should I do B and repost the patches? +Don't reach out to maintainers or reviewers via private email and/or other +communications channels: all the discussion must remain public, and +requesting special attention is unfair towards the community, at best. + .. _Changes requested: Changes requested diff --git a/MAINTAINERS b/MAINTAINERS index f5475597568e..460cb7268845 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1900,6 +1900,21 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: drivers/dma/dma-axi-dmac.c +ANALOG DEVICES INC ETHERNET DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/net/adi,ad3306.yaml +F: drivers/net/ethernet/adi/adin1140.c + +ANALOG DEVICES INC ETHERNET PHY DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: drivers/net/phy/adin1140-phy.c + ANALOG DEVICES INC IIO DRIVERS M: Nuno Sá M: Michael Hennerich @@ -3585,15 +3600,11 @@ M: Dinh Nguyen S: Maintained F: drivers/clk/socfpga/ -ARM/SOCFPGA DWMAC GLUE LAYER BINDINGS -M: Matthew Gerlach -S: Maintained -F: Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml -F: Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml - ARM/SOCFPGA DWMAC GLUE LAYER M: Maxime Chevallier S: Maintained +F: Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml +F: Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml F: drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c ARM/SOCFPGA EDAC BINDINGS @@ -4253,7 +4264,6 @@ W: http://linux-atm.sourceforge.net F: drivers/atm/ F: drivers/usb/atm/ F: include/linux/atm* -F: include/linux/sonet.h F: include/uapi/linux/atm* F: include/uapi/linux/sonet.h F: net/atm/ @@ -4703,6 +4713,7 @@ S: Supported W: http://www.bluez.org/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git +F: Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie F: Documentation/devicetree/bindings/net/bluetooth/ F: drivers/bluetooth/ @@ -7848,6 +7859,7 @@ DPLL SUBSYSTEM M: Vadim Fedorenko M: Arkadiusz Kubalewski M: Jiri Pirko +R: Ivan Vecera L: netdev@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/dpll/dpll-device.yaml @@ -9497,11 +9509,6 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/efifb.c -EHEA (IBM pSeries eHEA 10Gb ethernet adapter) DRIVER -L: netdev@vger.kernel.org -S: Orphan -F: drivers/net/ethernet/ibm/ehea/ - ELM327 CAN NETWORK DRIVER M: Max Staudt L: linux-can@vger.kernel.org @@ -13043,8 +13050,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tnguy/next-queue.git F: Documentation/networking/device_drivers/ethernet/intel/ F: drivers/net/ethernet/intel/ F: drivers/net/ethernet/intel/*/ -F: include/linux/avf/virtchnl.h -F: include/linux/net/intel/*/ +F: include/linux/net/intel/ INTEL ETHERNET PROTOCOL DRIVER FOR RDMA M: Tatyana Nikolova @@ -18228,6 +18234,14 @@ F: drivers/regulator/mpq7920.c F: drivers/regulator/mpq7920.h F: include/linux/mfd/mp2629.h +MORSE MICRO MM81X WIRELESS DRIVER +M: Lachlan Hodges +M: Dan Callaghan +R: Arien Judge +L: linux-wireless@vger.kernel.org +S: Supported +F: drivers/net/wireless/morsemicro/ + MOST(R) TECHNOLOGY DRIVER M: Parthiban Veerasooran M: Christian Gromm @@ -19139,6 +19153,7 @@ F: include/net/net_failover.h NFC SUBSYSTEM M: David Heidelberg L: oe-linux-nfc@lists.linux.dev +C: https://matrix.to/#/#linux-nfc:ixit.cz S: Maintained T: git https://codeberg.org/linux-nfc/linux.git F: Documentation/devicetree/bindings/net/nfc/ @@ -19585,6 +19600,13 @@ S: Maintained F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml F: drivers/ptp/ptp_netc.c +NXP NXPWIFI WIRELESS DRIVER +M: Jeff Chen +R: Francesco Dolcini +L: linux-wireless@vger.kernel.org +S: Maintained +F: drivers/net/wireless/nxp/ + NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER M: Woodrow Douglass S: Maintained @@ -22130,6 +22152,14 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: Documentation/devicetree/bindings/net/wireless/qca,ath9k.yaml F: drivers/net/wireless/ath/ath9k/ +QUALCOMM ATHEROS QCA8K DSA SWITCH DRIVER +M: Christian Marangi +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/dsa/qca8k.yaml +F: drivers/net/dsa/qca/qca8k* +F: net/dsa/tag_qca.c + QUALCOMM ATHEROS QCA7K ETHERNET DRIVER M: Stefan Wahren L: netdev@vger.kernel.org @@ -22782,6 +22812,13 @@ S: Maintained F: Documentation/devicetree/bindings/watchdog/realtek,otto-wdt.yaml F: drivers/watchdog/realtek_otto_wdt.c +REALTEK PSE MCU DRIVER +M: Jonas Jelonek +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml +F: drivers/net/pse-pd/realtek-pse-mcu* + REALTEK RTL83xx SMI DSA ROUTER CHIPS M: Linus Walleij M: Luiz Angelo Daros de Luca @@ -27302,6 +27339,7 @@ F: tools/testing/selftests/timers/ TIPC NETWORK LAYER M: Jon Maloy +M: Tung Quang Nguyen L: netdev@vger.kernel.org (core kernel code) L: tipc-discussion@lists.sourceforge.net (user apps, general discussion) S: Maintained @@ -28006,7 +28044,7 @@ M: Thangaraj Samynathan M: UNGLinuxDriver@microchip.com L: netdev@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/net/microchip,lan78xx.txt +F: Documentation/devicetree/bindings/net/microchip,lan7800.yaml F: drivers/net/usb/lan78xx.* F: include/dt-bindings/net/microchip-lan78xx.h @@ -29605,7 +29643,7 @@ F: Documentation/devicetree/bindings/net/xlnx,axi-ethernet.yaml F: drivers/net/ethernet/xilinx/xilinx_axienet* XILINX CAN DRIVER -M: Appana Durga Kedareswara rao +M: Harini T L: linux-can@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/can/xilinx,can.yaml @@ -29914,6 +29952,12 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git F: sound/hda/codecs/senarytech.c +ZTE DINGHAI ETHERNET DRIVER +M: Junyang Han +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/ethernet/zte/ + THE REST M: Linus Torvalds L: linux-kernel@vger.kernel.org diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h index 5ef57f88df6b..946a5fad2691 100644 --- a/arch/alpha/include/uapi/asm/socket.h +++ b/arch/alpha/include/uapi/asm/socket.h @@ -155,6 +155,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/mips/configs/mtx1_defconfig b/arch/mips/configs/mtx1_defconfig index 46b40784a828..e28a0b1e6d52 100644 --- a/arch/mips/configs/mtx1_defconfig +++ b/arch/mips/configs/mtx1_defconfig @@ -234,7 +234,6 @@ CONFIG_TULIP=m CONFIG_WINBOND_840=m CONFIG_DM9102=m CONFIG_ULI526X=m -CONFIG_PCMCIA_XIRCOM=m CONFIG_DL2K=m CONFIG_SUNDANCE=m CONFIG_E100=m diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h index 72fb1b006da9..f1641dde135f 100644 --- a/arch/mips/include/uapi/asm/socket.h +++ b/arch/mips/include/uapi/asm/socket.h @@ -166,6 +166,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h index c16ec36dfee6..f3a3815c7dc2 100644 --- a/arch/parisc/include/uapi/asm/socket.h +++ b/arch/parisc/include/uapi/asm/socket.h @@ -147,6 +147,8 @@ #define SO_INQ 0x4052 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 0x4053 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/powerpc/configs/ppc64_defconfig b/arch/powerpc/configs/ppc64_defconfig index f795b74602ec..1eb8e3457e8b 100644 --- a/arch/powerpc/configs/ppc64_defconfig +++ b/arch/powerpc/configs/ppc64_defconfig @@ -210,7 +210,6 @@ CONFIG_BNX2X=m CONFIG_CHELSIO_T1=m CONFIG_BE2NET=m CONFIG_IBMVETH=m -CONFIG_EHEA=m CONFIG_IBMVNIC=m CONFIG_E100=y CONFIG_E1000=y diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index 06e3cc55cb0d..33ef33ffc756 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -412,7 +412,6 @@ CONFIG_TULIP_MMIO=y CONFIG_WINBOND_840=m CONFIG_DM9102=m CONFIG_ULI526X=m -CONFIG_PCMCIA_XIRCOM=m CONFIG_DL2K=m CONFIG_SUNDANCE=m CONFIG_FEC_MPC52xx=m diff --git a/arch/powerpc/mm/mem.c b/arch/powerpc/mm/mem.c index 4c1afab91996..b617b69452cd 100644 --- a/arch/powerpc/mm/mem.c +++ b/arch/powerpc/mm/mem.c @@ -371,12 +371,6 @@ int devmem_is_allowed(unsigned long pfn) } #endif /* CONFIG_STRICT_DEVMEM */ -/* - * This is defined in kernel/resource.c but only powerpc needs to export it, for - * the EHEA driver. Drop this when drivers/net/ethernet/ibm/ehea is removed. - */ -EXPORT_SYMBOL_GPL(walk_system_ram_range); - #ifdef CONFIG_EXECMEM static struct execmem_info execmem_info __ro_after_init; diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h index 71befa109e1c..7907f3b1f0ee 100644 --- a/arch/sparc/include/uapi/asm/socket.h +++ b/arch/sparc/include/uapi/asm/socket.h @@ -148,6 +148,8 @@ #define SO_INQ 0x005d #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 0x005e + #if !defined(__KERNEL__) diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index bafc26250b63..e6b1c1180d1d 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -50,3 +50,5 @@ hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o hci_uart-$(CONFIG_BT_HCIUART_AML) += hci_aml.o hci_uart-objs := $(hci_uart-y) + +CONTEXT_ANALYSIS := y diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c index 8df310983bf6..d31d797639b5 100644 --- a/drivers/bluetooth/bfusb.c +++ b/drivers/bluetooth/bfusb.c @@ -301,6 +301,11 @@ static inline int bfusb_recv_block(struct bfusb_data *data, int hdr, unsigned ch return -EILSEQ; } break; + + default: + bt_dev_err(data->hdev, "unknown packet type 0x%02x", + pkt_type); + return -EILSEQ; } skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); @@ -319,6 +324,13 @@ static inline int bfusb_recv_block(struct bfusb_data *data, int hdr, unsigned ch } } + if (len > skb_tailroom(data->reassembly)) { + bt_dev_err(data->hdev, "block exceeds packet length"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + return -EILSEQ; + } + if (len > 0) skb_put_data(data->reassembly, buf, len); @@ -353,6 +365,13 @@ static void bfusb_rx_complete(struct urb *urb) skb_put(skb, count); while (count) { + if (count < 2) { + bt_dev_err(data->hdev, "short block header"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } + hdr = buf[0] | (buf[1] << 8); if (hdr & 0x4000) { @@ -360,16 +379,28 @@ static void bfusb_rx_complete(struct urb *urb) count -= 2; buf += 2; } else { + if (count < 3) { + bt_dev_err(data->hdev, "short block header"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } + len = (buf[2] == 0) ? 256 : buf[2]; count -= 3; buf += 3; } - if (count < len) + if (count < len) { bt_dev_err(data->hdev, "block extends over URB buffer ranges"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } - if ((hdr & 0xe1) == 0xc1) - bfusb_recv_block(data, hdr, buf, len); + if ((hdr & 0xe1) == 0xc1 && + bfusb_recv_block(data, hdr, buf, len) < 0) + data->hdev->stat.err_rx++; count -= len; buf += len; diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index bf567b7c5f00..bcb2514b7bc0 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -51,6 +51,7 @@ enum { #define BTINTEL_BT_DOMAIN 0x12 #define BTINTEL_SAR_LEGACY 0 #define BTINTEL_SAR_INC_PWR 1 +#define BTINTEL_SAR_REV2 2 #define BTINTEL_SAR_INC_PWR_SUPPORTED 0 #define CMD_WRITE_BOOT_PARAMS 0xfc0e @@ -2745,9 +2746,7 @@ static u8 btintel_classify_pkt_type(struct hci_dev *hdev, struct sk_buff *skb) * based on their connection handle value range. */ if (iso_capable(hdev) && hci_skb_pkt_type(skb) == HCI_ACLDATA_PKT) { - __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); - - if (hci_handle(handle) >= BTINTEL_ISODATA_HANDLE_BASE) + if (hci_acl_handle(skb) >= BTINTEL_ISODATA_HANDLE_BASE) return HCI_ISODATA_PKT; } @@ -3104,6 +3103,111 @@ static int btintel_set_mutual_sar(struct hci_dev *hdev, struct btintel_sar_inc_p return 0; } +/* btintel_send_sar_rev2_band - send DDC command for one Rev2 sub-band + * + * Each DDC 0x0311-0x0316 carries 2 bytes: [ChainA_value, ChainB_value]. + * cmd->len = 4 (2 id + 2 data) + * HCI total = 5 bytes (1 len + 4) + */ +static int btintel_send_sar_rev2_band(struct hci_dev *hdev, + struct btintel_cp_ddc_write *cmd, + u16 id, u8 chain_a, u8 chain_b) +{ + cmd->len = 4; + cmd->id = cpu_to_le16(id); + cmd->data[0] = chain_a; + cmd->data[1] = chain_b; + return btintel_send_sar_ddc(hdev, cmd, 5); +} + +static int btintel_set_sar_rev2(struct hci_dev *hdev, + struct btintel_sar_rev2 *sar) +{ + struct btintel_cp_ddc_write *cmd; + struct sk_buff *skb; + u8 buffer[64]; + u8 enable; + int ret; + + cmd = (void *)buffer; + + /* DDC 0x019e: enable/disable increased power mode SAR (1 byte) */ + cmd->len = 3; + cmd->id = cpu_to_le16(0x019e); + cmd->data[0] = (sar->inc_power_mode == BTINTEL_SAR_INC_PWR_SUPPORTED) ? + 0x01 : 0x00; + ret = btintel_send_sar_ddc(hdev, cmd, 4); + if (ret) + return ret; + + /* DDC 0x0311-0x0316: per sub-band ChainA + ChainB limits */ + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0311, + sar->chain_a.subband_2g4, + sar->chain_b.subband_2g4); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0312, + sar->chain_a.subband_5g2, + sar->chain_b.subband_5g2); + if (ret) + return ret; + + /* 0x0313 and 0x0314 both carry the 5G8/5G9 value */ + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0313, + sar->chain_a.subband_5g8_5g9, + sar->chain_b.subband_5g8_5g9); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0314, + sar->chain_a.subband_5g8_5g9, + sar->chain_b.subband_5g8_5g9); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0315, + sar->chain_a.subband_6g1, + sar->chain_b.subband_6g1); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0316, + sar->chain_a.subband_6g3, + sar->chain_b.subband_6g3); + if (ret) + return ret; + + /* Notify firmware that SAR initialisation is complete */ + enable = 0x01; + skb = __hci_cmd_sync(hdev, 0xfe25, sizeof(enable), &enable, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_warn(hdev, "Failed to send Intel SAR Rev2 Enable (%ld)", + PTR_ERR(skb)); + return PTR_ERR(skb); + } + + kfree_skb(skb); + return 0; +} + +static int btintel_sar_rev2_send_to_device(struct hci_dev *hdev, + struct btintel_sar_rev2 *sar, + struct intel_version_tlv *ver) +{ + u16 cnvi = ver->cnvi_top & 0xfff; + u16 cnvr = ver->cnvr_top & 0xfff; + + if (cnvi < BTINTEL_CNVI_BLAZARI || cnvr != BTINTEL_CNVR_WHP2) { + bt_dev_dbg(hdev, "BT SAR Rev2 not supported on this platform (cnvi=0x%x cnvr=0x%x)", + cnvi, cnvr); + return -EOPNOTSUPP; + } + + bt_dev_info(hdev, "Applying Bluetooth SAR Rev2"); + return btintel_set_sar_rev2(hdev, sar); +} + static int btintel_sar_send_to_device(struct hci_dev *hdev, struct btintel_sar_inc_pwr *sar, struct intel_version_tlv *ver) { @@ -3130,6 +3234,7 @@ static int btintel_acpi_set_sar(struct hci_dev *hdev, struct intel_version_tlv * { union acpi_object *bt_pkg, *buffer = NULL; struct btintel_sar_inc_pwr sar; + struct btintel_sar_rev2 sar_rev2; acpi_status status; u8 revision; int ret; @@ -3150,14 +3255,96 @@ static int btintel_acpi_set_sar(struct hci_dev *hdev, struct intel_version_tlv * goto error; } + if (buffer->package.elements[0].type != ACPI_TYPE_INTEGER) { + bt_dev_warn(hdev, "BT_SAR: unexpected ACPI type for revision field"); + ret = -EINVAL; + goto error; + } + revision = buffer->package.elements[0].integer.value; - if (revision > BTINTEL_SAR_INC_PWR) { + if (revision > BTINTEL_SAR_REV2) { bt_dev_dbg(hdev, "BT_SAR: revision: 0x%2.2x not supported", revision); ret = -EOPNOTSUPP; goto error; } + if (revision == BTINTEL_SAR_REV2 && bt_pkg->package.count == 13) { + /* Element layout: 0 = domain ID (BTINTEL_BT_DOMAIN, 0x12), + * 1 = bt_sar_bios (u32), 2 = inc_power_mode (u32), + * 3..12 = per-chain sub-band limits (u8 each). + */ + static const u64 rev2_max[13] = { + U8_MAX, /* domain ID */ + U32_MAX, U32_MAX, /* bt_sar_bios, inc_power_mode */ + U8_MAX, U8_MAX, U8_MAX, U8_MAX, U8_MAX, /* chain A */ + U8_MAX, U8_MAX, U8_MAX, U8_MAX, U8_MAX, /* chain B */ + }; + union acpi_object *e; + int i; + + for (i = 0; i < 13; i++) { + e = &bt_pkg->package.elements[i]; + if (e->type != ACPI_TYPE_INTEGER) { + bt_dev_warn(hdev, "BT SAR Rev2: unexpected ACPI type at element %d", + i); + ret = -EINVAL; + goto error; + } + if (e->integer.value > rev2_max[i]) { + bt_dev_warn(hdev, "BT SAR Rev2: element %d value 0x%llx out of range", + i, e->integer.value); + ret = -ERANGE; + goto error; + } + } + + memset(&sar_rev2, 0, sizeof(sar_rev2)); + sar_rev2.revision = revision; + sar_rev2.bt_sar_bios = bt_pkg->package.elements[1].integer.value; + + if (sar_rev2.bt_sar_bios != 1) { + bt_dev_warn(hdev, "Bluetooth SAR Rev2 is not enabled"); + ret = -EOPNOTSUPP; + goto error; + } + + sar_rev2.inc_power_mode = bt_pkg->package.elements[2].integer.value; + + sar_rev2.chain_a.subband_2g4 = bt_pkg->package.elements[3].integer.value; + sar_rev2.chain_a.subband_5g2 = bt_pkg->package.elements[4].integer.value; + sar_rev2.chain_a.subband_5g8_5g9 = bt_pkg->package.elements[5].integer.value; + sar_rev2.chain_a.subband_6g1 = bt_pkg->package.elements[6].integer.value; + sar_rev2.chain_a.subband_6g3 = bt_pkg->package.elements[7].integer.value; + + sar_rev2.chain_b.subband_2g4 = bt_pkg->package.elements[8].integer.value; + sar_rev2.chain_b.subband_5g2 = bt_pkg->package.elements[9].integer.value; + sar_rev2.chain_b.subband_5g8_5g9 = bt_pkg->package.elements[10].integer.value; + sar_rev2.chain_b.subband_6g1 = bt_pkg->package.elements[11].integer.value; + sar_rev2.chain_b.subband_6g3 = bt_pkg->package.elements[12].integer.value; + + bt_dev_dbg(hdev, "BT SAR Rev2: revision=%u bt_sar_bios=%u inc_power_mode=%u", + sar_rev2.revision, sar_rev2.bt_sar_bios, sar_rev2.inc_power_mode); + bt_dev_dbg(hdev, "BT SAR Rev2 Chain A: 2g4=%u 5g2=%u 5g8_5g9=%u 6g1=%u 6g3=%u", + sar_rev2.chain_a.subband_2g4, sar_rev2.chain_a.subband_5g2, + sar_rev2.chain_a.subband_5g8_5g9, sar_rev2.chain_a.subband_6g1, + sar_rev2.chain_a.subband_6g3); + bt_dev_dbg(hdev, "BT SAR Rev2 Chain B: 2g4=%u 5g2=%u 5g8_5g9=%u 6g1=%u 6g3=%u", + sar_rev2.chain_b.subband_2g4, sar_rev2.chain_b.subband_5g2, + sar_rev2.chain_b.subband_5g8_5g9, sar_rev2.chain_b.subband_6g1, + sar_rev2.chain_b.subband_6g3); + + ret = btintel_sar_rev2_send_to_device(hdev, &sar_rev2, ver); + goto error; + } + + if (revision == BTINTEL_SAR_REV2) { + bt_dev_warn(hdev, "BT SAR Rev2: unexpected ACPI package count %d (expected 13)", + bt_pkg->package.count); + ret = -EINVAL; + goto error; + } + memset(&sar, 0, sizeof(sar)); if (revision == BTINTEL_SAR_LEGACY && bt_pkg->package.count == 8) { @@ -3804,8 +3991,7 @@ int btintel_recv_event(struct hci_dev *hdev, struct sk_buff *skb) struct hci_event_hdr *hdr = (void *)skb->data; const char diagnostics_hdr[] = { 0x87, 0x80, 0x03 }; - if (skb->len > HCI_EVENT_HDR_SIZE && hdr->evt == 0xff && - hdr->plen > 0) { + if (skb->len > HCI_EVENT_HDR_SIZE && hdr->evt == 0xff) { const void *ptr = skb->data + HCI_EVENT_HDR_SIZE + 1; unsigned int len = skb->len - HCI_EVENT_HDR_SIZE - 1; @@ -3834,7 +4020,7 @@ int btintel_recv_event(struct hci_dev *hdev, struct sk_buff *skb) /* Handle all diagnostics events separately. May still call * hci_recv_frame. */ - if (len >= sizeof(diagnostics_hdr) && + if (len + 1 >= sizeof(diagnostics_hdr) && memcmp(&skb->data[2], diagnostics_hdr, sizeof(diagnostics_hdr)) == 0) { return btintel_diagnostics(hdev, skb); diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index 37d93abdd5a3..966ec1b02be2 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -65,6 +65,7 @@ struct intel_tlv { /* CNVR */ #define BTINTEL_CNVR_FMP2 0x910 +#define BTINTEL_CNVR_WHP2 0xA10 /* Whale Peak2 - Panther Lake */ #define BTINTEL_IMG_BOOTLOADER 0x01 /* Bootloader image */ #define BTINTEL_IMG_IML 0x02 /* Intermediate image */ @@ -204,6 +205,23 @@ struct btintel_sar_inc_pwr { u8 le_lr; }; +/* Bluetooth SAR feature (BRDS), Revision 2 - per-chain sub-band power limits */ +struct btintel_sar_band_limits { + u8 subband_2g4; + u8 subband_5g2; + u8 subband_5g8_5g9; + u8 subband_6g1; + u8 subband_6g3; +}; + +struct btintel_sar_rev2 { + u8 revision; + u32 bt_sar_bios; /* 1: BIOS-managed SAR enabled */ + u32 inc_power_mode; /* 0: supported, 1: disabled */ + struct btintel_sar_band_limits chain_a; + struct btintel_sar_band_limits chain_b; +}; + #define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8)) #define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16)) #define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 2b7231be5973..005c77a4f5eb 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1446,72 +1446,134 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data) return err; } +/* Queue a coredump dump_traces() pass. + * + * Returns true if a new coredump was queued, false if one was already + * in-flight (the BTINTEL_PCIE_COREDUMP_INPROGRESS bit serves as the + * single-writer guard for the @coredump_work item) or the workqueue is + * disabled (reset / remove in progress). + * + * Always queue this AFTER any companion event-reader work (hwexp / + * fwtrigger) so that, on the ordered @dump_workqueue, the event reader + * runs first and populates dmp_hdr.event_type / event_id before + * dump_traces consumes them. + */ +static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data, + u16 trigger_reason) +{ + if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) + return false; + + data->dmp_hdr.trigger_reason = trigger_reason; + + if (queue_work(data->dump_workqueue, &data->coredump_work)) + return true; + + /* Workqueue is disabled (reset/remove drained it). Release the + * guard so a later trigger, after re-probe, can succeed. + */ + clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); + return false; +} + static void btintel_pcie_msix_fw_trigger_handler(struct btintel_pcie_data *data) { bt_dev_dbg(data->hdev, "Received firmware smart trigger cause"); - if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) + /* Per-work guard: deduplicate concurrent FW-trigger interrupts. + * Cleared at the tail of btintel_pcie_fwtrigger_worker(). + */ + if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, + &data->flags)) return; - /* Trigger device core dump when there is FW assert */ - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; + if (!queue_work(data->dump_workqueue, &data->fwtrigger_work)) { + clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); + return; + } - queue_work(data->coredump_workqueue, &data->coredump_work); + /* Queue coredump after the fwtrigger event reader so dmp_hdr.event_* + * is populated before dump_traces consumes it. + */ + btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); } static void btintel_pcie_msix_hw_exp_handler(struct btintel_pcie_data *data) { bt_dev_err(data->hdev, "Received hw exception interrupt"); + /* CORE_HALTED is the single-writer guard for this handler. It is + * set once on first HW exception and cleared only by re-probe + * (data is reallocated), so it also serializes hwexp_work + * scheduling without needing a separate bit. + */ if (test_and_set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags)) return; - if (test_and_set_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags)) - return; + /* Queue companion coredump first so it is appended after hwexp_work + * on the ordered @dump_workqueue (preserves the original + * coredump-then-hwexp ordering). + */ + btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); - /* Trigger device core dump when there is HW exception */ - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; - - queue_work(data->coredump_workqueue, &data->coredump_work); + queue_work(data->dump_workqueue, &data->hwexp_work); } static void btintel_pcie_coredump_worker(struct work_struct *work) { struct btintel_pcie_data *data = container_of(work, struct btintel_pcie_data, coredump_work); - int err; /* hdev is NULL until setup_hdev() succeeds, and is cleared on * teardown after disable_work_sync() drains us; bail in that case. */ + if (!data->hdev) + goto out; + + btintel_pcie_dump_traces(data->hdev); +out: + /* Release guard last so a new trigger can run only after this + * pass has fully completed (including dev_coredumpv()). + */ + clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); +} + +static void btintel_pcie_hwexp_worker(struct work_struct *work) +{ + struct btintel_pcie_data *data = container_of(work, + struct btintel_pcie_data, hwexp_work); + if (!data->hdev) return; - if (test_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) { - err = btintel_pcie_dump_fwtrigger_event(data); - if (err) - bt_dev_warn(data->hdev, "failed to log fwtrigger event"); - clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); - } + /* Unlike usb products, controller will not send hardware exception + * event on exception. Instead controller writes the hardware event + * to device memory along with optional debug events, raises MSIX + * and halts. Driver shall read the exception event from device + * memory and passes it to the stack for further processing. + * + * Re-entry is gated by BTINTEL_PCIE_CORE_HALTED in the IRQ + * handler, which is only cleared by re-probe; no per-work bit + * is needed here. + */ + btintel_pcie_read_hwexp(data); +} - if (test_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) { - btintel_pcie_dump_traces(data->hdev); - clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); - } +static void btintel_pcie_fwtrigger_worker(struct work_struct *work) +{ + struct btintel_pcie_data *data = container_of(work, + struct btintel_pcie_data, fwtrigger_work); + int err; - if (test_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags)) { - /* Unlike usb products, controller will not send hardware - * exception event on exception. Instead controller writes the - * hardware event to device memory along with optional debug - * events, raises MSIX and halts. Driver shall read the - * exception event from device memory and passes it stack for - * further processing. - */ - btintel_pcie_read_hwexp(data); - clear_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags); - } + if (!data->hdev) + goto out; + + err = btintel_pcie_dump_fwtrigger_event(data); + if (err) + bt_dev_warn(data->hdev, "failed to log fwtrigger event"); +out: + /* Release guard last; matches set in fw_trigger handler. */ + clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); } static void btintel_pcie_rx_work(struct work_struct *work) @@ -2363,7 +2425,6 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev) INTEL_HW_VARIANT(ver_tlv.cnvi_bt)); err = -EINVAL; goto exit_error; - break; } data->dmp_hdr.cnvi_top = ver_tlv.cnvi_top; @@ -2488,8 +2549,6 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev, } } -static void btintel_pcie_reset(struct hci_dev *hdev); - static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data) { union acpi_object *obj, argv4; @@ -2650,20 +2709,22 @@ static void btintel_pcie_reset_work(struct work_struct *wk) btintel_pcie_synchronize_irqs(data); flush_work(&data->rx_work); - /* Drain any in-flight coredump and block new ones across reset. - * Safe from self-deadlock: coredump_work runs on a separate wq. + /* Drain any in-flight dump workers and block new ones across reset. + * Safe from self-deadlock: they all run on a separate wq. */ disable_work_sync(&data->coredump_work); + disable_work_sync(&data->hwexp_work); + disable_work_sync(&data->fwtrigger_work); bt_dev_dbg(data->hdev, "Release bluetooth interface"); /* Both reset paths follow the same contract: on success they * destroy 'data' via device_reprobe() (a fresh probe re-INIT_WORKs - * the coredump_work with disable count 0), so enable_work() must + * the dump workers with disable count 0), so enable_work() must * NOT be called on the success path. Only the FLR path can fail * with 'data' still alive, in which case we balance the - * disable_work_sync() above so a later successful reset is not - * permanently blocked. + * disable_work_sync() calls above so a later successful reset is + * not permanently blocked. * * pci_lock_rescan_remove() (held above) serializes against PCI * device addition/removal (hotplug), so no device can be added to @@ -2674,64 +2735,134 @@ static void btintel_pcie_reset_work(struct work_struct *wk) goto out; } - if (btintel_pcie_perform_flr(data)) + if (btintel_pcie_perform_flr(data)) { enable_work(&data->coredump_work); + enable_work(&data->hwexp_work); + enable_work(&data->fwtrigger_work); + } out: pci_dev_put(pdev); pci_unlock_rescan_remove(); } -static void btintel_pcie_reset(struct hci_dev *hdev) +/* Schedule a device reset of the requested type. + * + * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters + * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error + * path, etc.) so that: + * + * - dev_data->reset_type is written by exactly one caller (the + * thread that wins test_and_set_bit), eliminating the race where + * a second hw_error could clobber an already-scheduled reset's + * type; + * - the write happens AFTER the bit is set, so reset_work observes + * it through schedule_work()'s memory ordering; + * - losers return without touching reset_type or scheduling the + * work, so concurrent triggers are silently coalesced into the + * in-flight one (whose recovery will reinitialize the device + * regardless of the dropped trigger's variant). + * + * The bit is cleared only by .remove() / re-probe via fresh devm + * allocation, which is the intended one-shot semantics: a reset + * tears down and re-probes 'data', so there is no "in-flight" + * reset to follow up after device_reprobe() succeeds. + */ +static void btintel_pcie_request_reset(struct btintel_pcie_data *data, + enum btintel_pcie_reset_type type) { - struct btintel_pcie_data *data; - - data = hci_get_drvdata(hdev); - if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) return; if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) return; + data->reset_type = type; + pci_dev_get(data->pdev); schedule_work(&data->reset_work); } +static void btintel_pcie_hci_reset(struct hci_dev *hdev) +{ + struct btintel_pcie_data *data = hci_get_drvdata(hdev); + + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); +} + +static ssize_t vendor_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val; + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data = pci_get_drvdata(pdev); + + if (!data || !data->hdev) + return -ENODEV; + + if (kstrtouint(buf, 10, &val) || val != 0) { + bt_dev_warn(data->hdev, "PLDR rejected: invalid input"); + return -EINVAL; + } + + bt_dev_info(data->hdev, "PLDR triggered via sysfs"); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_PLDR); + + return count; +} + +static ssize_t vendor_reset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0 - PLDR\n"); +} + +static DEVICE_ATTR_RW(vendor_reset); + +static struct attribute *btintel_pcie_attrs[] = { + &dev_attr_vendor_reset.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(btintel_pcie); + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) { - struct btintel_pcie_dev_recovery *data; + struct btintel_pcie_dev_recovery *rec; struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); struct pci_dev *pdev = dev_data->pdev; + enum btintel_pcie_reset_type type; time64_t retry_window; + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) + return; + btintel_pcie_dump_debug_registers(hdev); - data = btintel_pcie_get_recovery(pdev, &hdev->dev); - if (!data) + rec = btintel_pcie_get_recovery(pdev, &hdev->dev); + if (!rec) return; - if (code == 0x13) - dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; - else - dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; + type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR + : BTINTEL_PCIE_IOSF_PRR_FLR; bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, - dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); - retry_window = ktime_get_boottime_seconds() - data->last_error; + type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); + retry_window = ktime_get_boottime_seconds() - rec->last_error; if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && - data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", - BTINTEL_PCIE_FLR_MAX_RETRY, data->count); + BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); bt_dev_dbg(hdev, "Boot time: %lld seconds", ktime_get_boottime_seconds()); bt_dev_dbg(hdev, "last error at: %lld seconds", - data->last_error); + rec->last_error); return; } btintel_pcie_inc_recovery_count(pdev, &hdev->dev); - btintel_pcie_reset(hdev); + btintel_pcie_request_reset(dev_data, type); } static bool btintel_pcie_wakeup(struct hci_dev *hdev) @@ -2821,7 +2952,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) hdev->hw_error = btintel_pcie_hw_error; hdev->set_diag = btintel_set_diag; hdev->set_bdaddr = btintel_set_bdaddr; - hdev->reset = btintel_pcie_reset; + hdev->reset = btintel_pcie_hci_reset; hdev->wakeup = btintel_pcie_wakeup; hdev->hci_drv = &btintel_pcie_hci_drv; @@ -2869,8 +3000,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev, if (!data->workqueue) return -ENOMEM; - data->coredump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0); - if (!data->coredump_workqueue) { + data->dump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0); + if (!data->dump_workqueue) { destroy_workqueue(data->workqueue); return -ENOMEM; } @@ -2879,6 +3010,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev, INIT_WORK(&data->rx_work, btintel_pcie_rx_work); INIT_WORK(&data->reset_work, btintel_pcie_reset_work); INIT_WORK(&data->coredump_work, btintel_pcie_coredump_worker); + INIT_WORK(&data->hwexp_work, btintel_pcie_hwexp_worker); + INIT_WORK(&data->fwtrigger_work, btintel_pcie_fwtrigger_worker); data->boot_stage_cache = 0x00; data->img_resp_cache = 0x00; @@ -2921,7 +3054,7 @@ static int btintel_pcie_probe(struct pci_dev *pdev, /* reset device before exit */ btintel_pcie_reset_bt(data); - destroy_workqueue(data->coredump_workqueue); + destroy_workqueue(data->dump_workqueue); pci_clear_master(pdev); @@ -2940,12 +3073,14 @@ static void btintel_pcie_remove(struct pci_dev *pdev) return; } - /* Permanently block coredump triggers and drain the worker before - * tearing down. Must run before cancel_work_sync(&reset_work) so - * the disable counter stays >= 1 even after reset_work()'s + /* Permanently block all dump triggers and drain the workers before + * tearing down. Must run before disable_work_sync(&reset_work) so + * the disable counters stay >= 1 even after reset_work()'s * balanced enable_work() (counter 2 -> 1, never reaching 0). */ disable_work_sync(&data->coredump_work); + disable_work_sync(&data->hwexp_work); + disable_work_sync(&data->fwtrigger_work); /* Cancel pending reset work. Skip only when remove() is called from * within the reset work itself (PLDR device_reprobe path) to avoid @@ -2973,7 +3108,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) btintel_pcie_release_hdev(data); - destroy_workqueue(data->coredump_workqueue); + destroy_workqueue(data->dump_workqueue); destroy_workqueue(data->workqueue); btintel_pcie_free(data); @@ -2992,16 +3127,8 @@ static void btintel_pcie_coredump(struct device *dev) if (!data) return; - if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - return; - - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER; - /* queue_work() returns false if the work is disabled (reset or - * remove in progress); clear the in-progress bit so a later - * trigger can succeed once the work is re-enabled. - */ - if (!queue_work(data->coredump_workqueue, &data->coredump_work)) - clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); + btintel_pcie_queue_coredump(data, + BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER); } #endif @@ -3113,8 +3240,7 @@ static int btintel_pcie_resume(struct device *dev) if (data->pm_sx_event == PM_EVENT_FREEZE || data->pm_sx_event == PM_EVENT_HIBERNATE) { set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); - data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; - btintel_pcie_reset(data->hdev); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); return 0; } @@ -3138,14 +3264,10 @@ static int btintel_pcie_resume(struct device *dev) if (btintel_pcie_in_error(data) || btintel_pcie_in_device_halt(data)) { bt_dev_err(data->hdev, "Controller in error state for D0 entry"); - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, - &data->flags)) { - data->dmp_hdr.trigger_reason = - BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; - queue_work(data->coredump_workqueue, &data->coredump_work); - } + btintel_pcie_queue_coredump(data, + BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); - btintel_pcie_reset(data->hdev); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); } return err; } @@ -3165,6 +3287,7 @@ static struct pci_driver btintel_pcie_driver = { .probe = btintel_pcie_probe, .remove = btintel_pcie_remove, .driver.pm = pm_sleep_ptr(&btintel_pcie_pm_ops), + .dev_groups = btintel_pcie_groups, #ifdef CONFIG_DEV_COREDUMP .driver.coredump = btintel_pcie_coredump #endif diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h index 7caee093e316..749369b24031 100644 --- a/drivers/bluetooth/btintel_pcie.h +++ b/drivers/bluetooth/btintel_pcie.h @@ -118,7 +118,6 @@ enum { enum { BTINTEL_PCIE_CORE_HALTED, - BTINTEL_PCIE_HWEXP_INPROGRESS, BTINTEL_PCIE_COREDUMP_INPROGRESS, BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, BTINTEL_PCIE_RECOVERY_IN_PROGRESS, @@ -466,8 +465,11 @@ struct btintel_pcie_dump_header { * @workqueue: workqueue for RX work * @rx_skb_q: SKB queue for RX packet * @rx_work: RX work struct to process the RX packet in @rx_skb_q - * @coredump_workqueue: dedicated workqueue for coredump collection - * @coredump_work: work struct for coredump trace collection + * @dump_workqueue: dedicated ordered workqueue serializing the coredump, + * hardware exception, and firmware-trigger dump workers + * @coredump_work: work struct for DRAM trace coredump collection + * @hwexp_work: work struct for hardware exception event read + * @fwtrigger_work: work struct for firmware-triggered diagnostic event read * @dma_pool: DMA pool for descriptors, index array and ci * @dma_p_addr: DMA address for pool * @dma_v_addr: address of pool @@ -516,8 +518,10 @@ struct btintel_pcie_data { struct work_struct rx_work; struct work_struct reset_work; - struct workqueue_struct *coredump_workqueue; + struct workqueue_struct *dump_workqueue; struct work_struct coredump_work; + struct work_struct hwexp_work; + struct work_struct fwtrigger_work; struct dma_pool *dma_pool; dma_addr_t dma_p_addr; diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c index d6f0ad0b4b6e..e25930351f64 100644 --- a/drivers/bluetooth/btmrvl_main.c +++ b/drivers/bluetooth/btmrvl_main.c @@ -43,10 +43,17 @@ bool btmrvl_check_evtpkt(struct btmrvl_private *priv, struct sk_buff *skb) { struct hci_event_hdr *hdr = (void *) skb->data; + if (skb->len < sizeof(*hdr)) + return true; + if (hdr->evt == HCI_EV_CMD_COMPLETE) { struct hci_ev_cmd_complete *ec; u16 opcode; + if (hdr->plen < sizeof(*ec) || + skb->len < HCI_EVENT_HDR_SIZE + sizeof(*ec)) + return true; + ec = (void *) (skb->data + HCI_EVENT_HDR_SIZE); opcode = __le16_to_cpu(ec->opcode); @@ -74,6 +81,9 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) struct btmrvl_event *event; int ret = 0; + if (skb->len <= offsetof(typeof(*event), data[0])) + return -EINVAL; + event = (struct btmrvl_event *) skb->data; if (event->ec != 0xff) { BT_DBG("Not Marvell Event=%x", event->ec); @@ -83,6 +93,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) switch (event->data[0]) { case BT_EVENT_AUTO_SLEEP_MODE: + if (skb->len <= offsetof(typeof(*event), data[2])) + return -EINVAL; if (!event->data[2]) { if (event->data[1] == BT_PS_ENABLE) adapter->psmode = 1; @@ -96,6 +108,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_HOST_SLEEP_CONFIG: + if (skb->len <= offsetof(typeof(*event), data[3])) + return -EINVAL; if (!event->data[3]) BT_DBG("gpio=%x, gap=%x", event->data[1], event->data[2]); @@ -104,6 +118,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_HOST_SLEEP_ENABLE: + if (skb->len <= offsetof(typeof(*event), data[1])) + return -EINVAL; if (!event->data[1]) { adapter->hs_state = HS_ACTIVATED; if (adapter->psmode) @@ -116,6 +132,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_MODULE_CFG_REQ: + if (skb->len <= offsetof(typeof(*event), data[2])) + return -EINVAL; if (priv->btmrvl_dev.sendcmdflag && event->data[1] == MODULE_BRINGUP_REQ) { BT_DBG("EVENT:%s", @@ -133,6 +151,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_POWER_STATE: + if (skb->len <= offsetof(typeof(*event), data[1])) + return -EINVAL; if (event->data[1] == BT_PS_SLEEP) adapter->ps_state = PS_SLEEP; BT_DBG("EVENT:%s", diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c index 93932a0d8625..b91fc63bc9fe 100644 --- a/drivers/bluetooth/btmrvl_sdio.c +++ b/drivers/bluetooth/btmrvl_sdio.c @@ -799,7 +799,7 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv) skb_pull(skb, SDIO_HEADER_LEN); if (btmrvl_process_event(priv, skb)) - hci_recv_frame(hdev, skb); + kfree_skb(skb); hdev->stat.byte_rx += buf_len; break; diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index c6f80c419e90..4e1012e90979 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1480,6 +1480,9 @@ static void btmtksdio_remove(struct sdio_func *func) if (test_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state)) btmtksdio_close(hdev); + if (bdev->data->pm_runtime_supported) + pm_runtime_dont_use_autosuspend(bdev->dev); + /* Be consistent the state in btmtksdio_probe */ pm_runtime_get_noresume(bdev->dev); diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 6a1cffe08d5f..81cdd8da5636 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -211,6 +213,7 @@ struct btnxpuart_dev { struct ps_data psdata; struct btnxpuart_data *nxp_data; + struct pwrseq_desc *pwrseq; struct reset_control *pdn; struct hci_uart hu; }; @@ -1331,19 +1334,7 @@ static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev) static int nxp_set_ind_reset(struct hci_dev *hdev, void *data) { - static const u8 ir_hw_err[] = { HCI_EV_HARDWARE_ERROR, - 0x01, BTNXPUART_IR_HW_ERR }; - struct sk_buff *skb; - - skb = bt_skb_alloc(3, GFP_ATOMIC); - if (!skb) - return -ENOMEM; - - hci_skb_pkt_type(skb) = HCI_EVENT_PKT; - skb_put_data(skb, ir_hw_err, 3); - - /* Inject Hardware Error to upper stack */ - return hci_recv_frame(hdev, skb); + return __hci_reset_dev(hdev, BTNXPUART_IR_HW_ERR); } /* Firmware dump */ @@ -1872,11 +1863,26 @@ static int nxp_serdev_probe(struct serdev_device *serdev) return err; } + if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + struct pwrseq_desc *pwrseq; + + pwrseq = pwrseq_get(&serdev->ctrl->dev, "uart"); + if (IS_ERR(pwrseq)) + return dev_err_probe(&serdev->dev, PTR_ERR(pwrseq), + "failed to get pwrseq\n"); + + nxpdev->pwrseq = pwrseq; + err = pwrseq_power_on(pwrseq); + if (err) + goto err_pwrseq_put; + } + /* Initialize and register HCI device */ hdev = hci_alloc_dev(); if (!hdev) { dev_err(&serdev->dev, "Can't allocate HCI device\n"); - return -ENOMEM; + err = -ENOMEM; + goto err_pwrseq_put; } reset_control_deassert(nxpdev->pdn); @@ -1907,23 +1913,31 @@ static int nxp_serdev_probe(struct serdev_device *serdev) if (bacmp(&ba, BDADDR_ANY)) hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); - if (hci_register_dev(hdev) < 0) { + err = hci_register_dev(hdev); + if (err < 0) { dev_err(&serdev->dev, "Can't register HCI device\n"); goto probe_fail; } - if (ps_setup(hdev)) - goto probe_fail; + if (ps_setup(hdev)) { + err = -ENODEV; + goto probe_fail_unregister; + } hci_devcd_register(hdev, nxp_coredump, nxp_coredump_hdr, nxp_coredump_notify); return 0; +probe_fail_unregister: + hci_unregister_dev(hdev); probe_fail: reset_control_assert(nxpdev->pdn); hci_free_dev(hdev); - return -ENODEV; +err_pwrseq_put: + if (nxpdev->pwrseq) + pwrseq_put(nxpdev->pwrseq); + return err; } static void nxp_serdev_remove(struct serdev_device *serdev) @@ -1950,6 +1964,8 @@ static void nxp_serdev_remove(struct serdev_device *serdev) ps_cleanup(nxpdev); hci_unregister_dev(hdev); reset_control_assert(nxpdev->pdn); + if (nxpdev->pwrseq) + pwrseq_put(nxpdev->pwrseq); hci_free_dev(hdev); } diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 10c496eaea2c..22b08ab05b82 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -190,25 +190,6 @@ static int qca_send_patch_config_cmd(struct hci_dev *hdev) return err; } -static int qca_send_reset(struct hci_dev *hdev) -{ - struct sk_buff *skb; - int err; - - bt_dev_dbg(hdev, "QCA HCI_RESET"); - - skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - bt_dev_err(hdev, "QCA Reset failed (%d)", err); - return err; - } - - kfree_skb(skb); - - return 0; -} - static int qca_read_fw_board_id(struct hci_dev *hdev, u16 *bid) { u8 cmd; @@ -990,11 +971,12 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, } /* Perform HCI reset */ - err = qca_send_reset(hdev); + err = __hci_reset_sync(hdev); if (err < 0) { bt_dev_err(hdev, "QCA Failed to run HCI_RESET (%d)", err); return err; } + bt_dev_dbg(hdev, "QCA HCI_RESET succeed"); switch (soc_type) { case QCA_WCN3991: @@ -1029,8 +1011,7 @@ int qca_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) baswap(&bdaddr_swapped, bdaddr); skb = __hci_cmd_sync_ev(hdev, EDL_WRITE_BD_ADDR_OPCODE, 6, - &bdaddr_swapped, HCI_EV_VENDOR, - HCI_INIT_TIMEOUT); + &bdaddr_swapped, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA Change address cmd failed (%d)", err); diff --git a/drivers/bluetooth/btrsi.c b/drivers/bluetooth/btrsi.c index 59ad0b9b14c3..3f802b7c83f7 100644 --- a/drivers/bluetooth/btrsi.c +++ b/drivers/bluetooth/btrsi.c @@ -107,7 +107,6 @@ static int rsi_hci_attach(void *priv, struct rsi_proto_ops *ops) return -ENOMEM; h_adapter->priv = priv; - ops->set_bt_context(priv, h_adapter); h_adapter->proto_ops = ops; hdev = hci_alloc_dev(); @@ -136,6 +135,8 @@ static int rsi_hci_attach(void *priv, struct rsi_proto_ops *ops) goto err; } + ops->set_bt_context(priv, h_adapter); + return 0; err: h_adapter->hdev = NULL; diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 184e95c1625e..be82bbbc1b5c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -297,6 +297,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3501), .driver_info = BTUSB_QCA_ROME | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3503), .driver_info = BTUSB_QCA_ROME | + BTUSB_WIDEBAND_SPEECH }, /* QCA WCN6855 chipset */ { USB_DEVICE(0x0489, 0xe0c7), .driver_info = BTUSB_QCA_WCN6855 | @@ -679,6 +681,8 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x13d3, 0x3606), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, /* MediaTek MT7902 Bluetooth devices */ + { USB_DEVICE(0x0489, 0xe156), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0e8d, 0x1ede), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3579), .driver_info = BTUSB_MEDIATEK | @@ -796,6 +800,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | @@ -850,6 +856,12 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x37ad, 0x0600), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + /* Additional Realtek 8761CU Bluetooth devices */ + { USB_DEVICE(0x0b05, 0x1bef), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0b05, 0x1d70), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, + /* Additional Realtek 8821AE Bluetooth devices */ { USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK }, { USB_DEVICE(0x13d3, 0x3414), .driver_info = BTUSB_REALTEK }, @@ -882,6 +894,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0bda, 0xc123), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x1357, 0xc123), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0cb5, 0xc547), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, @@ -937,6 +951,10 @@ struct qca_dump_info { u16 ram_dump_seqno; }; +struct btqca_data { + struct qca_dump_info qca_dump; +}; + #define BTUSB_MAX_ISOC_FRAMES 10 #define BTUSB_INTR_RUNNING 0 @@ -1010,6 +1028,7 @@ struct btusb_data { bool usb_alt6_packet_flow; int isoc_altsetting; int suspend_count; + const struct usb_device_id *match_id; int (*recv_event)(struct hci_dev *hdev, struct sk_buff *skb); int (*recv_acl)(struct hci_dev *hdev, struct sk_buff *skb); @@ -1022,8 +1041,6 @@ struct btusb_data { int (*disconnect)(struct hci_dev *hdev); int oob_wake_irq; /* irq for out-of-band wake-on-bt */ - - struct qca_dump_info qca_dump; }; static void btusb_reset(struct hci_dev *hdev) @@ -1235,14 +1252,16 @@ static inline void btusb_free_frags(struct btusb_data *data) spin_unlock_irqrestore(&data->rxlock, flags); } -static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) +static int btusb_recv_event(struct hci_dev *hdev, struct sk_buff *skb) { + struct btusb_data *data = hci_get_drvdata(hdev); + if (data->intr_interval) { /* Trigger dequeue immediately if an event is received */ schedule_delayed_work(&data->rx_work, 0); } - return data->recv_event(data->hdev, skb); + return data->recv_event(hdev, skb); } static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) @@ -1302,7 +1321,7 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) } /* Complete frame */ - btusb_recv_event(data, skb); + btusb_recv_event(data->hdev, skb); skb = NULL; } } @@ -1313,13 +1332,15 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) return err; } -static int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) +static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) { + struct btusb_data *data = hci_get_drvdata(hdev); + /* Only queue ACL packet if intr_interval is set as it means * force_poll_sync has been enabled. */ if (!data->intr_interval) - return data->recv_acl(data->hdev, skb); + return data->recv_acl(hdev, skb); skb_queue_tail(&data->acl_q, skb); schedule_delayed_work(&data->rx_work, data->intr_interval); @@ -1358,10 +1379,8 @@ static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) hci_skb_expect(skb) -= len; if (skb->len == HCI_ACL_HDR_SIZE) { - __le16 dlen = hci_acl_hdr(skb)->dlen; - /* Complete ACL header */ - hci_skb_expect(skb) = __le16_to_cpu(dlen); + hci_skb_expect(skb) = hci_acl_dlen(skb); if (skb_tailroom(skb) < hci_skb_expect(skb)) { kfree_skb(skb); @@ -1374,7 +1393,7 @@ static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) if (!hci_skb_expect(skb)) { /* Complete frame */ - btusb_recv_acl(data, skb); + btusb_recv_acl(data->hdev, skb); skb = NULL; } } @@ -2053,6 +2072,14 @@ static void btusb_stop_traffic(struct btusb_data *data) usb_kill_anchored_urbs(&data->ctrl_anchor); } +static void btusb_prepare_reset(struct hci_dev *hdev) +{ + struct btusb_data *data = hci_get_drvdata(hdev); + + btusb_stop_traffic(data); + usb_kill_anchored_urbs(&data->tx_anchor); +} + static int btusb_close(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); @@ -2783,7 +2810,7 @@ static int btusb_setup_realtek(struct hci_dev *hdev) static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb) { if (skb->len >= HCI_EVENT_HDR_SIZE + 1 && - skb->data[0] == HCI_VENDOR_PKT && + skb->data[0] == HCI_EV_VENDOR && skb->data[2] == RTK_SUB_EVENT_CODE_COREDUMP) { struct rtk_dev_coredump_hdr hdr = { .code = RTK_DEVCOREDUMP_CODE_MEMDUMP, @@ -2897,8 +2924,7 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) /* Release MediaTek ISO data interface */ btusb_mtk_release_iso_intf(hdev); - btusb_stop_traffic(data); - usb_kill_anchored_urbs(&data->tx_anchor); + btusb_prepare_reset(hdev); /* Toggle the hard reset line. The MediaTek device is going to * yank itself off the USB and then replug. The cleanup is handled @@ -3072,14 +3098,15 @@ static int btusb_set_bdaddr_ath3012(struct hci_dev *hdev, static int btusb_set_bdaddr_wcn6855(struct hci_dev *hdev, const bdaddr_t *bdaddr) { + bdaddr_t bdaddr_swapped; struct sk_buff *skb; - u8 buf[6]; long ret; - memcpy(buf, bdaddr, sizeof(bdaddr_t)); + baswap(&bdaddr_swapped, bdaddr); - skb = __hci_cmd_sync_ev(hdev, 0xfc14, sizeof(buf), buf, - HCI_EV_CMD_COMPLETE, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync_ev(hdev, 0xfc14, sizeof(bdaddr_swapped), + &bdaddr_swapped, HCI_EV_CMD_COMPLETE, + HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { ret = PTR_ERR(skb); bt_dev_err(hdev, "Change address command failed (%ld)", ret); @@ -3115,14 +3142,15 @@ struct qca_dump_hdr { static void btusb_dump_hdr_qca(struct hci_dev *hdev, struct sk_buff *skb) { char buf[128]; - struct btusb_data *btdata = hci_get_drvdata(hdev); + struct btqca_data *btqca_data = hci_get_priv(hdev); + struct qca_dump_info *qca_dump_ptr = &btqca_data->qca_dump; snprintf(buf, sizeof(buf), "Controller Name: 0x%x\n", - btdata->qca_dump.controller_id); + qca_dump_ptr->controller_id); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Firmware Version: 0x%x\n", - btdata->qca_dump.fw_version); + qca_dump_ptr->fw_version); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Driver: %s\nVendor: qca\n", @@ -3130,7 +3158,7 @@ static void btusb_dump_hdr_qca(struct hci_dev *hdev, struct sk_buff *skb) skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "VID: 0x%x\nPID:0x%x\n", - btdata->qca_dump.id_vendor, btdata->qca_dump.id_product); + qca_dump_ptr->id_vendor, qca_dump_ptr->id_product); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Lmp Subversion: 0x%x\n", @@ -3159,6 +3187,8 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) struct qca_dump_hdr *dump_hdr; struct btusb_data *btdata = hci_get_drvdata(hdev); + struct btqca_data *btqca_data = hci_get_priv(hdev); + struct qca_dump_info *qca_dump_ptr = &btqca_data->qca_dump; struct usb_device *udev = btdata->udev; pkt_type = hci_skb_pkt_type(skb); @@ -3186,8 +3216,8 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) goto out; } - btdata->qca_dump.ram_dump_size = dump_size; - btdata->qca_dump.ram_dump_seqno = 0; + qca_dump_ptr->ram_dump_size = dump_size; + qca_dump_ptr->ram_dump_seqno = 0; skb_pull(skb, offsetof(struct qca_dump_hdr, data0)); @@ -3199,29 +3229,29 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) skb_pull(skb, offsetof(struct qca_dump_hdr, data)); } - if (!btdata->qca_dump.ram_dump_size) { + if (!qca_dump_ptr->ram_dump_size) { ret = -EINVAL; bt_dev_err(hdev, "memdump is not active"); goto out; } - if ((seqno > btdata->qca_dump.ram_dump_seqno + 1) && (seqno != QCA_LAST_SEQUENCE_NUM)) { - dump_size = QCA_MEMDUMP_PKT_SIZE * (seqno - btdata->qca_dump.ram_dump_seqno - 1); + if ((seqno > qca_dump_ptr->ram_dump_seqno + 1) && seqno != QCA_LAST_SEQUENCE_NUM) { + dump_size = QCA_MEMDUMP_PKT_SIZE * (seqno - qca_dump_ptr->ram_dump_seqno - 1); hci_devcd_append_pattern(hdev, 0x0, dump_size); bt_dev_err(hdev, "expected memdump seqno(%u) is not received(%u)\n", - btdata->qca_dump.ram_dump_seqno, seqno); - btdata->qca_dump.ram_dump_seqno = seqno; + qca_dump_ptr->ram_dump_seqno, seqno); + qca_dump_ptr->ram_dump_seqno = seqno; kfree_skb(skb); return ret; } hci_devcd_append(hdev, skb); - btdata->qca_dump.ram_dump_seqno++; + qca_dump_ptr->ram_dump_seqno++; if (seqno == QCA_LAST_SEQUENCE_NUM) { bt_dev_info(hdev, "memdump done: pkts(%u), total(%u)\n", - btdata->qca_dump.ram_dump_seqno, btdata->qca_dump.ram_dump_size); + qca_dump_ptr->ram_dump_seqno, qca_dump_ptr->ram_dump_size); hci_devcd_complete(hdev); goto out; @@ -3229,10 +3259,10 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) return ret; out: - if (btdata->qca_dump.ram_dump_size) + if (qca_dump_ptr->ram_dump_size) usb_enable_autosuspend(udev); - btdata->qca_dump.ram_dump_size = 0; - btdata->qca_dump.ram_dump_seqno = 0; + qca_dump_ptr->ram_dump_size = 0; + qca_dump_ptr->ram_dump_seqno = 0; clear_bit(BTUSB_HW_SSR_ACTIVE, &btdata->flags); if (ret < 0) @@ -3257,7 +3287,7 @@ static bool acl_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) goto out; event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); - if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) + if (!event_hdr || event_hdr->evt != HCI_EV_VENDOR) goto out; dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); @@ -3283,7 +3313,7 @@ static bool evt_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) return false; event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); - if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) + if (!event_hdr || event_hdr->evt != HCI_EV_VENDOR) goto out; dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); @@ -3695,8 +3725,12 @@ static int btusb_setup_qca(struct hci_dev *hdev) if (err) return err; - btdata->qca_dump.fw_version = le32_to_cpu(ver.patch_version); - btdata->qca_dump.controller_id = le32_to_cpu(ver.rom_version); + if (btdata->match_id->driver_info & BTUSB_QCA_WCN6855) { + struct btqca_data *btqca_data = hci_get_priv(hdev); + + btqca_data->qca_dump.fw_version = le32_to_cpu(ver.patch_version); + btqca_data->qca_dump.controller_id = le32_to_cpu(ver.rom_version); + } if (!(status & QCA_SYSCFG_UPDATED)) { err = btusb_setup_qca_load_nvm(hdev, &ver, info); @@ -3877,16 +3911,13 @@ static bool btusb_wakeup(struct hci_dev *hdev) static int btusb_shutdown_qca(struct hci_dev *hdev) { - struct sk_buff *skb; + int err; - skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { + err = __hci_reset_sync(hdev); + if (err) bt_dev_err(hdev, "HCI reset during shutdown failed"); - return PTR_ERR(skb); - } - kfree_skb(skb); - return 0; + return err; } static ssize_t force_poll_sync_read(struct file *file, char __user *user_buf, @@ -4070,7 +4101,7 @@ static int btusb_probe(struct usb_interface *intf, struct btusb_data *data; struct hci_dev *hdev; unsigned ifnum_base; - int err, priv_size; + int err, priv_size = 0; BT_DBG("intf %p id %p", intf, id); @@ -4089,7 +4120,7 @@ static int btusb_probe(struct usb_interface *intf, id = match; } - if (id->driver_info == BTUSB_IGNORE) + if (id->driver_info & BTUSB_IGNORE) return -ENODEV; if (id->driver_info & BTUSB_ATH3012) { @@ -4107,6 +4138,7 @@ static int btusb_probe(struct usb_interface *intf, if (!data) return -ENOMEM; + data->match_id = id; err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep, &data->bulk_tx_ep, &data->intr_ep, NULL); if (err) @@ -4140,8 +4172,6 @@ static int btusb_probe(struct usb_interface *intf, init_usb_anchor(&data->ctrl_anchor); spin_lock_init(&data->rxlock); - priv_size = 0; - data->recv_event = hci_recv_frame; data->recv_bulk = btusb_recv_bulk; @@ -4160,6 +4190,9 @@ static int btusb_probe(struct usb_interface *intf, } else if (id->driver_info & BTUSB_MEDIATEK) { /* Allocate extra space for Mediatek device */ priv_size += sizeof(struct btmtk_data); + } else if (id->driver_info & BTUSB_QCA_WCN6855) { + /* Allocate extra space for QCA WCN6855 device */ + priv_size += sizeof(struct btqca_data); } data->recv_acl = hci_recv_frame; @@ -4302,8 +4335,10 @@ static int btusb_probe(struct usb_interface *intf, } if (id->driver_info & BTUSB_QCA_WCN6855) { - data->qca_dump.id_vendor = id->idVendor; - data->qca_dump.id_product = id->idProduct; + struct btqca_data *btqca_data = hci_get_priv(hdev); + + btqca_data->qca_dump.id_vendor = id->idVendor; + btqca_data->qca_dump.id_product = id->idProduct; data->recv_event = btusb_recv_evt_qca; data->recv_acl = btusb_recv_acl_qca; hci_devcd_register(hdev, btusb_coredump_qca, btusb_dump_hdr_qca, NULL); diff --git a/drivers/bluetooth/hci_aml.c b/drivers/bluetooth/hci_aml.c index 959d9e67b669..067fbf278b44 100644 --- a/drivers/bluetooth/hci_aml.c +++ b/drivers/bluetooth/hci_aml.c @@ -247,7 +247,7 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name) struct hci_uart *hu = hci_get_drvdata(hdev); struct aml_serdev *amldev = serdev_device_get_drvdata(hu->serdev); const struct firmware *firmware = NULL; - struct aml_fw_len *fw_len = NULL; + const struct aml_fw_len *fw_len = NULL; u8 *iccm_start = NULL, *dccm_start = NULL; u32 iccm_len, dccm_len; u32 value = 0; @@ -281,7 +281,21 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name) goto exit; } - fw_len = (struct aml_fw_len *)firmware->data; + if (firmware->size < sizeof(*fw_len)) { + bt_dev_err(hdev, "Firmware is too small for its header"); + ret = -EINVAL; + goto exit; + } + + fw_len = (const struct aml_fw_len *)firmware->data; + if (fw_len->iccm_len < amldev->aml_dev_data->iccm_offset || + fw_len->iccm_len > firmware->size - sizeof(*fw_len) || + fw_len->dccm_len > firmware->size - sizeof(*fw_len) - + fw_len->iccm_len) { + bt_dev_err(hdev, "Invalid firmware segment lengths"); + ret = -EINVAL; + goto exit; + } /* Download ICCM */ iccm_start = (u8 *)(firmware->data) + sizeof(struct aml_fw_len) diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c index 1a4fc3882fd2..01da3fecb536 100644 --- a/drivers/bluetooth/hci_bcm.c +++ b/drivers/bluetooth/hci_bcm.c @@ -1314,174 +1314,174 @@ static struct bcm_device_data bcm43430_device_data = { }; static const struct acpi_device_id bcm_acpi_match[] = { - { "BCM2E00" }, - { "BCM2E01" }, - { "BCM2E02" }, - { "BCM2E03" }, - { "BCM2E04" }, - { "BCM2E05" }, - { "BCM2E06" }, - { "BCM2E07" }, - { "BCM2E08" }, - { "BCM2E09" }, - { "BCM2E0A" }, - { "BCM2E0B" }, - { "BCM2E0C" }, - { "BCM2E0D" }, - { "BCM2E0E" }, - { "BCM2E0F" }, - { "BCM2E10" }, - { "BCM2E11" }, - { "BCM2E12" }, - { "BCM2E13" }, - { "BCM2E14" }, - { "BCM2E15" }, - { "BCM2E16" }, - { "BCM2E17" }, - { "BCM2E18" }, - { "BCM2E19" }, - { "BCM2E1A" }, - { "BCM2E1B" }, - { "BCM2E1C" }, - { "BCM2E1D" }, - { "BCM2E1F" }, - { "BCM2E20" }, - { "BCM2E21" }, - { "BCM2E22" }, - { "BCM2E23" }, - { "BCM2E24" }, - { "BCM2E25" }, - { "BCM2E26" }, - { "BCM2E27" }, - { "BCM2E28" }, - { "BCM2E29" }, - { "BCM2E2A" }, - { "BCM2E2B" }, - { "BCM2E2C" }, - { "BCM2E2D" }, - { "BCM2E2E" }, - { "BCM2E2F" }, - { "BCM2E30" }, - { "BCM2E31" }, - { "BCM2E32" }, - { "BCM2E33" }, - { "BCM2E34" }, - { "BCM2E35" }, - { "BCM2E36" }, - { "BCM2E37" }, - { "BCM2E38" }, - { "BCM2E39" }, - { "BCM2E3A" }, - { "BCM2E3B" }, - { "BCM2E3C" }, - { "BCM2E3D" }, - { "BCM2E3E" }, - { "BCM2E3F" }, - { "BCM2E40" }, - { "BCM2E41" }, - { "BCM2E42" }, - { "BCM2E43" }, - { "BCM2E44" }, - { "BCM2E45" }, - { "BCM2E46" }, - { "BCM2E47" }, - { "BCM2E48" }, - { "BCM2E49" }, - { "BCM2E4A" }, - { "BCM2E4B" }, - { "BCM2E4C" }, - { "BCM2E4D" }, - { "BCM2E4E" }, - { "BCM2E4F" }, - { "BCM2E50" }, - { "BCM2E51" }, - { "BCM2E52" }, - { "BCM2E53" }, - { "BCM2E54" }, - { "BCM2E55" }, - { "BCM2E56" }, - { "BCM2E57" }, - { "BCM2E58" }, - { "BCM2E59" }, - { "BCM2E5A" }, - { "BCM2E5B" }, - { "BCM2E5C" }, - { "BCM2E5D" }, - { "BCM2E5E" }, - { "BCM2E5F" }, - { "BCM2E60" }, - { "BCM2E61" }, - { "BCM2E62" }, - { "BCM2E63" }, - { "BCM2E64" }, - { "BCM2E65" }, - { "BCM2E66" }, - { "BCM2E67" }, - { "BCM2E68" }, - { "BCM2E69" }, - { "BCM2E6B" }, - { "BCM2E6D" }, - { "BCM2E6E" }, - { "BCM2E6F" }, - { "BCM2E70" }, - { "BCM2E71" }, - { "BCM2E72" }, - { "BCM2E73" }, - { "BCM2E74", (long)&bcm43430_device_data }, - { "BCM2E75", (long)&bcm43430_device_data }, - { "BCM2E76" }, - { "BCM2E77" }, - { "BCM2E78" }, - { "BCM2E79" }, - { "BCM2E7A" }, - { "BCM2E7B", (long)&bcm43430_device_data }, - { "BCM2E7C" }, - { "BCM2E7D" }, - { "BCM2E7E" }, - { "BCM2E7F" }, - { "BCM2E80", (long)&bcm43430_device_data }, - { "BCM2E81" }, - { "BCM2E82" }, - { "BCM2E83" }, - { "BCM2E84" }, - { "BCM2E85" }, - { "BCM2E86" }, - { "BCM2E87" }, - { "BCM2E88" }, - { "BCM2E89", (long)&bcm43430_device_data }, - { "BCM2E8A" }, - { "BCM2E8B" }, - { "BCM2E8C" }, - { "BCM2E8D" }, - { "BCM2E8E" }, - { "BCM2E90" }, - { "BCM2E92" }, - { "BCM2E93" }, - { "BCM2E94", (long)&bcm43430_device_data }, - { "BCM2E95" }, - { "BCM2E96" }, - { "BCM2E97" }, - { "BCM2E98" }, - { "BCM2E99", (long)&bcm43430_device_data }, - { "BCM2E9A" }, - { "BCM2E9B", (long)&bcm43430_device_data }, - { "BCM2E9C" }, - { "BCM2E9D" }, - { "BCM2E9F", (long)&bcm43430_device_data }, - { "BCM2EA0" }, - { "BCM2EA1" }, - { "BCM2EA2", (long)&bcm43430_device_data }, - { "BCM2EA3", (long)&bcm43430_device_data }, - { "BCM2EA4", (long)&bcm43430_device_data }, /* bcm43455 */ - { "BCM2EA5" }, - { "BCM2EA6" }, - { "BCM2EA7" }, - { "BCM2EA8" }, - { "BCM2EA9" }, - { "BCM2EAA", (long)&bcm43430_device_data }, - { "BCM2EAB", (long)&bcm43430_device_data }, - { "BCM2EAC", (long)&bcm43430_device_data }, - { }, + { .id = "BCM2E00" }, + { .id = "BCM2E01" }, + { .id = "BCM2E02" }, + { .id = "BCM2E03" }, + { .id = "BCM2E04" }, + { .id = "BCM2E05" }, + { .id = "BCM2E06" }, + { .id = "BCM2E07" }, + { .id = "BCM2E08" }, + { .id = "BCM2E09" }, + { .id = "BCM2E0A" }, + { .id = "BCM2E0B" }, + { .id = "BCM2E0C" }, + { .id = "BCM2E0D" }, + { .id = "BCM2E0E" }, + { .id = "BCM2E0F" }, + { .id = "BCM2E10" }, + { .id = "BCM2E11" }, + { .id = "BCM2E12" }, + { .id = "BCM2E13" }, + { .id = "BCM2E14" }, + { .id = "BCM2E15" }, + { .id = "BCM2E16" }, + { .id = "BCM2E17" }, + { .id = "BCM2E18" }, + { .id = "BCM2E19" }, + { .id = "BCM2E1A" }, + { .id = "BCM2E1B" }, + { .id = "BCM2E1C" }, + { .id = "BCM2E1D" }, + { .id = "BCM2E1F" }, + { .id = "BCM2E20" }, + { .id = "BCM2E21" }, + { .id = "BCM2E22" }, + { .id = "BCM2E23" }, + { .id = "BCM2E24" }, + { .id = "BCM2E25" }, + { .id = "BCM2E26" }, + { .id = "BCM2E27" }, + { .id = "BCM2E28" }, + { .id = "BCM2E29" }, + { .id = "BCM2E2A" }, + { .id = "BCM2E2B" }, + { .id = "BCM2E2C" }, + { .id = "BCM2E2D" }, + { .id = "BCM2E2E" }, + { .id = "BCM2E2F" }, + { .id = "BCM2E30" }, + { .id = "BCM2E31" }, + { .id = "BCM2E32" }, + { .id = "BCM2E33" }, + { .id = "BCM2E34" }, + { .id = "BCM2E35" }, + { .id = "BCM2E36" }, + { .id = "BCM2E37" }, + { .id = "BCM2E38" }, + { .id = "BCM2E39" }, + { .id = "BCM2E3A" }, + { .id = "BCM2E3B" }, + { .id = "BCM2E3C" }, + { .id = "BCM2E3D" }, + { .id = "BCM2E3E" }, + { .id = "BCM2E3F" }, + { .id = "BCM2E40" }, + { .id = "BCM2E41" }, + { .id = "BCM2E42" }, + { .id = "BCM2E43" }, + { .id = "BCM2E44" }, + { .id = "BCM2E45" }, + { .id = "BCM2E46" }, + { .id = "BCM2E47" }, + { .id = "BCM2E48" }, + { .id = "BCM2E49" }, + { .id = "BCM2E4A" }, + { .id = "BCM2E4B" }, + { .id = "BCM2E4C" }, + { .id = "BCM2E4D" }, + { .id = "BCM2E4E" }, + { .id = "BCM2E4F" }, + { .id = "BCM2E50" }, + { .id = "BCM2E51" }, + { .id = "BCM2E52" }, + { .id = "BCM2E53" }, + { .id = "BCM2E54" }, + { .id = "BCM2E55" }, + { .id = "BCM2E56" }, + { .id = "BCM2E57" }, + { .id = "BCM2E58" }, + { .id = "BCM2E59" }, + { .id = "BCM2E5A" }, + { .id = "BCM2E5B" }, + { .id = "BCM2E5C" }, + { .id = "BCM2E5D" }, + { .id = "BCM2E5E" }, + { .id = "BCM2E5F" }, + { .id = "BCM2E60" }, + { .id = "BCM2E61" }, + { .id = "BCM2E62" }, + { .id = "BCM2E63" }, + { .id = "BCM2E64" }, + { .id = "BCM2E65" }, + { .id = "BCM2E66" }, + { .id = "BCM2E67" }, + { .id = "BCM2E68" }, + { .id = "BCM2E69" }, + { .id = "BCM2E6B" }, + { .id = "BCM2E6D" }, + { .id = "BCM2E6E" }, + { .id = "BCM2E6F" }, + { .id = "BCM2E70" }, + { .id = "BCM2E71" }, + { .id = "BCM2E72" }, + { .id = "BCM2E73" }, + { .id = "BCM2E74", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E75", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E76" }, + { .id = "BCM2E77" }, + { .id = "BCM2E78" }, + { .id = "BCM2E79" }, + { .id = "BCM2E7A" }, + { .id = "BCM2E7B", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E7C" }, + { .id = "BCM2E7D" }, + { .id = "BCM2E7E" }, + { .id = "BCM2E7F" }, + { .id = "BCM2E80", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E81" }, + { .id = "BCM2E82" }, + { .id = "BCM2E83" }, + { .id = "BCM2E84" }, + { .id = "BCM2E85" }, + { .id = "BCM2E86" }, + { .id = "BCM2E87" }, + { .id = "BCM2E88" }, + { .id = "BCM2E89", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E8A" }, + { .id = "BCM2E8B" }, + { .id = "BCM2E8C" }, + { .id = "BCM2E8D" }, + { .id = "BCM2E8E" }, + { .id = "BCM2E90" }, + { .id = "BCM2E92" }, + { .id = "BCM2E93" }, + { .id = "BCM2E94", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E95" }, + { .id = "BCM2E96" }, + { .id = "BCM2E97" }, + { .id = "BCM2E98" }, + { .id = "BCM2E99", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E9A" }, + { .id = "BCM2E9B", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E9C" }, + { .id = "BCM2E9D" }, + { .id = "BCM2E9F", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA0" }, + { .id = "BCM2EA1" }, + { .id = "BCM2EA2", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA3", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA4", .driver_data = (long)&bcm43430_device_data }, /* bcm43455 */ + { .id = "BCM2EA5" }, + { .id = "BCM2EA6" }, + { .id = "BCM2EA7" }, + { .id = "BCM2EA8" }, + { .id = "BCM2EA9" }, + { .id = "BCM2EAA", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EAB", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EAC", .driver_data = (long)&bcm43430_device_data }, + { } }; MODULE_DEVICE_TABLE(acpi, bcm_acpi_match); #endif diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c index db56eead27ce..0323db21c428 100644 --- a/drivers/bluetooth/hci_bcsp.c +++ b/drivers/bluetooth/hci_bcsp.c @@ -194,7 +194,7 @@ static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, return NULL; } - if (hciextn && chan == 5) { + if (hciextn && chan == 5 && len > HCI_COMMAND_HDR_SIZE) { __le16 opcode = ((struct hci_command_hdr *)data)->opcode; /* Vendor specific commands */ @@ -402,6 +402,9 @@ static void bcsp_handle_le_pkt(struct hci_uart *hu) u8 sync_pkt[4] = { 0xda, 0xdc, 0xed, 0xed }; /* spot "conf" pkts and reply with a "conf rsp" pkt */ + if (bcsp->rx_skb->len < 8) + return; + if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && !memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) { struct sk_buff *nskb = alloc_skb(4, GFP_ATOMIC); diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index 93cdde981840..60b90f1e11fc 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -1124,10 +1124,10 @@ static const struct h5_device_data h5_data_rtl8723bs = { #ifdef CONFIG_ACPI static const struct acpi_device_id h5_acpi_match[] = { #ifdef CONFIG_BT_HCIUART_RTL - { "OBDA0623", (kernel_ulong_t)&h5_data_rtl8723bs }, - { "OBDA8723", (kernel_ulong_t)&h5_data_rtl8723bs }, + { .id = "OBDA0623", .driver_data = (kernel_ulong_t)&h5_data_rtl8723bs }, + { .id = "OBDA8723", .driver_data = (kernel_ulong_t)&h5_data_rtl8723bs }, #endif - { }, + { } }; MODULE_DEVICE_TABLE(acpi, h5_acpi_match); #endif diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index c31105b91e47..ecf597f3e201 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -1057,8 +1057,8 @@ static const struct hci_uart_proto intel_proto = { #ifdef CONFIG_ACPI static const struct acpi_device_id intel_acpi_match[] = { - { "INT33E1", 0 }, - { "INT33E3", 0 }, + { .id = "INT33E1" }, + { .id = "INT33E3" }, { } }; MODULE_DEVICE_TABLE(acpi, intel_acpi_match); diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 2ad42c3bbaac..58f5504a336e 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -163,6 +163,12 @@ static void hci_uart_write_work(struct work_struct *work) set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); len = tty->ops->write(tty, skb->data, skb->len); + if (len < 0 || len > skb->len) { + hdev->stat.err_tx++; + kfree_skb(skb); + continue; + } + hdev->stat.byte_tx += len; skb_pull(skb, len); @@ -756,9 +762,9 @@ static int hci_uart_set_proto(struct hci_uart *hu, int id) hu->proto = p; err = hci_uart_register_dev(hu); - if (err) { + if (err) return err; - } + set_bit(HCI_UART_PROTO_READY, &hu->flags); clear_bit(HCI_UART_PROTO_INIT, &hu->flags); diff --git a/drivers/bluetooth/hci_nokia.c b/drivers/bluetooth/hci_nokia.c index 1e65b541f8ad..be2923231e71 100644 --- a/drivers/bluetooth/hci_nokia.c +++ b/drivers/bluetooth/hci_nokia.c @@ -354,9 +354,29 @@ static int nokia_setup_fw(struct hci_uart *hu) u16 opcode; struct sk_buff *skb; + if (pkt_size > fw_size - 2) { + err = -EINVAL; + dev_err(dev, "%s: Malformed firmware packet\n", + hu->hdev->name); + goto done; + } + switch (pkt_type) { case HCI_COMMAND_PKT: + if (pkt_size < 1 + HCI_COMMAND_HDR_SIZE) { + err = -EINVAL; + dev_err(dev, "%s: Malformed firmware command\n", + hu->hdev->name); + goto done; + } + cmd = (struct hci_command_hdr *)(fw_ptr + 3); + if (cmd->plen > pkt_size - 1 - HCI_COMMAND_HDR_SIZE) { + err = -EINVAL; + dev_err(dev, "%s: Truncated firmware command\n", + hu->hdev->name); + goto done; + } opcode = le16_to_cpu(cmd->opcode); skb = __hci_cmd_sync(hu->hdev, opcode, cmd->plen, diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 1222f97800f4..345f602e9ce2 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -1239,8 +1239,8 @@ static int qca_recv_event(struct hci_dev *hdev, struct sk_buff *skb) * received we store dump into a file before closing hci. This * dump will help in triaging the issues. */ - if ((skb->data[0] == HCI_VENDOR_PKT) && - (get_unaligned_be16(skb->data + 2) == QCA_SSR_DUMP_HANDLE)) + if (skb->data[0] == HCI_EV_VENDOR && + get_unaligned_be16(skb->data + 2) == QCA_SSR_DUMP_HANDLE) return qca_controller_memdump_event(hdev, skb); return hci_recv_frame(hdev, skb); @@ -2792,12 +2792,12 @@ MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id qca_bluetooth_acpi_match[] = { - { "QCOM2066", (kernel_ulong_t)&qca_soc_data_qca2066 }, - { "QCOM6390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLA16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLB16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLB26390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { }, + { .id = "QCOM2066", .driver_data = (kernel_ulong_t)&qca_soc_data_qca2066 }, + { .id = "QCOM6390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLA16390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLB16390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLB26390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { } }; MODULE_DEVICE_TABLE(acpi, qca_bluetooth_acpi_match); #endif diff --git a/drivers/bluetooth/virtio_bt.c b/drivers/bluetooth/virtio_bt.c index 140ab55c9fc5..c20d54088c8c 100644 --- a/drivers/bluetooth/virtio_bt.c +++ b/drivers/bluetooth/virtio_bt.c @@ -120,9 +120,13 @@ static int virtbt_setup_zephyr(struct hci_dev *hdev) if (IS_ERR(skb)) return PTR_ERR(skb); - bt_dev_info(hdev, "%s", (char *)(skb->data + 1)); + /* Bounded print: the backend controls skb->len. */ + if (skb->len > 1) { + int len = skb->len - 1; - hci_set_fw_info(hdev, "%s", skb->data + 1); + bt_dev_info(hdev, "%.*s", len, (char *)(skb->data + 1)); + hci_set_fw_info(hdev, "%.*s", len, skb->data + 1); + } kfree_skb(skb); return 0; diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 07555ae63859..f7cebb026552 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -101,6 +101,8 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr, struct dentry *cxl_debugfs_create_dir(const char *dir); int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled, enum cxl_partition_mode mode); +struct cxl_memdev_state; +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds); int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size); int cxl_dpa_free(struct cxl_endpoint_decoder *cxled); resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled); diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 7c6c5b7450a5..97b1e61ad018 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -1152,7 +1152,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_mem_get_event_records, "CXL"); * * See CXL @8.2.9.5.2.1 Get Partition Info */ -static int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; struct cxl_mbox_get_partition_info pi; @@ -1308,55 +1308,6 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd) return -EBUSY; } -static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) -{ - int i = info->nr_partitions; - - if (size == 0) - return; - - info->part[i].range = (struct range) { - .start = start, - .end = start + size - 1, - }; - info->part[i].mode = mode; - info->nr_partitions++; -} - -int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) -{ - struct cxl_dev_state *cxlds = &mds->cxlds; - struct device *dev = cxlds->dev; - int rc; - - if (!cxlds->media_ready) { - info->size = 0; - return 0; - } - - info->size = mds->total_bytes; - - if (mds->partition_align_bytes == 0) { - add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->volatile_only_bytes, - mds->persistent_only_bytes, CXL_PARTMODE_PMEM); - return 0; - } - - rc = cxl_mem_get_partition_info(mds); - if (rc) { - dev_err(dev, "Failed to query partition information\n"); - return rc; - } - - add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, - CXL_PARTMODE_PMEM); - - return 0; -} -EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); - int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 33a3d2e7b13a..2e457b1ebc7d 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -594,6 +594,73 @@ bool is_cxl_memdev(const struct device *dev) } EXPORT_SYMBOL_NS_GPL(is_cxl_memdev, "CXL"); +static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) +{ + int i = info->nr_partitions; + + if (size == 0) + return; + + info->part[i].range = (struct range) { + .start = start, + .end = start + size - 1, + }; + info->part[i].mode = mode; + info->nr_partitions++; +} + +int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; + struct device *dev = cxlds->dev; + int rc; + + if (!cxlds->media_ready) { + info->size = 0; + return 0; + } + + info->size = mds->total_bytes; + + if (mds->partition_align_bytes == 0) { + add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->volatile_only_bytes, + mds->persistent_only_bytes, CXL_PARTMODE_PMEM); + return 0; + } + + rc = cxl_mem_get_partition_info(mds); + if (rc) { + dev_err(dev, "Failed to query partition information\n"); + return rc; + } + + add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, + CXL_PARTMODE_PMEM); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); + + +/** + * cxl_set_capacity: initialize dpa by a driver without a mailbox. + * + * @cxlds: pointer to cxl_dev_state + * @capacity: device volatile memory size + */ +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity) +{ + struct cxl_dpa_info range_info = { + .size = capacity, + }; + + add_part(&range_info, 0, capacity, CXL_PARTMODE_RAM); + return cxl_dpa_setup(cxlds, &range_info); +} +EXPORT_SYMBOL_NS_GPL(cxl_set_capacity, "CXL"); + /** * set_exclusive_cxl_commands() - atomically disable user cxl commands * @mds: The device state to operate on diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index e4338fd7e01b..9d807c1a002c 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 1215ee4f4035..cb633e19151b 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c index 93710cf4f0a6..20c2d9fbcfe7 100644 --- a/drivers/cxl/core/regs.c +++ b/drivers/cxl/core/regs.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index b826eb53cf7b..110ec9c44f09 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -13,16 +13,6 @@ */ #define CXL_PCI_DEFAULT_MAX_VECTORS 16 -/* Register Block Identifier (RBI) */ -enum cxl_regloc_type { - CXL_REGLOC_RBI_EMPTY = 0, - CXL_REGLOC_RBI_COMPONENT, - CXL_REGLOC_RBI_VIRT, - CXL_REGLOC_RBI_MEMDEV, - CXL_REGLOC_RBI_PMU, - CXL_REGLOC_RBI_TYPES -}; - /* * Table Access DOE, CDAT Read Entry Response * @@ -112,6 +102,4 @@ static inline void devm_cxl_port_ras_setup(struct cxl_port *port) } #endif -int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, - struct cxl_register_map *map); #endif /* __CXL_PCI_H__ */ diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 267c679b0b3c..bb892dbfdd6d 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "cxlmem.h" #include "cxlpci.h" diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 43d51d942ead..8389983f4256 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -876,19 +876,29 @@ int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv) { + const struct dpll_device_ops *dev_ops; int ret; if (WARN_ON(!ops) || WARN_ON(!ops->state_on_dpll_get) || WARN_ON(!ops->direction_get) || - WARN_ON(ops->measured_freq_get && - (!dpll_device_ops(dpll)->freq_monitor_get || - !dpll_device_ops(dpll)->freq_monitor_set)) || - WARN_ON(ops->supported_ffo && !ops->ffo_get)) + WARN_ON(ops->supported_ffo && !ops->ffo_get) || + WARN_ON((pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && + !(pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL; mutex_lock(&dpll_lock); + dev_ops = dpll_device_ops(dpll); + if (WARN_ON(ops->measured_freq_get && + (!dev_ops || !dev_ops->freq_monitor_get || + !dev_ops->freq_monitor_set))) { + ret = -EINVAL; + goto out_unlock; + } + /* * For pins identified via firmware (pin->fwnode), allow registration * even if the pin's (module, clock_id) differs from the target DPLL. @@ -1081,12 +1091,8 @@ EXPORT_SYMBOL_GPL(dpll_pin_ref_sync_pair_add); static struct dpll_device_registration * dpll_device_registration_first(struct dpll_device *dpll) { - struct dpll_device_registration *reg; - - reg = list_first_entry_or_null((struct list_head *)&dpll->registration_list, - struct dpll_device_registration, list); - WARN_ON(!reg); - return reg; + return list_first_entry_or_null((struct list_head *)&dpll->registration_list, + struct dpll_device_registration, list); } void *dpll_priv(struct dpll_device *dpll) @@ -1094,6 +1100,8 @@ void *dpll_priv(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->priv; } @@ -1102,6 +1110,8 @@ const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->ops; } diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index afb31c004038..523d76a5fd49 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -66,6 +66,22 @@ static bool dpll_pin_available(struct dpll_pin *pin) return false; } +static bool dpll_device_registered(struct dpll_device *dpll) +{ + return dpll_device_ops(dpll); +} + +static struct dpll_pin_ref *dpll_pin_first_registered_ref(struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(&pin->dpll_refs, i, ref) + if (dpll_device_registered(ref->dpll)) + return ref; + return NULL; +} + /** * dpll_msg_add_pin_handle - attach pin handle attribute to a given message * @msg: pointer to sk_buff message to attach a pin handle @@ -656,6 +672,8 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, int ret; xa_for_each(&pin->dpll_refs, index, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; attr = nla_nest_start(msg, DPLL_A_PIN_PARENT_DEVICE); if (!attr) return -EMSGSIZE; @@ -700,9 +718,10 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, int ret; ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref || !dpll_device_registered(ref->dpll)) + ref = dpll_pin_first_registered_ref(pin); if (!ref) - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); - ASSERT_NOT_NULL(ref); + return -ENODEV; ret = dpll_msg_add_pin_handle(msg, pin); if (ret) @@ -1079,10 +1098,9 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) { u64 freq = nla_get_u64(a), old_freq; - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; if (!dpll_pin_is_freq_supported(pin, freq)) { @@ -1090,22 +1108,17 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->frequency_set || !ops->frequency_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, - "frequency set not supported by the device"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->frequency_set || !ops->frequency_get) { + NL_SET_ERR_MSG(extack, + "frequency set not supported by the device"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->frequency_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &old_freq, extack); @@ -1116,68 +1129,42 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, if (freq == old_freq) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->frequency_set) - continue; - dpll = ref->dpll; - ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), freq, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, "frequency set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), freq, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "frequency set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->frequency_set) - continue; - dpll = ref->dpll; - if (ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), old_freq, extack)) - NL_SET_ERR_MSG(extack, "set frequency rollback failed"); - } - return ret; } static int dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; struct dpll_pin_esync esync; u64 freq = nla_get_u64(a); + struct dpll_pin_ref *ref; struct dpll_device *dpll; bool supported = false; - unsigned long i; - int ret; + int ret, i; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->esync_set || !ops->esync_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, - "embedded sync feature is not supported by this device"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->esync_set || !ops->esync_get) { + NL_SET_ERR_MSG(extack, + "embedded sync feature is not supported by this device"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->esync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &esync, extack); @@ -1196,44 +1183,17 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - void *pin_dpll_priv; - - ops = dpll_pin_ops(ref); - if (!ops->esync_set) - continue; - dpll = ref->dpll; - pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); - ret = ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), - freq, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, - "embedded sync frequency set failed for dpll_id: %u", - dpll->id); - goto rollback; - } + ret = ops->esync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, + dpll_priv(dpll), freq, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "embedded sync frequency set failed for dpll_id: %u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - void *pin_dpll_priv; - - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->esync_set) - continue; - dpll = ref->dpll; - pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); - if (ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), - esync.freq, extack)) - NL_SET_ERR_MSG(extack, "set embedded sync frequency rollback failed"); - } - return ret; } static int @@ -1241,14 +1201,12 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, unsigned long ref_sync_pin_idx, const enum dpll_pin_state state, struct netlink_ext_ack *extack) - { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; enum dpll_pin_state old_state; struct dpll_pin *ref_sync_pin; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; ref_sync_pin = xa_find(&pin->ref_sync_pins, &ref_sync_pin_idx, @@ -1262,7 +1220,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EINVAL; } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1282,42 +1240,20 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, } if (state == old_state) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->ref_sync_set) - continue; - dpll = ref->dpll; - ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, - ref_sync_pin), - state, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, "reference sync set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + + ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + ref_sync_pin, + dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + state, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "reference sync set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->ref_sync_set) - continue; - dpll = ref->dpll; - if (ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), - old_state, extack)) - NL_SET_ERR_MSG(extack, "set reference sync rollback failed"); - } - return ret; } static int @@ -1478,11 +1414,10 @@ static int dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, struct netlink_ext_ack *extack) { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; s32 phase_adj, old_phase_adj; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; phase_adj = nla_get_s32(phase_adj_attr); @@ -1499,21 +1434,16 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->phase_adjust_set || !ops->phase_adjust_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, "phase adjust not supported"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->phase_adjust_set || !ops->phase_adjust_get) { + NL_SET_ERR_MSG(extack, "phase adjust not supported"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->phase_adjust_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &old_phase_adj, @@ -1525,41 +1455,17 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, if (phase_adj == old_phase_adj) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->phase_adjust_set) - continue; - dpll = ref->dpll; - ret = ops->phase_adjust_set(pin, - dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), phase_adj, - extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, - "phase adjust set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + ret = ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), phase_adj, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "phase adjust set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->phase_adjust_set) - continue; - dpll = ref->dpll; - if (ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), old_phase_adj, - extack)) - NL_SET_ERR_MSG(extack, "set phase adjust rollback failed"); - } - return ret; } static int @@ -1581,7 +1487,7 @@ dpll_pin_parent_device_set(struct dpll_pin *pin, struct nlattr *parent_nest, return -EINVAL; } pdpll_idx = nla_get_u32(tb[DPLL_A_PIN_PARENT_ID]); - dpll = xa_load(&dpll_device_xa, pdpll_idx); + dpll = dpll_device_get_by_id(pdpll_idx); if (!dpll) { NL_SET_ERR_MSG(extack, "parent device not found"); return -EINVAL; @@ -1873,6 +1779,10 @@ int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) ret = dpll_cmd_pin_get_one(skb, pin, cb->extack); if (ret) { genlmsg_cancel(skb, hdr); + if (ret == -ENODEV) { + ret = 0; + continue; + } break; } genlmsg_end(skb, hdr); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index ed3bbe9841ea..b1ba490e72b0 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = [DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, }, - [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5), + [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6), }; /* DPLL_CMD_PIN_GET - do */ diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig index 5bbca1400581..da1731ace15f 100644 --- a/drivers/dpll/zl3073x/Kconfig +++ b/drivers/dpll/zl3073x/Kconfig @@ -2,7 +2,7 @@ config ZL3073X tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST - depends on NET + depends on NET && PTP_1588_CLOCK select DPLL select NET_DEVLINK select REGMAP @@ -16,7 +16,7 @@ config ZL3073X config ZL3073X_I2C tristate "I2C bus implementation for Microchip Azurite devices" - depends on I2C && NET + depends on I2C && NET && PTP_1588_CLOCK select REGMAP_I2C select ZL3073X help @@ -28,7 +28,7 @@ config ZL3073X_I2C config ZL3073X_SPI tristate "SPI bus implementation for Microchip Azurite devices" - depends on NET && SPI + depends on NET && SPI && PTP_1588_CLOCK select REGMAP_SPI select ZL3073X help diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 2fe3c3da84bb..ba4d303d41b4 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include +#include #include #include @@ -31,9 +33,18 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - /* Read df_offset vs tracked reference */ + /* Read df_offset only when locked to a reference. In NCO mode + * df_offset was captured at entry by nco_mode_set() - preserve it. + */ + if (!zl3073x_chan_is_locked(chan)) { + if (!zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -43,7 +54,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return rc; rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -56,13 +68,103 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return 0; } +/** + * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Switches the channel to NCO mode and reads the df_offset + * auto-captured by nco_auto_read directly from the register. + * No DF_READ handshake is needed as nco_auto_read populates + * the register before the mode switch completes. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + u8 prev_mode, df_read; + u64 val; + int rc; + + prev_mode = zl3073x_chan_mode_get(chan); + + /* nco_auto_read captures the tracking offset at NCO entry only + * from reflock, auto or holdover mode. From freerun the captured + * value is not meaningful. + */ + if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) { + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + /* Configure df_read for nco_auto_read: + * ref_ofst=0 - reads offset relative to master clock (not input ref) + * cmd=CMD_ACC_I - accumulated I-part covering both locked and + * holdover entry. + * + * No semaphore is set - this only configures what the df_offset + * value represents after the mode switch; nco_auto_read performs + * the actual read automatically. + */ + df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) | + FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read); + if (rc) + return rc; + + /* Wait for df_read configuration to take effect before + * triggering nco_auto_read via mode switch. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + /* Wait for nco_auto_read to populate df_offset. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + /* Read df_offset captured by nco_auto_read during mode switch. + * No DF_READ semaphore handshake needed. Mode switch already + * succeeded, so don't propagate a read failure back to userspace. + */ + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) { + dev_warn(zldev->dev, + "Failed to read DPLL%u df_offset: %pe\n", + index, ERR_PTR(rc)); + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + } else { + chan->df_offset = sign_extend64(val, 47); + } + + return 0; +} + /** * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware * @zldev: pointer to zl3073x_dev structure * @index: DPLL channel index to fetch state for * - * Reads the mode_refsel register and reference priority registers for - * the given DPLL channel and stores the raw values for later use. + * Reads the mode_refsel, status and reference priority registers for + * the given DPLL channel and stores the values for later use. * * Return: 0 on success, <0 on error */ @@ -71,6 +173,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) struct zl3073x_chan *chan = &zldev->chan[index]; int rc, i; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl); + if (rc) + return rc; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), &chan->mode_refsel); if (rc) @@ -83,6 +189,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; + /* If firmware left the channel in NCO mode, mark df_offset as + * unknown - we cannot know whether the preconditions for a valid + * nco_auto_read capture were met. + */ + if (zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + dev_dbg(zldev->dev, "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", index, zl3073x_chan_lock_state_get(chan), @@ -122,6 +235,322 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev, return &zldev->chan[index]; } +/** + * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * + * Checks the ToD control register semaphore bit. If clear, returns + * immediately. Otherwise polls until the bit is cleared by the device. + * + * Return: + * * 0 - success + * * %-EBUSY - timeout + * * %-EOPNOTSUPP - unknown command detected + * * negative - other error + */ +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch) +{ + unsigned int timeout; + u8 tod_ctrl; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl); + if (rc) + return rc; + + if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM)) + return 0; + + switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) { + case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ: + timeout = ZL_POLL_TOD_WR_TIMEOUT_US; + break; + case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT: + case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ: + timeout = ZL_POLL_TOD_RD_TIMEOUT_US; + break; + default: + return -EOPNOTSUPP; + } + + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM, timeout); + + return rc == -ETIMEDOUT ? -EBUSY : rc; +} + +/** + * zl3073x_chan_tod_ctrl - issue ToD command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @cmd: ToD command to execute + * + * Writes the semaphore and command to dpll_tod_ctrl. The caller must + * ensure the device is ready (semaphore clear) before calling and + * must wait for completion if needed. + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd) +{ + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM | cmd); +} + +/** + * zl3073x_chan_tod_read - read ToD registers after issuing a command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current + * @ts: timespec to store the result + * @sts: optional system timestamp pair for cross-timestamping + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + u32 nsec; + u64 sec; + u8 cmd; + int rc; + + if (next_hz) + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ; + else + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + ptp_read_system_prets(sts); + rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd); + if (rc) + return rc; + + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + ptp_read_system_postts(sts); + + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec); + if (rc) + return rc; + + /* HW nanoseconds are always in [0, NSEC_PER_SEC) range */ + rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec); + if (rc) + return rc; + + ts->tv_sec = sec; + ts->tv_nsec = nsec; + + return 0; +} + +/** + * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @ts: time to set + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts) +{ + int rc; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec); + if (rc) + return rc; + + return zl3073x_chan_tod_ctrl(zldev, ch, + ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ); +} + +/** + * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta: time adjustment to apply + * + * Reads the next-Hz ToD and current ToD, then checks whether enough time + * remains before the next 1 Hz rollover to safely complete the write. + * Re-reads if the 1 Hz tick crossed between the two reads or if less + * than 20 ms remains before the next rollover. Applies @delta and writes + * the result back. + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta) +{ +#define ZL_TOD_MAX_RETRIES 20 + static const long threshold_ns = 20 * NSEC_PER_MSEC; + struct timespec64 ts_next, ts_cur, diff; + int rc, i; + + for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) { + rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL); + if (rc) + return rc; + + rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL); + if (rc) + return rc; + + /* Ensure the 1 Hz tick did not cross between the two reads + * and that enough margin remains to complete the write. + */ + diff = timespec64_sub(ts_next, ts_cur); + if (diff.tv_sec > 0 || + (!diff.tv_sec && diff.tv_nsec >= threshold_ns)) + break; + } + if (i == ZL_TOD_MAX_RETRIES) { + dev_warn(zldev->dev, + "DPLL%u ToD adjust failed to get stable margin\n", + ch); + return -EBUSY; + } + + /* Apply delta to the next-Hz ToD */ + ts_next = timespec64_add(ts_next, delta); + if (!timespec64_valid_settod(&ts_next)) + return -EINVAL; + + return zl3073x_chan_tod_write(zldev, ch, ts_next); +#undef ZL_TOD_MAX_RETRIES +} + +/** + * zl3073x_chan_df_offset_set - write delta frequency offset to hardware + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @offset: frequency offset in 2^-48 steps + * + * Context: Caller must hold the per-DPLL lock. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset) +{ + int rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset); + if (!rc) + zldev->chan[ch].df_offset = offset; + + return rc; +} + +/** + * zl3073x_chan_tie_write - adjust DPLL phase using TIE write + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta_ns: phase adjustment in nanoseconds (must be in (-1s, 1s)) + * + * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data + * to the specified channel. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns) +{ + s64 tie_data; + int rc; + + guard(mutex)(&zldev->tie_lock); + + /* Wait for any previous TIE operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP, + ZL_POLL_TIE_WR_TIMEOUT_US); + if (rc) + return rc; + + /* Convert ns to TIE units (0.01 ps = 10^-14 s) */ + tie_data = delta_ns * 100000LL; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch)); + if (rc) + return rc; + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP_WR); +} + +/** + * zl3073x_chan_phase_step - execute one output phase step operation + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @out_mask: bitmask of outputs to step + * @step_cycles: phase step in synthesizer clock cycles + * @tod_step: also step the ToD counter + * + * All masked outputs must use synthesizers of the same frequency since + * the step value is in synthesizer clock cycles. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, + bool tod_step) +{ + u8 ctrl; + int rc; + + guard(mutex)(&zldev->phase_step_lock); + + /* Wait for any previous phase step operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, + ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_POLL_PHASE_STEP_TIMEOUT_US); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA, + step_cycles); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1); + if (rc) + return rc; + + ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) | + FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE); + if (tod_step) + ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP; + + return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl); +} + /** * zl3073x_chan_state_set - commit DPLL channel state changes to hardware * @zldev: pointer to zl3073x_dev structure @@ -145,7 +574,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg))) return 0; - /* Direct register write for mode_refsel */ + /* Direct register writes for ctrl and mode_refsel */ + if (dchan->ctrl != chan->ctrl) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index), + chan->ctrl); + if (rc) + return rc; + dchan->ctrl = chan->ctrl; + } + if (dchan->mode_refsel != chan->mode_refsel) { rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), chan->mode_refsel); diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h index 4353809c6912..99c3b7088f67 100644 --- a/drivers/dpll/zl3073x/chan.h +++ b/drivers/dpll/zl3073x/chan.h @@ -5,14 +5,17 @@ #include #include +#include #include #include "regs.h" +struct ptp_system_timestamp; struct zl3073x_dev; /** * struct zl3073x_chan - DPLL channel state + * @ctrl: DPLL control register value * @mode_refsel: mode and reference selection register value * @ref_prio: reference priority registers (4 bits per ref, P/N packed) * @mon_status: monitor status register value @@ -21,6 +24,7 @@ struct zl3073x_dev; */ struct zl3073x_chan { struct_group(cfg, + u8 ctrl; u8 mode_refsel; u8 ref_prio[ZL3073X_NUM_REFS / 2]; ); @@ -38,6 +42,22 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_chan *chan); int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); + +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch); +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts); +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts); +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta); +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, bool tod_step); + +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset); + +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns); /** * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference @@ -152,6 +172,66 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan) return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status); } +/** + * zl3073x_chan_is_locked - check if channel is locked to a reference + * @chan: pointer to channel state + * + * Return: true if channel is locked, false otherwise + */ +static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan) +{ + u8 lock_state = zl3073x_chan_lock_state_get(chan); + return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK; +} + +/** + * zl3073x_chan_mode_is_auto - check if channel is in automatic mode + * @chan: pointer to channel state + * + * Return: true if channel is in automatic mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO; +} + +/** + * zl3073x_chan_mode_is_nco - check if channel is in NCO mode + * @chan: pointer to channel state + * + * Return: true if channel is in NCO mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO; +} + +/** + * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode + * @chan: pointer to channel state + * + * Return: true if channel is in reflock mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; +} + +/** + * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write + * @chan: pointer to channel state + * + * TIE write is supported in AUTO and REFLOCK modes regardless of lock state. + * + * Return: true if TIE write is supported, false otherwise + */ +static inline bool +zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_is_auto(chan) || + zl3073x_chan_mode_is_reflock(chan); +} + /** * zl3073x_chan_is_ho_ready - check if holdover is ready * @chan: pointer to channel state diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 8e6416a4741d..230df08e27cd 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -25,6 +25,7 @@ static const struct zl3073x_chip_info zl3073x_chip_ids[] = { ZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E3B, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E93, 1, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E94, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), @@ -311,17 +312,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) * @zldev: zl3073x device pointer * @reg: register to poll (has to be 8bit register) * @mask: bit mask for polling + * @timeout_us: timeout in microseconds * * Waits for bits specified by @mask in register @reg value to be cleared * by the device. * * Returns: 0 on success, <0 on error */ -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us) { - /* Register polling sleep & timeout */ -#define ZL_POLL_SLEEP_US 10 -#define ZL_POLL_TIMEOUT_US 2000000 + unsigned int sleep_us = timeout_us / 50; unsigned int val; /* Check the register is 8bit */ @@ -335,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), - ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US); + sleep_us, timeout_us); } int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, @@ -354,7 +355,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, return rc; /* Wait for the operation to actually finish */ - return zl3073x_poll_zero_u8(zldev, op_reg, op_val); + return zl3073x_poll_zero_u8(zldev, op_reg, op_val, + ZL_POLL_MB_TIMEOUT_US); } /** @@ -377,8 +379,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) return rc; /* Poll for completion - pending bit cleared */ - return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, - ZL_HWREG_OP_PENDING); + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING, + ZL_POLL_HWREG_TIMEOUT_US); } /** @@ -509,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) int rc; u8 i; + rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK, + &zldev->out_step_time_mask); + if (rc) + return rc; + for (i = 0; i < ZL3073X_NUM_REFS; i++) { rc = zl3073x_ref_state_fetch(zldev, i); if (rc) { @@ -566,19 +573,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev) } } -static void -zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev) -{ - int i, rc; - for (i = 0; i < zldev->info->num_channels; i++) { - rc = zl3073x_chan_state_update(zldev, i); - if (rc) - dev_warn(zldev->dev, - "Failed to get DPLL%u state: %pe\n", i, - ERR_PTR(rc)); - } -} /** * zl3073x_ref_phase_offsets_update - update reference phase offsets @@ -609,7 +604,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) * to be zero to ensure that the measured data are coherent. */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); if (rc) return rc; @@ -628,7 +624,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); } /** @@ -648,7 +645,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for previous measurement to finish */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); if (rc) return rc; @@ -669,7 +667,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); } /** @@ -715,9 +714,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update input references' states */ zl3073x_dev_ref_states_update(zldev); - /* Update DPLL channels' states */ - zl3073x_dev_chan_states_update(zldev); - /* Update DPLL-to-connected-ref phase offsets registers */ rc = zl3073x_ref_phase_offsets_update(zldev, -1); if (rc) @@ -727,7 +723,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update measured input reference frequencies if frequency * monitoring is enabled. */ - if (zldev->freq_monitor) { + if (READ_ONCE(zldev->freq_monitor)) { rc = zl3073x_ref_freq_meas_update(zldev); if (rc) dev_warn(zldev->dev, @@ -763,7 +759,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) return rc; /* Save the new factor */ - zldev->phase_avg_factor = factor; + WRITE_ONCE(zldev->phase_avg_factor, factor); return 0; } @@ -1043,6 +1039,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev) * and/or polls are required to be done atomically. */ rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->tie_lock); if (rc) return dev_err_probe(zldev->dev, rc, "Failed to initialize mutex\n"); diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index addba378b0df..67c10e259511 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "chan.h" @@ -19,6 +20,16 @@ struct device; struct regmap; struct zl3073x_dpll; +/* Per-operation poll timeouts */ +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC) +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC) +#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) +#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) enum zl3073x_flags { ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, @@ -48,6 +59,8 @@ struct zl3073x_chip_info { * @regmap: regmap to access device registers * @info: detected chip info * @multiop_lock: to serialize multiple register operations + * @tie_lock: to serialize TIE write operations + * @phase_step_lock: to serialize output phase step operations * @ref: array of input references' invariants * @out: array of outs' invariants * @synth: array of synths' invariants @@ -56,6 +69,7 @@ struct zl3073x_chip_info { * @kworker: thread for periodic work * @work: periodic work * @clock_id: clock id of the device + * @out_step_time_mask: output step-time mask (device-global) * @phase_avg_factor: phase offset measurement averaging factor * @freq_monitor: is frequency monitor enabled */ @@ -64,6 +78,8 @@ struct zl3073x_dev { struct regmap *regmap; const struct zl3073x_chip_info *info; struct mutex multiop_lock; + struct mutex tie_lock; + struct mutex phase_step_lock; /* Invariants */ struct zl3073x_ref ref[ZL3073X_NUM_REFS]; @@ -80,6 +96,7 @@ struct zl3073x_dev { /* Per-chip parameters */ u64 clock_id; + u16 out_step_time_mask; u8 phase_avg_factor; bool freq_monitor; }; @@ -94,7 +111,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev); static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) { - return zldev->phase_avg_factor; + return READ_ONCE(zldev->phase_avg_factor); } int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); @@ -127,7 +144,8 @@ struct zl3073x_hwreg_seq_item { int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, unsigned int mask_reg, u16 mask_val); -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us); int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); @@ -300,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index) return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out); } +/** + * zl3073x_dev_out_is_stepped - check if output is in step-time mask + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if output is affected by step-time operations + */ +static inline bool +zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index) +{ + return !!(zldev->out_step_time_mask & BIT(index)); +} + /** * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by * @zldev: pointer to zl3073x device diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 4ab045e85a89..f2e137475b40 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only -#include #include #include +#include #include #include #include @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -57,7 +58,7 @@ struct zl3073x_dpll_pin { s32 phase_gran; enum dpll_pin_operstate operstate; s64 phase_offset; - atomic64_t freq_offset; + s64 freq_offset; u32 measured_freq; }; @@ -80,6 +81,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) return pin->dir == DPLL_PIN_DIRECTION_INPUT; } +/** + * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin + * @pin: pin to check + * + * Return: true if pin is a virtual NCO pin, false otherwise. + */ +static bool +zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->id == ZL3073X_NCO_PIN_ID; +} + /** * zl3073x_dpll_is_p_pin - check if the pin is P-pin * @pin: pin to check @@ -119,6 +132,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) return NULL; } +static struct zl3073x_dpll_pin * +zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_nco_pin(pin)) + return pin; + } + + return NULL; +} + static int zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, void *pin_priv, @@ -133,6 +159,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -169,6 +197,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id, sync_mode; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -204,6 +234,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id, mode, pair; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); mode = zl3073x_ref_sync_mode_get(ref); @@ -233,8 +265,11 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, u8 mode, ref_id, sync_ref_id; struct zl3073x_chan chan; struct zl3073x_ref ref; + bool sync_ntf = false; int rc; + mutex_lock(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -253,17 +288,20 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, if (sync_freq > 8000) { NL_SET_ERR_MSG(extack, "sync frequency must be 8 kHz or less"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } if (ref_freq < 1000) { NL_SET_ERR_MSG(extack, "clock frequency must be 1 kHz or more"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } if (ref_freq <= sync_freq) { NL_SET_ERR_MSG(extack, "clock frequency must be higher than sync frequency"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } zl3073x_ref_sync_pair_set(&ref, sync_ref_id); @@ -276,20 +314,54 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, rc = zl3073x_ref_state_set(zldev, ref_id, &ref); if (rc) - return rc; + goto unlock; - /* Exclude sync source from automatic reference selection by setting - * its priority to NONE. On disconnect the priority is left as NONE - * and the user must explicitly make the pin selectable again. + /* All code paths accessing per-channel reference priorities are + * serialized by the subsystem dpll_lock, so it is safe to release + * our lock here before iterating over the other channels. */ - if (state == DPLL_PIN_STATE_CONNECTED) { + mutex_unlock(&zldpll->lock); + + if (state != DPLL_PIN_STATE_CONNECTED) + return 0; + + /* The datasheet recommends excluding the sync source from automatic + * reference selection by setting its priority to NONE on all DPLL + * channels. This is advisory - the ref sync pair is already + * configured, so a failure here is not fatal. On disconnect the + * priority is left as NONE and the user must explicitly make the + * pin selectable again. + */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + u8 prio; + + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldev, zldpll->id); + prio = zl3073x_chan_ref_prio_get(&chan, sync_ref_id); + if (prio == ZL_DPLL_REF_PRIO_NONE) { + mutex_unlock(&zldpll->lock); + continue; /* Ref is already non-selectable */ + } + zl3073x_chan_ref_prio_set(&chan, sync_ref_id, ZL_DPLL_REF_PRIO_NONE); - return zl3073x_chan_state_set(zldev, zldpll->id, &chan); + if (zl3073x_chan_state_set(zldev, zldpll->id, &chan)) + dev_warn(zldev->dev, + "Failed to set ref prio on DPLL%u\n", + zldpll->id); + else + sync_ntf = true; + + mutex_unlock(&zldpll->lock); } + if (sync_ntf) + __dpll_pin_change_ntf(sync_pin->dpll_pin); return 0; +unlock: + mutex_unlock(&zldpll->lock); + return rc; } static int @@ -298,12 +370,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, struct dpll_ffo_param *ffo, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return -ENODATA; - ffo->ffo = atomic64_read(&pin->freq_offset); + ffo->ffo = pin->freq_offset; return 0; } @@ -315,8 +390,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, void *dpll_priv, u64 *measured_freq, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *measured_freq = pin->measured_freq; *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; @@ -334,6 +412,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id); @@ -353,6 +433,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Get reference state */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -401,6 +483,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, u8 conn_id, ref_id; s64 ref_phase; + guard(mutex)(&zldpll->lock); + /* Get currently connected reference */ conn_id = zl3073x_dpll_connected_ref_get(zldpll); @@ -458,6 +542,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, s64 phase_comp; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -490,6 +576,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -523,6 +611,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, const struct zl3073x_ref *ref; u8 ref_id; + lockdep_assert_held(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); /* Check if this pin is the currently locked reference */ @@ -556,6 +646,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, const struct zl3073x_chan *chan; u8 mode, ref; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); mode = zl3073x_chan_mode_get(chan); @@ -589,8 +681,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, enum dpll_pin_operstate *operstate, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + return zl3073x_dpll_ref_operstate_get(pin, operstate); } @@ -604,9 +699,12 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, { struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 mode, ref; - int rc; + int rc = 0; + + mutex_lock(&zldpll->lock); chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); @@ -633,6 +731,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, goto invalid_state; } break; + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + if (state == DPLL_PIN_STATE_CONNECTED) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + fallthrough; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { @@ -648,13 +750,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { if (zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already selectable */ + goto unlock; /* Pin is already selectable */ /* Restore pin priority in HW */ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { if (!zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already disconnected */ + goto unlock; /* Pin is already disconnected */ /* Set pin priority to none in HW */ zl3073x_chan_ref_prio_set(&chan, ref, @@ -667,18 +769,27 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); - return -EOPNOTSUPP; + rc = -EOPNOTSUPP; + goto unlock; } /* Commit DPLL channel changes */ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); - if (rc) - return rc; + goto unlock; - return 0; invalid_state: NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); - return -EINVAL; + rc = -EINVAL; +unlock: + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (!rc && nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + + return rc; } static int @@ -686,8 +797,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 *prio, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *prio = pin->prio; return 0; @@ -704,6 +818,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv, u8 ref; int rc; + guard(mutex)(&zldpll->lock); + if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; @@ -739,6 +855,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, u32 synth_freq, out_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -796,6 +914,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, u32 synth_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -816,7 +936,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, /* If esync is being disabled just write mailbox and finish */ if (!freq) - goto write_mailbox; + return zl3073x_out_state_set(zldev, out_id, &out); /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); @@ -833,7 +953,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, */ out.esync_n_width = out.div / 2; -write_mailbox: /* Commit output configuration */ return zl3073x_out_state_set(zldev, out_id, &out); } @@ -848,6 +967,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id); return 0; @@ -868,6 +989,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -941,6 +1064,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, const struct zl3073x_out *out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -964,6 +1089,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -988,6 +1115,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, return 0; } +static int +zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *ref_pin = NULL; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_chan chan; + u8 ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + if (zl3073x_chan_mode_is_auto(&chan)) { + NL_SET_ERR_MSG(extack, + "NCO pin cannot be connected in automatic mode"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + if (zl3073x_chan_mode_is_reflock(&chan)) { + /* Get currently connected pin */ + ref = zl3073x_chan_ref_get(&chan); + ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + } + rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (!zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + /* Switch to freerun - holdover averaging was not + * updated during NCO mode. + */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_FREERUN); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + break; + default: + NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + + mutex_unlock(&zldpll->lock); + + if (!rc && ref_pin) + __dpll_pin_change_ntf(ref_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + s64 df_offset; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return -ENODATA; + + /* Do not report FFO if a failure occurred during switching to NCO. */ + df_offset = zl3073x_chan_df_offset_get(chan); + if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN) + return -ENODATA; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * NCO pin reports DPLL output offset from nominal, so negate. + * Convert to PPT: ppt = -df * 5^12 / 2^36 + */ + ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36); + + return 0; +} + static int zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, s32 *temp, struct netlink_ext_ack *extack) @@ -997,6 +1262,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, u16 val; int rc; + guard(mutex)(&zldpll->lock); + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); if (rc) return rc; @@ -1008,14 +1275,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, - enum dpll_lock_status *status, - enum dpll_lock_status_error *status_error, - struct netlink_ext_ack *extack) +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) { - struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + lockdep_assert_held(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1051,24 +1317,25 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, return 0; } +static int +zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + return __zl3073x_dpll_lock_status_get(zldpll, status); +} + static int zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, void *dpll_priv, unsigned long *modes, struct netlink_ext_ack *extack) { - struct zl3073x_dpll *zldpll = dpll_priv; - const struct zl3073x_chan *chan; - - chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); - - /* We support switching between automatic and manual mode, except in - * a case where the DPLL channel is configured to run in NCO mode. - * In this case, report only the manual mode to which the NCO is mapped - * as the only supported one. - */ - if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO) - __set_bit(DPLL_MODE_AUTOMATIC, modes); - + __set_bit(DPLL_MODE_AUTOMATIC, modes); __set_bit(DPLL_MODE_MANUAL, modes); return 0; @@ -1081,6 +1348,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1137,8 +1406,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, return rc; } - /* The averaging factor is common for all DPLL channels so after change - * we have to send a notification for other DPLL devices. + /* The averaging factor is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. */ list_for_each_entry(item, &zldpll->dev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); @@ -1155,10 +1424,13 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 hw_mode, ref; int rc; + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_chan_refsel_ref_get(&chan); @@ -1178,6 +1450,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, else hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; } else { + if (zl3073x_chan_mode_is_nco(&chan)) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + /* We are switching from manual to automatic mode: * - if there is a valid reference selected then ensure that * it is selectable after switch to automatic mode @@ -1206,9 +1481,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, if (rc) { NL_SET_ERR_MSG_MOD(extack, "failed to set reference selection mode"); + mutex_unlock(&zldpll->lock); return rc; } + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + return 0; } @@ -1220,6 +1504,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + if (zldpll->phase_monitor) *state = DPLL_FEATURE_STATE_ENABLE; else @@ -1236,6 +1522,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); return 0; @@ -1249,7 +1537,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; - if (zldpll->dev->freq_monitor) + if (READ_ONCE(zldpll->dev->freq_monitor)) *state = DPLL_FEATURE_STATE_ENABLE; else *state = DPLL_FEATURE_STATE_DISABLE; @@ -1264,13 +1552,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, struct netlink_ext_ack *extack) { struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; - zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE); /* The frequency monitoring is common for all DPLL channels so after * change we have to send a notification for other DPLL devices. */ - list_for_each_entry(item, &zldpll->dev->dplls, list) { + list_for_each_entry(item, &zldev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); if (item != zldpll && dpll_dev) @@ -1312,6 +1601,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get, }; +static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .ffo_get = zl3073x_dpll_nco_pin_ffo_get, + .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get, + .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set, +}; + static const struct dpll_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, @@ -1459,7 +1757,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); - if (zl3073x_dpll_is_input_pin(pin)) + if (zl3073x_dpll_is_nco_pin(pin)) + ops = &zl3073x_dpll_nco_pin_ops; + else if (zl3073x_dpll_is_input_pin(pin)) ops = &zl3073x_dpll_input_pin_ops; else ops = &zl3073x_dpll_output_pin_ops; @@ -1512,20 +1812,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, u8 index) { struct zl3073x_dev *zldev = zldpll->dev; - const struct zl3073x_chan *chan; bool is_diff, is_enabled; const char *name; - chan = zl3073x_chan_state_get(zldev, zldpll->id); - if (dir == DPLL_PIN_DIRECTION_INPUT) { u8 ref_id = zl3073x_input_pin_ref_get(index); const struct zl3073x_ref *ref; - /* Skip the pin if the DPLL is running in NCO mode */ - if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO) - return false; - name = "REF"; ref = zl3073x_ref_state_get(zldev, ref_id); is_diff = zl3073x_ref_is_diff(ref); @@ -1566,6 +1859,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, return true; } +static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = { + .type = DPLL_PIN_TYPE_INT_NCO, + .package_label = "NCO", + .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, +}; + +static int +zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + struct zl3073x_chan chan; + int rc; + + /* Ensure that ctrl bits are configured for NCO operation: + * - nco_auto_read: auto-capture tracking offset on NCO entry + * - tod_step_reset: apply negated ToD step on NCO exit + * - tie_clear: PPS DPLLs re-align outputs on NCO exit + */ + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl, + zldpll->type == DPLL_TYPE_PPS ? 1 : 0); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + mutex_unlock(&zldpll->lock); + if (rc) + return rc; + + pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT, + ZL3073X_NCO_PIN_ID); + if (IS_ERR(pin)) + return PTR_ERR(pin); + + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID, + THIS_MODULE, &zl3073x_dpll_nco_pin_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, + &zl3073x_dpll_nco_pin_ops, pin); + if (rc) + goto err_register; + + list_add(&pin->list, &zldpll->pins); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + kfree(pin); + + return rc; +} + /** * zl3073x_dpll_pins_register - register all registerable DPLL pins * @zldpll: pointer to zl3073x_dpll structure @@ -1613,6 +1966,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) list_add(&pin->list, &zldpll->pins); } + /* Register NCO virtual input pin */ + rc = zl3073x_dpll_nco_pin_register(zldpll); + if (rc) + goto error; + return 0; error: @@ -1648,8 +2006,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) return rc; } - rc = dpll_device_register(zldpll->dpll_dev, - zl3073x_prop_dpll_type_get(zldev, zldpll->id), + zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id); + rc = dpll_device_register(zldpll->dpll_dev, zldpll->type, &zldpll->ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); @@ -1696,6 +2054,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) u8 ref_id; int rc; + lockdep_assert_held(&zldpll->lock); + /* No phase offset if the ref monitor reports signal errors */ ref_id = zl3073x_input_pin_ref_get(pin->id); if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id)) @@ -1752,16 +2112,27 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) const struct zl3073x_chan *chan; s64 ffo; + lockdep_assert_held(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return false; chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN) + return false; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference), + * so the two inversions cancel out: ppt = df * 5^12 / 2^36 + */ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), 244140625, 36); - if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) { + if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", pin->label, ffo); + pin->freq_offset = ffo; return true; } @@ -1786,7 +2157,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) u8 ref_id; u32 freq; - if (!zldpll->dev->freq_monitor) + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) return false; ref_id = zl3073x_input_pin_ref_get(pin->id); @@ -1816,27 +2189,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) { + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS); struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; struct zl3073x_dpll_pin *pin; + bool dev_changed = false; int rc; + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS); + + mutex_lock(&zldpll->lock); + zldpll->check_count++; - /* Get current lock status for the DPLL */ - rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll, - &lock_status, NULL, NULL); + rc = zl3073x_chan_state_update(zldev, zldpll->id); + if (rc) { + dev_err(dev, "Failed to get DPLL%u state: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status); if (rc) { dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } - /* If lock status was changed then notify DPLL core */ if (zldpll->lock_status != lock_status) { zldpll->lock_status = lock_status; - dpll_device_change_ntf(zldpll->dpll_dev); + dev_changed = true; } /* Update phase offset latch registers for this DPLL if the phase @@ -1848,18 +2231,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(zldev->dev, "Failed to update phase offsets: %pe\n", ERR_PTR(rc)); - return; + goto unlock; } } list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_operstate operstate; - bool pin_changed = false; - /* Output pins change checks are not necessary because output - * states are constant. - */ - if (!zl3073x_dpll_is_input_pin(pin)) + /* Only physical input pins need monitoring */ + if (!zl3073x_dpll_is_input_pin(pin) || + zl3073x_dpll_is_nco_pin(pin)) continue; rc = zl3073x_dpll_ref_operstate_get(pin, &operstate); @@ -1867,31 +2248,41 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(dev, "Failed to get %s on DPLL%u oper state: %pe\n", pin->label, zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } if (operstate != pin->operstate) { dev_dbg(dev, "%s oper state changed: %u->%u\n", pin->label, pin->operstate, operstate); pin->operstate = operstate; - pin_changed = true; + set_bit(pin->id, changed_pins); } - /* Check for phase offset, ffo, and measured freq change - * once per second. - */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_ffo_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_measured_freq_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); } + } - if (pin_changed) +unlock: + mutex_unlock(&zldpll->lock); + + /* Send notifications outside the lock to avoid ABBA deadlock + * with dpll_lock taken by notification functions. + */ + if (dev_changed) + dpll_device_change_ntf(zldpll->dpll_dev); + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + !zl3073x_dpll_is_nco_pin(pin) && + test_bit(pin->id, changed_pins)) dpll_pin_change_ntf(pin->dpll_pin); } } @@ -1921,50 +2312,387 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) if (rc) return rc; - rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); - if (rc) - return rc; + return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); +} + +/* Maximum frequency adjustment: +-1% of nominal in ppb */ +#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000 + +/** + * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters + * @info: PTP clock info + * @ts: timespec to store current time + * @sts: optional system timestamp pair for cross-timestamping + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts); +} + +/** + * zl3073x_dpll_ptp_settime64 - set ToD counters to given time + * @info: PTP clock info + * @ts: timespec with time to set + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts); +} + +/** + * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step + * @zldpll: DPLL channel + * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC, + * NSEC_PER_SEC)) + * + * Uses the output phase step mechanism with tod_step=1 to adjust both + * the output clock phase and the ToD counter simultaneously. This keeps + * outputs and ToD coherent. Only valid for NCO. + * + * Outputs are grouped by synthesizer since the phase step value is in + * synthesizer clock cycles. The first synth group with enabled outputs + * uses tod_step to adjust both outputs and the ToD counter. Remaining + * groups step outputs only. If no synth has enabled outputs, the ToD + * counter is stepped alone using an empty output mask (the FW uses + * the first enabled synth's period for the conversion). + * + * Return: + * * %0 - success (or partial success if a later synth group + * failed after the first was already stepped) + * * %-EOPNOTSUPP - no synths available + * * negative - error + */ +static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll, + s64 delta) +{ + u16 synth_mask[ZL3073X_NUM_SYNTHS] = {}; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_synth *synth; + struct zl3073x_dpll_pin *pin; + u32 first_synth_freq = 0; + bool tod_stepped = false; + s32 step_cycles; + u32 synth_freq; + int rc; + u8 i; + + /* Build per-synth output masks from registered output pins */ + list_for_each_entry(pin, &zldpll->pins, list) { + u8 out_id, synth_id; + + if (zl3073x_dpll_is_input_pin(pin)) + continue; + + out_id = zl3073x_output_pin_out_get(pin->id); + + if (!zl3073x_dev_out_is_stepped(zldev, out_id)) + continue; + + synth_id = zl3073x_dev_out_synth_get(zldev, out_id); + if (synth_id >= ZL3073X_NUM_SYNTHS) { + dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n", + out_id); + continue; + } + synth_mask[synth_id] |= BIT(out_id); + } + + /* Process each synth group */ + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + synth = zl3073x_synth_state_get(zldev, i); + if (!zl3073x_synth_is_enabled(synth) || + zl3073x_synth_dpll_get(synth) != zldpll->id) + continue; + + synth_freq = zl3073x_synth_freq_get(synth); + + /* Remember first enabled synth freq for ToD-only fallback */ + if (!first_synth_freq) + first_synth_freq = synth_freq; + + if (!synth_mask[i]) + continue; + + /* Safe for s32: max synth freq is 750 MHz */ + step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC); + + rc = zl3073x_chan_phase_step(zldev, zldpll->id, + synth_mask[i], step_cycles, + !tod_stepped); + if (rc) { + if (tod_stepped) { + dev_warn(zldev->dev, + "Partial phase step failure\n"); + return 0; + } + return rc; + } + tod_stepped = true; + } + + if (!first_synth_freq) + return -EOPNOTSUPP; + + /* No enabled outputs found; step ToD counter only using the + * first enabled synth's period (empty output mask). + */ + if (!tod_stepped) { + step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC); + return zl3073x_chan_phase_step(zldev, zldpll->id, 0, + step_cycles, true); + } + + return 0; +} + +/** + * zl3073x_dpll_ptp_adjtime - adjust PTP clock time + * @info: PTP clock info + * @delta: time adjustment in nanoseconds + * + * For NCO, large deltas (>= 1 second) are split into a ToD + * read-modify-write for the seconds part and an output phase step for + * the sub-second remainder. Sub-second deltas use phase step directly, + * falling back to ToD read-modify-write if phase step or TIE write + * fails. In AUTO/REFLOCK modes, large deltas are split into ToD + * read-modify-write for seconds and TIE write for the sub-second + * remainder. Sub-second deltas use TIE write directly. + * + * If the seconds part was already committed when the sub-second + * mechanism fails, returns 0 to prevent the PTP servo from retrying + * the full delta and applying seconds again. + * + * Return: 0 on success (or partial success), <0 on error + */ +static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + bool sec_adjusted = false; + struct timespec64 ts; + int rc; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + /* Modes without phase step or TIE use plain ToD adjust */ + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan) && + !zl3073x_chan_mode_supports_tie(chan)) + return zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* Split off seconds via ToD read-modify-write so the sub-second + * remainder can be applied through the output-coherent mechanism + * (phase step or TIE write). + */ + if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) { + s32 remainder; + + ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder); + ts.tv_nsec = 0; + delta = remainder; + + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts); + if (rc) + return rc; + + /* No sub-second remainder, done */ + if (!delta) + return 0; + + /* Wait for the ToD write to be applied at the 1 Hz edge + * before issuing phase step or TIE write, so the pending + * WR_NEXT_1HZ does not overwrite the sub-second adjustment. + */ + rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id); + if (rc) + return rc; + + sec_adjusted = true; + } + + /* Apply sub-second delta via phase step (NCO) or TIE write */ + if (zl3073x_chan_mode_is_nco(chan)) { + rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta); + if (!rc) + return 0; + } else { + rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta); + if (!rc) + return 0; + } + + /* Phase step or TIE write failed, fall back to ToD adjust */ + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* In the unlikely event that both phase step/TIE write and fallback + * ToD adjust fail after seconds were already committed, return + * success to prevent the PTP servo from retrying the full delta and + * applying seconds again. The sub-second residual will self-correct + * in the next servo cycle. + */ + if (rc && sec_adjusted) { + dev_warn(zldev->dev, + "Sub-second adjustment failed after seconds applied\n"); + return 0; + } return rc; } /** - * zl3073x_dpll_alloc - allocate DPLL device - * @zldev: pointer to zl3073x device - * @ch: DPLL channel number + * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency + * @info: PTP clock info + * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16) * - * Allocates DPLL device structure for given DPLL channel. + * Only supported for NCO. Writes the delta frequency offset register. * - * Return: pointer to DPLL device on success, error pointer on error + * Return: + * * %0 - success or @scaled_ppm is zero (no-op) + * * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero + * * negative - other error */ -struct zl3073x_dpll * -zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +static int +zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm) { - struct zl3073x_dpll *zldpll; + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + const struct zl3073x_chan *chan; + s64 offset; - zldpll = kzalloc_obj(*zldpll); - if (!zldpll) - return ERR_PTR(-ENOMEM); + /* Convert scaled_ppm to df_offset in 2^-48 steps: + * df_offset = -(scaled_ppm * 2^32) / 10^6 + * + * Simplify to avoid overflow: + * df_offset = -(scaled_ppm * 2^26) / 5^6 + * df_offset = -(scaled_ppm * 67108864) / 15625 + */ + offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625); - zldpll->dev = zldev; - zldpll->id = ch; - INIT_LIST_HEAD(&zldpll->pins); + guard(mutex)(&zldpll->lock); - return zldpll; + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return scaled_ppm ? -EOPNOTSUPP : 0; + if (offset == chan->df_offset) + return 0; + + return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset); } /** - * zl3073x_dpll_free - free DPLL device - * @zldpll: pointer to zl3073x_dpll structure + * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase + * @info: PTP clock info + * @delta: phase adjustment in nanoseconds * - * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + * Only supported in AUTO and REFLOCK modes. Uses TIE write for + * nanosecond resolution phase adjustment. + * + * Return: + * * %0 - success or @delta is zero (no-op) + * * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero + * * negative - other error */ -void -zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta) { - WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; - kfree(zldpll); + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldev, zldpll->id); + + if (!zl3073x_chan_mode_supports_tie(chan)) + return -EOPNOTSUPP; + + return zl3073x_chan_tie_write(zldev, zldpll->id, delta); +} + +static s32 +zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused) +{ + /* HW limits TIE write to +-1 second. Return the constant HW + * limit and let adjphase handle mode-specific checks. + */ + return NSEC_PER_SEC - 1; +} + +static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = { + .owner = THIS_MODULE, + .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ, + .gettimex64 = zl3073x_dpll_ptp_gettimex64, + .settime64 = zl3073x_dpll_ptp_settime64, + .adjtime = zl3073x_dpll_ptp_adjtime, + .adjfine = zl3073x_dpll_ptp_adjfine, + .adjphase = zl3073x_dpll_ptp_adjphase, + .getmaxphase = zl3073x_dpll_ptp_getmaxphase, +}; + +/** + * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel + * @zldpll: DPLL channel to register PTP clock for + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + struct ptp_clock *ptp_clock; + + zldpll->ptp_info = zl3073x_dpll_ptp_clock_info; + snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name), + "%s-dpll%u", dev_name(zldev->dev), zldpll->id); + + ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev); + if (IS_ERR(ptp_clock)) { + dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n", + zldpll->id); + return PTR_ERR(ptp_clock); + } + + zldpll->ptp_clock = ptp_clock; + + return 0; +} + +/** + * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel + * @zldpll: DPLL channel to unregister PTP clock for + */ +static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll) +{ + if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) { + ptp_clock_unregister(zldpll->ptp_clock); + zldpll->ptp_clock = NULL; + } } /** @@ -2049,6 +2777,47 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) return 0; } +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc_obj(*zldpll); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + mutex_init(&zldpll->lock); + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + mutex_destroy(&zldpll->lock); + kfree(zldpll); +} + /** * zl3073x_dpll_register - register DPLL device and all its pins * @zldpll: pointer to zl3073x_dpll structure @@ -2079,6 +2848,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll) return rc; } + rc = zl3073x_dpll_ptp_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + return 0; } @@ -2092,7 +2868,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll) void zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll) { - /* Unregister all pins and dpll */ + zl3073x_dpll_ptp_unregister(zldpll); zl3073x_dpll_pins_unregister(zldpll); zl3073x_dpll_device_unregister(zldpll); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 21adcc18e45e..993221dc6324 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -5,6 +5,7 @@ #include #include +#include #include "core.h" @@ -18,8 +19,12 @@ * @ops: DPLL device operations for this instance * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference + * @lock: per-DPLL mutex serializing all operations + * @type: DPLL type (PPS or EEC) * @lock_status: last saved DPLL lock status * @pins: list of pins + * @ptp_info: PTP clock info + * @ptp_clock: registered PTP clock (or NULL) */ struct zl3073x_dpll { struct list_head list; @@ -30,8 +35,12 @@ struct zl3073x_dpll { struct dpll_device_ops ops; struct dpll_device *dpll_dev; dpll_tracker tracker; + struct mutex lock; + enum dpll_type type; enum dpll_lock_status lock_status; struct list_head pins; + struct ptp_clock_info ptp_info; + struct ptp_clock *ptp_clock; }; struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch); diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c index eb5628aebcee..410d15b96d0b 100644 --- a/drivers/dpll/zl3073x/out.c +++ b/drivers/dpll/zl3073x/out.c @@ -85,12 +85,8 @@ int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, - &out->phase_comp); - if (rc) - return rc; - - return rc; + return zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + &out->phase_comp); } /** diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 9578f0009528..f3a5e1215aa3 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -5,6 +5,7 @@ #include #include +#include /* * Hardware limits for ZL3073x chip family @@ -17,6 +18,7 @@ #define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) #define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ ZL3073X_NUM_OUTPUT_PINS) +#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS /* * Register address structure: @@ -164,10 +166,32 @@ #define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 #define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) +#define ZL_REG_DPLL_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0) +#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2) +#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7) + #define ZL_REG_DPLL_DF_READ(_idx) \ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1) #define ZL_DPLL_DF_READ_SEM BIT(4) #define ZL_DPLL_DF_READ_REF_OFST BIT(3) +#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) +#define ZL_DPLL_DF_READ_CMD_ACC_I 4 + +#define ZL_REG_DPLL_TIE_CTRL ZL_REG(5, 0x30, 1) +#define ZL_DPLL_TIE_CTRL_OP GENMASK(2, 0) +#define ZL_DPLL_TIE_CTRL_OP_WR 4 + +#define ZL_REG_DPLL_TIE_CTRL_MASK ZL_REG(5, 0x31, 1) + +#define ZL_REG_DPLL_TOD_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x38, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_TOD_CTRL_SEM BIT(4) +#define ZL_DPLL_TOD_CTRL_CMD GENMASK(3, 0) +#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ 1 +#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT 8 +#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ 9 #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_DPLL_MEAS_CTRL_EN BIT(0) @@ -183,6 +207,9 @@ /******************************* * Register Pages 6-7, DPLL Data + * + * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6, + * channel 4 on page 7. *******************************/ #define ZL_REG_DPLL_DF_OFFSET_03(_idx) \ @@ -190,6 +217,25 @@ #define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6) #define ZL_REG_DPLL_DF_OFFSET(_idx) \ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) +#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN + +#define ZL_REG_DPLL_TIE_DATA_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20) +#define ZL_REG_DPLL_TIE_DATA_4 ZL_REG(7, 0x0C, 6) +#define ZL_REG_DPLL_TIE_DATA(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4) + +#define ZL_REG_DPLL_TOD_SEC_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20) +#define ZL_REG_DPLL_TOD_SEC_4 ZL_REG(7, 0x12, 6) +#define ZL_REG_DPLL_TOD_SEC(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4) + +#define ZL_REG_DPLL_TOD_NS_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20) +#define ZL_REG_DPLL_TOD_NS_4 ZL_REG(7, 0x18, 4) +#define ZL_REG_DPLL_TOD_NS(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4) /*********************************** * Register Page 9, Synth and Output @@ -210,6 +256,23 @@ #define ZL_OUTPUT_CTRL_EN BIT(0) #define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4) +#define ZL_REG_OUTPUT_STEP_TIME_MASK ZL_REG(9, 0x36, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_CTRL ZL_REG(9, 0x38, 1) +#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL GENMASK(6, 4) +#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP BIT(3) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP GENMASK(1, 0) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE 0 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET 1 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ 2 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE 3 + +#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER ZL_REG(9, 0x39, 1) + +#define ZL_REG_OUTPUT_PHASE_STEP_MASK ZL_REG(9, 0x3a, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_DATA ZL_REG(9, 0x3c, 4) + /******************************* * Register Page 10, Ref Mailbox *******************************/ diff --git a/drivers/infiniband/core/lag.c b/drivers/infiniband/core/lag.c index 8fd80adfe833..00fe241737ff 100644 --- a/drivers/infiniband/core/lag.c +++ b/drivers/infiniband/core/lag.c @@ -36,7 +36,7 @@ static struct sk_buff *rdma_build_skb(struct net_device *netdev, uh->source = htons(rdma_flow_label_to_udp_sport(ah_attr->grh.flow_label)); uh->dest = htons(ROCE_V2_UDP_DPORT); - uh->len = htons(sizeof(struct udphdr)); + udp_set_len_short(uh, sizeof(struct udphdr)); if (is_ipv4) { skb_push(skb, sizeof(struct iphdr)); diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c index 3741b2c4b0bb..53daaf4c1eb2 100644 --- a/drivers/infiniband/sw/rxe/rxe_net.c +++ b/drivers/infiniband/sw/rxe/rxe_net.c @@ -242,7 +242,7 @@ static int rxe_udp_encap_recv(struct sock *sk, struct sk_buff *skb) pkt->port_num = 1; pkt->hdr = (u8 *)(udph + 1); pkt->mask = RXE_GRH_MASK; - pkt->paylen = be16_to_cpu(udph->len) - sizeof(*udph); + pkt->paylen = udp_get_len_short(udph) - sizeof(*udph); /* remove udp header */ skb_pull(skb, sizeof(struct udphdr)); @@ -305,7 +305,7 @@ static void prepare_udp_hdr(struct sk_buff *skb, __be16 src_port, udph->dest = dst_port; udph->source = src_port; - udph->len = htons(skb->len); + udp_set_len_short(udph, skb->len); udph->check = 0; } diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h index 940549d3b95d..ae3ece89d0aa 100644 --- a/drivers/mmc/core/quirks.h +++ b/drivers/mmc/core/quirks.h @@ -208,6 +208,9 @@ static const struct mmc_fixup __maybe_unused sdio_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_F0, add_limit_rate_quirk, 150000000), + SDIO_FIXUP(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X_BASE, + add_quirk, MMC_QUIRK_BLKSZ_FOR_BYTE_MODE), + END_FIXUP }; diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 182a41d59a75..bddc24e1856d 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -667,7 +667,7 @@ static void amt_send_discovery(struct amt_dev *amt) udph = udp_hdr(skb); udph->source = amt->gw_port; udph->dest = amt->relay_port; - udph->len = htons(sizeof(*udph) + sizeof(*amtd)); + udp_set_len_short(udph, sizeof(*udph) + sizeof(*amtd)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -708,11 +708,13 @@ static void amt_send_request(struct amt_dev *amt, bool v6) struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + __be32 remote_ip; struct sock *sk; u32 len; int err; rcu_read_lock(); + remote_ip = READ_ONCE(amt->remote_ip); sk = rcu_dereference(amt->sk); if (!sk) goto out; @@ -721,7 +723,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) goto out; rt = ip_route_output_ports(amt->net, &fl4, sk, - amt->remote_ip, amt->local_ip, + remote_ip, amt->local_ip, amt->gw_port, amt->relay_port, IPPROTO_UDP, 0, amt->stream_dev->ifindex); @@ -758,11 +760,11 @@ static void amt_send_request(struct amt_dev *amt, bool v6) udph = udp_hdr(skb); udph->source = amt->gw_port; udph->dest = amt->relay_port; - udph->len = htons(sizeof(*amtrh) + sizeof(*udph)); + udp_set_len_short(udph, sizeof(*amtrh) + sizeof(*udph)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); - udph->check = csum_tcpudp_magic(amt->local_ip, amt->remote_ip, + udph->check = csum_tcpudp_magic(amt->local_ip, remote_ip, sizeof(*udph) + sizeof(*amtrh), IPPROTO_UDP, skb->csum); @@ -773,7 +775,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) iph->tos = AMT_TOS; iph->frag_off = 0; iph->ttl = ip4_dst_hoplimit(&rt->dst); - iph->daddr = amt->remote_ip; + iph->daddr = remote_ip; iph->saddr = amt->local_ip; iph->protocol = IPPROTO_UDP; iph->tot_len = htons(len); @@ -962,7 +964,7 @@ static void amt_event_send_request(struct amt_dev *amt) amt->qi = AMT_INIT_REQ_TIMEOUT; WRITE_ONCE(amt->ready4, false); WRITE_ONCE(amt->ready6, false); - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt_update_gw_status(amt, AMT_STATUS_INIT, false); amt->req_cnt = 0; amt->nonce = 0; @@ -999,6 +1001,7 @@ static bool amt_send_membership_update(struct amt_dev *amt, struct sk_buff *skb, bool v6) { + __be32 remote_ip = READ_ONCE(amt->remote_ip); struct amt_header_membership_update *amtmu; struct iphdr *iph; struct flowi4 fl4; @@ -1018,13 +1021,13 @@ static bool amt_send_membership_update(struct amt_dev *amt, skb_reset_inner_headers(skb); memset(&fl4, 0, sizeof(struct flowi4)); fl4.flowi4_oif = amt->stream_dev->ifindex; - fl4.daddr = amt->remote_ip; + fl4.daddr = remote_ip; fl4.saddr = amt->local_ip; fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS); fl4.flowi4_proto = IPPROTO_UDP; rt = ip_route_output_key(amt->net, &fl4); if (IS_ERR(rt)) { - netdev_dbg(amt->dev, "no route to %pI4\n", &amt->remote_ip); + netdev_dbg(amt->dev, "no route to %pI4\n", &remote_ip); return true; } @@ -2286,8 +2289,8 @@ static bool amt_advertisement_handler(struct amt_dev *amt, struct sk_buff *skb) amt->nonce != amta->nonce) return true; - amt->remote_ip = amta->ip4; - netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amt->remote_ip); + WRITE_ONCE(amt->remote_ip, amta->ip4); + netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amta->ip4); mod_delayed_work(amt_wq, &amt->req_wq, 0); amt_update_gw_status(amt, AMT_STATUS_RECEIVED_ADVERTISEMENT, true); @@ -2647,7 +2650,7 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce, udph = udp_hdr(skb); udph->source = amt->relay_port; udph->dest = dport; - udph->len = htons(sizeof(*amta) + sizeof(*udph)); + udp_set_len_short(udph, sizeof(*amta) + sizeof(*udph)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -2811,6 +2814,7 @@ static void amt_gw_rcv(struct amt_dev *amt, struct sk_buff *skb) static int amt_rcv(struct sock *sk, struct sk_buff *skb) { struct amt_dev *amt; + __be32 remote_ip; __be32 saddr; int type; bool err; @@ -2822,6 +2826,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) kfree_skb(skb); goto out; } + remote_ip = READ_ONCE(amt->remote_ip); skb->dev = amt->dev; saddr = ip_hdr(skb)->saddr; @@ -2846,7 +2851,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) } goto out; case AMT_MSG_MULTICAST_DATA: - if (saddr != amt->remote_ip) { + if (saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -2857,7 +2862,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) else goto out; case AMT_MSG_MEMBERSHIP_QUERY: - if (saddr != amt->remote_ip) { + if (saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -3045,7 +3050,7 @@ static int amt_dev_open(struct net_device *dev) } amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->nonce = 0; get_random_bytes(&amt->key, sizeof(siphash_key_t)); @@ -3090,7 +3095,7 @@ static int amt_dev_stop(struct net_device *dev) amt->ready4 = false; amt->ready6 = false; amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) { list_del_rcu(&tunnel->list); @@ -3221,6 +3226,9 @@ static int amt_newlink(struct net_device *dev, struct nlattr **tb = params->tb; int err = -EINVAL; + if (!net_eq(link_net, dev_net(dev))) + return err; + amt->net = link_net; amt->mode = nla_get_u32(data[IFLA_AMT_MODE]); @@ -3289,7 +3297,7 @@ static int amt_newlink(struct net_device *dev, "gateway port must not be 0"); goto err; } - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->discovery_ip = nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]); if (ipv4_is_loopback(amt->discovery_ip) || ipv4_is_zeronet(amt->discovery_ip) || @@ -3355,8 +3363,10 @@ static size_t amt_get_size(const struct net_device *dev) static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct amt_dev *amt = netdev_priv(dev); + const struct amt_dev *amt = netdev_priv(dev); + __be32 remote_ip; + rcu_read_lock(); if (nla_put_u32(skb, IFLA_AMT_MODE, amt->mode)) goto nla_put_failure; if (nla_put_be16(skb, IFLA_AMT_RELAY_PORT, amt->relay_port)) @@ -3369,15 +3379,19 @@ static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) goto nla_put_failure; if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, amt->discovery_ip)) goto nla_put_failure; - if (amt->remote_ip) - if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, amt->remote_ip)) + + remote_ip = READ_ONCE(amt->remote_ip); + if (remote_ip) + if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_AMT_MAX_TUNNELS, amt->max_tunnels)) goto nla_put_failure; + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index 5ef841c85526..c3b5ed52d877 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -36,6 +36,7 @@ static unsigned int bareudp_net_id; struct bareudp_net { struct list_head bareudp_list; + struct mutex lock; }; struct bareudp_conf { @@ -636,10 +637,15 @@ static struct bareudp_dev *bareudp_find_dev(struct bareudp_net *bn, { struct bareudp_dev *bareudp, *t = NULL; + mutex_lock(&bn->lock); + list_for_each_entry(bareudp, &bn->bareudp_list, next) { if (conf->port == bareudp->port) t = bareudp; } + + mutex_unlock(&bn->lock); + return t; } @@ -675,7 +681,10 @@ static int bareudp_configure(struct net *net, struct net_device *dev, if (err) return err; + mutex_lock(&bn->lock); list_add(&bareudp->next, &bn->bareudp_list); + mutex_unlock(&bn->lock); + return 0; } @@ -692,12 +701,26 @@ static int bareudp_link_config(struct net_device *dev, return 0; } -static void bareudp_dellink(struct net_device *dev, struct list_head *head) +static void __bareudp_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct bareudp_dev *bareudp = netdev_priv(dev); - list_del(&bareudp->next); - unregister_netdevice_queue(dev, head); + list_del_init(&bareudp->next); + unregister_netdevice_queue_net(net, dev, head); +} + +static void bareudp_dellink(struct net_device *dev, struct list_head *head) +{ + struct bareudp_dev *bareudp = netdev_priv(dev); + struct bareudp_net *bn; + + bn = net_generic(bareudp->net, bareudp_net_id); + + mutex_lock(&bn->lock); + if (!list_empty(&bareudp->next)) + __bareudp_dellink(dev_net(dev), dev, head); + mutex_unlock(&bn->lock); } static int bareudp_newlink(struct net_device *dev, @@ -776,6 +799,8 @@ static __net_init int bareudp_init_net(struct net *net) struct bareudp_net *bn = net_generic(net, bareudp_net_id); INIT_LIST_HEAD(&bn->bareudp_list); + mutex_init(&bn->lock); + return 0; } @@ -785,13 +810,25 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net, struct bareudp_net *bn = net_generic(net, bareudp_net_id); struct bareudp_dev *bareudp, *next; + mutex_lock(&bn->lock); + list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next) - bareudp_dellink(bareudp->dev, dev_kill_list); + __bareudp_dellink(net, bareudp->dev, dev_kill_list); + + mutex_unlock(&bn->lock); +} + +static void __net_exit bareudp_exit_net(struct net *net) +{ + struct bareudp_net *bn = net_generic(net, bareudp_net_id); + + WARN_ON_ONCE(!list_empty(&bn->bareudp_list)); } static struct pernet_operations bareudp_net_ops = { .init = bareudp_init_net, .exit_rtnl = bareudp_exit_rtnl_net, + .exit = bareudp_exit_net, .id = &bareudp_net_id, .size = sizeof(struct bareudp_net), }; diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c index acbba08dbdfa..b8e4b4d68dd6 100644 --- a/drivers/net/bonding/bond_3ad.c +++ b/drivers/net/bonding/bond_3ad.c @@ -760,14 +760,14 @@ static int __agg_usable_ports(struct aggregator *agg) return valid; } -static int __agg_active_ports(struct aggregator *agg) +static int __agg_active_ports(const struct aggregator *agg) { - struct port *port; + const struct port *port; int active = 0; for (port = agg->lag_ports; port; port = port->next_port_in_aggregator) { - if (port->is_enabled) + if (READ_ONCE(port->is_enabled)) active++; } @@ -2801,11 +2801,11 @@ void bond_3ad_handle_link_change(struct slave *slave, char link) * some of he adaptors(ce1000.lan) report. */ if (link == BOND_LINK_UP) { - port->is_enabled = true; + WRITE_ONCE(port->is_enabled, true); ad_update_actor_keys(port, false); } else { /* link has failed */ - port->is_enabled = false; + WRITE_ONCE(port->is_enabled, false); ad_update_actor_keys(port, true); } agg = __get_first_agg(port); @@ -2878,16 +2878,20 @@ int bond_3ad_set_carrier(struct bonding *bond) * Returns: 0 on success * < 0 on error */ -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { - struct aggregator *aggregator = NULL, *tmp; + const struct aggregator *aggregator = NULL, *tmp; + struct ad_slave_info *ad_slave_info; + const struct port *port; struct list_head *iter; struct slave *slave; - struct port *port; bond_for_each_slave_rcu(bond, slave, iter) { - port = &(SLAVE_AD_INFO(slave)->port); + ad_slave_info = SLAVE_AD_INFO(slave); + if (!ad_slave_info) + continue; + port = &ad_slave_info->port; tmp = rcu_dereference(port->aggregator); if (tmp && tmp->is_active) { aggregator = tmp; @@ -2907,7 +2911,7 @@ int __bond_3ad_get_active_agg_info(struct bonding *bond, return 0; } -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info) +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { int ret; diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 522eab060f9e..ef9eb0c53c66 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -4858,12 +4858,6 @@ static int bond_set_mac_address(struct net_device *bond_dev, void *addr) __func__, slave); res = dev_set_mac_address(slave->dev, addr, NULL); if (res) { - /* TODO: consider downing the slave - * and retry ? - * User should expect communications - * breakage anyway until ARP finish - * updating, so... - */ slave_dbg(bond_dev, slave->dev, "%s: err %d\n", __func__, res); goto unwind; @@ -6644,7 +6638,9 @@ static int __init bonding_init(void) flow_keys_bonding_keys, ARRAY_SIZE(flow_keys_bonding_keys)); - register_netdevice_notifier(&bond_netdev_notifier); + res = register_netdevice_notifier(&bond_netdev_notifier); + if (res) + goto err; out: return res; err: diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 4a11572f663d..a28828b6e284 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -220,7 +220,7 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], struct bonding *bond = netdev_priv(bond_dev); struct bond_opt_value newval; int miimon = 0; - int err; + int err = 0; if (!data) return 0; @@ -372,7 +372,7 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], int arp_validate = nla_get_u32(data[IFLA_BOND_ARP_VALIDATE]); if (arp_validate && miimon) { - NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_INTERVAL], + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_VALIDATE], "ARP validating cannot be used with MII monitoring"); return -EINVAL; } @@ -686,53 +686,58 @@ static size_t bond_get_size(const struct net_device *bond_dev) 0; } -static int bond_option_active_slave_get_ifindex(struct bonding *bond) +static int bond_option_active_slave_get_ifindex_rcu(const struct bonding *bond) { - const struct net_device *slave; - int ifindex; + const struct net_device *dev = NULL; + const struct slave *slave; - rcu_read_lock(); - slave = bond_option_active_slave_get_rcu(bond); - ifindex = slave ? slave->ifindex : 0; - rcu_read_unlock(); - return ifindex; + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + dev = slave->dev; + return dev ? dev->ifindex : 0; } static int bond_fill_info(struct sk_buff *skb, const struct net_device *bond_dev) { - struct bonding *bond = netdev_priv(bond_dev); - unsigned int packets_per_slave; - int ifindex, i, targets_added; + const struct bonding *bond = netdev_priv(bond_dev); + int i, targets_added, miimon, mode; + const struct slave *primary; struct nlattr *targets; - struct slave *primary; - if (nla_put_u8(skb, IFLA_BOND_MODE, BOND_MODE(bond))) + rcu_read_lock(); + mode = READ_ONCE(bond->params.mode); + if (nla_put_u8(skb, IFLA_BOND_MODE, mode)) goto nla_put_failure; - ifindex = bond_option_active_slave_get_ifindex(bond); - if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) - goto nla_put_failure; + if (bond_mode_uses_primary(mode)) { + int ifindex = bond_option_active_slave_get_ifindex_rcu(bond); - if (nla_put_u32(skb, IFLA_BOND_MIIMON, bond->params.miimon)) + if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) + goto nla_put_failure; + } + + miimon = READ_ONCE(bond->params.miimon); + if (nla_put_u32(skb, IFLA_BOND_MIIMON, miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_UPDELAY, - bond->params.updelay * bond->params.miimon)) + READ_ONCE(bond->params.updelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_DOWNDELAY, - bond->params.downdelay * bond->params.miimon)) + READ_ONCE(bond->params.downdelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_PEER_NOTIF_DELAY, - bond->params.peer_notif_delay * bond->params.miimon)) + READ_ONCE(bond->params.peer_notif_delay) * miimon)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_USE_CARRIER, 1)) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, bond->params.arp_interval)) + if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, + READ_ONCE(bond->params.arp_interval))) goto nla_put_failure; targets = nla_nest_start_noflag(skb, IFLA_BOND_ARP_IP_TARGET); @@ -741,8 +746,10 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i]) { - if (nla_put_be32(skb, i, bond->params.arp_targets[i])) + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (t) { + if (nla_put_be32(skb, i, t)) goto nla_put_failure; targets_added = 1; } @@ -753,11 +760,12 @@ static int bond_fill_info(struct sk_buff *skb, else nla_nest_cancel(skb, targets); - if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, bond->params.arp_validate)) + if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, + READ_ONCE(bond->params.arp_validate))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_ARP_ALL_TARGETS, - bond->params.arp_all_targets)) + READ_ONCE(bond->params.arp_all_targets))) goto nla_put_failure; #if IS_ENABLED(CONFIG_IPV6) @@ -767,6 +775,9 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_NS_TARGETS; i++) { + /* Note: IPv6 addresses can not be read in an atomic READ_ONCE() yet. + * We accept this minor race for the moment. + */ if (!ipv6_addr_any(&bond->params.ns_targets[i])) { if (nla_put_in6_addr(skb, i, &bond->params.ns_targets[i])) goto nla_put_failure; @@ -780,97 +791,97 @@ static int bond_fill_info(struct sk_buff *skb, nla_nest_cancel(skb, targets); #endif - primary = rtnl_dereference(bond->primary_slave); + primary = rcu_dereference(bond->primary_slave); if (primary && nla_put_u32(skb, IFLA_BOND_PRIMARY, primary->dev->ifindex)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_PRIMARY_RESELECT, - bond->params.primary_reselect)) + READ_ONCE(bond->params.primary_reselect))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_FAIL_OVER_MAC, - bond->params.fail_over_mac)) + READ_ONCE(bond->params.fail_over_mac))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_XMIT_HASH_POLICY, - bond->params.xmit_policy)) + READ_ONCE(bond->params.xmit_policy))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_RESEND_IGMP, - bond->params.resend_igmp)) + READ_ONCE(bond->params.resend_igmp))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_NUM_PEER_NOTIF, - bond->params.num_peer_notif)) + READ_ONCE(bond->params.num_peer_notif))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_ALL_SLAVES_ACTIVE, - bond->params.all_slaves_active)) + READ_ONCE(bond->params.all_slaves_active))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_MIN_LINKS, - bond->params.min_links)) + READ_ONCE(bond->params.min_links))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_LP_INTERVAL, - bond->params.lp_interval)) + READ_ONCE(bond->params.lp_interval))) goto nla_put_failure; - packets_per_slave = bond->params.packets_per_slave; if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE, - packets_per_slave)) + READ_ONCE(bond->params.packets_per_slave))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_ACTIVE, - bond->params.lacp_active)) + READ_ONCE(bond->params.lacp_active))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_RATE, - bond->params.lacp_fast)) + READ_ONCE(bond->params.lacp_fast))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_SELECT, - bond->params.ad_select)) + READ_ONCE(bond->params.ad_select))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_TLB_DYNAMIC_LB, - bond->params.tlb_dynamic_lb)) + READ_ONCE(bond->params.tlb_dynamic_lb))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_MISSED_MAX, - bond->params.missed_max)) + READ_ONCE(bond->params.missed_max))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_COUPLED_CONTROL, - bond->params.coupled_control)) + READ_ONCE(bond->params.coupled_control))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_BROADCAST_NEIGH, - bond->params.broadcast_neighbor)) + READ_ONCE(bond->params.broadcast_neighbor))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT, - bond->params.lacp_strict)) + READ_ONCE(bond->params.lacp_strict))) goto nla_put_failure; - if (BOND_MODE(bond) == BOND_MODE_8023AD) { + if (mode == BOND_MODE_8023AD) { struct ad_info info; if (capable(CAP_NET_ADMIN)) { if (nla_put_u16(skb, IFLA_BOND_AD_ACTOR_SYS_PRIO, - bond->params.ad_actor_sys_prio)) + READ_ONCE(bond->params.ad_actor_sys_prio))) goto nla_put_failure; if (nla_put_u16(skb, IFLA_BOND_AD_USER_PORT_KEY, - bond->params.ad_user_port_key)) + READ_ONCE(bond->params.ad_user_port_key))) goto nla_put_failure; + /* Small race here, this is a minor trade off. */ if (nla_put(skb, IFLA_BOND_AD_ACTOR_SYSTEM, ETH_ALEN, &bond->params.ad_actor_system)) goto nla_put_failure; } - if (!bond_3ad_get_active_agg_info(bond, &info)) { + if (!__bond_3ad_get_active_agg_info(bond, &info)) { struct nlattr *nest; nest = nla_nest_start_noflag(skb, IFLA_BOND_AD_INFO); @@ -898,9 +909,11 @@ static int bond_fill_info(struct sk_buff *skb, } } + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index e590c8dee86e..36b8d89387ee 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -934,14 +934,14 @@ static int bond_option_mode_set(struct bonding *bond, /* don't cache arp_validate between modes */ WRITE_ONCE(bond->params.arp_validate, BOND_ARP_VALIDATE_NONE); - bond->params.mode = newval->value; + WRITE_ONCE(bond->params.mode, newval->value); /* When changing mode, the bond device is down, we may reduce * the bond_bcast_neigh_enabled in bond_close() if broadcast_neighbor * enabled in 8023ad mode. Therefore, only clear broadcast_neighbor * to 0. */ - bond->params.broadcast_neighbor = 0; + WRITE_ONCE(bond->params.broadcast_neighbor, 0); if (bond->dev->reg_state == NETREG_REGISTERED) { bool update = false; @@ -1706,7 +1706,7 @@ static int bond_option_lacp_strict_set(struct bonding *bond, { netdev_dbg(bond->dev, "Setting LACP fallback to %s (%llu)\n", newval->string, newval->value); - bond->params.lacp_strict = newval->value; + WRITE_ONCE(bond->params.lacp_strict, newval->value); bond_3ad_set_carrier(bond); return 0; @@ -1927,7 +1927,7 @@ static int bond_option_broadcast_neigh_set(struct bonding *bond, if (bond->params.broadcast_neighbor == newval->value) return 0; - bond->params.broadcast_neighbor = newval->value; + WRITE_ONCE(bond->params.broadcast_neighbor, newval->value); if (bond->dev->flags & IFF_UP) { if (bond->params.broadcast_neighbor) static_branch_inc(&bond_bcast_neigh_enabled); diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index eb856547ae7d..16f80607e150 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -2464,7 +2464,7 @@ struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, return ERR_PTR(ret); } - if (dev->of_node && of_property_read_bool(dev->of_node, "wakeup-source")) + if (dev->of_node && of_property_present(dev->of_node, "wakeup-source")) device_set_wakeup_capable(dev, true); /* Get TX FIFO size diff --git a/drivers/net/can/m_can/m_can_pci.c b/drivers/net/can/m_can/m_can_pci.c index eb31ed1f9644..d11a7c88fc32 100644 --- a/drivers/net/can/m_can/m_can_pci.c +++ b/drivers/net/can/m_can/m_can_pci.c @@ -126,7 +126,7 @@ static int m_can_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) mcan_class->net->irq = pci_irq_vector(pci, 0); mcan_class->pm_clock_support = 1; mcan_class->pm_wake_source = 0; - mcan_class->can.clock.freq = id->driver_data; + mcan_class->can.clock.freq = M_CAN_CLOCK_FREQ_EHL; mcan_class->irq_edge_triggered = true; mcan_class->ops = &m_can_pci_ops; @@ -183,8 +183,8 @@ static SIMPLE_DEV_PM_OPS(m_can_pci_pm_ops, m_can_pci_suspend, m_can_pci_resume); static const struct pci_device_id m_can_pci_id_table[] = { - { PCI_VDEVICE(INTEL, 0x4bc1), M_CAN_CLOCK_FREQ_EHL, }, - { PCI_VDEVICE(INTEL, 0x4bc2), M_CAN_CLOCK_FREQ_EHL, }, + { PCI_VDEVICE(INTEL, 0x4bc1) }, + { PCI_VDEVICE(INTEL, 0x4bc2) }, { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, m_can_pci_id_table); diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index 31cc9d0abd45..a5b8829aa519 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -211,8 +211,31 @@ static int tcan4x5x_write_fifo(struct m_can_classdev *cdev, return regmap_bulk_write(priv->regmap, TCAN4X5X_MRAM_START + addr_offset, val, val_count); } -static int tcan4x5x_power_enable(struct regulator *reg, int enable) +static int tcan4x5x_power_enable(struct tcan4x5x_priv *priv, int enable) { + struct regulator *reg = priv->power; + + /* + * Put the device into sleep mode if the RST pin is available, + * since a wake-up event, RST pin toggle, or power cycle are the only + * ways to exit sleep mode. + * Redundant if the regulator is exclusive to this device, but that + * can't be determined here. + * + * Datasheet: TCAN4550, section "8.4.3 Sleep Mode" + * https://www.ti.com/lit/gpn/tcan4550 + */ + if (priv->reset_gpio && !enable) { + int ret; + + ret = regmap_update_bits(priv->regmap, TCAN4X5X_CONFIG, + TCAN4X5X_MODE_SEL_MASK, + TCAN4X5X_MODE_SLEEP); + if (ret) + dev_err(&priv->spi->dev, "Setting sleep mode failed %pe\n", + ERR_PTR(ret)); + } + if (IS_ERR_OR_NULL(reg)) return 0; @@ -476,7 +499,7 @@ static int tcan4x5x_can_probe(struct spi_device *spi) goto out_m_can_class_free_dev; } - ret = tcan4x5x_power_enable(priv->power, 1); + ret = tcan4x5x_power_enable(priv, 1); if (ret) { dev_err(&spi->dev, "Enabling regulator failed %pe\n", ERR_PTR(ret)); @@ -531,7 +554,7 @@ static int tcan4x5x_can_probe(struct spi_device *spi) return 0; out_power: - tcan4x5x_power_enable(priv->power, 0); + tcan4x5x_power_enable(priv, 0); out_m_can_class_free_dev: m_can_class_free_dev(mcan_class->net); return ret; @@ -543,7 +566,7 @@ static void tcan4x5x_can_remove(struct spi_device *spi) m_can_class_unregister(&priv->cdev); - tcan4x5x_power_enable(priv->power, 0); + tcan4x5x_power_enable(priv, 0); m_can_class_free_dev(priv->cdev.net); } diff --git a/drivers/net/can/rockchip/rockchip_canfd-core.c b/drivers/net/can/rockchip/rockchip_canfd-core.c index 29de0c01e4ed..37c1c22c40c9 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-core.c +++ b/drivers/net/can/rockchip/rockchip_canfd-core.c @@ -50,6 +50,12 @@ static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3568v3 = { RKCANFD_QUIRK_CANFD_BROKEN, }; +static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3588 = { + .model = RKCANFD_MODEL_RK3588, + .quirks = RKCANFD_QUIRK_RK3568_ERRATUM_5 | + RKCANFD_QUIRK_RK3568_ERRATUM_6, +}; + static const char *__rkcanfd_get_model_str(enum rkcanfd_model model) { switch (model) { @@ -57,6 +63,8 @@ static const char *__rkcanfd_get_model_str(enum rkcanfd_model model) return "rk3568v2"; case RKCANFD_MODEL_RK3568V3: return "rk3568v3"; + case RKCANFD_MODEL_RK3588: + return "rk3588"; } return ""; @@ -148,6 +156,12 @@ static int rkcanfd_set_bittiming(struct rkcanfd_priv *priv) rkcanfd_write(priv, RKCANFD_REG_FD_DATA_BITTIMING, reg_dbt); + /* RK3588 CAN-FD BRS works with TDC disabled. */ + if (priv->devtype_data.model == RKCANFD_MODEL_RK3588) { + rkcanfd_write(priv, RKCANFD_REG_TRANSMIT_DELAY_COMPENSATION, 0); + return 0; + } + tdco = (priv->can.clock.freq / dbt->bitrate) * 2 / 3; tdco = min(tdco, FIELD_MAX(RKCANFD_REG_TRANSMIT_DELAY_COMPENSATION_TDC_OFFSET)); @@ -846,6 +860,9 @@ static const struct of_device_id rkcanfd_of_match[] = { }, { .compatible = "rockchip,rk3568v3-canfd", .data = &rkcanfd_devtype_data_rk3568v3, + }, { + .compatible = "rockchip,rk3588-canfd", + .data = &rkcanfd_devtype_data_rk3588, }, { /* sentinel */ }, diff --git a/drivers/net/can/rockchip/rockchip_canfd-rx.c b/drivers/net/can/rockchip/rockchip_canfd-rx.c index 475c0409e215..24e87daa1df0 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-rx.c +++ b/drivers/net/can/rockchip/rockchip_canfd-rx.c @@ -281,7 +281,10 @@ rkcanfd_rx_fifo_get_len(const struct rkcanfd_priv *priv) { const u32 reg = rkcanfd_read(priv, RKCANFD_REG_RX_FIFO_CTRL); - return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT, reg); + if (priv->devtype_data.model == RKCANFD_MODEL_RK3588) + return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588, reg); + + return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568, reg); } int rkcanfd_handle_rx_int(struct rkcanfd_priv *priv) diff --git a/drivers/net/can/rockchip/rockchip_canfd.h b/drivers/net/can/rockchip/rockchip_canfd.h index 93131c7d7f54..95bea9bfd8a2 100644 --- a/drivers/net/can/rockchip/rockchip_canfd.h +++ b/drivers/net/can/rockchip/rockchip_canfd.h @@ -214,7 +214,8 @@ #define RKCANFD_REG_TXEVENT_FIFO_CTRL_TXE_FIFO_ENABLE BIT(0) #define RKCANFD_REG_RX_FIFO_CTRL 0x118 -#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT GENMASK(6, 4) +#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568 GENMASK(6, 4) +#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588 GENMASK(7, 5) #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_FULL_WATERMARK GENMASK(3, 1) #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_ENABLE BIT(0) @@ -331,6 +332,11 @@ * rarely with the standard clock of 300 MHz, but almost immediately * at 80 MHz. * + * Tests on the rk3588 show the same empty FIFO condition. + * In that setup rx_fifo_empty_errors increments when the bus + * transitions from idle to high CAN-FD load and stops growing once + * the bus reaches a steady state. + * * To workaround this problem, check for empty FIFO with * rkcanfd_fifo_header_empty() in rkcanfd_handle_rx_int_one() and exit * early. @@ -344,6 +350,8 @@ /* Erratum 6: The CAN controller's transmission of extended frames may * intermittently change into standard frames * + * Tests on the rk3588 show the same problem. + * * Work around this issue by activating self reception (RXSTX). If we * have pending TX CAN frames, check all RX'ed CAN frames in * rkcanfd_rxstx_filter(). @@ -424,6 +432,9 @@ * cansequence -rv -i 1 * * - TX starvation after repeated Bus-Off + * Tests on the rk3588 show the same problem. In a + * 10-cycle Bus-Off recovery test, 9 cycles failed to send after the + * controller restarted. * To reproduce: * host: * sleep 3 && cangen can0 -I2 -Li -Di -p10 -g 0.0 @@ -434,6 +445,7 @@ enum rkcanfd_model { RKCANFD_MODEL_RK3568V2 = 0x35682, RKCANFD_MODEL_RK3568V3 = 0x35683, + RKCANFD_MODEL_RK3588 = 0x3588, }; struct rkcanfd_devtype_data { diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index 82508a865095..3b9b2f104d86 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -1565,7 +1565,7 @@ static int gs_usb_probe(struct usb_interface *intf, if (icount > type_max(parent->channel_cnt)) { dev_err(&intf->dev, - "Driver cannot handle more that %u CAN interfaces\n", + "Driver cannot handle more than %u CAN interfaces\n", type_max(parent->channel_cnt)); return -EINVAL; } diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index e882250180ef..9e2e25d02471 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -33,6 +33,7 @@ MODULE_ALIAS_RTNL_LINK(DRV_NAME); struct vxcan_priv { struct net_device __rcu *peer; + netdevice_tracker peer_tracker; }; static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) @@ -268,9 +269,11 @@ static int vxcan_newlink(struct net_device *dev, /* cross link the device pair */ priv = netdev_priv(dev); rcu_assign_pointer(priv->peer, peer); + netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL); priv = netdev_priv(peer); rcu_assign_pointer(priv->peer, dev); + netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL); return 0; @@ -281,24 +284,25 @@ static int vxcan_newlink(struct net_device *dev, static void vxcan_dellink(struct net_device *dev, struct list_head *head) { + netdevice_tracker *peer_tracker; struct vxcan_priv *priv; struct net_device *peer; priv = netdev_priv(dev); - peer = rtnl_dereference(priv->peer); + peer_tracker = &priv->peer_tracker; + peer = unrcu_pointer(xchg(&priv->peer, NULL)); + if (!peer) + return; - /* Note : dellink() is called from default_device_exit_batch(), - * before a rcu_synchronize() point. The devices are guaranteed - * not being freed before one RCU grace period. - */ - RCU_INIT_POINTER(priv->peer, NULL); unregister_netdevice_queue(dev, head); - if (peer) { - priv = netdev_priv(peer); - RCU_INIT_POINTER(priv->peer, NULL); - unregister_netdevice_queue(peer, head); - } + priv = netdev_priv(peer); + dev = unrcu_pointer(xchg(&priv->peer, NULL)); + if (dev) + unregister_netdevice_queue_net(dev_net(dev), peer, head); + + netdev_put(peer, peer_tracker); + netdev_put(dev, &priv->peer_tracker); } static const struct nla_policy vxcan_policy[VXCAN_INFO_MAX + 1] = { diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 4ab567c5bbaf..676fb7dffe14 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -63,6 +63,16 @@ config NET_DSA_MT7530_MMIO are directly mapped into the SoCs register space rather than being accessible via MDIO. +config NET_DSA_MT7628 + tristate "MediaTek MT7628 Embedded Ethernet switch support" + depends on HAS_IOMEM && (SOC_MT7620 || COMPILE_TEST) + select NET_DSA_TAG_MT7628 + select MEDIATEK_FE_SOC_PHY + select REGMAP_MMIO + help + This enables support for the built-in Ethernet switch found + in the MT7628 SoC. + config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" select NET_DSA_TAG_TRAILER diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index d2975badffc0..6ceb78a755d7 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o +obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 3f5b9592794d..0880310c9ce3 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2219,7 +2219,7 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, mutex_unlock(&priv->arl_mutex); - return 0; + return ret; } EXPORT_SYMBOL(b53_fdb_dump); diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index cd27a7344e89..29cca7945df7 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "b53_regs.h" @@ -467,7 +468,7 @@ static inline void b53_arl_search_read(struct b53_device *dev, u8 idx, #ifdef CONFIG_BCM47XX #include -#include +#include #include static inline struct gpio_desc *b53_switch_get_reset_gpio(struct b53_device *dev) { diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 138f2ab0774e..941ae9f66f70 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -36,6 +36,59 @@ #include "ksz8_reg.h" #include "ksz8.h" +/* + * We use only the high-byte (so odd addresses) of the 16-bits registers to fit + * in the common IRQ framework + */ +#define KSZ8463_REG_ISR 0x191 +#define KSZ8463_REG_IER 0x193 + +/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. + * variants. + * This values are documented in KSZ8873 and KSZ8863 datasheets. + */ +static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { + { 0, 8000 }, + { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, +}; + +struct ksz88xx_stats_raw { + u64 rx; + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 tx; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_discards; + u64 tx_discards; +}; + static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { ksz_rmw8(dev, addr, bits, set ? bits : 0); @@ -135,6 +188,69 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat return ksz8_ind_write8(dev, table, (u8)(offset), data); } +static void ksz8463_irq_mask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked &= ~BIT(d->hwirq); +} + +static void ksz8463_irq_unmask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked |= BIT(d->hwirq); +} + +static const struct irq_chip ksz8463_irq_chip = { + .name = "ksz8463-irq", + .irq_mask = ksz8463_irq_mask, + .irq_unmask = ksz8463_irq_unmask, + .irq_bus_lock = ksz_irq_bus_lock, + .irq_bus_sync_unlock = ksz_irq_bus_sync_unlock, +}; + +static int ksz8463_irq_domain_map(struct irq_domain *d, + unsigned int irq, irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ksz8463_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops ksz8463_irq_domain_ops = { + .map = ksz8463_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int ksz8463_girq_setup(struct ksz_device *dev) +{ + struct ksz_irq *girq = &dev->girq; + + girq->nirqs = 8; + girq->reg_mask = KSZ8463_REG_IER; + girq->reg_status = KSZ8463_REG_ISR; + girq->masked = 0; + snprintf(girq->name, sizeof(girq->name), "ksz8463-girq"); + + girq->irq_num = dev->irq; + + return ksz_irq_common_setup(dev, girq, &ksz8463_irq_domain_ops); +} + +static int ksz8463_reset_switch(struct ksz_device *dev) +{ + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, + true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, + false); + return 0; +} + static int ksz8_reset_switch(struct ksz_device *dev) { if (ksz_is_ksz88x3(dev)) { @@ -143,11 +259,6 @@ static int ksz8_reset_switch(struct ksz_device *dev) KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true); ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); - } else if (ksz_is_ksz8463(dev)) { - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, true); - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, false); } else { /* reset switch */ ksz_write8(dev, REG_POWER_MANAGEMENT_1, @@ -158,10 +269,17 @@ static int ksz8_reset_switch(struct ksz_device *dev) return 0; } -static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz88xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; + int frame_size; u8 ctrl2 = 0; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size <= KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE; else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -171,11 +289,18 @@ static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) KSZ8863_HUGE_PACKET_ENABLE, ctrl2); } -static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz87xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; u8 ctrl1 = 0, ctrl2 = 0; + u16 frame_size; int ret; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size > KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= SW_LEGAL_PACKET_DISABLE; if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -188,29 +313,14 @@ static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) return ksz_rmw8(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, ctrl2); } -static int ksz8_change_mtu(struct dsa_switch *ds, int port, int mtu) +static int ksz87xx_max_mtu(struct dsa_switch *ds, int port) { - struct ksz_device *dev = ds->priv; - u16 frame_size; + return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} - if (!dsa_is_cpu_port(dev->ds, port)) - return 0; - - frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return ksz8795_change_mtu(dev, frame_size); - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return ksz8863_change_mtu(dev, frame_size); - } - - return -EOPNOTSUPP; +static int ksz88xx_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; } static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) @@ -1746,6 +1856,159 @@ static void ksz8_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ + u8 reg; + + reg = 0xC8 + port * 4; + reg += ((3 - queue) / 2) * 2; + reg++; + reg -= (queue & 1); + return reg; +} + +/** + * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to configure + * @p: Pointer to offload replace parameters describing ETS bands and mapping + * + * The KSZ88x3 supports two scheduling modes: Strict Priority and + * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: + * - No configurable queue-to-priority mapping + * - No weight adjustment in WFQ mode + * + * This function configures the switch to use strict priority mode by + * clearing the WFQ enable bit for all queues associated with ETS bands. + * If strict priority is not explicitly requested, the switch will default + * to WFQ mode. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) +{ + int ret, band; + + /* Only strict priority mode is supported for now. + * WFQ is implicitly enabled when strict mode is disabled. + */ + for (band = 0; band < p->bands; band++) { + int queue = ksz_ets_band_to_queue(p, band); + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Clear WFQ enable bit to select strict priority scheduling */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); + if (ret) + return ret; + } + + return 0; +} + +/** + * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to reset + * + * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or + * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or + * queue mapping. This function resets the port’s scheduling mode to + * the default, which is WFQ, by enabling the WFQ bit for all queues. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) +{ + int ret, queue; + + /* Iterate over all transmit queues for this port */ + for (queue = 0; queue < dev->info->num_tx_queues; queue++) { + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Set WFQ enable bit to revert back to default scheduling + * mode + */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, + KSZ8873_TXQ_WFQ_ENABLE); + if (ret) + return ret; + } + + return 0; +} + +static int ksz8_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, + struct tc_ets_qopt_offload *qopt) +{ + struct ksz_device *dev = ds->priv; + int ret; + + if (!(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) + return -EOPNOTSUPP; + + if (qopt->parent != TC_H_ROOT) { + dev_err(dev->dev, "Parent should be \"root\"\n"); + return -EOPNOTSUPP; + } + + switch (qopt->command) { + case TC_ETS_REPLACE: + ret = ksz_tc_ets_validate(dev, port, &qopt->replace_params); + if (ret) + return ret; + + return ksz88x3_tc_ets_add(dev, port, &qopt->replace_params); + case TC_ETS_DESTROY: + return ksz88x3_tc_ets_del(dev, port); + case TC_ETS_STATS: + case TC_ETS_GRAFT: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int ksz87xx_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + +static int ksz8_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + case TC_SETUP_QDISC_ETS: + return ksz8_tc_setup_qdisc_ets(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + static void ksz8795_cpu_interface_select(struct ksz_device *dev, int port) { struct ksz_port *p = &dev->ports[port]; @@ -1828,6 +2091,50 @@ static void ksz88x3_config_rmii_clk(struct ksz_device *dev) KSZ88X3_PORT3_RMII_CLK_INTERNAL, rmii_clk_internal); } +static void ksz8463_config_cpu_port(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *p; + u8 fiber_ports = 0; + const u32 *masks; + const u16 *regs; + int i; + + masks = dev->info->masks; + regs = dev->info->regs; + + ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true); + + ksz8_port_setup(dev, dev->cpu_port, true); + + for (i = 0; i < dev->phy_port_cnt; i++) + ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); + + for (i = 0; i < dev->phy_port_cnt; i++) { + p = &dev->ports[i]; + ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, + p->fiber); + if (p->fiber) + fiber_ports |= (1 << i); + } + + /* Setup fiber ports. */ + if (fiber_ports) { + fiber_ports &= 3; + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_CFG_CTRL, + fiber_ports << PORT_COPPER_MODE_S, + 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_DSP_CTRL_6, + COPPER_RECEIVE_ADJUSTMENT, 0); + } + + /* Turn off PTP function as the switch enables it by default */ + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_MSG_CONF1, + PTP_ENABLE, 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_CLK_CTRL, + PTP_CLK_ENABLE, 0); +} + static void ksz8_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -1835,7 +2142,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) const u32 *masks; const u16 *regs; u8 remote; - u8 fiber_ports = 0; int i; masks = dev->info->masks; @@ -1866,32 +2172,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) else ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, false); - if (p->fiber) - fiber_ports |= (1 << i); - } - if (ksz_is_ksz8463(dev)) { - /* Setup fiber ports. */ - if (fiber_ports) { - fiber_ports &= 3; - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_CFG_CTRL, - fiber_ports << PORT_COPPER_MODE_S, - 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_DSP_CTRL_6, - COPPER_RECEIVE_ADJUSTMENT, 0); - } - - /* Turn off PTP function as the switch's proprietary way of - * handling timestamp is not supported in current Linux PTP - * stack implementation. - */ - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_MSG_CONF1, - PTP_ENABLE, 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_CLK_CTRL, - PTP_CLK_ENABLE, 0); } } @@ -2053,6 +2333,315 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev) return ksz8_w_sta_mac_table(dev, 0, &alu); } +static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ethtool_pause_stats *pstats; + struct rtnl_link_stats64 *stats; + struct ksz88xx_stats_raw *raw; + struct ksz_port_mib *mib; + + mib = &dev->ports[port].mib; + stats = &mib->stats64; + pstats = &mib->pause_stats; + raw = (struct ksz88xx_stats_raw *)mib->counters; + + spin_lock(&mib->stats64_lock); + + stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + + raw->rx_pause; + stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + + raw->tx_pause; + + /* HW counters are counting bytes + FCS which is not acceptable + * for rtnl_link_stats64 interface + */ + stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; + stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; + + stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + + raw->rx_oversize; + + stats->rx_crc_errors = raw->rx_crc_err; + stats->rx_frame_errors = raw->rx_align_err; + stats->rx_dropped = raw->rx_discards; + stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + + stats->rx_frame_errors + stats->rx_dropped; + + stats->tx_window_errors = raw->tx_late_col; + stats->tx_fifo_errors = raw->tx_discards; + stats->tx_aborted_errors = raw->tx_exc_col; + stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + + stats->tx_aborted_errors; + + stats->multicast = raw->rx_mcast; + stats->collisions = raw->tx_total_col; + + pstats->tx_pause_frames = raw->tx_pause; + pstats->rx_pause_frames = raw->rx_pause; + + spin_unlock(&mib->stats64_lock); +} + +static int ksz8463_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + u16 storm_mask, storm_rate; + struct ksz_port *p; + const u16 *regs; + int i, ret; + + regs = dev->info->regs; + + dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table), + dev->info->num_vlans, GFP_KERNEL); + if (!dev->vlan_cache) + return -ENOMEM; + + ret = ksz8463_reset_switch(dev); + if (ret) { + dev_err(ds->dev, "failed to reset switch\n"); + return ret; + } + + /* set broadcast storm protection 10% rate */ + storm_mask = BROADCAST_STORM_RATE; + storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; + storm_mask = swab16(storm_mask); + storm_rate = swab16(storm_rate); + regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], + storm_mask, storm_rate); + + ksz8463_config_cpu_port(ds); + + ksz8_enable_stp_addr(dev); + + ds->num_tx_queues = dev->info->num_tx_queues; + + regmap_update_bits(ksz_regmap_8(dev), regs[S_MULTICAST_CTRL], + MULTICAST_STORM_DISABLE, MULTICAST_STORM_DISABLE); + + ksz_init_mib_timer(dev); + + ds->configure_vlan_while_not_filtering = false; + ds->dscp_prio_mapping_is_global = true; + ds->mtu_enforcement_ingress = true; + + /* We rely on software untagging on the CPU port, so that we + * can support both tagged and untagged VLANs + */ + ds->untag_bridge_pvid = true; + + /* VLAN filtering is partly controlled by the global VLAN + * Enable flag + */ + ds->vlan_filtering_is_global = true; + + /* Enable automatic fast aging when link changed detected. */ + ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true); + + /* Enable aggressive back off algorithm in half duplex mode. */ + ret = ksz_rmw8(dev, REG_SW_CTRL_1, SW_AGGR_BACKOFF, SW_AGGR_BACKOFF); + if (ret) + return ret; + + /* + * Make sure unicast VLAN boundary is set as default and + * enable no excessive collision drop. + */ + ret = ksz_rmw8(dev, REG_SW_CTRL_2, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP); + if (ret) + return ret; + + ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false); + + ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); + + for (i = 0; i < (dev->info->num_vlans / 4); i++) + ksz8_r_vlan_entries(dev, i); + + /* Start with learning disabled on standalone user ports, and enabled + * on the CPU port. In lack of other finer mechanisms, learning on the + * CPU port will avoid flooding bridge local addresses on the network + * in some cases. + */ + p = &dev->ports[dev->cpu_port]; + p->learning = true; + + if (dev->irq > 0) { + ret = ksz8463_girq_setup(dev); + if (ret) + return ret; + + ret = ksz8463_ptp_irq_setup(ds); + if (ret) + goto free_girq; + + ret = ksz_ptp_clock_register(ds); + if (ret) { + dev_err(dev->dev, "Failed to register PTP clock: %d\n", + ret); + goto free_ptp_irq; + } + } + + ret = ksz_mdio_register(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to register the mdio"); + goto ptp_clock_unregister; + } + + ret = ksz_dcb_init(dev); + if (ret) + goto ptp_clock_unregister; + + /* start switch */ + regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], + SW_START, SW_START); + + return 0; + +ptp_clock_unregister: + if (dev->irq > 0) + ksz_ptp_clock_unregister(ds); +free_ptp_irq: + if (dev->irq > 0) + ksz8463_ptp_irq_free(ds); +free_girq: + if (dev->irq > 0) + ksz_irq_free(&dev->girq); + + return ret; +} + +static void ksz8463_teardown(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + + if (dev->irq > 0) { + ksz_ptp_clock_unregister(ds); + ksz8463_ptp_irq_free(ds); + ksz_irq_free(&dev->girq); + } +} + +/** + * ksz88x3_drive_strength_write() - Set the drive strength configuration for + * KSZ8863 compatible chip variants. + * @dev: ksz device + * @props: Array of drive strength properties to be set + * @num_props: Number of properties in the array + * + * This function applies the specified drive strength settings to KSZ88X3 chip + * variants (KSZ8873, KSZ8863). + * It ensures the configurations align with what the chip variant supports and + * warns or errors out on unsupported settings. + * + * Return: 0 on success, error code otherwise + */ +static int ksz88x3_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) +{ + size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); + int microamp; + int i, ret; + + for (i = 0; i < num_props; i++) { + if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + props[i].name); + } + + microamp = props[KSZ_DRIVER_STRENGTH_IO].value; + ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, + microamp); + if (ret < 0) { + ksz_drive_strength_error(dev, ksz88x3_drive_strengths, + array_size, microamp); + return ret; + } + + return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, + KSZ8873_DRIVE_STRENGTH_16MA, ret); +} + +/** + * ksz8_parse_drive_strength() - Extract and apply drive strength configurations + * from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz8_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + switch (dev->chip_id) { + case KSZ88X3_CHIP_ID: + return ksz88x3_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + case KSZ8795_CHIP_ID: + case KSZ8794_CHIP_ID: + case KSZ8765_CHIP_ID: + return ksz_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + default: + /* KSZ8864, KSZ8895 */ + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + if (of_props[i].value == -1) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + of_props[i].name); + } + } + + return 0; +} + static int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -2075,17 +2664,13 @@ static int ksz8_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz8_parse_drive_strength(dev); if (ret) return ret; /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; - if (ksz_is_ksz8463(dev)) { - storm_mask = swab16(storm_mask); - storm_rate = swab16(storm_rate); - } regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], storm_mask, storm_rate); @@ -2136,7 +2721,7 @@ static int ksz8_setup(struct dsa_switch *ds) ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); - if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev)) + if (!ksz_is_ksz88x3(dev)) ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true); for (i = 0; i < (dev->info->num_vlans / 4); i++) @@ -2260,6 +2845,9 @@ static u32 ksz8_get_port_addr(int port, int offset) static u32 ksz8463_get_port_addr(int port, int offset) { + if (offset >= KSZ8463_PTP_CLK_CTRL) + return offset + 0x20 * port; + return offset + 0x18 * port; } @@ -2392,7 +2980,7 @@ static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) { - return DSA_TAG_PROTO_KSZ9893; + return DSA_TAG_PROTO_KSZ8463; } static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, @@ -2400,7 +2988,7 @@ static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, { struct ksz_tagger_data *tagger_data; - if (proto != DSA_TAG_PROTO_KSZ9893) + if (proto != DSA_TAG_PROTO_KSZ8463) return -EPROTONOSUPPORT; tagger_data = ksz_tagger_data(ds); @@ -2526,8 +3114,8 @@ const struct ksz_dev_ops ksz88xx_dev_ops = { const struct dsa_switch_ops ksz8463_switch_ops = { .get_tag_protocol = ksz8463_get_tag_protocol, .connect_tag_protocol = ksz8463_connect_tag_protocol, - .setup = ksz8_setup, - .teardown = ksz_teardown, + .setup = ksz8463_setup, + .teardown = ksz8463_teardown, .phy_read = ksz8463_phy_read16, .phy_write = ksz8463_phy_write16, .phylink_get_caps = ksz8_phylink_get_caps, @@ -2551,16 +3139,16 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz88xx_change_mtu, + .port_max_mtu = ksz88xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, - .get_ts_info = ksz_get_ts_info, + .get_ts_info = ksz8463_get_ts_info, .port_hwtstamp_get = ksz_hwtstamp_get, - .port_hwtstamp_set = ksz_hwtstamp_set, + .port_hwtstamp_set = ksz8463_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2601,8 +3189,8 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz87xx_change_mtu, + .port_max_mtu = ksz87xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, @@ -2610,7 +3198,7 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz87xx_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2652,8 +3240,8 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz88xx_change_mtu, + .port_max_mtu = ksz88xx_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, @@ -2663,7 +3251,7 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 981ab441d9b7..6bc511da1f7d 100644 --- a/drivers/net/dsa/microchip/ksz8_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -786,6 +786,7 @@ #define KSZ8463_REG_SW_RESET 0x126 #define KSZ8463_GLOBAL_SOFTWARE_RESET BIT(0) +#define KSZ8463_PTP_SOFTWARE_RESET BIT(2) #define KSZ8463_PTP_CLK_CTRL 0x600 diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index f3f0c98dfb5a..3ee995545c57 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "ksz9477_reg.h" @@ -60,6 +61,11 @@ static int ksz9477_change_mtu(struct dsa_switch *ds, int port, int mtu) REG_SW_MTU_MASK, frame_size); } +int ksz9477_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { unsigned int val; @@ -478,8 +484,8 @@ static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port, return ret; } -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col) +static int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) { u8 status; int ret; @@ -497,6 +503,24 @@ int ksz9477_errata_monitor(struct ksz_device *dev, int port, return ret; } +static void ksz9477_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ksz_stats_raw *raw; + struct ksz_port_mib *mib; + int ret; + + ksz_r_mib_stats64(dev, port); + + if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { + mib = &dev->ports[port].mib; + raw = (struct ksz_stats_raw *)mib->counters; + + ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); + if (ret) + dev_err(dev->dev, "Failed to monitor transmission halt\n"); + } +}; + void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; @@ -1176,6 +1200,58 @@ void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static bool ksz9477_get_gbit(struct ksz_device *dev, int port) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + bool gbit = false; + u8 data8; + bool val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_GMII_1GBIT_M, data8); + + if (val == bitval[P_GMII_1GBIT]) + gbit = true; + + return gbit; +} + +static phy_interface_t ksz9477_get_xmii(struct ksz_device *dev, int port, + bool gbit) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + phy_interface_t interface; + u8 data8; + u8 val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_MII_SEL_M, data8); + + if (val == bitval[P_MII_SEL]) { + if (gbit) + interface = PHY_INTERFACE_MODE_GMII; + else + interface = PHY_INTERFACE_MODE_MII; + } else if (val == bitval[P_RMII_SEL]) { + interface = PHY_INTERFACE_MODE_RMII; + } else { + interface = PHY_INTERFACE_MODE_RGMII; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_TXID; + if (data8 & P_RGMII_ID_IG_ENABLE) { + interface = PHY_INTERFACE_MODE_RGMII_RXID; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_ID; + } + } + + return interface; +} + static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) { phy_interface_t interface; @@ -1184,9 +1260,9 @@ static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) if (dev->info->internal_phy[port]) return PHY_INTERFACE_MODE_NA; - gbit = ksz_get_gbit(dev, port); + gbit = ksz9477_get_gbit(dev, port); - interface = ksz_get_xmii(dev, port, gbit); + interface = ksz9477_get_xmii(dev, port, gbit); return interface; } @@ -1335,6 +1411,28 @@ static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) +{ + u32 queue_map = 0; + int ipm; + + for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { + int queue; + + /* Traffic Type (TT) is corresponding to the Internal Priority + * Map (IPM) in the switch. Traffic Class (TC) is + * corresponding to the queue in the switch. + */ + queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); + if (queue < 0) + return queue; + + queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); + } + + return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); +} + static int ksz9477_dsa_port_setup(struct dsa_switch *ds, int port) { struct ksz_device *dev = ds->priv; @@ -1517,6 +1615,58 @@ int ksz9477_enable_stp_addr(struct ksz_device *dev) return 0; } +/** + * ksz9477_parse_drive_strength() - Extract and apply drive strength + * configurations from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz9477_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + return ksz_drive_strength_write(dev, of_props, ARRAY_SIZE(of_props)); +} static int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -1539,7 +1689,7 @@ static int ksz9477_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz9477_parse_drive_strength(dev); if (ret) return ret; @@ -2052,7 +2202,7 @@ const struct ksz_dev_ops ksz9477_dev_ops = { .cfg_port_member = ksz9477_cfg_port_member, .r_mib_cnt = ksz9477_r_mib_cnt, .r_mib_pkt = ksz9477_r_mib_pkt, - .r_mib_stat64 = ksz_r_mib_stats64, + .r_mib_stat64 = ksz9477_r_mib_stats64, .freeze_mib = ksz9477_freeze_mib, .port_init_cnt = ksz9477_port_init_cnt, .pme_write8 = ksz_write8, @@ -2098,7 +2248,7 @@ const struct dsa_switch_ops ksz9477_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz9477_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index 92a1d889224d..a84c000000e6 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -21,6 +21,7 @@ void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze); void ksz9477_port_init_cnt(struct ksz_device *dev, int port); int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag, struct netlink_ext_ack *extack); +int ksz9477_max_mtu(struct dsa_switch *ds, int port); int ksz9477_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack); @@ -31,8 +32,6 @@ int ksz9477_port_mirror_add(struct dsa_switch *ds, int port, bool ingress, struct netlink_ext_ack *extack); void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col); int ksz9477_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data); int ksz9477_fdb_add(struct dsa_switch *ds, int port, @@ -45,6 +44,7 @@ int ksz9477_mdb_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db); int ksz9477_enable_stp_addr(struct ksz_device *dev); void ksz9477_port_queue_split(struct ksz_device *dev, int port); +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); int ksz9477_port_acl_init(struct ksz_device *dev, int port); void ksz9477_port_acl_free(struct ksz_device *dev, int port); diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index d1726778bb48..1a9d6f83a023 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -37,82 +37,6 @@ #define MIB_COUNTER_NUM 0x20 -struct ksz_stats_raw { - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 rx_1523_2000; - u64 rx_2001; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_total; - u64 tx_total; - u64 rx_discards; - u64 tx_discards; -}; - -struct ksz88xx_stats_raw { - u64 rx; - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 tx; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_discards; - u64 tx_discards; -}; - static const struct ksz_mib_names ksz88xx_mib_names[] = { { 0x00, "rx" }, { 0x01, "rx_hi" }, @@ -189,28 +113,6 @@ static const struct ksz_mib_names ksz9477_mib_names[] = { { 0x83, "tx_discards" }, }; -struct ksz_driver_strength_prop { - const char *name; - int offset; - int value; -}; - -enum ksz_driver_strength_type { - KSZ_DRIVER_STRENGTH_HI, - KSZ_DRIVER_STRENGTH_LO, - KSZ_DRIVER_STRENGTH_IO, -}; - -/** - * struct ksz_drive_strength - drive strength mapping - * @reg_val: register value - * @microamp: microamp value - */ -struct ksz_drive_strength { - u32 reg_val; - u32 microamp; -}; - /* ksz9477_drive_strengths - Drive strength mapping for KSZ9477 variants * * This values are not documented in KSZ9477 variants but confirmed by @@ -246,15 +148,6 @@ static const struct ksz_drive_strength ksz9477_drive_strengths[] = { { SW_DRIVE_STRENGTH_28MA, 28000 }, }; -/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. - * variants. - * This values are documented in KSZ8873 and KSZ8863 datasheets. - */ -static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { - { 0, 8000 }, - { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, -}; - /** * ksz_phylink_mac_disable_tx_lpi() - Callback to signal LPI support (Dummy) * @config: phylink config structure @@ -2013,7 +1906,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) struct rtnl_link_stats64 *stats; struct ksz_stats_raw *raw; struct ksz_port_mib *mib; - int ret; mib = &dev->ports[port].mib; stats = &mib->stats64; @@ -2055,61 +1947,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) pstats->rx_pause_frames = raw->rx_pause; spin_unlock(&mib->stats64_lock); - - if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { - ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); - if (ret) - dev_err(dev->dev, "Failed to monitor transmission halt\n"); - } -} - -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) -{ - struct ethtool_pause_stats *pstats; - struct rtnl_link_stats64 *stats; - struct ksz88xx_stats_raw *raw; - struct ksz_port_mib *mib; - - mib = &dev->ports[port].mib; - stats = &mib->stats64; - pstats = &mib->pause_stats; - raw = (struct ksz88xx_stats_raw *)mib->counters; - - spin_lock(&mib->stats64_lock); - - stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + - raw->rx_pause; - stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + - raw->tx_pause; - - /* HW counters are counting bytes + FCS which is not acceptable - * for rtnl_link_stats64 interface - */ - stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; - stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; - - stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + - raw->rx_oversize; - - stats->rx_crc_errors = raw->rx_crc_err; - stats->rx_frame_errors = raw->rx_align_err; - stats->rx_dropped = raw->rx_discards; - stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + - stats->rx_frame_errors + stats->rx_dropped; - - stats->tx_window_errors = raw->tx_late_col; - stats->tx_fifo_errors = raw->tx_discards; - stats->tx_aborted_errors = raw->tx_exc_col; - stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + - stats->tx_aborted_errors; - - stats->multicast = raw->rx_mcast; - stats->collisions = raw->tx_total_col; - - pstats->tx_pause_frames = raw->tx_pause; - pstats->rx_pause_frames = raw->rx_pause; - - spin_unlock(&mib->stats64_lock); } void ksz_get_stats64(struct dsa_switch *ds, int port, @@ -2582,14 +2419,14 @@ static void ksz_irq_unmask(struct irq_data *d) kirq->masked &= ~BIT(d->hwirq); } -static void ksz_irq_bus_lock(struct irq_data *d) +void ksz_irq_bus_lock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); mutex_lock(&kirq->dev->lock_irq); } -static void ksz_irq_bus_sync_unlock(struct irq_data *d) +void ksz_irq_bus_sync_unlock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); struct ksz_device *dev = kirq->dev; @@ -2667,14 +2504,15 @@ static irqreturn_t ksz_irq_thread_fn(int irq, void *dev_id) return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); } -static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops) { int ret, n; kirq->dev = dev; - kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, - &ksz_irq_domain_ops, kirq); + kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), + kirq->nirqs, 0, ops, kirq); if (!kirq->domain) return -ENOMEM; @@ -2706,7 +2544,7 @@ int ksz_girq_setup(struct ksz_device *dev) girq->irq_num = dev->irq; - return ksz_irq_common_setup(dev, girq); + return ksz_irq_common_setup(dev, girq, &ksz_irq_domain_ops); } int ksz_pirq_setup(struct ksz_device *dev, u8 p) @@ -2723,7 +2561,7 @@ int ksz_pirq_setup(struct ksz_device *dev, u8 p) if (!pirq->irq_num) return -EINVAL; - return ksz_irq_common_setup(dev, pirq); + return ksz_irq_common_setup(dev, pirq, &ksz_irq_domain_ops); } void ksz_teardown(struct dsa_switch *ds) @@ -2885,28 +2723,6 @@ void ksz_port_bridge_leave(struct dsa_switch *ds, int port, */ } -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) -{ - u32 queue_map = 0; - int ipm; - - for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { - int queue; - - /* Traffic Type (TT) is corresponding to the Internal Priority - * Map (IPM) in the switch. Traffic Class (TC) is - * corresponding to the queue in the switch. - */ - queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); - if (queue < 0) - return queue; - - queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); - } - - return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); -} - void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) { struct ksz_device *dev = ds->priv; @@ -2984,40 +2800,6 @@ int ksz_port_bridge_flags(struct dsa_switch *ds, int port, return 0; } -int ksz_max_mtu(struct dsa_switch *ds, int port) -{ - struct ksz_device *dev = ds->priv; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9370_CHIP_ID: - case LAN9371_CHIP_ID: - case LAN9372_CHIP_ID: - case LAN9373_CHIP_ID: - case LAN9374_CHIP_ID: - case LAN9646_CHIP_ID: - return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - } - - return -EOPNOTSUPP; -} - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e) { @@ -3086,39 +2868,6 @@ void ksz_set_xmii(struct ksz_device *dev, int port, phy_interface_t interface) ksz_pwrite8(dev, port, regs[P_XMII_CTRL_1], data8); } -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - phy_interface_t interface; - u8 data8; - u8 val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_MII_SEL_M, data8); - - if (val == bitval[P_MII_SEL]) { - if (gbit) - interface = PHY_INTERFACE_MODE_GMII; - else - interface = PHY_INTERFACE_MODE_MII; - } else if (val == bitval[P_RMII_SEL]) { - interface = PHY_INTERFACE_MODE_RMII; - } else { - interface = PHY_INTERFACE_MODE_RGMII; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_TXID; - if (data8 & P_RGMII_ID_IG_ENABLE) { - interface = PHY_INTERFACE_MODE_RGMII_RXID; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_ID; - } - } - - return interface; -} - bool ksz_phylink_need_config(struct phylink_config *config, unsigned int mode) { @@ -3154,24 +2903,6 @@ void ksz_phylink_mac_config(struct phylink_config *config, ksz_set_xmii(dev, port, state->interface); } -bool ksz_get_gbit(struct ksz_device *dev, int port) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - bool gbit = false; - u8 data8; - bool val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_GMII_1GBIT_M, data8); - - if (val == bitval[P_GMII_1GBIT]) - gbit = true; - - return gbit; -} - static int ksz_switch_detect(struct ksz_device *dev) { u8 id1, id2, id4; @@ -3264,7 +2995,7 @@ static int ksz_switch_detect(struct ksz_device *dev) break; default: dev_err(dev->dev, - "unsupported switch detected %x)\n", id32); + "unsupported switch detected (0x%x)\n", id32); return -ENODEV; } } @@ -3314,8 +3045,8 @@ static int ksz_setup_tc_mode(struct ksz_device *dev, int port, u8 scheduler, FIELD_PREP(MTI_SHAPING_M, shaper)); } -static int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, - struct tc_cbs_qopt_offload *qopt) +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt) { struct ksz_device *dev = ds->priv; int ret; @@ -3380,8 +3111,8 @@ static int ksz_disable_egress_rate_limit(struct ksz_device *dev, int port) return 0; } -static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, - int band) +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band) { /* Compared to queues, bands prioritize packets differently. In strict * priority mode, the lowest priority is assigned to Queue 0 while the @@ -3390,104 +3121,6 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, return p->bands - 1 - band; } -static u8 ksz8463_tc_ctrl(int port, int queue) -{ - u8 reg; - - reg = 0xC8 + port * 4; - reg += ((3 - queue) / 2) * 2; - reg++; - reg -= (queue & 1); - return reg; -} - -/** - * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to configure - * @p: Pointer to offload replace parameters describing ETS bands and mapping - * - * The KSZ88x3 supports two scheduling modes: Strict Priority and - * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: - * - No configurable queue-to-priority mapping - * - No weight adjustment in WFQ mode - * - * This function configures the switch to use strict priority mode by - * clearing the WFQ enable bit for all queues associated with ETS bands. - * If strict priority is not explicitly requested, the switch will default - * to WFQ mode. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) -{ - int ret, band; - - /* Only strict priority mode is supported for now. - * WFQ is implicitly enabled when strict mode is disabled. - */ - for (band = 0; band < p->bands; band++) { - int queue = ksz_ets_band_to_queue(p, band); - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Clear WFQ enable bit to select strict priority scheduling */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); - if (ret) - return ret; - } - - return 0; -} - -/** - * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to reset - * - * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or - * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or - * queue mapping. This function resets the port’s scheduling mode to - * the default, which is WFQ, by enabling the WFQ bit for all queues. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) -{ - int ret, queue; - - /* Iterate over all transmit queues for this port */ - for (queue = 0; queue < dev->info->num_tx_queues; queue++) { - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Set WFQ enable bit to revert back to default scheduling - * mode - */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, - KSZ8873_TXQ_WFQ_ENABLE); - if (ret) - return ret; - } - - return 0; -} - static int ksz_queue_set_strict(struct ksz_device *dev, int port, int queue) { int ret; @@ -3580,8 +3213,8 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port) return ksz9477_set_default_prio_queue_mapping(dev, port); } -static int ksz_tc_ets_validate(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) { int band; @@ -3622,9 +3255,6 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) - return -EOPNOTSUPP; - if (qopt->parent != TC_H_ROOT) { dev_err(dev->dev, "Parent should be \"root\"\n"); return -EOPNOTSUPP; @@ -3636,16 +3266,9 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, if (ret) return ret; - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_add(dev, port, - &qopt->replace_params); - else - return ksz_tc_ets_add(dev, port, &qopt->replace_params); + return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_del(dev, port); - else - return ksz_tc_ets_del(dev, port); + return ksz_tc_ets_del(dev, port); case TC_ETS_STATS: case TC_ETS_GRAFT: return -EOPNOTSUPP; @@ -3704,6 +3327,34 @@ int ksz_handle_wake_reason(struct ksz_device *dev, int port) pme_status); } +/** + * ksz_is_port_mac_global_usable - Check if the MAC address on a given port + * can be used as a global address. + * @ds: Pointer to the DSA switch structure. + * @port: The port number on which the MAC address is to be checked. + * + * This function examines the MAC address set on the specified port and + * determines if it can be used as a global address for the switch. + * + * Return: true if the port's MAC address can be used as a global address, false + * otherwise. + */ +static bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) +{ + struct net_device *user = dsa_to_port(ds, port)->user; + const unsigned char *addr = user->dev_addr; + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) + return false; + + return true; +} + /** * ksz_get_wol - Get Wake-on-LAN settings for a specified port. * @ds: The dsa_switch structure. @@ -3897,34 +3548,6 @@ int ksz_port_set_mac_address(struct dsa_switch *ds, int port, return 0; } -/** - * ksz_is_port_mac_global_usable - Check if the MAC address on a given port - * can be used as a global address. - * @ds: Pointer to the DSA switch structure. - * @port: The port number on which the MAC address is to be checked. - * - * This function examines the MAC address set on the specified port and - * determines if it can be used as a global address for the switch. - * - * Return: true if the port's MAC address can be used as a global address, false - * otherwise. - */ -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) -{ - struct net_device *user = dsa_to_port(ds, port)->user; - const unsigned char *addr = user->dev_addr; - struct ksz_switch_macaddr *switch_macaddr; - struct ksz_device *dev = ds->priv; - - ASSERT_RTNL(); - - switch_macaddr = dev->switch_macaddr; - if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) - return false; - - return true; -} - /** * ksz_switch_macaddr_get - Program the switch's MAC address register. * @ds: DSA switch instance. @@ -4132,8 +3755,8 @@ static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num, * Returns: If found, the corresponding register value for that drive strength * is returned. Otherwise, -EINVAL is returned indicating an invalid value. */ -static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, - size_t array_size, int microamp) +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp) { int i; @@ -4156,9 +3779,9 @@ static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, * is detected. It lists out all the supported drive strength values for * reference in the error message. */ -static void ksz_drive_strength_error(struct ksz_device *dev, - const struct ksz_drive_strength *array, - size_t array_size, int microamp) +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp) { char supported_values[100]; size_t remaining_size; @@ -4185,8 +3808,8 @@ static void ksz_drive_strength_error(struct ksz_device *dev, } /** - * ksz9477_drive_strength_write() - Set the drive strength for specific KSZ9477 - * chip variants. + * ksz_drive_strength_write() - Set the drive strength for specific KSZ9477 + * and the KSZ87xx chip variants. * @dev: ksz device * @props: Array of drive strength properties to be applied * @num_props: Number of properties in the array @@ -4197,9 +3820,9 @@ static void ksz_drive_strength_error(struct ksz_device *dev, * * Return: 0 on successful configuration, a negative error code on failure. */ -static int ksz9477_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) { size_t array_size = ARRAY_SIZE(ksz9477_drive_strengths); int i, ret, reg; @@ -4236,130 +3859,6 @@ static int ksz9477_drive_strength_write(struct ksz_device *dev, return ksz_rmw8(dev, reg, mask, val); } -/** - * ksz88x3_drive_strength_write() - Set the drive strength configuration for - * KSZ8863 compatible chip variants. - * @dev: ksz device - * @props: Array of drive strength properties to be set - * @num_props: Number of properties in the array - * - * This function applies the specified drive strength settings to KSZ88X3 chip - * variants (KSZ8873, KSZ8863). - * It ensures the configurations align with what the chip variant supports and - * warns or errors out on unsupported settings. - * - * Return: 0 on success, error code otherwise - */ -static int ksz88x3_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) -{ - size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); - int microamp; - int i, ret; - - for (i = 0; i < num_props; i++) { - if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - props[i].name); - } - - microamp = props[KSZ_DRIVER_STRENGTH_IO].value; - ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, - microamp); - if (ret < 0) { - ksz_drive_strength_error(dev, ksz88x3_drive_strengths, - array_size, microamp); - return ret; - } - - return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, - KSZ8873_DRIVE_STRENGTH_16MA, ret); -} - -/** - * ksz_parse_drive_strength() - Extract and apply drive strength configurations - * from device tree properties. - * @dev: ksz device - * - * This function reads the specified drive strength properties from the - * device tree, validates against the supported chip variants, and sets - * them accordingly. An error should be critical here, as the drive strength - * settings are crucial for EMI compliance. - * - * Return: 0 on success, error code otherwise - */ -int ksz_parse_drive_strength(struct ksz_device *dev) -{ - struct ksz_driver_strength_prop of_props[] = { - [KSZ_DRIVER_STRENGTH_HI] = { - .name = "microchip,hi-drive-strength-microamp", - .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_LO] = { - .name = "microchip,lo-drive-strength-microamp", - .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_IO] = { - .name = "microchip,io-drive-strength-microamp", - .offset = 0, /* don't care */ - .value = -1, - }, - }; - struct device_node *np = dev->dev->of_node; - bool have_any_prop = false; - int i, ret; - - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - ret = of_property_read_u32(np, of_props[i].name, - &of_props[i].value); - if (ret && ret != -EINVAL) - dev_warn(dev->dev, "Failed to read %s\n", - of_props[i].name); - if (ret) - continue; - - have_any_prop = true; - } - - if (!have_any_prop) - return 0; - - switch (dev->chip_id) { - case KSZ88X3_CHIP_ID: - return ksz88x3_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9646_CHIP_ID: - return ksz9477_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - default: - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - if (of_props[i].value == -1) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - of_props[i].name); - } - } - - return 0; -} - static int ksz8463_configure_straps_spi(struct ksz_device *dev) { struct pinctrl *pinctrl; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index b4a5673ba365..cbe98494578c 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -8,12 +8,14 @@ #define __KSZ_COMMON_H #include +#include #include #include #include #include #include #include +#include #include #include @@ -40,6 +42,45 @@ struct vlan_table { u32 table[3]; }; +struct ksz_stats_raw { + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 rx_1523_2000; + u64 rx_2001; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_total; + u64 tx_total; + u64 rx_discards; + u64 tx_discards; +}; + struct ksz_port_mib { struct mutex cnt_mutex; /* structure access */ u8 cnt_ptr; @@ -153,6 +194,7 @@ struct ksz_port { struct kernel_hwtstamp_config tstamp_config; bool hwts_tx_en; bool hwts_rx_en; + bool last_tx_is_pdelayresp; struct ksz_irq ptpirq; struct ksz_ptp_irq ptpmsg_irq[3]; ktime_t tstamp_msg; @@ -393,12 +435,8 @@ int ksz_switch_resume(struct device *dev); void ksz_teardown(struct dsa_switch *ds); void ksz_init_mib_timer(struct ksz_device *dev); -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port); void ksz_r_mib_stats64(struct ksz_device *dev, int port); -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); -bool ksz_get_gbit(struct ksz_device *dev, int port); -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit); extern const struct ksz_chip_data ksz_switch_chips[]; int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, struct netlink_ext_ack *extack); @@ -443,11 +481,15 @@ void ksz_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface); -int ksz_max_mtu(struct dsa_switch *ds, int port); - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e); +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band); +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt); +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p); int ksz_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); @@ -468,11 +510,44 @@ int ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum); int ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_mdio_register(struct ksz_device *dev); +void ksz_irq_bus_lock(struct irq_data *d); +void ksz_irq_bus_sync_unlock(struct irq_data *d); +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops); int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); -int ksz_parse_drive_strength(struct ksz_device *dev); -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); + +struct ksz_driver_strength_prop { + const char *name; + int offset; + int value; +}; + +enum ksz_driver_strength_type { + KSZ_DRIVER_STRENGTH_HI, + KSZ_DRIVER_STRENGTH_LO, + KSZ_DRIVER_STRENGTH_IO, +}; + +/** + * struct ksz_drive_strength - drive strength mapping + * @reg_val: register value + * @microamp: microamp value + */ +struct ksz_drive_strength { + u32 reg_val; + u32 microamp; +}; + +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 8b98039320ad..39cc70d65900 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -32,6 +32,15 @@ #define KSZ_PTP_INT_START 13 +/* + * PTP interrupt bit is the bit 12 of the 16-bits ISR/IER. But ksz_common.c only + * accesses the high-byte of these registers so the PTP interrupt bit becomes 4. + */ +#define KSZ8463_SRC_PTP_INT 4 +#define KSZ8463_PTP_PORT1_INT_START 12 +#define KSZ8463_PTP_PORT2_INT_START 14 +#define KSZ8463_PTP_INT_START KSZ8463_PTP_PORT1_INT_START + static int ksz_ptp_tou_gpio(struct ksz_device *dev) { int ret; @@ -288,6 +297,31 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) tag_en ? PTP_ENABLE : 0); } +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts) +{ + struct ksz_device *dev = ds->priv; + struct ksz_ptp_data *ptp_data; + + ptp_data = &dev->ptp_data; + + if (!ptp_data->clock) + return -ENODEV; + + ts->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + ts->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); + + ts->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT); + + ts->phc_index = ptp_clock_index(ptp_data->clock); + + return 0; +} + /* The function is return back the capability of timestamping feature when * requested through ethtool -T utility */ @@ -332,6 +366,72 @@ int ksz_hwtstamp_get(struct dsa_switch *ds, int port, return 0; } +static int ksz8463_set_hwtstamp_config(struct ksz_device *dev, + struct ksz_port *prt, + struct kernel_hwtstamp_config *config) +{ + const u16 *regs = dev->info->regs; + int ret; + + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = false; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = false; + prt->hwts_tx_en = false; + break; + case HWTSTAMP_TX_ON: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = true; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = true; + prt->hwts_tx_en = true; + + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, 0); + if (ret) + return ret; + + break; + default: + return -ERANGE; + } + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + prt->hwts_rx_en = false; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + prt->hwts_rx_en = true; + break; + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + return ksz_ptp_enable_mode(dev); +} + +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *prt; + int ret; + + prt = &dev->ports[port]; + + ret = ksz8463_set_hwtstamp_config(dev, prt, config); + if (ret) + return ret; + + prt->tstamp_config = *config; + + return 0; +} + static int ksz_set_hwtstamp_config(struct ksz_device *dev, struct ksz_port *prt, struct kernel_hwtstamp_config *config) @@ -562,6 +662,31 @@ static void ksz_ptp_txtstamp_skb(struct ksz_device *dev, skb_complete_tx_timestamp(skb, &hwtstamps); } +static void ksz8463_set_pdelayresp_flag(struct ksz_port *prt, + struct sk_buff *skb) +{ + struct ptp_header *hdr; + unsigned int type; + u8 ptp_msg_type; + + if (!ksz_is_ksz8463(prt->ksz_dev)) + return; + + if (skb_linearize(skb)) + return; + + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return; + + hdr = ptp_parse_header(skb, type); + if (!hdr) + return; + + ptp_msg_type = ptp_get_msgtype(hdr, type); + prt->last_tx_is_pdelayresp = (ptp_msg_type == PTP_MSGTYPE_PDELAY_RESP); +} + void ksz_port_deferred_xmit(struct kthread_work *work) { struct ksz_deferred_xmit_work *xmit_work = work_to_xmit_work(work); @@ -578,6 +703,8 @@ void ksz_port_deferred_xmit(struct kthread_work *work) reinit_completion(&prt->tstamp_msg_comp); + ksz8463_set_pdelayresp_flag(prt, skb); + dsa_enqueue_skb(skb, skb->dev); ksz_ptp_txtstamp_skb(dev, prt, clone); @@ -944,8 +1071,9 @@ int ksz_ptp_clock_register(struct dsa_switch *ds) /* Currently only P2P mode is supported. When 802_1AS bit is set, it * forwards all PTP packets to host port and none to other ports. */ - ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_TC_P2P | PTP_802_1AS, - PTP_TC_P2P | PTP_802_1AS); + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE, + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE); if (ret) return ret; @@ -969,7 +1097,22 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) static int ksz_read_ts(struct ksz_port *port, u16 reg, u32 *ts) { - return ksz_read32(port->ksz_dev, reg, ts); + u16 ts_reg = reg; + + /** + * On KSZ8463 DREQ and DRESP timestamps share one interrupt line + * so we have to check the nature of the latest event sent to know + * where the timestamp is located + */ + if (ksz_is_ksz8463(port->ksz_dev)) { + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + + if (port->last_tx_is_pdelayresp && + ts_reg == ops->get_port_addr(port->num, KSZ8463_REG_PORT_DREQ_TS)) + ts_reg += KSZ8463_DRESP_TS_OFFSET; + } + + return ksz_read32(port->ksz_dev, ts_reg, ts); } static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) @@ -1101,8 +1244,10 @@ static void ksz_ptp_msg_irq_free(struct ksz_port *port, u8 n) static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) { - u16 ts_reg[] = {REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, - REG_PTP_PORT_SYNC_TS}; + static const u16 ts_reg[] = { + REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, + REG_PTP_PORT_SYNC_TS + }; static const char * const name[] = {"pdresp-msg", "xdreq-msg", "sync-msg"}; const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; @@ -1129,6 +1274,128 @@ static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) return ret; } +static int ksz8463_ptp_port_irq_setup(struct ksz_irq *ptpirq, + struct ksz_port *port, int hw_irq) +{ + u16 ts_reg[] = {KSZ8463_REG_PORT_SYNC_TS, KSZ8463_REG_PORT_DREQ_TS}; + static const char * const name[] = {"sync-msg", "delay-msg"}; + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + struct ksz_ptp_irq *ptpmsg_irq; + int ret; + int i; + + init_completion(&port->tstamp_msg_comp); + + for (i = 0; i < 2; i++) { + ptpmsg_irq = &port->ptpmsg_irq[i]; + ptpmsg_irq->num = irq_create_mapping(ptpirq->domain, + hw_irq + i); + if (!ptpmsg_irq->num) { + ret = -EINVAL; + goto release_msg_irq; + } + + ptpmsg_irq->port = port; + ptpmsg_irq->ts_reg = ops->get_port_addr(port->num, ts_reg[i]); + + strscpy(ptpmsg_irq->name, name[i]); + + ret = request_threaded_irq(ptpmsg_irq->num, NULL, + ksz_ptp_msg_thread_fn, IRQF_ONESHOT, + ptpmsg_irq->name, ptpmsg_irq); + if (ret) { + irq_dispose_mapping(ptpmsg_irq->num); + goto release_msg_irq; + } + } + + return 0; + +release_msg_irq: + while (i--) + ksz_ptp_msg_irq_free(port, i); + + return ret; +} + +static void ksz8463_ptp_port_irq_teardown(struct ksz_port *port) +{ + int i; + + for (i = 0; i < 2; i++) + ksz_ptp_msg_irq_free(port, i); +} + +int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1, *port2; + struct ksz_irq *ptpirq; + int ret; + + port1 = &dev->ports[0]; + port2 = &dev->ports[1]; + ptpirq = &port1->ptpirq; + + ptpirq->irq_num = irq_find_mapping(dev->girq.domain, + KSZ8463_SRC_PTP_INT); + if (!ptpirq->irq_num) + return -EINVAL; + + ptpirq->dev = dev; + ptpirq->nirqs = 4; + ptpirq->reg_mask = KSZ8463_PTP_TS_IER; + ptpirq->reg_status = KSZ8463_PTP_TS_ISR; + ptpirq->irq0_offset = KSZ8463_PTP_INT_START; + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp-irq"); + + ptpirq->domain = irq_domain_create_linear(dev_fwnode(dev->dev), + ptpirq->nirqs, + &ksz_ptp_irq_domain_ops, + ptpirq); + if (!ptpirq->domain) + return -ENOMEM; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port1, + KSZ8463_PTP_PORT1_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto release_domain; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port2, + KSZ8463_PTP_PORT2_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto free_port1; + + ret = request_threaded_irq(ptpirq->irq_num, NULL, ksz_ptp_irq_thread_fn, + IRQF_ONESHOT, ptpirq->name, ptpirq); + if (ret) + goto free_port2; + + return 0; + +free_port2: + ksz8463_ptp_port_irq_teardown(port2); +free_port1: + ksz8463_ptp_port_irq_teardown(port1); +release_domain: + irq_domain_remove(ptpirq->domain); + + return ret; +} + +void ksz8463_ptp_irq_free(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1 = &dev->ports[0]; + struct ksz_port *port2 = &dev->ports[1]; + struct ksz_irq *ptpirq = &port1->ptpirq; + + free_irq(ptpirq->irq_num, ptpirq); + ksz8463_ptp_port_irq_teardown(port2); + ksz8463_ptp_port_irq_teardown(port1); + irq_domain_remove(ptpirq->domain); +} + int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 3086e519b1b6..7067ec9bd1e6 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -39,17 +39,24 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds); int ksz_get_ts_info(struct dsa_switch *ds, int port, struct kernel_ethtool_ts_info *ts); +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts); int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config); int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack); void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb); void ksz_port_deferred_xmit(struct kthread_work *work); bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb, unsigned int type); int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p); void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p); +int ksz8463_ptp_irq_setup(struct dsa_switch *ds); +void ksz8463_ptp_irq_free(struct dsa_switch *ds); #else @@ -72,11 +79,20 @@ static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {} +static inline int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + return 0; +} + +static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {} + #define ksz_get_ts_info NULL +#define ksz8463_get_ts_info NULL #define ksz_hwtstamp_get NULL #define ksz_hwtstamp_set NULL +#define ksz8463_hwtstamp_set NULL #define ksz_port_rxtstamp NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index eab9aecb7fa8..65ea8577af75 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -121,6 +121,12 @@ #define REG_PTP_PORT_SYNC_TS 0x0C0C #define REG_PTP_PORT_PDRESP_TS 0x0C10 +#define KSZ8463_REG_PORT_DREQ_TS 0x0648 +#define KSZ8463_REG_PORT_SYNC_TS 0x064C +#define KSZ8463_REG_PORT_DRESP_TS 0x0650 +#define KSZ8463_PTP_TS_ISR 0x068C +#define KSZ8463_PTP_TS_IER 0x068E + #define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14 #define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16 @@ -131,4 +137,8 @@ #define KSZ_XDREQ_MSG 1 #define KSZ_PDRES_MSG 0 +#define KSZ8463_DRESP_TS_OFFSET (KSZ8463_REG_PORT_DRESP_TS - KSZ8463_REG_PORT_DREQ_TS) +#define KSZ8463_SYNC_MSG 0 +#define KSZ8463_XDREQ_PDRES_MSG 1 + #endif diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index 8eb5337b0c10..86ce3a86705f 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -787,10 +787,6 @@ static int lan937x_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); - if (ret) - return ret; - /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; @@ -983,7 +979,7 @@ const struct dsa_switch_ops lan937x_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = lan937x_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, diff --git a/drivers/net/dsa/mt7530-mmio.c b/drivers/net/dsa/mt7530-mmio.c index 119fdd863d91..fd68b1cd0630 100644 --- a/drivers/net/dsa/mt7530-mmio.c +++ b/drivers/net/dsa/mt7530-mmio.c @@ -12,6 +12,7 @@ static const struct of_device_id mt7988_of_match[] = { { .compatible = "airoha,an7583-switch", .data = &mt753x_table[ID_AN7583], }, { .compatible = "airoha,en7581-switch", .data = &mt753x_table[ID_EN7581], }, + { .compatible = "econet,en7528-switch", .data = &mt753x_table[ID_EN7528], }, { .compatible = "mediatek,mt7988-switch", .data = &mt753x_table[ID_MT7988], }, { /* sentinel */ }, }; diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index aa33d94e11b5..2b7be091c056 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -124,8 +124,12 @@ core_rmw(struct mt7530_priv *priv, u32 reg, u32 mask, u32 set) goto err; /* Read the content of the MMD's selected register */ - val = bus->read(bus, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), + ret = bus->read(bus, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), MII_MMD_DATA); + if (ret < 0) + goto err; + val = ret; + val &= ~mask; val |= set; /* Write the data into MMD's selected register */ @@ -181,14 +185,18 @@ mt7530_mii_read(struct mt7530_priv *priv, u32 reg) return val; } -static void +static int mt7530_write(struct mt7530_priv *priv, u32 reg, u32 val) { + int ret; + mt7530_mutex_lock(priv); - mt7530_mii_write(priv, reg, val); + ret = mt7530_mii_write(priv, reg, val); mt7530_mutex_unlock(priv); + + return ret; } static u32 @@ -245,7 +253,9 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) /* Set the command operating upon the MAC address entries */ val = ATC_BUSY | ATC_MAT(0) | cmd; - mt7530_write(priv, MT7530_ATC, val); + ret = mt7530_write(priv, MT7530_ATC, val); + if (ret) + return ret; mt7530_mutex_lock(priv); @@ -561,7 +571,9 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | regnum; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -572,7 +584,9 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_READ | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad); - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -606,7 +620,9 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | regnum; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -617,7 +633,9 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_WRITE | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | data; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -650,7 +668,9 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum) val = MT7531_MDIO_CL22_READ | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_REG_ADDR(regnum); - mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -685,7 +705,9 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum, reg = MT7531_MDIO_CL22_WRITE | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_REG_ADDR(regnum) | data; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, reg, !(reg & MT7531_PHY_ACS_ST), 20, 100000); @@ -1616,7 +1638,9 @@ mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) int ret; val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; - mt7530_write(priv, MT7530_VTCR, val); + ret = mt7530_write(priv, MT7530_VTCR, val); + if (ret) + return ret; mt7530_mutex_lock(priv); @@ -2295,6 +2319,21 @@ static const struct regmap_irq mt7530_irqs[] = { REGMAP_IRQ_REG_LINE(31, 32), /* ACL */ }; +/* Serialize regmap-irq's mask sync like every other regmap user */ +static int mt7530_irq_mask_sync(int index, unsigned int mask_buf_def, + unsigned int mask_buf, void *irq_drv_data) +{ + struct mt7530_priv *priv = irq_drv_data; + int ret; + + mt7530_mutex_lock(priv); + ret = regmap_update_bits(priv->regmap, MT7530_SYS_INT_EN, + mask_buf_def, ~mask_buf); + mt7530_mutex_unlock(priv); + + return ret; +} + static const struct regmap_irq_chip mt7530_regmap_irq_chip = { .name = KBUILD_MODNAME, .status_base = MT7530_SYS_INT_STS, @@ -2304,12 +2343,14 @@ static const struct regmap_irq_chip mt7530_regmap_irq_chip = { .irqs = mt7530_irqs, .num_irqs = ARRAY_SIZE(mt7530_irqs), .num_regs = 1, + .handle_mask_sync = mt7530_irq_mask_sync, }; static int mt7530_setup_irq(struct mt7530_priv *priv) { struct regmap_irq_chip_data *irq_data; + struct regmap_irq_chip *chip; struct device *dev = priv->dev; struct device_node *np = dev->of_node; int irq, ret; @@ -2329,10 +2370,17 @@ mt7530_setup_irq(struct mt7530_priv *priv) if (priv->id == ID_MT7530 || priv->id == ID_MT7621) mt7530_set(priv, MT7530_TOP_SIG_CTRL, TOP_SIG_CTRL_NORMAL); + chip = devm_kmemdup(dev, &mt7530_regmap_irq_chip, sizeof(*chip), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->irq_drv_data = priv; + ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), priv->regmap, irq, IRQF_ONESHOT, - 0, &mt7530_regmap_irq_chip, + 0, chip, &irq_data); if (ret) return ret; @@ -2765,14 +2813,20 @@ mt7531_setup(struct dsa_switch *ds) * phy_[read,write]_mmd_indirect is called, we provide our own * mt7531_ind_mmd_phy_[read,write] to complete this function. */ - val = mt7531_ind_c45_phy_read(priv, + ret = mt7531_ind_c45_phy_read(priv, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), MDIO_MMD_VEND2, CORE_PLL_GROUP4); + if (ret < 0) + return ret; + + val = ret; val |= MT7531_RG_SYSPLL_DMY2 | MT7531_PHY_PLL_BYPASS_MODE; val &= ~MT7531_PHY_PLL_OFF; - mt7531_ind_c45_phy_write(priv, - MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), - MDIO_MMD_VEND2, CORE_PLL_GROUP4, val); + ret = mt7531_ind_c45_phy_write(priv, + MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), + MDIO_MMD_VEND2, CORE_PLL_GROUP4, val); + if (ret < 0) + return ret; /* Disable EEE advertisement on the switch PHYs. */ for (i = MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr); @@ -2902,6 +2956,30 @@ static void en7581_mac_port_get_caps(struct dsa_switch *ds, int port, } } +static void en7528_mac_port_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + switch (port) { + /* Ports which are connected to switch PHYs. There is no MII pinout. */ + case 1 ... 4: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + + config->mac_capabilities |= MAC_10 | MAC_100 | MAC_1000FD; + break; + + /* Port 6 is connected to SoC's GMAC at 1000 Mbps full duplex. There + * is no MII pinout. + */ + case 6: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + + config->mac_capabilities |= MAC_1000FD; + break; + } +} + static void mt7530_mac_config(struct dsa_switch *ds, int port, unsigned int mode, phy_interface_t interface) @@ -3091,17 +3169,24 @@ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { struct mt7530_priv *priv = ds->priv; - u32 eeecr; config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; - config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; - - eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); - /* tx_lpi_timer should be in microseconds. The time units for - * LPI threshold are unspecified. + /* The EN7528 GPHYs report EEE capability, but negotiating EEE with + * common link partners (e.g. Realtek GbE NICs) results in an unstable + * link with dropped frames. Leave the LPI capabilities empty so that + * phylink disables EEE on these PHYs and refuses to enable it from + * userspace. */ - config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); + if (priv->id != ID_EN7528) { + u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); + + config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; + /* tx_lpi_timer should be in microseconds. The time units for + * LPI threshold are unspecified. + */ + config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); + } priv->info->mac_port_get_caps(ds, port, config); } @@ -3244,7 +3329,8 @@ mt753x_conduit_state_change(struct dsa_switch *ds, * forwarded to the numerically smallest CPU port whose conduit * interface is up. */ - if (priv->id != ID_MT7530 && priv->id != ID_MT7621) + if (priv->id != ID_MT7530 && priv->id != ID_MT7621 && + priv->id != ID_EN7528) return; mask = BIT(cpu_dp->index); @@ -3449,6 +3535,16 @@ const struct mt753x_info mt753x_table[] = { .phy_write_c45 = mt7531_ind_c45_phy_write, .mac_port_get_caps = en7581_mac_port_get_caps, }, + [ID_EN7528] = { + .id = ID_EN7528, + .pcs_ops = &mt7530_pcs_ops, + .sw_setup = mt7988_setup, + .phy_read_c22 = mt7531_ind_c22_phy_read, + .phy_write_c22 = mt7531_ind_c22_phy_write, + .phy_read_c45 = mt7531_ind_c45_phy_read, + .phy_write_c45 = mt7531_ind_c45_phy_write, + .mac_port_get_caps = en7528_mac_port_get_caps, + }, }; EXPORT_SYMBOL_GPL(mt753x_table); diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index dd33b0df3419..5f1e841f42c0 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -21,6 +21,7 @@ enum mt753x_id { ID_MT7988 = 3, ID_EN7581 = 4, ID_AN7583 = 5, + ID_EN7528 = 6, }; #define NUM_TRGMII_CTRL 5 diff --git a/drivers/net/dsa/mt7628.c b/drivers/net/dsa/mt7628.c new file mode 100644 index 000000000000..fb63f6f644b9 --- /dev/null +++ b/drivers/net/dsa/mt7628.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mediatek MT7628 Embedded Switch (ESW) DSA driver + * Copyright (C) 2026 Joris Vaisvila + * + * Portions derived from OpenWRT esw_rt3050 driver: + * Copyright (C) 2009-2015 John Crispin + * Copyright (C) 2009-2015 Felix Fietkau + * Copyright (C) 2013-2015 Michael Lee + * Copyright (C) 2016 Vittorio Gambaletta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT7628_ESW_REG_IMR 0x04 +#define MT7628_ESW_REG_FCT0 0x08 +#define MT7628_ESW_REG_PFC1 0x14 +#define MT7628_ESW_REG_PVIDC(port) (0x40 + 4 * ((port) / 2)) +#define MT7628_ESW_REG_VLANI(vlan) (0x50 + 4 * ((vlan) / 2)) +#define MT7628_ESW_REG_VMSC(vlan) (0x70 + 4 * ((vlan) / 4)) +#define MT7628_ESW_REG_VUB(vlan) (0x100 + 4 * ((vlan) / 4)) +#define MT7628_ESW_REG_SOCPC 0x8c +#define MT7628_ESW_REG_POC0 0x90 +#define MT7628_ESW_REG_POC2 0x98 +#define MT7628_ESW_REG_SGC 0x9c +#define MT7628_ESW_REG_PCR0 0xc0 +#define MT7628_ESW_REG_PCR1 0xc4 +#define MT7628_ESW_REG_FPA2 0xc8 +#define MT7628_ESW_REG_FCT2 0xcc +#define MT7628_ESW_REG_SGC2 0xe4 + +#define MT7628_ESW_FCT0_DROP_SET_TH GENMASK(7, 0) +#define MT7628_ESW_FCT0_DROP_RLS_TH GENMASK(15, 8) +#define MT7628_ESW_FCT0_FC_SET_TH GENMASK(23, 16) +#define MT7628_ESW_FCT0_FC_RLS_TH GENMASK(31, 24) + +#define MT7628_ESW_PFC1_EN_VLAN GENMASK(22, 16) + +#define MT7628_ESW_PVID_S 12 +#define MT7628_ESW_PVID_M GENMASK(11, 0) +#define MT7628_ESW_PVID_SHIFT(port) \ + (MT7628_ESW_PVID_S * ((port) % 2)) +#define MT7628_ESW_PVID_MASK(port) \ + (MT7628_ESW_PVID_M << MT7628_ESW_PVID_SHIFT(port)) +#define MT7628_ESW_PVID_PREP(port, pvid) \ + (((pvid) & MT7628_ESW_PVID_M) << MT7628_ESW_PVID_SHIFT(port)) + +#define MT7628_ESW_VID_S 12 +#define MT7628_ESW_VID_M GENMASK(11, 0) +#define MT7628_ESW_VID_SHIFT(vlan) \ + (MT7628_ESW_VID_S * ((vlan) % 2)) +#define MT7628_ESW_VID_MASK(vlan) \ + (MT7628_ESW_VID_M << MT7628_ESW_VID_SHIFT(vlan)) +#define MT7628_ESW_VID_PREP(vlan, vid) \ + (((vid) & MT7628_ESW_VID_M) << MT7628_ESW_VID_SHIFT(vlan)) + +#define MT7628_ESW_VMSC_S 8 +#define MT7628_ESW_VMSC_M GENMASK(7, 0) +#define MT7628_ESW_VMSC_SHIFT(vlan) \ + (MT7628_ESW_VMSC_S * ((vlan) % 4)) +#define MT7628_ESW_VMSC_MASK(vlan) \ + (MT7628_ESW_VMSC_M << MT7628_ESW_VMSC_SHIFT(vlan)) +#define MT7628_ESW_VMSC_PREP(vlan, vmsc) \ + (((vmsc) & MT7628_ESW_VMSC_M) << MT7628_ESW_VMSC_SHIFT(vlan)) + +#define MT7628_ESW_VUB_S 7 +#define MT7628_ESW_VUB_M GENMASK(6, 0) +#define MT7628_ESW_VUB_SHIFT(vlan) \ + (MT7628_ESW_VUB_S * ((vlan) % 4)) +#define MT7628_ESW_VUB_MASK(vlan) \ + (MT7628_ESW_VUB_M << MT7628_ESW_VUB_SHIFT(vlan)) +#define MT7628_ESW_VUB_PREP(vlan, vub) \ + (((vub) & MT7628_ESW_VUB_M) << MT7628_ESW_VUB_SHIFT(vlan)) + +#define MT7628_ESW_SOCPC_CRC_PADDING BIT(25) +#define MT7628_ESW_SOCPC_DISBC2CPU GENMASK(22, 16) +#define MT7628_ESW_SOCPC_DISMC2CPU GENMASK(14, 8) +#define MT7628_ESW_SOCPC_DISUN2CPU GENMASK(6, 0) + +#define MT7628_ESW_POC0_PORT_DISABLE GENMASK(29, 23) + +#define MT7628_ESW_POC2_PER_VLAN_UNTAG_EN BIT(15) + +#define MT7628_ESW_SGC_AGING_INTERVAL GENMASK(3, 0) +#define MT7628_ESW_BC_STORM_PROT GENMASK(5, 4) +#define MT7628_ESW_PKT_MAX_LEN GENMASK(7, 6) +#define MT7628_ESW_DIS_PKT_ABORT BIT(8) +#define MT7628_ESW_ADDRESS_HASH_ALG GENMASK(10, 9) +#define MT7628_ESW_DISABLE_TX_BACKOFF BIT(11) +#define MT7628_ESW_BP_JAM_CNT GENMASK(15, 12) +#define MT7628_ESW_DISMIIPORT_WASTX GENMASK(17, 16) +#define MT7628_ESW_BP_MODE GENMASK(19, 18) +#define MT7628_ESW_BISH_DIS BIT(20) +#define MT7628_ESW_BISH_TH GENMASK(22, 21) +#define MT7628_ESW_LED_FLASH_TIME GENMASK(24, 23) +#define MT7628_ESW_RMC_RULE GENMASK(26, 25) +#define MT7628_ESW_IP_MULT_RULE GENMASK(28, 27) +#define MT7628_ESW_LEN_ERR_CHK BIT(29) +#define MT7628_ESW_BKOFF_ALG BIT(30) + +#define MT7628_ESW_PCR0_WT_NWAY_DATA GENMASK(31, 16) +#define MT7628_ESW_PCR0_RD_PHY_CMD BIT(14) +#define MT7628_ESW_PCR0_WT_PHY_CMD BIT(13) +#define MT7628_ESW_PCR0_CPU_PHY_REG GENMASK(12, 8) +#define MT7628_ESW_PCR0_CPU_PHY_ADDR GENMASK(4, 0) + +#define MT7628_ESW_PCR1_RD_DATA GENMASK(31, 16) +#define MT7628_ESW_PCR1_RD_DONE BIT(1) +#define MT7628_ESW_PCR1_WT_DONE BIT(0) + +#define MT7628_ESW_FPA2_AP_EN BIT(29) +#define MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE GENMASK(28, 24) +#define MT7628_ESW_FPA2_FORCE_RGMII_LINK1 BIT(13) +#define MT7628_ESW_FPA2_FORCE_RGMII_EN1 BIT(11) + +#define MT7628_ESW_FCT2_MUST_DROP_RLS_TH GENMASK(17, 13) +#define MT7628_ESW_FCT2_MUST_DROP_SET_TH GENMASK(12, 8) +#define MT7628_ESW_FCT2_MC_PER_PORT_TH GENMASK(5, 0) + +#define MT7628_ESW_SGC2_SPECIAL_TAG_EN BIT(23) +#define MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP GENMASK(22, 16) +#define MT7628_ESW_SGC2_DOUBLE_TAG_EN GENMASK(6, 0) + +#define MT7628_ESW_PORTS_NOCPU GENMASK(5, 0) +#define MT7628_ESW_PORTS_CPU BIT(6) +#define MT7628_ESW_PORTS_ALL GENMASK(6, 0) + +#define MT7628_ESW_NUM_PORTS 7 +#define MT7628_NUM_VLANS 16 + +static const struct regmap_config mt7628_esw_regmap_cfg = { + .name = "mt7628-esw", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +struct mt7628_vlan { + bool active; + u8 members; + u8 untag; + u16 vid; +}; + +struct mt7628_esw { + struct reset_control *rst_ephy; + struct reset_control *rst_esw; + struct regmap *regmap; + struct dsa_switch *ds; + u16 tag_8021q_pvid[MT7628_ESW_NUM_PORTS]; + struct mt7628_vlan vlans[MT7628_NUM_VLANS]; + struct device *dev; +}; + +static int mt7628_mii_read(struct mii_bus *bus, int port, int regnum) +{ + struct mt7628_esw *esw = bus->priv; + int ret; + u32 val; + + /* + * RD_DONE bit is read to clear. Read PCR1 once to acknowledge any + * stale completion indicator before starting a new transaction. + */ + ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); + if (ret) + goto out; + + ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, + regnum) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, + port) | MT7628_ESW_PCR0_RD_PHY_CMD); + if (ret) + goto out; + + ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, + (val & MT7628_ESW_PCR1_RD_DONE), 10, + 5000); + if (ret) + goto out; + + return FIELD_GET(MT7628_ESW_PCR1_RD_DATA, val); + +out: + dev_err(&bus->dev, "read failed. MDIO timeout?\n"); + return ret; +} + +static int mt7628_mii_write(struct mii_bus *bus, int port, int regnum, u16 dat) +{ + struct mt7628_esw *esw = bus->priv; + u32 val; + int ret; + + /* + * WT_DONE bit is read to clear. Read PCR1 once to acknowledge any + * stale completion indicator before starting a new transaction. + */ + ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); + if (ret) + goto out; + + ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, + FIELD_PREP(MT7628_ESW_PCR0_WT_NWAY_DATA, dat) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, + regnum) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, + port) | MT7628_ESW_PCR0_WT_PHY_CMD); + if (ret) + goto out; + + ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, + (val & MT7628_ESW_PCR1_WT_DONE), 10, + 5000); + if (ret) + goto out; + + return 0; + +out: + dev_err(&bus->dev, "write failed. MDIO timeout?\n"); + return ret; +} + +static int mt7628_setup_internal_mdio(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + struct device *dev = ds->dev; + struct mii_bus *bus; + + bus = devm_mdiobus_alloc(dev); + if (!bus) + return -ENOMEM; + + bus->name = "MT7628 internal MDIO bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); + bus->priv = esw; + bus->read = mt7628_mii_read; + bus->write = mt7628_mii_write; + bus->parent = dev; + + ds->user_mii_bus = bus; + bus->phy_mask = ~ds->phys_mii_mask; + + return devm_mdiobus_register(dev, bus); +} + +static void mt7628_switch_init(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + + regmap_write(esw->regmap, MT7628_ESW_REG_FCT0, + FIELD_PREP(MT7628_ESW_FCT0_DROP_SET_TH, 0x50) | + FIELD_PREP(MT7628_ESW_FCT0_DROP_RLS_TH, 0x78) | + FIELD_PREP(MT7628_ESW_FCT0_FC_SET_TH, 0xa0) | + FIELD_PREP(MT7628_ESW_FCT0_FC_RLS_TH, 0xc8)); + + regmap_write(esw->regmap, MT7628_ESW_REG_FCT2, + FIELD_PREP(MT7628_ESW_FCT2_MC_PER_PORT_TH, 0xc) | + FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_SET_TH, 0x10) | + FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_RLS_TH, 0x12)); + + /* + * general switch configuration: + * 300s aging interval + * broadcast storm prevention disabled + * max packet length 1536 bytes + * disable collision 16 packet abort and late collision abort + * use xor48 for address hashing + * disable tx backoff + * 10 packet back pressure jam + * disable was_transmit + * jam until BP condition released + * 30ms LED flash + * rmc tb fault to all ports + * unmatched IGMP as broadcast + */ + regmap_write(esw->regmap, MT7628_ESW_REG_SGC, + FIELD_PREP(MT7628_ESW_SGC_AGING_INTERVAL, 1) | + FIELD_PREP(MT7628_ESW_BC_STORM_PROT, 0) | + FIELD_PREP(MT7628_ESW_PKT_MAX_LEN, 0) | + MT7628_ESW_DIS_PKT_ABORT | + FIELD_PREP(MT7628_ESW_ADDRESS_HASH_ALG, 1) | + MT7628_ESW_DISABLE_TX_BACKOFF | + FIELD_PREP(MT7628_ESW_BP_JAM_CNT, 10) | + FIELD_PREP(MT7628_ESW_DISMIIPORT_WASTX, 0) | + FIELD_PREP(MT7628_ESW_BP_MODE, 0b10) | + FIELD_PREP(MT7628_ESW_LED_FLASH_TIME, 0) | + FIELD_PREP(MT7628_ESW_RMC_RULE, 0) | + FIELD_PREP(MT7628_ESW_IP_MULT_RULE, 0)); + + regmap_write(esw->regmap, MT7628_ESW_REG_SOCPC, + MT7628_ESW_SOCPC_CRC_PADDING | + FIELD_PREP(MT7628_ESW_SOCPC_DISUN2CPU, + MT7628_ESW_PORTS_CPU) | + FIELD_PREP(MT7628_ESW_SOCPC_DISMC2CPU, + MT7628_ESW_PORTS_CPU) | + FIELD_PREP(MT7628_ESW_SOCPC_DISBC2CPU, + MT7628_ESW_PORTS_CPU)); + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_FPA2, + MT7628_ESW_FPA2_FORCE_RGMII_EN1 | + MT7628_ESW_FPA2_FORCE_RGMII_LINK1 | + MT7628_ESW_FPA2_AP_EN); + + regmap_update_bits(esw->regmap, MT7628_ESW_REG_FPA2, + MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, + FIELD_PREP(MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, 31)); + + /* disable all interrupts */ + regmap_write(esw->regmap, MT7628_ESW_REG_IMR, 0); + + /* enable MT7628 DSA tag on CPU port */ + regmap_write(esw->regmap, MT7628_ESW_REG_SGC2, + MT7628_ESW_SGC2_SPECIAL_TAG_EN | + FIELD_PREP(MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP, + MT7628_ESW_PORTS_CPU)); + + /* + * Double tag feature allows switch to always append the port PVID VLAN tag + * regardless of if the incoming packet already has a VLAN tag. + * This is enabled to simulate VLAN unawareness. + */ + regmap_set_bits(esw->regmap, MT7628_ESW_REG_SGC2, + FIELD_PREP(MT7628_ESW_SGC2_DOUBLE_TAG_EN, + MT7628_ESW_PORTS_NOCPU)); + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC2, + MT7628_ESW_POC2_PER_VLAN_UNTAG_EN); + + regmap_update_bits(esw->regmap, MT7628_ESW_REG_PFC1, + MT7628_ESW_PFC1_EN_VLAN, + FIELD_PREP(MT7628_ESW_PFC1_EN_VLAN, + MT7628_ESW_PORTS_ALL)); +} + +static void mt7628_esw_set_pvid(struct mt7628_esw *esw, unsigned int port, + unsigned int pvid) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_PVIDC(port), + MT7628_ESW_PVID_MASK(port), + MT7628_ESW_PVID_PREP(port, pvid)); +} + +static void mt7628_esw_set_vlan_id(struct mt7628_esw *esw, unsigned int vlan, + unsigned int vid) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VLANI(vlan), + MT7628_ESW_VID_MASK(vlan), + MT7628_ESW_VID_PREP(vlan, vid)); +} + +static void mt7628_esw_set_vmsc(struct mt7628_esw *esw, unsigned int vlan, + unsigned int msc) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VMSC(vlan), + MT7628_ESW_VMSC_MASK(vlan), + MT7628_ESW_VMSC_PREP(vlan, msc)); +} + +static void mt7628_esw_set_vub(struct mt7628_esw *esw, unsigned int vlan, + unsigned int vub) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VUB(vlan), + MT7628_ESW_VUB_MASK(vlan), + MT7628_ESW_VUB_PREP(vlan, vub)); +} + +static void mt7628_vlan_sync(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *vlan = &esw->vlans[i]; + + mt7628_esw_set_vmsc(esw, i, vlan->members); + mt7628_esw_set_vlan_id(esw, i, vlan->vid); + mt7628_esw_set_vub(esw, i, vlan->untag); + } + + for (i = 0; i < ds->num_ports; i++) + mt7628_esw_set_pvid(esw, i, esw->tag_8021q_pvid[i]); +} + +static int mt7628_setup(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + int ret; + + ret = reset_control_reset(esw->rst_esw); + if (ret) + return ret; + usleep_range(1000, 2000); + + ret = reset_control_reset(esw->rst_ephy); + if (ret) + return ret; + usleep_range(1000, 2000); + /* + * all MMIO reads hang if esw is not out of reset + * ephy needs extra time to get out of reset or it ends up misconfigured + */ + + mt7628_switch_init(ds); + + ret = mt7628_setup_internal_mdio(ds); + if (ret) + return ret; + + rtnl_lock(); + ret = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q)); + rtnl_unlock(); + + return ret; +} + +static int mt7628_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct mt7628_esw *esw = ds->priv; + + /* + * All switch ports are disabled by esw reset and enabled here by DSA. + */ + regmap_clear_bits(esw->regmap, MT7628_ESW_REG_POC0, + FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); + return 0; +} + +static void mt7628_port_disable(struct dsa_switch *ds, int port) +{ + struct mt7628_esw *esw = ds->priv; + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC0, + FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); +} + +static enum dsa_tag_protocol +mt7628_get_tag_proto(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) +{ + return DSA_TAG_PROTO_MT7628; +} + +static void mt7628_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + switch (port) { + case 6: + config->mac_capabilities |= MAC_1000; + fallthrough; + case 0 ... 4: + config->mac_capabilities |= MAC_100 | MAC_10; + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + break; + default: + break; /* port 5 does not exist on MT7628 */ + } +} + +static int mt7628_dsa_8021q_vlan_add(struct dsa_switch *ds, int port, + u16 vid, u16 flags) +{ + struct mt7628_esw *esw = ds->priv; + struct mt7628_vlan *vlan = NULL; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *check_vlan = &esw->vlans[i]; + + if (!check_vlan->active && !vlan) + vlan = check_vlan; + + if (check_vlan->active && check_vlan->vid == vid) { + vlan = check_vlan; + break; + } + } + + if (!vlan) + return -ENOSPC; + + vlan->vid = vid; + vlan->active = true; + vlan->members |= BIT(port); + + if (flags & BRIDGE_VLAN_INFO_PVID) + esw->tag_8021q_pvid[port] = vid; + + if (flags & BRIDGE_VLAN_INFO_UNTAGGED) + vlan->untag |= BIT(port); + + mt7628_vlan_sync(ds); + return 0; +} + +static int mt7628_dsa_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid) +{ + struct mt7628_esw *esw = ds->priv; + struct mt7628_vlan *vlan = NULL; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *check_vlan = &esw->vlans[i]; + + if (!check_vlan->active || check_vlan->vid != vid) + continue; + vlan = check_vlan; + break; + } + if (!vlan) + return -ENOENT; + + if (esw->tag_8021q_pvid[port] == vid) + esw->tag_8021q_pvid[port] = 0; + + vlan->members &= ~BIT(port); + vlan->untag &= ~BIT(port); + + if (!vlan->members) { + vlan->active = false; + vlan->vid = 0; + } + + mt7628_vlan_sync(ds); + return 0; +} + +static void mt7628_teardown(struct dsa_switch *ds) +{ + rtnl_lock(); + dsa_tag_8021q_unregister(ds); + rtnl_unlock(); +} + +static const struct dsa_switch_ops mt7628_switch_ops = { + .get_tag_protocol = mt7628_get_tag_proto, + .setup = mt7628_setup, + .teardown = mt7628_teardown, + .port_enable = mt7628_port_enable, + .port_disable = mt7628_port_disable, + .phylink_get_caps = mt7628_phylink_get_caps, + .tag_8021q_vlan_add = mt7628_dsa_8021q_vlan_add, + .tag_8021q_vlan_del = mt7628_dsa_8021q_vlan_del, +}; + +static int mt7628_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt7628_esw *esw; + struct dsa_switch *ds; + void __iomem *base; + + ds = devm_kzalloc(&pdev->dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return -ENOMEM; + + esw = devm_kzalloc(&pdev->dev, sizeof(*esw), GFP_KERNEL); + if (!esw) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + esw->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &mt7628_esw_regmap_cfg); + if (IS_ERR(esw->regmap)) + return PTR_ERR(esw->regmap); + + esw->rst_ephy = devm_reset_control_get_exclusive(&pdev->dev, "ephy"); + if (IS_ERR(esw->rst_ephy)) + return dev_err_probe(dev, PTR_ERR(esw->rst_ephy), + "failed to get EPHY reset\n"); + + esw->rst_esw = devm_reset_control_get_exclusive(&pdev->dev, "esw"); + if (IS_ERR(esw->rst_esw)) + return dev_err_probe(dev, PTR_ERR(esw->rst_esw), + "failed to get ESW reset\n"); + + ds->dev = dev; + ds->num_ports = MT7628_ESW_NUM_PORTS; + ds->ops = &mt7628_switch_ops; + ds->priv = esw; + esw->ds = ds; + esw->dev = dev; + dev_set_drvdata(dev, esw); + + return dsa_register_switch(ds); +} + +static void mt7628_remove(struct platform_device *pdev) +{ + struct mt7628_esw *esw = platform_get_drvdata(pdev); + + if (!esw) + return; + + dsa_unregister_switch(esw->ds); +} + +static void mt7628_shutdown(struct platform_device *pdev) +{ + struct mt7628_esw *esw = platform_get_drvdata(pdev); + + if (!esw) + return; + + dsa_switch_shutdown(esw->ds); + dev_set_drvdata(&pdev->dev, NULL); +} + +static const struct of_device_id mt7628_of_match[] = { + { .compatible = "mediatek,mt7628-esw" }, + {} +}; + +MODULE_DEVICE_TABLE(of, mt7628_of_match); + +static struct platform_driver mt7628_driver = { + .driver = { + .name = "mt7628-esw", + .of_match_table = mt7628_of_match, + }, + .probe = mt7628_probe, + .remove = mt7628_remove, + .shutdown = mt7628_shutdown, +}; + +module_platform_driver(mt7628_driver); + +MODULE_AUTHOR("Joris Vaisvila "); +MODULE_DESCRIPTION("Driver for Mediatek MT7628 embedded switch"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/mv88e6xxx/pcs-6352.c b/drivers/net/dsa/mv88e6xxx/pcs-6352.c index 4228ae5bb9db..437054711a2d 100644 --- a/drivers/net/dsa/mv88e6xxx/pcs-6352.c +++ b/drivers/net/dsa/mv88e6xxx/pcs-6352.c @@ -305,13 +305,16 @@ static bool mv88e6352_pcs_link_check(struct marvell_c22_pcs *mpcs) struct mv88e6xxx_port *port = mpcs->port; struct mv88e6xxx_chip *chip = port->chip; u8 cmode; + int err; /* Port 4 can be in auto-media mode. Check that the port is * associated with the mpcs. */ mv88e6xxx_reg_lock(chip); - chip->info->ops->port_get_cmode(chip, port->port, &cmode); + err = chip->info->ops->port_get_cmode(chip, port->port, &cmode); mv88e6xxx_reg_unlock(chip); + if (err) + return false; return cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX || cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX || diff --git a/drivers/net/dsa/qca/qca8k-leds.c b/drivers/net/dsa/qca/qca8k-leds.c index ef496e345a4e..0ada28377f46 100644 --- a/drivers/net/dsa/qca/qca8k-leds.c +++ b/drivers/net/dsa/qca/qca8k-leds.c @@ -457,6 +457,9 @@ qca8k_setup_led_ctrl(struct qca8k_priv *priv) int ret; ports = device_get_named_child_node(priv->dev, "ports"); + if (!ports) + ports = device_get_named_child_node(priv->dev, "ethernet-ports"); + if (!ports) { dev_info(priv->dev, "No ports node specified in device tree!"); return 0; diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index aa05375b090a..728231d8f94c 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -40,7 +40,8 @@ * driver has only been tested with a fixed-link, but in principle it should not * matter. * - * NOTE: Currently, only the RGMII interface is implemented in this driver. + * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented + * in this driver. * * The interrupt line is asserted on link UP/DOWN events. The driver creates a * custom irqchip to handle this interrupt and demultiplex the events by reading @@ -94,11 +95,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "realtek.h" #include "realtek-smi.h" @@ -129,6 +132,7 @@ /* Chip reset register */ #define RTL8365MB_CHIP_RESET_REG 0x1322 +#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010 #define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 #define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 @@ -238,6 +242,88 @@ #define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 #define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 +/* External interface line rate bypass register - one bit per external + * interface, indexed by the external port number with port 5 (the first + * external port) as the base. Other RTL8367 families index this register + * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only + * holds for the RTL8367C-style parts this driver supports. + */ +#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 +#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) + +/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit + * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a + * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see + * rtl8365mb_sds_raise_rate_limits(). + */ +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0 +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007 +#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398 +#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399 +#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007 + +/* SerDes indirect access registers */ +#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 +#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 +#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080 +#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040 +#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601 +#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602 + +/* SerDes miscellaneous configuration register */ +#define RTL8365MB_SDS_MISC_REG 0x1D11 +#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000 +#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000 +#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800 +#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400 +#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200 +#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180 +#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040 + +/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR + * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the + * vendor-specific low bits from phase 1 to phase 2, which triggers a data path + * reset and PLL resync. + */ +#define RTL8365MB_SDS_REG_BMCR 0x0000 +#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1) +#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3) +#define RTL8365MB_SDS_REG_NWAY 0x0002 +#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200 +#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100 +#define RTL8365MB_SDS_REG_RESET 0x0003 +#define RTL8365MB_SDS_RESET_DEASSERT 0x7106 +#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d +#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010 + +/* The embedded SerDes can only be muxed to external interface 1 (MAC8), + * which is port 6. + */ +#define RTL8365MB_SDS_EXT_INTERFACE_ID 1 +#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6 + +/* Line rate bypass bit for the SerDes external interface */ +#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \ + RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT) + +/* SerDes tuning parameter variant selector. The vendor driver picks between + * two sets of SerDes tuning parameters based on this chip option. Reading it + * requires first arming the read by writing a magic key to the arm register, + * then disarming it afterwards. + */ +#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0 +#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 +#define RTL8365MB_SDS_OPTION_REG 0x13C1 + +/* Embedded DW8051 microcontroller control registers. The microcontroller + * can run firmware to manage the SerDes link, but this driver keeps it in + * reset and disabled: phylink already performs the link management that + * the firmware would otherwise do. + */ +#define RTL8365MB_MISC_CFG0_REG 0x130C +#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020 + /* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ #define RTL8365MB_PORT_SPEED_10M 0 #define RTL8365MB_PORT_SPEED_100M 1 @@ -551,6 +637,30 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { { 0x1D32, 0x0002 }, }; +/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The + * vendor driver keeps two variants of this table and selects between them + * based on the chip option register; these are the values for a non-zero + * option, which is what RTL8367S parts seen so far report. See + * rtl8365mb_sds_probe_option(). + */ +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = { + { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 }, + { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 }, + { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, +}; + +/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As + * with the SGMII table, the vendor driver keeps several variants and selects + * one based on the chip option register; these are the values for a non-zero + * option, which is what RTL8367S parts seen so far report. See + * rtl8365mb_sds_probe_option(). + */ +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = { + { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 }, + { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 }, + { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 }, +}; + enum rtl8365mb_phy_interface_mode { RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), @@ -730,6 +840,9 @@ struct rtl8365mb_port { * @cpu: CPU tagging and CPU port configuration for this chip * @mib_lock: prevent concurrent reads of MIB counters * @ports: per-port data + * @pcs: PCS for the SerDes external interface + * @sds_supported: SerDes tuning parameters match the chip option, so the + * SerDes interface modes can be advertised * * Private data for this driver. */ @@ -740,8 +853,12 @@ struct rtl8365mb { struct rtl8365mb_cpu cpu; struct mutex mib_lock; struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; + struct phylink_pcs pcs; + bool sds_supported; }; +#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs) + static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) { u32 val; @@ -1042,6 +1159,391 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, return 0; } +static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) +{ + int ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); + if (ret) + return ret; + + /* The SerDes indirect access engine completes the command within the + * register write transaction, so there is no need to wait or poll for + * completion before the next access, matching the vendor driver. + */ + return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + RTL8365MB_SDS_INDACS_CMD_RUN_MASK | + RTL8365MB_SDS_INDACS_CMD_WR_MASK); +} + +static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) +{ + u32 val; + int ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + RTL8365MB_SDS_INDACS_CMD_RUN_MASK); + if (ret) + return ret; + + /* Wait for the indirect read to complete: the engine clears the BUSY + * bit once the data register holds the result. + */ + ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + val, + !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK), + 10, 1000); + if (ret) + return ret; + + ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val); + if (ret) + return ret; + + *data = val; + + return 0; +} + +/* The vendor driver selects between two sets of SerDes tuning parameters based + * on the chip option register. Only the variant for a non-zero option has been + * tested on real hardware - the RTL8367S parts seen so far all report 1. The + * variant for option 0 uses different tuning values that cannot be verified, + * so probe the option once at setup and only advertise the SerDes interface + * modes when the tuning parameters are known to match, so that an unsupported + * variant fails at phylink validation time rather than when configuring the + * link. + */ +static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) +{ + struct rtl8365mb *mb = priv->chip_data; + const struct rtl8365mb_extint *extint; + u32 option; + int ret; + int i; + + /* Nothing to probe if no external interface is wired to the SerDes */ + for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { + extint = &mb->chip_info->extints[i]; + + if (extint->supported_interfaces & + (RTL8365MB_PHY_INTERFACE_MODE_SGMII | + RTL8365MB_PHY_INTERFACE_MODE_HSGMII)) + break; + } + if (i == RTL8365MB_MAX_NUM_EXTINTS) + return 0; + + ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, + RTL8365MB_SDS_OPTION_ARM_KEY); + if (ret) + return ret; + + ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0); + if (ret) + return ret; + + if (option == 0) { + dev_warn(priv->dev, + "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n"); + return 0; + } + + mb->sds_supported = true; + + return 0; +} + +/* The vendor driver raises the port 6 ingress and egress rate limiters to + * their maximum in its switch init, unconditionally for the whole chip + * family. The chip reset in rtl8365mb_setup() puts them back to their reset + * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate + * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor + * documentation describes the reset default as disabling the limiter, but + * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF + * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler + * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its + * maximum by the common init jam table. + */ +static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv) +{ + int ret; + + ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG, + 0xFFFF); + if (ret) + return ret; + + ret = regmap_update_bits(priv->map, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG, + 0xFFFF); + if (ret) + return ret; + + return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG, + RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK, + RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK); +} + +static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + const struct rtl8365mb_jam_tbl_entry *sds_jam; + const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv; + size_t sds_jam_size; + u32 mode; + u16 val; + int ret; + int i; + + priv = mb->priv; + + if (interface == PHY_INTERFACE_MODE_2500BASEX) { + sds_jam = rtl8365mb_sds_jam_hsgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); + mode = RTL8365MB_EXT_PORT_MODE_HSGMII; + } else { + sds_jam = rtl8365mb_sds_jam_sgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); + mode = RTL8365MB_EXT_PORT_MODE_SGMII; + } + + /* Hold the embedded DW8051 microcontroller in reset and keep it + * disabled. The vendor driver loads firmware into it to manage the + * SerDes link, but the firmware only duplicates work that phylink + * already does: it polls the port status and forces the external + * interface configuration in the very registers this driver manages. + * Letting it run would race with phylink. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG, + RTL8365MB_CHIP_RESET_DW8051_MASK, + RTL8365MB_CHIP_RESET_DW8051_MASK); + if (ret) + return ret; + + ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG, + RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0); + if (ret) + return ret; + + /* The vendor driver clears the line rate bypass for all interface + * modes except TMII. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG, + RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0); + if (ret) + return ret; + + /* Tune the SerDes with vendor-prescribed parameters */ + for (i = 0; i < sds_jam_size; i++) { + ret = rtl8365mb_sds_write(priv, sds_jam[i].reg, + sds_jam[i].val); + if (ret) + return ret; + } + + /* Mux the SerDes to MAC8 in the requested mode */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, + mode == RTL8365MB_EXT_PORT_MODE_SGMII ? + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK); + if (ret) + return ret; + + val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + ret = regmap_update_bits(priv->map, + RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), + RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), + val); + if (ret) + return ret; + + /* Take the SerDes out of reset. The vendor driver does this only + * after the SerDes mux and the interface mode are configured. + */ + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET, + RTL8365MB_SDS_RESET_DEASSERT); + if (ret) + return ret; + + /* Reset the SerDes data path and resync its PLL, mirroring what the + * vendor firmware does right after deasserting the SerDes reset. + * This flushes the FIFOs and ensures a clean state for the link, + * preventing silent drops and CRC errors. + */ + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + RTL8365MB_SDS_BMCR_DPRST_PHASE1); + if (ret) + return ret; + + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + RTL8365MB_SDS_BMCR_DPRST_PHASE2); + if (ret) + return ret; + + /* Keep SGMII in-band autonegotiation disabled: the link parameters are + * forced from rtl8365mb_pcs_link_up() instead. + */ + ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val); + if (ret) + return ret; + + val &= ~RTL8365MB_SDS_NWAY_EN_MASK; + val |= RTL8365MB_SDS_NWAY_RESTART_MASK; + + return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val); +} + +static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) +{ + return interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_2500BASEX; +} + +static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + /* In-band autonegotiation is not implemented; the link is always + * forced. Report that to phylink so that it never selects an + * in-band-enabled negotiation mode for this PCS. + */ + return LINK_INBAND_DISABLE; +} + +static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, + unsigned int neg_mode, + struct phylink_link_state *state) +{ + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv = mb->priv; + u16 status; + u32 val; + int ret; + + /* In-band autonegotiation is not implemented, so the link parameters are + * forced from rtl8365mb_pcs_link_up(). The real link state must still be + * read from the SerDes itself: the embedded DW8051 microcontroller that + * the vendor firmware uses to poll the SerDes is kept disabled (see + * rtl8365mb_pcs_config()), so the link status register can be read + * directly through the SDS_INDACS window without racing the auto-poll. + */ + ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status); + if (ret) { + state->link = false; + return; + } + + state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK); + state->an_complete = state->link; + if (!state->link) + return; + + /* The speed and duplex are forced; read them back from the values + * programmed into the SerDes MISC register. + */ + ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val); + if (ret) { + state->link = false; + return; + } + + state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ? + DUPLEX_FULL : DUPLEX_HALF; + + switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { + case RTL8365MB_PORT_SPEED_1000M: + state->speed = + state->interface == PHY_INTERFACE_MODE_2500BASEX ? + SPEED_2500 : SPEED_1000; + break; + case RTL8365MB_PORT_SPEED_100M: + state->speed = SPEED_100; + break; + case RTL8365MB_PORT_SPEED_10M: + state->speed = SPEED_10; + break; + } +} + +static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv = mb->priv; + u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK | + RTL8365MB_SDS_MISC_SGMII_LINK_MASK | + RTL8365MB_SDS_MISC_SGMII_SPD_MASK; + u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK; + u32 r_speed; + int ret; + + /* The speed field has no value for 2.5 Gbps: the rate is determined by + * the HSGMII SerDes configuration, and the vendor driver programs the + * 1 Gbps value here. + */ + if (speed == SPEED_2500 || speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; + } else if (speed == SPEED_10) { + r_speed = RTL8365MB_PORT_SPEED_10M; + } else { + dev_err(priv->dev, "unsupported SerDes speed %s\n", + phy_speed_to_str(speed)); + return; + } + + val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed); + + if (duplex == DUPLEX_FULL) + val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK; + + /* pcs_link_up() carries no pause information, so the SerDes flow + * control bits are programmed together with the MAC external interface + * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause + * modes are known. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val); + if (ret) { + dev_err(priv->dev, "failed to force SerDes link: %pe\n", + ERR_PTR(ret)); + return; + } +} + +static const struct phylink_pcs_ops rtl8365mb_pcs_ops = { + .pcs_inband_caps = rtl8365mb_pcs_inband_caps, + .pcs_config = rtl8365mb_pcs_config, + .pcs_get_state = rtl8365mb_pcs_get_state, + .pcs_link_up = rtl8365mb_pcs_link_up, +}; + static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, bool link, int speed, int duplex, bool tx_pause, bool rx_pause) @@ -1065,7 +1567,11 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, r_rx_pause = rx_pause ? 1 : 0; r_tx_pause = tx_pause ? 1 : 0; - if (speed == SPEED_1000) { + /* The speed field has no value for 2.5 Gbps: the rate is + * determined by the HSGMII SerDes configuration, and the + * vendor driver programs the 1 Gbps value here. + */ + if (speed == SPEED_2500 || speed == SPEED_1000) { r_speed = RTL8365MB_PORT_SPEED_1000M; } else if (speed == SPEED_100) { r_speed = RTL8365MB_PORT_SPEED_100M; @@ -1118,6 +1624,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, { const struct rtl8365mb_extint *extint = rtl8365mb_get_port_extint(ds->priv, port); + struct realtek_priv *priv = ds->priv; + struct rtl8365mb *mb = priv->chip_data; config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; @@ -1141,6 +1649,32 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) phy_interface_set_rgmii(config->supported_interfaces); + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && + mb->sds_supported) + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII && + mb->sds_supported) { + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + config->supported_interfaces); + config->mac_capabilities |= MAC_2500FD; + } +} + +static struct phylink_pcs * +rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct realtek_priv *priv = dp->ds->priv; + struct rtl8365mb *mb = priv->chip_data; + + if (rtl8365mb_interface_is_serdes(interface)) + return &mb->pcs; + + return NULL; } static void rtl8365mb_phylink_mac_config(struct phylink_config *config, @@ -1168,6 +1702,12 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config, return; } + /* SGMII and 2500base-x are handled by the SerDes PCS, configured + * through the phylink_pcs ops, so nothing to do here for them. + */ + if (rtl8365mb_interface_is_serdes(state->interface)) + return; + /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also * supports */ @@ -1188,7 +1728,13 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, p = &mb->ports[port]; cancel_delayed_work_sync(&p->mib_work); - if (phy_interface_mode_is_rgmii(interface)) { + /* phylink has no pcs_link_down callback, so on the SerDes path only the + * MAC external interface force is reset here. Clearing the MAC force is + * enough to bring the link down; the SerDes keeps presenting its last + * forced state until the next pcs_link_up() reprograms it. + */ + if (phy_interface_mode_is_rgmii(interface) || + rtl8365mb_interface_is_serdes(interface)) { ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, false, false); if (ret) @@ -1218,14 +1764,51 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, p = &mb->ports[port]; schedule_delayed_work(&p->mib_work, 0); - if (phy_interface_mode_is_rgmii(interface)) { + /* The SerDes forced link state is programmed by the PCS in + * rtl8365mb_pcs_link_up(); here only the MAC external interface force + * is configured, for both RGMII and SerDes. + */ + if (phy_interface_mode_is_rgmii(interface) || + rtl8365mb_interface_is_serdes(interface)) { ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, duplex, tx_pause, rx_pause); - if (ret) + if (ret) { dev_err(priv->dev, "failed to force mode on port %d: %pe\n", port, ERR_PTR(ret)); + return; + } + + /* The SerDes has its own pause enables; program them from + * the resolved pause modes, as the vendor driver does when + * forcing the link on a SerDes external interface. These + * bits, not the MAC force pause bits, gate pause on the + * SerDes external interface: flow control testing shows + * that pause frames are only emitted with the SerDes TXFC + * bit set, while the MAC force pause bits alone have no + * effect on this port. This is done here rather than in + * rtl8365mb_pcs_link_up() because pcs_link_up() carries no + * pause information. + */ + if (rtl8365mb_interface_is_serdes(interface)) { + u32 val = 0; + + if (tx_pause) + val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK; + if (rx_pause) + val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK; + + ret = regmap_update_bits(priv->map, + RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_SGMII_TXFC_MASK | + RTL8365MB_SDS_MISC_SGMII_RXFC_MASK, + val); + if (ret) + dev_err(priv->dev, + "failed to force SerDes pause modes on port %d: %pe\n", + port, ERR_PTR(ret)); + } return; } @@ -2424,6 +3007,14 @@ static int rtl8365mb_setup(struct dsa_switch *ds) mb = priv->chip_data; cpu = &mb->cpu; + mb->pcs.ops = &rtl8365mb_pcs_ops; + + /* The SerDes has no link interrupt wired up, so phylink must poll the + * PCS for link changes when it tracks the link through pcs_get_state() + * (in-band mode with autonegotiation disabled). + */ + mb->pcs.poll = true; + ret = rtl8365mb_reset_chip(priv); if (ret) { dev_err(priv->dev, "failed to reset chip: %pe\n", @@ -2431,6 +3022,13 @@ static int rtl8365mb_setup(struct dsa_switch *ds) goto out_error; } + ret = rtl8365mb_sds_probe_option(priv); + if (ret) { + dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n", + ERR_PTR(ret)); + goto out_error; + } + /* Configure switch to vendor-defined initial state */ ret = rtl8365mb_switch_init(priv); if (ret) { @@ -2439,6 +3037,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds) goto out_error; } + if (mb->sds_supported) { + ret = rtl8365mb_sds_raise_rate_limits(priv); + if (ret) { + dev_err(priv->dev, + "failed to raise port rate limits: %pe\n", + ERR_PTR(ret)); + goto out_error; + } + } + /* Set up cascading IRQs */ ret = rtl8365mb_irq_setup(priv); if (ret == -EPROBE_DEFER) @@ -2668,6 +3276,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv) } static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { + .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs, .mac_config = rtl8365mb_phylink_mac_config, .mac_link_down = rtl8365mb_phylink_mac_link_down, .mac_link_up = rtl8365mb_phylink_mac_link_up, diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 103039fe3086..f11831b66de8 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -791,11 +791,86 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv) return ret; } +static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* Bit 0 enables isolation, the mask indicates allowed forwarding + * ports + */ + mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN; + + return regmap_write(priv->map, RTL8366RB_PORT_ISO(port), + mask); +} + +static int rtl8366rb_port_add_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* We assume isolation bit is on */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), + RTL8366RB_PORT_ISO_PORTS(mask)); +} + +static int rtl8366rb_port_remove_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), 0); +} + +static void +rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct realtek_priv *priv = ds->priv; + u32 val; + int i; + + switch (state) { + case BR_STATE_DISABLED: + val = RTL8366RB_STP_STATE_DISABLED; + break; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + val = RTL8366RB_STP_STATE_BLOCKING; + break; + case BR_STATE_LEARNING: + val = RTL8366RB_STP_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + val = RTL8366RB_STP_STATE_FORWARDING; + break; + default: + dev_err(priv->dev, "unknown bridge state requested\n"); + return; + } + + /* Set the same status for the port on all the FIDs */ + for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { + regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, + RTL8366RB_STP_STATE_MASK(port), + RTL8366RB_STP_STATE(port, val)); + } +} + +static int rtl8366rb_port_set_learning(struct realtek_priv *priv, int port, + bool enable) +{ + /* Notice inverted semantics in this register: setting a bit disables + * learning instead of enabling it. + */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, + BIT(port), enable ? 0 : BIT(port)); +} + static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; const struct rtl8366rb_jam_tbl_entry *jam_table; + u32 downports_mask = 0; struct rtl8366rb *rb; + u32 upports_mask = 0; + struct dsa_port *dp; u32 chip_ver = 0; u32 chip_id = 0; int jam_size; @@ -866,20 +941,58 @@ static int rtl8366rb_setup(struct dsa_switch *ds) if (ret) return ret; - /* Isolate all user ports so they can only send packets to itself and the CPU port */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) | - RTL8366RB_PORT_ISO_EN); + /* Start with all ports blocked, including unused ports */ + dsa_switch_for_each_port(dp, ds) { + /* Set the initial STP state of all ports to DISABLED, otherwise + * ports will still forward frames to the CPU despite being + * administratively down by default. + */ + rtl8366rb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + + /* Start with all ports completely isolated */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, 0); + if (ret) + return ret; + + /* Disable learning */ + ret = rtl8366rb_port_set_learning(priv, dp->index, false); + if (ret) + return ret; + + /* Collect CPU ports. If we support cascade switches, it should + * also include the upstream DSA ports. + */ + if (!dsa_port_is_cpu(dp)) + continue; + + upports_mask |= BIT(dp->index); + } + + /* Configure user ports */ + dsa_switch_for_each_port(dp, ds) { + if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU(s), isolate from all other ports */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask); + if (ret) + return ret; + + /* If we support cascade switches, it should also include the + * downstream DSA ports. + */ + downports_mask |= BIT(dp->index); + } + + /* Configure CPU ports. If we support cascade switches, this will also + * include DSA ports. + */ + dsa_switch_for_each_cpu_port(dp, ds) { + /* Forward to all user ports */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, downports_mask); if (ret) return ret; } - /* CPU port can send packets to all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU), - RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) | - RTL8366RB_PORT_ISO_EN); - if (ret) - return ret; /* Set up the "green ethernet" feature */ ret = rtl8366rb_jam_table(rtl8366rb_green_jam, @@ -938,12 +1051,6 @@ static int rtl8366rb_setup(struct dsa_switch *ds) rb->max_mtu[i] = ETH_DATA_LEN; } - /* Disable learning for all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - RTL8366RB_PORT_ALL); - if (ret) - return ret; - /* Enable auto ageing for all ports */ ret = regmap_write(priv->map, RTL8366RB_SECURITY_CTRL, 0); if (ret) @@ -1184,70 +1291,6 @@ rtl8366rb_port_disable(struct dsa_switch *ds, int port) return; } -static int -rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port, - struct dsa_bridge bridge, - bool *tx_fwd_offload, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than the current one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Join this port to each other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), - RTL8366RB_PORT_ISO_PORTS(BIT(port))); - if (ret) - dev_err(priv->dev, "failed to join port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Set the bits for the ports we can access */ - return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), - RTL8366RB_PORT_ISO_PORTS(port_bitmap)); -} - -static void -rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port, - struct dsa_bridge bridge) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than this one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Remove this port from any other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0); - if (ret) - dev_err(priv->dev, "failed to leave port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Clear the bits for the ports we can not access, leave ourselves */ - regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0); -} - /** * rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames * @priv: SMI state container @@ -1306,59 +1349,6 @@ rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port, return 0; } -static int -rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port, - struct switchdev_brport_flags flags, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - int ret; - - if (flags.mask & BR_LEARNING) { - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - BIT(port), - (flags.val & BR_LEARNING) ? 0 : BIT(port)); - if (ret) - return ret; - } - - return 0; -} - -static void -rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{ - struct realtek_priv *priv = ds->priv; - u32 val; - int i; - - switch (state) { - case BR_STATE_DISABLED: - val = RTL8366RB_STP_STATE_DISABLED; - break; - case BR_STATE_BLOCKING: - case BR_STATE_LISTENING: - val = RTL8366RB_STP_STATE_BLOCKING; - break; - case BR_STATE_LEARNING: - val = RTL8366RB_STP_STATE_LEARNING; - break; - case BR_STATE_FORWARDING: - val = RTL8366RB_STP_STATE_FORWARDING; - break; - default: - dev_err(priv->dev, "unknown bridge state requested\n"); - return; - } - - /* Set the same status for the port on all the FIDs */ - for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { - regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, - RTL8366RB_STP_STATE_MASK(port), - RTL8366RB_STP_STATE(port, val)); - } -} - static void rtl8366rb_port_fast_age(struct dsa_switch *ds, int port) { @@ -1801,15 +1791,15 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = { .get_strings = rtl8366_get_strings, .get_ethtool_stats = rtl8366_get_ethtool_stats, .get_sset_count = rtl8366_get_sset_count, - .port_bridge_join = rtl8366rb_port_bridge_join, - .port_bridge_leave = rtl8366rb_port_bridge_leave, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, .port_vlan_filtering = rtl8366rb_vlan_filtering, .port_vlan_add = rtl8366_vlan_add, .port_vlan_del = rtl8366_vlan_del, .port_enable = rtl8366rb_port_enable, .port_disable = rtl8366rb_port_disable, .port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags, - .port_bridge_flags = rtl8366rb_port_bridge_flags, + .port_bridge_flags = rtl83xx_port_bridge_flags, .port_stp_state_set = rtl8366rb_port_stp_state_set, .port_fast_age = rtl8366rb_port_fast_age, .port_change_mtu = rtl8366rb_change_mtu, @@ -1830,6 +1820,9 @@ static const struct realtek_ops rtl8366rb_ops = { .is_vlan_valid = rtl8366rb_is_vlan_valid, .enable_vlan = rtl8366rb_enable_vlan, .enable_vlan4k = rtl8366rb_enable_vlan4k, + .port_add_isolation = rtl8366rb_port_add_isolation, + .port_remove_isolation = rtl8366rb_port_remove_isolation, + .port_set_learning = rtl8366rb_port_set_learning, .phy_read = rtl8366rb_phy_read, .phy_write = rtl8366rb_phy_write, }; diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 9dd50b20c000..a96beea72ef5 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -321,7 +321,7 @@ void rtl83xx_reset_assert(struct realtek_priv *priv) "Failed to assert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, true); + gpiod_set_value_cansleep(priv->reset, true); } void rtl83xx_reset_deassert(struct realtek_priv *priv) @@ -334,7 +334,7 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv) "Failed to deassert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, false); + gpiod_set_value_cansleep(priv->reset, false); } /** @@ -364,9 +364,6 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, if (!priv->ops->port_add_isolation) return -EOPNOTSUPP; - if (!priv->ops->port_set_learning) - return -EOPNOTSUPP; - dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); /* Add this port to the isolation group of every other port @@ -404,9 +401,11 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, goto undo_self_isolation; } - ret = priv->ops->port_set_learning(priv, port, true); - if (ret) - goto undo_efid; + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, true); + if (ret) + goto undo_efid; + } return 0; @@ -451,9 +450,6 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, if (!priv->ops->port_remove_isolation) return; - if (!priv->ops->port_set_learning) - return; - dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); /* Remove this port from the isolation group of every other @@ -482,11 +478,13 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, * downstream DSA ports from the isolation group. */ - ret = priv->ops->port_set_learning(priv, port, false); - if (ret) - dev_err(priv->dev, - "failed to disable learning on port %d: %pe\n", - port, ERR_PTR(ret)); + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, false); + if (ret) + dev_err(priv->dev, + "failed to disable learning on port %d: %pe\n", + port, ERR_PTR(ret)); + } /* Remove those ports from the isolation group of this port */ ret = priv->ops->port_remove_isolation(priv, port, mask); diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/yt921x.h index 555046526669..5f3b99e189c4 100644 --- a/drivers/net/dsa/yt921x.h +++ b/drivers/net/dsa/yt921x.h @@ -854,7 +854,7 @@ enum yt921x_fdb_entry_status { #define YT921X_PORT_NUM 11 #define yt921x_port_is_internal(port) ((port) < 8) -#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9) +#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9) struct yt921x_mib { u64 rx_broadcast; diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 78c79ad7bba5..8581ccba1505 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -189,5 +189,6 @@ source "drivers/net/ethernet/wangxun/Kconfig" source "drivers/net/ethernet/wiznet/Kconfig" source "drivers/net/ethernet/xilinx/Kconfig" source "drivers/net/ethernet/xircom/Kconfig" +source "drivers/net/ethernet/zte/Kconfig" endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index bba55d9af387..2b1153d35b52 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -105,3 +105,4 @@ obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/ obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/ obj-$(CONFIG_OA_TC6) += oa_tc6.o +obj-$(CONFIG_NET_VENDOR_ZTE) += zte/ diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig index 760a9a60bc15..bdb8ff7d15da 100644 --- a/drivers/net/ethernet/adi/Kconfig +++ b/drivers/net/ethernet/adi/Kconfig @@ -26,4 +26,16 @@ config ADIN1110 Say yes here to build support for Analog Devices ADIN1110 Low Power 10BASE-T1L Ethernet MAC-PHY. +config ADIN1140 + tristate "Analog Devices ADIN1140 MAC-PHY" + depends on SPI + select ADIN1140_PHY + select OA_TC6 + help + Say yes here to build support for Analog Devices, Inc. ADIN1140 + 10BASE-T1S Ethernet MAC-PHY. + + To compile this driver as a module, choose M here. The module will be + called adin1140. + endif # NET_VENDOR_ADI diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile index d0383d94303c..0390ca8ccc49 100644 --- a/drivers/net/ethernet/adi/Makefile +++ b/drivers/net/ethernet/adi/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_ADIN1110) += adin1110.o +obj-$(CONFIG_ADIN1140) += adin1140.o diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c new file mode 100644 index 000000000000..93710baca151 --- /dev/null +++ b/drivers/net/ethernet/adi/adin1140.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ADIN1140_MAC_CONFIG2_STAT_CLR_RD BIT(6) +#define ADIN1140_MAC_CONFIG2_FWD_UNK2HOST BIT(2) + +#define ADIN1140_MAC_P1_LOOP_ADDR_REG 0xC4 + +#define ADIN1140_MAC_ADDR_FILT_UPR_REG 0x50 +#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30) +#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16) + +#define ADIN1140_MAC_ADDR_FILT_LWR_REG 0x51 + +#define ADIN1140_MAC_ADDR_MASK_UPR_REG 0x70 +#define ADIN1140_MAC_ADDR_MASK_LWR_REG 0x71 + +#define ADIN1140_MAC_FILT_MC_SLOT 0U +#define ADIN1140_MAC_FILT_BC_SLOT 1U +#define ADIN1140_MAC_FILT_UC_SLOT 2U +#define ADIN1140_MAC_FILT_MAX_SLOT 16U +#define ADIN1140_MAC_FILT_MASK_LIMIT 2U + +#define ADIN1140_MAC_RX_FRAME_CNT 0xA1 +#define ADIN1140_MAC_RX_BC_FRAME_CNT 0xA2 +#define ADIN1140_MAC_RX_MC_FRAME_CNT 0xA3 +#define ADIN1140_MAC_RX_CRC_ERR_CNT 0xA5 +#define ADIN1140_MAC_RX_ALIGN_ERR_CNT 0xA6 +#define ADIN1140_MAC_RX_PREAMBLE_ERR_CNT 0xA7 +#define ADIN1140_MAC_RX_SHORT_ERR_CNT 0xA8 +#define ADIN1140_MAC_RX_LONG_ERR_CNT 0xA9 +#define ADIN1140_MAC_RX_PHY_ERR_CNT 0xAA +#define ADIN1140_MAC_RX_DRP_FULL_CNT 0xAB +#define ADIN1140_MAC_RX_DRP_FILTER_CNT 0xAD +#define ADIN1140_MAC_RX_IFG_ERR_CNT 0xAE +#define ADIN1140_MAC_TX_FRAME_CNT 0xB1 +#define ADIN1140_MAC_TX_BC_FRAME_CNT 0xB2 +#define ADIN1140_MAC_TX_MC_FRAME_CNT 0xB3 +#define ADIN1140_MAC_TX_SINGLE_COL_CNT 0xB5 +#define ADIN1140_MAC_TX_MULTI_COL_CNT 0xB6 +#define ADIN1140_MAC_TX_DEFERRED_CNT 0xB7 +#define ADIN1140_MAC_TX_LATE_COL_CNT 0xB8 +#define ADIN1140_MAC_TX_EXCESS_COL_CNT 0xB9 +#define ADIN1140_MAC_TX_UNDERRUN_CNT 0xBA + +/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast + * reserved slots) + */ +#define ADIN1140_MAC_FILT_AVAIL 13U + +#define ADIN1140_PHY_CTRL_DEFAULT 0x1000 +#define ADIN1140_PHY_STATUS_DEFAULT 0x082D +#define ADIN1140_PHY_ID1 0x0283 +#define ADIN1140_PHY_ID2 0xBE00 + +#define ADIN1140_STATS_CHECK_DELAY (3 * HZ) + +enum adin1140_statistics_entry { + rx_frames, + rx_bc_frames, + rx_mc_frames, + rx_crc_errors, + rx_align_errors, + rx_preamble_errors, + rx_short_frame_errors, + rx_long_frame_errors, + rx_phy_errors, + rx_fifo_full_dropped, + rx_addr_filter_dropped, + rx_ifg_errors, + tx_frames, + tx_bc_frames, + tx_mc_frames, + tx_single_collision, + tx_multi_collision, + tx_deferred, + tx_late_collision, + tx_excess_collision, + tx_underrun, + ADIN1140_STATS_CNT, +}; + +struct adin1140_statistics_reg { + const char *name; + enum adin1140_statistics_entry idx; +}; + +struct adin1140_priv { + struct net_device *netdev; + struct oa_tc6 *tc6; + struct mii_bus *mdiobus; + struct phy_device *phydev; + struct delayed_work stats_work; + + /* Protects stats[] from concurrent updates in adin1140_stats_work + * and reads in the get_stats functions + */ + spinlock_t stat_lock; + u64 stats[ADIN1140_STATS_CNT]; +}; + +static const u32 adin1140_stat_regs[] = { + [rx_frames] = ADIN1140_MAC_RX_FRAME_CNT, + [rx_bc_frames] = ADIN1140_MAC_RX_BC_FRAME_CNT, + [rx_mc_frames] = ADIN1140_MAC_RX_MC_FRAME_CNT, + [rx_crc_errors] = ADIN1140_MAC_RX_CRC_ERR_CNT, + [rx_align_errors] = ADIN1140_MAC_RX_ALIGN_ERR_CNT, + [rx_preamble_errors] = ADIN1140_MAC_RX_PREAMBLE_ERR_CNT, + [rx_short_frame_errors] = ADIN1140_MAC_RX_SHORT_ERR_CNT, + [rx_long_frame_errors] = ADIN1140_MAC_RX_LONG_ERR_CNT, + [rx_phy_errors] = ADIN1140_MAC_RX_PHY_ERR_CNT, + [rx_fifo_full_dropped] = ADIN1140_MAC_RX_DRP_FULL_CNT, + [rx_addr_filter_dropped] = ADIN1140_MAC_RX_DRP_FILTER_CNT, + [rx_ifg_errors] = ADIN1140_MAC_RX_IFG_ERR_CNT, + [tx_frames] = ADIN1140_MAC_TX_FRAME_CNT, + [tx_bc_frames] = ADIN1140_MAC_TX_BC_FRAME_CNT, + [tx_mc_frames] = ADIN1140_MAC_TX_MC_FRAME_CNT, + [tx_single_collision] = ADIN1140_MAC_TX_SINGLE_COL_CNT, + [tx_multi_collision] = ADIN1140_MAC_TX_MULTI_COL_CNT, + [tx_deferred] = ADIN1140_MAC_TX_DEFERRED_CNT, + [tx_late_collision] = ADIN1140_MAC_TX_LATE_COL_CNT, + [tx_excess_collision] = ADIN1140_MAC_TX_EXCESS_COL_CNT, + [tx_underrun] = ADIN1140_MAC_TX_UNDERRUN_CNT, +}; + +static const struct adin1140_statistics_reg adin1140_stats[] = { + {.name = "rx_preamble_errors", .idx = rx_preamble_errors}, + {.name = "rx_ifg_errors", .idx = rx_ifg_errors}, +}; + +static int adin1140_mac_filter_set(struct adin1140_priv *priv, + const u8 *addr, const u8 *mask, + u8 slot) +{ + u32 reg_address; + u32 val; + int ret; + + if (slot >= ADIN1140_MAC_FILT_MAX_SLOT) + return -ENOSPC; + + reg_address = ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot; + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be16(&addr[0]) | + ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 | + ADIN1140_MAC_ADDR_FILT_TO_HOST); + if (ret) + return ret; + + reg_address = ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot; + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be32(&addr[2])); + if (ret) + return ret; + + /* Only the first 2 destination MAC filter slots support masking. + * For the other entries, the destination address in the received + * frame must match exactly. + */ + if (slot >= ADIN1140_MAC_FILT_MASK_LIMIT) + return 0; + + val = get_unaligned_be16(&mask[0]); + reg_address = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot); + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); + if (ret) + return ret; + + val = get_unaligned_be32(&mask[2]); + reg_address = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot); + + return oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); +} + +static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot) +{ + u8 mask[ETH_ALEN]; + u8 addr[ETH_ALEN]; + + memset(mask, 0xFF, ETH_ALEN); + memset(addr, 0x0, ETH_ALEN); + + return adin1140_mac_filter_set(priv, addr, mask, slot); +} + +static int adin1140_filter_unicast(struct adin1140_priv *priv) +{ + /* Only the first 2 filter slots support masking, so no unicast + * address will ever need a mask. The first slots are used for the + * all multicast and broadcast filter. + */ + return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, NULL, + ADIN1140_MAC_FILT_UC_SLOT); +} + +static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en) +{ + u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0}; + + if (en) + return adin1140_mac_filter_set(priv, multicast_addr, + multicast_addr, + ADIN1140_MAC_FILT_MC_SLOT); + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT); +} + +static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled) +{ + u8 mask[ETH_ALEN]; + + if (enabled) { + memset(mask, 0xFF, ETH_ALEN); + return adin1140_mac_filter_set(priv, mask, mask, + ADIN1140_MAC_FILT_BC_SLOT); + } + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT); +} + +static int adin1140_default_filter_config(struct adin1140_priv *priv) +{ + int ret; + + ret = adin1140_filter_broadcast(priv, true); + if (ret) + return ret; + + return adin1140_filter_unicast(priv); +} + +static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled) +{ + int ret; + u32 val; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, &val); + if (ret) + return ret; + + if (enabled) + val |= ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + else + val &= ~ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + + return oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, val); +} + +static int adin1140_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct adin1140_priv *priv = netdev_priv(dev); + struct netdev_hw_addr *ha; + bool all_multi, promisc; + u32 mac_addrs; + u8 slot, i; + int ret; + + /* The ADIN1140 has 16 dest MAC address filter slots: + * 0 - reserved for all multicast filter. + * 1 - reserved for broadcast filter. + * 2 - reserved for the device's own unicast MAC. + * 3 -> 15 - available for other unicast/multicast filters. + */ + + mac_addrs = netdev_hw_addr_list_count(uc); + all_multi = false; + promisc = false; + + if (priv->netdev->flags & IFF_PROMISC) + promisc = true; + else if (priv->netdev->flags & IFF_ALLMULTI) + all_multi = true; + else + mac_addrs += netdev_hw_addr_list_count(mc); + + if (mac_addrs > ADIN1140_MAC_FILT_AVAIL) { + /* The filter table is full. Enable promisc mode. */ + promisc = true; + } + + ret = adin1140_promiscuous_mode(priv, promisc); + if (ret) + return ret; + + ret = adin1140_filter_all_multicast(priv, all_multi); + if (ret) + return ret; + + slot = ADIN1140_MAC_FILT_UC_SLOT + 1; + if (!promisc) { + netdev_hw_addr_list_for_each(ha, uc) { + ret = adin1140_mac_filter_set(priv, ha->addr, NULL, + slot); + if (ret) + return ret; + + slot++; + } + + if (!all_multi) { + netdev_hw_addr_list_for_each(ha, mc) { + ret = adin1140_mac_filter_set(priv, ha->addr, + NULL, slot); + if (ret) + return ret; + + slot++; + } + } + } + + for (i = slot; i < ADIN1140_MAC_FILT_MAX_SLOT; i++) { + ret = adin1140_mac_filter_clear(priv, i); + if (ret) + return ret; + } + + return 0; +} + +static void adin1140_stats_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adin1140_priv *priv; + u32 reg_val; + int ret; + u32 i; + + priv = container_of(dwork, struct adin1140_priv, stats_work); + + for (i = 0; i < ARRAY_SIZE(adin1140_stat_regs); i++) { + ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + adin1140_stat_regs[i], + ®_val); + if (ret) + goto out; + + scoped_guard(spinlock, &priv->stat_lock) + priv->stats[i] += reg_val; + } + +out: + schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY); +} + +static int adin1140_configure(struct adin1140_priv *priv) +{ + u32 reg_val; + int ret; + + ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6); + if (ret) + return ret; + + /* Disable MAC loopback */ + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0); + if (ret) + return ret; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, ®_val); + if (ret) + return ret; + + reg_val |= ADIN1140_MAC_CONFIG2_STAT_CLR_RD; + + ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, reg_val); + if (ret) + return ret; + + return adin1140_default_filter_config(priv); +} + +static int adin1140_open(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY); + + phy_start(netdev->phydev); + netif_start_queue(netdev); + + return 0; +} + +static int adin1140_close(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + cancel_delayed_work_sync(&priv->stats_work); + + netif_stop_queue(netdev); + phy_stop(netdev->phydev); + + return 0; +} + +static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + /* The MAC doesn't automatically pad the frame to a 60 byte minimum + * size in case the host sends a shorter skb, so we have to do it in + * the driver. The FCS will be added by the MAC. + */ + if (skb_put_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + return oa_tc6_start_xmit(priv->tc6, skb); +} + +static int adin1140_set_mac_address(struct net_device *netdev, void *addr) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + struct sockaddr *address = addr; + u8 mask[ETH_ALEN]; + int ret; + + ret = eth_prepare_mac_addr_change(netdev, addr); + if (ret < 0) + return ret; + + if (ether_addr_equal(address->sa_data, netdev->dev_addr)) + return 0; + + memset(mask, 0xFF, ETH_ALEN); + ret = adin1140_mac_filter_set(priv, address->sa_data, mask, + ADIN1140_MAC_FILT_UC_SLOT); + if (ret) + return ret; + + eth_commit_mac_addr_change(netdev, addr); + + return 0; +} + +static void __adin1140_ndo_get_stats64(struct adin1140_priv *priv, + struct rtnl_link_stats64 *storage) +{ + storage->rx_errors = priv->stats[rx_crc_errors] + + priv->stats[rx_align_errors] + + priv->stats[rx_preamble_errors] + + priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors] + + priv->stats[rx_phy_errors] + + priv->stats[rx_ifg_errors]; + + storage->tx_errors = priv->stats[tx_excess_collision] + + priv->stats[tx_underrun]; + + storage->rx_dropped = priv->stats[rx_fifo_full_dropped] + + priv->stats[rx_addr_filter_dropped]; + + storage->multicast = priv->stats[rx_mc_frames]; + + storage->collisions = priv->stats[tx_single_collision] + + priv->stats[tx_multi_collision]; + + storage->rx_length_errors = priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors]; + storage->rx_over_errors = priv->stats[rx_fifo_full_dropped]; + storage->rx_crc_errors = priv->stats[rx_crc_errors]; + storage->rx_frame_errors = priv->stats[rx_align_errors]; + storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped]; + + storage->tx_aborted_errors = priv->stats[tx_excess_collision]; + storage->tx_fifo_errors = priv->stats[tx_underrun]; + storage->tx_window_errors = priv->stats[tx_late_collision]; +} + +static void adin1140_ndo_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *storage) +{ + struct adin1140_priv *priv = netdev_priv(dev); + + storage->rx_packets = priv->netdev->stats.rx_packets; + storage->tx_packets = priv->netdev->stats.tx_packets; + + storage->rx_bytes = priv->netdev->stats.rx_bytes; + storage->tx_bytes = priv->netdev->stats.tx_bytes; + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_ndo_get_stats64(priv, storage); +} + +static void adin1140_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, "ADIN1140", sizeof(info->driver)); + strscpy(info->bus_info, dev_name(netdev->dev.parent), + sizeof(info->bus_info)); +} + +static void adin1140_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + u32 i; + + scoped_guard(spinlock, &priv->stat_lock) { + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + data[i] = priv->stats[adin1140_stats[i].idx]; + } +} + +static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset, + u8 *p) +{ + u32 i; + + switch (sset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + ethtool_puts(&p, adin1140_stats[i].name); + + break; + } +} + +static int adin1140_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(adin1140_stats); + default: + return -EOPNOTSUPP; + } +} + +static void __adin1140_eth_mac_stats(struct adin1140_priv *priv, + struct ethtool_eth_mac_stats *mac_stats) +{ + mac_stats->FramesReceivedOK = priv->stats[rx_frames]; + mac_stats->BroadcastFramesReceivedOK = priv->stats[rx_bc_frames]; + mac_stats->MulticastFramesReceivedOK = priv->stats[rx_mc_frames]; + mac_stats->FrameCheckSequenceErrors = priv->stats[rx_crc_errors]; + mac_stats->AlignmentErrors = priv->stats[rx_align_errors]; + mac_stats->FrameTooLongErrors = priv->stats[rx_long_frame_errors]; + mac_stats->FramesLostDueToIntMACRcvError = + priv->stats[rx_fifo_full_dropped]; + mac_stats->FramesTransmittedOK = priv->stats[tx_frames]; + mac_stats->BroadcastFramesXmittedOK = priv->stats[tx_bc_frames]; + mac_stats->MulticastFramesXmittedOK = priv->stats[tx_mc_frames]; + mac_stats->SingleCollisionFrames = priv->stats[tx_single_collision]; + mac_stats->MultipleCollisionFrames = priv->stats[tx_multi_collision]; + mac_stats->FramesWithDeferredXmissions = priv->stats[tx_deferred]; + mac_stats->LateCollisions = priv->stats[tx_late_collision]; + mac_stats->FramesAbortedDueToXSColls = + priv->stats[tx_excess_collision]; + mac_stats->FramesLostDueToIntMACXmitError = priv->stats[tx_underrun]; +} + +static void adin1140_get_eth_mac_stats(struct net_device *netdev, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_eth_mac_stats(priv, mac_stats); +} + +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum) +{ + /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 - + * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be + * accessed while frames are being received by the PHY. In case this + * happens the CONFIG0 and CONFIG2 register values will get corrupted, + * getting a random value. Both reads and writes cause the same + * behavior. This is a workaround that avoids MDIO accesses all + * together. Since this is a 10BASE-T1S PHY, only the loopback and + * reset (AN) bits in the control register (0x0) can be written. + * These functionalities have custom implementations in the PHY + * driver. C45 accesses do not cause this issue. + */ + + switch (regnum) { + case MII_BMCR: + return ADIN1140_PHY_CTRL_DEFAULT; + case MII_BMSR: + return ADIN1140_PHY_STATUS_DEFAULT; + case MII_PHYSID1: + return ADIN1140_PHY_ID1; + case MII_PHYSID2: + return ADIN1140_PHY_ID2; + default: + return 0xFFFF; + } +} + +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + return -EIO; +} + +static int adin1140_mdio_register(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + priv->mdiobus = devm_mdiobus_alloc(&spidev->dev); + if (!priv->mdiobus) + return dev_err_probe(&spidev->dev, -ENOMEM, + "MDIO bus alloc failed\n"); + + priv->mdiobus->name = "adin1140-mdiobus"; + priv->mdiobus->priv = priv->tc6; + priv->mdiobus->parent = &spidev->dev; + priv->mdiobus->phy_mask = GENMASK(31, 1); + priv->mdiobus->read = adin1140_mdiobus_read; + priv->mdiobus->write = adin1140_mdiobus_write; + priv->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45; + priv->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45; + + snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "adin1140-%s.%u", + dev_name(&spidev->dev), spi_get_chipselect(spidev, 0)); + + return devm_mdiobus_register(&spidev->dev, priv->mdiobus); +} + +static void adin1140_handle_link_change(struct net_device *netdev) +{ + phy_print_status(netdev->phydev); +} + +static void adin1140_phy_remove(void *data) +{ + phy_disconnect(data); +} + +static int adin1140_phy_init(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + int ret; + + ret = adin1140_mdio_register(priv, spidev); + if (ret) + return ret; + + priv->phydev = phy_find_first(priv->mdiobus); + if (!priv->phydev) + return dev_err_probe(&spidev->dev, -ENODEV, "No PHY found\n"); + + priv->phydev->is_internal = true; + ret = phy_connect_direct(priv->netdev, priv->phydev, + &adin1140_handle_link_change, + PHY_INTERFACE_MODE_INTERNAL); + if (ret) + return dev_err_probe(&spidev->dev, ret, + "Can't attach PHY to %s\n", + priv->mdiobus->id); + + ret = devm_add_action_or_reset(&spidev->dev, adin1140_phy_remove, + priv->phydev); + if (ret) + return ret; + + phy_attached_info(priv->phydev); + + return 0; +} + +static const struct ethtool_ops adin1140_ethtool_ops = { + .get_drvinfo = adin1140_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ethtool_stats = adin1140_get_ethtool_stats, + .get_sset_count = adin1140_get_sset_count, + .get_strings = adin1140_get_ethtool_strings, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_eth_mac_stats = adin1140_get_eth_mac_stats, +}; + +static const struct net_device_ops adin1140_netdev_ops = { + .ndo_open = adin1140_open, + .ndo_stop = adin1140_close, + .ndo_start_xmit = adin1140_start_xmit, + .ndo_set_mac_address = adin1140_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode_async = adin1140_rx_mode, + .ndo_eth_ioctl = phy_do_ioctl_running, + .ndo_get_stats64 = adin1140_ndo_get_stats64, +}; + +static void adin1140_oa_tc6_remove(void *data) +{ + oa_tc6_exit(data); +} + +static int adin1140_probe(struct spi_device *spi) +{ + struct oa_tc6_quirks tc6_quirks = {}; + struct net_device *netdev; + struct adin1140_priv *priv; + int ret; + + netdev = devm_alloc_etherdev(&spi->dev, sizeof(struct adin1140_priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + spi_set_drvdata(spi, priv); + spin_lock_init(&priv->stat_lock); + + tc6_quirks.quirk_flags = OA_TC6_BROKEN_PHY; + + priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks); + if (!priv->tc6) + return -ENODEV; + + ret = devm_add_action_or_reset(&spi->dev, adin1140_oa_tc6_remove, + priv->tc6); + if (ret) + return ret; + + ret = adin1140_phy_init(priv, spi); + if (ret) + return ret; + + if (device_get_ethdev_address(&spi->dev, netdev)) + eth_hw_addr_random(netdev); + + ret = adin1140_configure(priv); + if (ret) + return ret; + + INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work); + + netdev->if_port = IF_PORT_10BASET; + netdev->irq = spi->irq; + netdev->netdev_ops = &adin1140_netdev_ops; + netdev->ethtool_ops = &adin1140_ethtool_ops; + netdev->netns_immutable = true; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE | + IFF_UNICAST_FLT; + + ret = devm_register_netdev(&spi->dev, netdev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register netdev"); + + return 0; +} + +static const struct spi_device_id adin1140_spi_id[] = { + { .name = "ad3306" }, + { .name = "adin1140" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adin1140_spi_id); + +static const struct of_device_id adin1140_match_table[] = { + { .compatible = "adi,ad3306" }, + { .compatible = "adi,adin1140" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1140_match_table); + +static struct spi_driver adin1140_driver = { + .driver = { + .name = "adin1140", + .of_match_table = adin1140_match_table, + }, + .probe = adin1140_probe, + .id_table = adin1140_spi_id, +}; +module_spi_driver(adin1140_driver); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/agere/et131x.c b/drivers/net/ethernet/agere/et131x.c index 1b465a167672..4b6a579e9c66 100644 --- a/drivers/net/ethernet/agere/et131x.c +++ b/drivers/net/ethernet/agere/et131x.c @@ -567,7 +567,7 @@ static int eeprom_write(struct et131x_adapter *adapter, u32 addr, u8 data) */ err = eeprom_wait_ready(pdev, &status); if (err < 0) - return 0; + return err; /* Check bit 3 of the LBCIF Status Register. If equal to 1, * an error has occurred.Don't break here if we are revision diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 79418e682f71..64619e9a704d 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -934,7 +934,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q) if (!dev) continue; - if (dev->qdma != qdma) + if (rcu_access_pointer(dev->qdma) != qdma) continue; netdev = netdev_from_priv(dev); @@ -1038,10 +1038,16 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget) q->queued--; if (skb) { + struct airoha_gdm_dev *dev = netdev_priv(skb->dev); + u16 qidx = skb_get_queue_mapping(skb); struct netdev_queue *txq; txq = skb_get_tx_queue(skb->dev, skb); + + spin_lock_bh(&dev->txq_lock[qidx]); netdev_tx_completed_queue(txq, 1, skb->len); + spin_unlock_bh(&dev->txq_lock[qidx]); + dev_kfree_skb_any(skb); } @@ -1905,8 +1911,8 @@ static int airoha_dev_open(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; - struct airoha_qdma *qdma = dev->qdma; u32 pse_port = FE_PSE_PORT_PPE1; + struct airoha_qdma *qdma; int err; netif_tx_start_all_queues(netdev); @@ -1914,6 +1920,7 @@ static int airoha_dev_open(struct net_device *netdev) if (err) return err; + qdma = airoha_qdma_deref(dev); if (netdev_uses_dsa(netdev)) airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id), GDM_STAG_EN_MASK); @@ -1937,7 +1944,6 @@ static int airoha_dev_stop(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; - struct airoha_qdma *qdma = dev->qdma; netif_tx_disable(netdev); airoha_set_vip_for_gdm_port(dev, false); @@ -1945,7 +1951,7 @@ static int airoha_dev_stop(struct net_device *netdev) if (--port->users) airoha_ppe_set_xmit_frame_size(dev); else - airoha_set_gdm_port_fwd_cfg(qdma->eth, + airoha_set_gdm_port_fwd_cfg(dev->eth, REG_GDM_FWD_CFG(port->id), FE_PSE_PORT_DROP); return 0; @@ -2028,6 +2034,53 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev) return 0; } +static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev) +{ + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int i, src_port; + u32 pse_port; + + src_port = eth->soc->ops.get_sport(dev->port, dev->nbq); + if (src_port < 0) + return src_port; + + airoha_fe_clear(eth, + REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)), + SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK)); + + airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + GDM_STRIP_CRC_MASK); + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + FE_PSE_PORT_DROP); + airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX), + LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK); + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + pse_port); + + airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK, + FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX)); + + for (i = 0; i < eth->soc->num_ppe; i++) + airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX), + DFT_CPORT_MASK(AIROHA_GDM2_IDX)); + + /* Enable VIP and IFC for GDM2 */ + airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX)); + airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX)); + + if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) { + u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq); + + airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask, + FC_MAP6_DEF_VALUE & mask); + } + + return 0; +} + static struct airoha_gdm_dev * airoha_get_wan_gdm_dev(struct airoha_eth *eth) { @@ -2054,15 +2107,37 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth) static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev) { struct net_device *netdev = netdev_from_priv(dev); + struct airoha_qdma *cur_qdma, *qdma; struct airoha_eth *eth = dev->eth; - int ppe_id; + int i, ppe_id; /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */ - dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; - netdev->irq = dev->qdma->irq_banks[0].irq; + qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; + cur_qdma = airoha_qdma_deref(dev); + + if (cur_qdma) + netif_tx_stop_all_queues(netdev); + + rcu_assign_pointer(dev->qdma, qdma); + netdev->irq = qdma->irq_banks[0].irq; + synchronize_rcu(); ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1); airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev)); + + /* Seed qos_stats baselines with the new QDMA block's current + * counter values to avoid a spurious spike on the first stats + * query after init or migration. + */ + for (i = 0; i < ARRAY_SIZE(dev->qos_stats); i++) { + dev->qos_stats[i].cpu_tx_packets = + airoha_qdma_rr(qdma, REG_CNTR_VAL(i << 1)); + dev->qos_stats[i].fwd_tx_packets = + airoha_qdma_rr(qdma, REG_CNTR_VAL((i << 1) + 1)); + } + + if (cur_qdma) + netif_tx_wake_all_queues(netdev); } static int airoha_dev_init(struct net_device *netdev) @@ -2078,7 +2153,7 @@ static int airoha_dev_init(struct net_device *netdev) fallthrough; case AIROHA_GDM2_IDX: /* GDM2 is always used as wan */ - dev->flags |= AIROHA_PRIV_F_WAN; + set_bit(AIROHA_DEV_F_WAN, &dev->flags); break; default: break; @@ -2217,9 +2292,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; u32 nr_frags, tag, msg0, msg1, len; struct airoha_queue_entry *e; + struct airoha_qdma *qdma; struct netdev_queue *txq; struct airoha_queue *q; LIST_HEAD(tx_list); @@ -2228,6 +2303,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, u16 index; u8 fport; + rcu_read_lock(); + qdma = rcu_dereference(dev->qdma); qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb)); tag = airoha_get_dsa_tag(skb, netdev); @@ -2277,6 +2354,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, netif_tx_stop_queue(txq); q->txq_stopped = true; spin_unlock_bh(&q->lock); + rcu_read_unlock(); + return NETDEV_TX_BUSY; } @@ -2342,6 +2421,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, FIELD_PREP(TX_RING_CPU_IDX_MASK, index)); spin_unlock_bh(&q->lock); + rcu_read_unlock(); return NETDEV_TX_OK; @@ -2354,6 +2434,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, error: dev_kfree_skb_any(skb); netdev->stats.tx_dropped++; + rcu_read_unlock(); return NETDEV_TX_OK; } @@ -2433,17 +2514,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, const u16 *weights, u8 n_weights) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i; + qdma = airoha_qdma_deref(dev); for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++) - airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel), + airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel), TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i)); for (i = 0; i < n_weights; i++) { u32 status; int err; - airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG, + airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG, TWRR_RW_CMD_MASK | FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) | FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) | @@ -2451,12 +2534,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, err = read_poll_timeout(airoha_qdma_rr, status, status & TWRR_RW_CMD_DONE, USEC_PER_MSEC, 10 * USEC_PER_MSEC, - true, dev->qdma, REG_TXWRR_WEIGHT_CFG); + true, qdma, REG_TXWRR_WEIGHT_CFG); if (err) return err; } - airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3), + airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3), CHAN_QOS_MODE_MASK(channel), __field_prep(CHAN_QOS_MODE_MASK(channel), mode)); @@ -2520,17 +2603,22 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel, struct tc_ets_qopt_offload *opt) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + u32 cpu_tx_packets, fwd_tx_packets; + struct airoha_qdma *qdma; + u64 tx_packets; - u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); - u64 fwd_tx_packets = airoha_qdma_rr(qdma, - REG_CNTR_VAL((channel << 1) + 1)); - u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) + - (fwd_tx_packets - dev->fwd_tx_packets); + qdma = airoha_qdma_deref(dev); + cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); + fwd_tx_packets = airoha_qdma_rr(qdma, + REG_CNTR_VAL((channel << 1) + 1)); + tx_packets = (u32)(cpu_tx_packets - + dev->qos_stats[channel].cpu_tx_packets); + tx_packets += (u32)(fwd_tx_packets - + dev->qos_stats[channel].fwd_tx_packets); _bstats_update(opt->stats.bstats, 0, tx_packets); - dev->cpu_tx_packets = cpu_tx_packets; - dev->fwd_tx_packets = fwd_tx_packets; + dev->qos_stats[channel].cpu_tx_packets = cpu_tx_packets; + dev->qos_stats[channel].fwd_tx_packets = fwd_tx_packets; return 0; } @@ -2785,16 +2873,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev, u32 bucket_size) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i, err; + qdma = airoha_qdma_deref(dev); for (i = 0; i <= TRTCM_PEAK_MODE; i++) { - err = airoha_qdma_set_trtcm_config(dev->qdma, channel, + err = airoha_qdma_set_trtcm_config(qdma, channel, REG_EGRESS_TRTCM_CFG, i, !!rate, TRTCM_METER_MODE); if (err) return err; - err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel, + err = airoha_qdma_set_trtcm_token_bucket(qdma, channel, REG_EGRESS_TRTCM_CFG, i, rate, bucket_size); if (err) @@ -2830,11 +2920,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev, u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS; int err, num_tx_queues = AIROHA_NUM_TX_RING + channel + 1; struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; /* Here we need to check the requested QDMA channel is not already * in use by another net_device running on the same QDMA block. */ + qdma = airoha_qdma_deref(dev); if (test_and_set_bit(channel, qdma->qos_channel_map)) { NL_SET_ERR_MSG_MOD(opt->extack, "qdma qos channel already in use"); @@ -2870,7 +2961,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev, u32 rate, u32 bucket_size, enum trtcm_unit_type unit_type) { - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); int i; for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) { @@ -3045,10 +3136,11 @@ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue) { struct airoha_gdm_dev *dev = netdev_priv(netdev); int num_tx_queues = AIROHA_NUM_TX_RING; - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0); + qdma = airoha_qdma_deref(dev); clear_bit(queue, qdma->qos_channel_map); clear_bit(queue, dev->qos_sq_bmap); @@ -3074,6 +3166,98 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev, return 0; } +static void airoha_disable_qos_for_gdm34(struct net_device *netdev) +{ + struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + int err; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) + return; + + err = airoha_disable_gdm2_loopback(dev); + if (err) + netdev_warn(netdev, + "failed disabling GDM2 loopback: %d\n", err); + + clear_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); + + airoha_set_macaddr(dev, netdev->dev_addr); + airoha_ppe_set_xmit_frame_size(dev); + + if (netif_running(netdev)) + airoha_set_gdm_port_fwd_cfg(dev->eth, + REG_GDM_FWD_CFG(port->id), + FE_PSE_PORT_PPE1); +} + +static int airoha_enable_qos_for_gdm34(struct net_device *netdev, + struct netlink_ext_ack *extack) +{ + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int err = -EBUSY; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) { + /* HW QoS is always supported by GDM1 and GDM2 */ + return 0; + } + + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ + return 0; + + mutex_lock(&flow_offload_mutex); + + wan_dev = airoha_get_wan_gdm_dev(eth); + if (wan_dev) { + if (test_bit(AIROHA_DEV_F_TX_QOS, &wan_dev->flags) || + wan_dev->port->id == AIROHA_GDM2_IDX) { + NL_SET_ERR_MSG_MOD(extack, + "QoS configured for WAN device"); + goto error_unlock; + } + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev)); + } + + set_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); + err = airoha_enable_gdm2_loopback(dev); + if (err) + goto error_disable_wan; + + err = airoha_set_macaddr(dev, netdev->dev_addr); + if (err) + goto error_disable_loopback; + + airoha_dev_set_xmit_frame_size(netdev); + if (netif_running(netdev)) { + u32 pse_port; + + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), + pse_port); + } + + mutex_unlock(&flow_offload_mutex); + + return 0; + +error_disable_loopback: + airoha_disable_gdm2_loopback(dev); +error_disable_wan: + clear_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); +error_unlock: + mutex_unlock(&flow_offload_mutex); + + return err; +} + static int airoha_tc_htb_destroy(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); @@ -3082,6 +3266,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev) for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS) airoha_tc_remove_htb_queue(netdev, q); + clear_bit(AIROHA_DEV_F_TX_QOS, &dev->flags); + return 0; } @@ -3101,24 +3287,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev, return 0; } -static int airoha_tc_setup_qdisc_htb(struct net_device *dev, +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev, struct tc_htb_qopt_offload *opt) { switch (opt->command) { - case TC_HTB_CREATE: + case TC_HTB_CREATE: { + struct airoha_gdm_dev *dev = netdev_priv(netdev); + int err; + + err = airoha_enable_qos_for_gdm34(netdev, opt->extack); + if (err) + return err; + + set_bit(AIROHA_DEV_F_TX_QOS, &dev->flags); break; + } case TC_HTB_DESTROY: - return airoha_tc_htb_destroy(dev); + return airoha_tc_htb_destroy(netdev); case TC_HTB_NODE_MODIFY: - return airoha_tc_htb_modify_queue(dev, opt); + return airoha_tc_htb_modify_queue(netdev, opt); case TC_HTB_LEAF_ALLOC_QUEUE: - return airoha_tc_htb_alloc_leaf_queue(dev, opt); + return airoha_tc_htb_alloc_leaf_queue(netdev, opt); case TC_HTB_LEAF_DEL: case TC_HTB_LEAF_DEL_LAST: case TC_HTB_LEAF_DEL_LAST_FORCE: - return airoha_tc_htb_delete_leaf_queue(dev, opt); + return airoha_tc_htb_delete_leaf_queue(netdev, opt); case TC_HTB_LEAF_QUERY_QUEUE: - return airoha_tc_get_htb_get_leaf_queue(dev, opt); + return airoha_tc_get_htb_get_leaf_queue(netdev, opt); default: return -EOPNOTSUPP; } @@ -3220,8 +3415,8 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth, { struct net_device *netdev; struct airoha_gdm_dev *dev; + int err, i; u8 index; - int err; netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev), AIROHA_NUM_NETDEV_TX_RINGS, @@ -3272,6 +3467,8 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth, netdev->dev.of_node = of_node_get(np); dev = netdev_priv(netdev); u64_stats_init(&dev->stats.syncp); + for (i = 0; i < AIROHA_NUM_NETDEV_TX_RINGS; i++) + spin_lock_init(&dev->txq_lock[i]); dev->port = port; dev->eth = eth; dev->nbq = nbq; diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index fe934f9ffe8a..fa9a8edce22f 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -570,24 +570,31 @@ struct airoha_qdma { DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS); }; -enum airoha_priv_flags { - AIROHA_PRIV_F_WAN = BIT(0), +enum airoha_dev_flags { + AIROHA_DEV_F_WAN, + AIROHA_DEV_F_TX_QOS, }; struct airoha_gdm_dev { + struct airoha_qdma __rcu *qdma; struct airoha_gdm_port *port; - struct airoha_qdma *qdma; struct airoha_eth *eth; DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); - /* qos stats counters */ - u64 cpu_tx_packets; - u64 fwd_tx_packets; + struct { + u32 cpu_tx_packets; + u32 fwd_tx_packets; + } qos_stats[AIROHA_NUM_QOS_CHANNELS]; - u32 flags; + unsigned long flags; int nbq; struct airoha_hw_stats stats; + + /* Serialize netdev_tx_completed_queue() calls per TX queue during + * QDMA migration. + */ + spinlock_t txq_lock[AIROHA_NUM_NETDEV_TX_RINGS]; }; struct airoha_gdm_port { @@ -694,7 +701,7 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid) static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev) { - return !(dev->flags & AIROHA_PRIV_F_WAN); + return !test_bit(AIROHA_DEV_F_WAN, &dev->flags); } static inline bool airoha_is_7581(struct airoha_eth *eth) @@ -711,6 +718,16 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev); bool airoha_is_valid_gdm_dev(struct airoha_eth *eth, struct airoha_gdm_dev *dev); +extern struct mutex flow_offload_mutex; + +static inline struct airoha_qdma * +airoha_qdma_deref(struct airoha_gdm_dev *dev) +{ + return rcu_dereference_protected(dev->qdma, + lockdep_rtnl_is_held() || + lockdep_is_held(&flow_offload_mutex)); +} + void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev); void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport); bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index); diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index b679bed952de..de75376db194 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -202,9 +202,10 @@ static int airoha_npu_load_firmware(struct device *dev, void __iomem *addr, const struct firmware *fw; int ret; - ret = request_firmware(&fw, fw_name, dev); + ret = request_firmware_direct(&fw, fw_name, dev); if (ret) - return ret == -ENOENT ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret == -ENOENT ? -EPROBE_DEFER : ret, + "failed to load %s\n", fw_name); if (fw->size > fw_max_size) { dev_err(dev, "%s: fw size too overlimit (%zu)\n", @@ -230,7 +231,8 @@ airoha_npu_load_firmware_from_dts(struct device *dev, void __iomem *addr, ret = of_property_read_string_array(dev->of_node, "firmware-name", fw_names, ARRAY_SIZE(fw_names)); if (ret != ARRAY_SIZE(fw_names)) - return -EINVAL; + return dev_err_probe(dev, -EINVAL, + "invalid firmware-name property\n"); ret = airoha_npu_load_firmware(dev, addr, fw_names[0], NPU_EN7581_FIRMWARE_RV32_MAX_SIZE); @@ -772,7 +774,7 @@ static int airoha_npu_probe(struct platform_device *pdev) err = airoha_npu_run_firmware(dev, base, &res); if (err) - return dev_err_probe(dev, err, "failed to run npu firmware\n"); + return err; regmap_write(npu->regmap, REG_CR_NPU_MIB(10), res.start + NPU_EN7581_FIRMWARE_RV32_MAX_SIZE); diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index f6396925722d..92611802801e 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -15,7 +15,10 @@ #include "airoha_regs.h" #include "airoha_eth.h" -static DEFINE_MUTEX(flow_offload_mutex); +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port + * configuration. + */ +DEFINE_MUTEX(flow_offload_mutex); static DEFINE_SPINLOCK(ppe_lock); static const struct rhashtable_params airoha_flow_table_params = { @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe) void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport) { - struct airoha_qdma *qdma = dev->qdma; - struct airoha_eth *eth = qdma->eth; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); + struct airoha_eth *eth = dev->eth; u8 qdma_id = qdma - ð->qdma[0]; u32 fe_cpu_port; @@ -280,27 +283,36 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr, struct airoha_wdma_info *info) { struct net_device_path_stack stack; + struct net_device_path_ctx ctx = { + .dev = dev, + }; struct net_device_path *path; int err; if (!dev) return -ENODEV; + ether_addr_copy(ctx.daddr, addr); + rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(&ctx, &stack); rcu_read_unlock(); if (err) return err; path = &stack.path[stack.num_paths - 1]; - if (path->type != DEV_PATH_MTK_WDMA) - return -EINVAL; + if (path->type != DEV_PATH_MTK_WDMA) { + err = -EINVAL; + goto err_out; + } info->idx = path->mtk_wdma.wdma_idx; info->bss = path->mtk_wdma.bss; info->wcid = path->mtk_wdma.wcid; +err_out: + dev_fill_forward_path_release(&stack); - return 0; + return err; } static int airoha_get_dsa_port(struct net_device **dev) @@ -331,7 +343,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, struct airoha_foe_entry *hwe, struct net_device *netdev, int type, struct airoha_flow_data *data, - int l4proto) + int l4proto, u8 priority) { u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val; int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev); @@ -386,7 +398,9 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, */ channel = dsa_port >= 0 ? dsa_port : port->id; channel = channel % AIROHA_NUM_QOS_CHANNELS; - qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel); + priority = priority % AIROHA_NUM_QOS_QUEUES; + qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel) | + FIELD_PREP(AIROHA_FOE_QID, priority); val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port) | AIROHA_FOE_IB2_PSE_QOS; @@ -1079,10 +1093,10 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, struct airoha_flow_data data = {}; struct net_device *odev = NULL; struct flow_action_entry *act; + u8 l4proto = 0, priority = 0; struct airoha_foe_entry hwe; int err, i, offload_type; u16 addr_type = 0; - u8 l4proto = 0; if (rhashtable_lookup(ð->flow_table, &f->cookie, airoha_flow_table_params)) @@ -1177,7 +1191,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, return -EINVAL; err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type, - &data, l4proto); + &data, l4proto, priority); if (err) return err; diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h index 6fed63d013b4..442b48c9b991 100644 --- a/drivers/net/ethernet/airoha/airoha_regs.h +++ b/drivers/net/ethernet/airoha/airoha_regs.h @@ -375,6 +375,7 @@ #define REG_SRC_PORT_FC_MAP6 0x2298 #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3)) +#define FC_MAP6_DEF_VALUE 0x1b1a1918 #define REG_WAN_MTU0 0x2300 #define WAN_MTU1_MASK GENMASK(29, 16) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index fc7341a5cbb7..42174249ef61 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index 5d05020a6d05..ea89619039d8 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -2303,11 +2303,7 @@ static int ena_open(struct net_device *netdev) return rc; } - rc = ena_up(adapter); - if (rc) - return rc; - - return rc; + return ena_up(adapter); } /* ena_close - Disables a network interface diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig index e35991141a1a..743e3d4b6b94 100644 --- a/drivers/net/ethernet/amd/Kconfig +++ b/drivers/net/ethernet/amd/Kconfig @@ -171,6 +171,7 @@ config PDS_CORE depends on 64BIT && PCI select AUXILIARY_BUS select NET_DEVLINK + select PLDMFW help This enables the support for the AMD/Pensando Core device family of adapters. More specific information on this driver can be diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 04ec2569c61c..922e3ec8af1b 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -489,6 +489,15 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) pdsc_devcmd_reset(pdsc); pci_clear_master(pdsc->pdev); + if (!pdsc->pdev->is_virtfn) { + u16 val; + + /* Flush any in-flight DMA before freeing buffers. + * A config read completion cannot return until all prior + * device-initiated memory writes have completed. + */ + pci_read_config_word(pdsc->pdev, PCI_VENDOR_ID, &val); + } pdsc_core_uninit(pdsc); @@ -497,6 +506,8 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) pdsc->viftype_status = NULL; } + pdsc_debugfs_del_host_mem(pdsc); + pdsc_host_mem_free(pdsc); pdsc_dev_uninit(pdsc); set_bit(PDSC_S_FW_DEAD, &pdsc->state); @@ -506,6 +517,8 @@ int pdsc_start(struct pdsc *pdsc) { pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx], PDS_CORE_INTR_MASK_CLEAR); + pdsc_host_mem_add(pdsc); + pdsc_debugfs_add_host_mem(pdsc); return 0; } @@ -587,6 +600,8 @@ void pdsc_fw_up(struct pdsc *pdsc) return; } + pdsc_fw_components_invalidate(pdsc); + err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY); if (err) goto err_out; @@ -670,3 +685,161 @@ void pdsc_health_thread(struct work_struct *work) out_unlock: mutex_unlock(&pdsc->config_lock); } + +static void pdsc_host_mem_del_one(struct pdsc *pdsc, u16 tag, u8 reason) +{ + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = { + .host_mem.opcode = PDS_CORE_CMD_HOST_MEM, + .host_mem.oper = PDS_CORE_HOST_MEM_DEL, + .host_mem.tag = cpu_to_le16(tag), + .host_mem.reason = reason, + }; + + dev_dbg(pdsc->dev, "Sending devcmd for mem del tag %d\n", tag); + pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index) +{ + struct pdsc_host_mem *hm = &pdsc->host_mem_reqs[index]; + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + int err; + + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_QUERY; + cmd.host_mem.index = cpu_to_le16(index); + dev_dbg(pdsc->dev, "Sending devcmd for mem query index %d\n", index); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem query failed err %d status %d\n", + err, comp.status); + return err ? err : -EIO; + } + hm->size = le32_to_cpu(comp.host_mem.size); + hm->tag = le16_to_cpu(comp.host_mem.tag); + dev_dbg(pdsc->dev, "mem query returned size %d tag %d\n", + hm->size, hm->tag); + + if (!hm->size || hm->size > PDSC_HOST_MEM_MAX_CONTIG) { + dev_err(pdsc->dev, "invalid size %d for tag %d\n", + hm->size, hm->tag); + err = -EINVAL; + goto err_del; + } + + hm->order = get_order(hm->size); + hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm->order); + if (!hm->pg) { + dev_warn(pdsc->dev, "alloc order %d failed for tag %d\n", + hm->order, hm->tag); + err = -ENOMEM; + goto err_del; + } + + hm->pa = dma_map_page(pdsc->dev, hm->pg, 0, hm->size, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(pdsc->dev, hm->pa)) { + dev_err(pdsc->dev, "dma map failed for tag %d size %d\n", + hm->tag, hm->size); + __free_pages(hm->pg, hm->order); + hm->pg = NULL; + err = -EIO; + goto err_del; + } + + /* Track this allocation so pdsc_host_mem_free() can clean it up */ + pdsc->num_host_mem_reqs++; + + memset(&cmd, 0, sizeof(cmd)); + memset(&comp, 0, sizeof(comp)); + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD; + cmd.host_mem.tag = cpu_to_le16(hm->tag); + cmd.host_mem.size = cpu_to_le32(hm->size); + cmd.host_mem.buf_pa = cpu_to_le64(hm->pa); + + dev_dbg(pdsc->dev, "Sending devcmd for mem add tag %d size %d pa %pad\n", + hm->tag, hm->size, &hm->pa); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem add failed err %d status %d for tag %d\n", + err, comp.status, hm->tag); + err = err ? err : -EIO; + goto err_del; + } + dev_dbg(pdsc->dev, "mem add completed for tag %d\n", hm->tag); + + return 0; + +err_del: + /* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */ + pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM); + return err; +} + +void pdsc_host_mem_add(struct pdsc *pdsc) +{ + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + u16 count; + int err; + int i; + + if (!(pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM))) + return; + + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_GET_COUNT; + cmd.host_mem.index = cpu_to_le16(PDSC_HOST_MEM_MAX_COUNT); + cmd.host_mem.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG); + dev_dbg(pdsc->dev, "Sending devcmd for mem get count max_contig %u\n", + PDSC_HOST_MEM_MAX_CONTIG); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem get count failed err %d status %d\n", + err, comp.status); + return; + } + + count = min(le16_to_cpu(comp.host_mem.count), + PDSC_HOST_MEM_MAX_COUNT); + dev_dbg(pdsc->dev, "mem get count returned count %d\n", count); + if (count == 0) + return; + + pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count, + GFP_KERNEL); + if (!pdsc->host_mem_reqs) { + dev_err(pdsc->dev, "failed to alloc host_mem_reqs array\n"); + return; + } + + for (i = 0; i < count; i++) { + err = pdsc_host_mem_add_one(pdsc, i); + if (err) + break; + } +} + +void pdsc_host_mem_free(struct pdsc *pdsc) +{ + int i; + + if (!pdsc->host_mem_reqs) + return; + + for (i = 0; i < pdsc->num_host_mem_reqs; i++) { + dma_unmap_page(pdsc->dev, pdsc->host_mem_reqs[i].pa, + pdsc->host_mem_reqs[i].size, + DMA_BIDIRECTIONAL); + __free_pages(pdsc->host_mem_reqs[i].pg, + pdsc->host_mem_reqs[i].order); + } + + kfree(pdsc->host_mem_reqs); + pdsc->host_mem_reqs = NULL; + pdsc->num_host_mem_reqs = 0; +} diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index a1d41329209f..dddbf238390f 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -5,6 +5,7 @@ #define _PDSC_H_ #include +#include #include #include @@ -23,6 +24,20 @@ #define PDSC_SETUP_RECOVERY false #define PDSC_SETUP_INIT true +/* Use fixed 4MB instead of PAGE_SIZE << MAX_PAGE_ORDER to avoid + * cpu_to_le32() truncation on large-page configs + */ +#define PDSC_HOST_MEM_MAX_CONTIG (4 * 1024 * 1024) +#define PDSC_HOST_MEM_MAX_COUNT 256 + +struct pdsc_deferred_dma { + struct list_head list; + dma_addr_t dma_addr; + void *va; + size_t size; + enum dma_data_direction dir; +}; + struct pdsc_dev_bar { void __iomem *vaddr; phys_addr_t bus_addr; @@ -141,6 +156,14 @@ struct pdsc_viftype { struct pds_auxiliary_dev *padev; }; +struct pdsc_host_mem { + u32 size; + u16 tag; + u8 order; + struct page *pg; + dma_addr_t pa; +}; + /* No state flags set means we are in a steady running state */ enum pdsc_state_flags { PDSC_S_FW_DEAD, /* stopped, wait on startup or recovery */ @@ -186,6 +209,8 @@ struct pdsc { struct mutex devcmd_lock; /* lock for dev_cmd operations */ struct mutex config_lock; /* lock for configuration operations */ spinlock_t adminq_lock; /* lock for adminq operations */ + struct list_head deferred_dma_list; + spinlock_t deferred_dma_lock; /* lock for deferred DMA list */ refcount_t adminq_refcnt; struct pds_core_dev_info_regs __iomem *info_regs; struct pds_core_dev_cmd_regs __iomem *cmd_regs; @@ -200,6 +225,11 @@ struct pdsc { u64 last_eid; struct pdsc_viftype *viftype_status; struct work_struct pci_reset_work; + + struct pdsc_host_mem *host_mem_reqs; + u16 num_host_mem_reqs; + + struct pds_core_component_list_info fw_components; }; /** enum pds_core_dbell_bits - bitwise composition of dbell values. @@ -276,14 +306,24 @@ void pdsc_debugfs_add_viftype(struct pdsc *pdsc); void pdsc_debugfs_add_irqs(struct pdsc *pdsc); void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq); void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq); +void pdsc_debugfs_add_host_mem(struct pdsc *pdsc); +void pdsc_debugfs_del_host_mem(struct pdsc *pdsc); int pdsc_err_to_errno(enum pds_core_status_code code); bool pdsc_is_fw_running(struct pdsc *pdsc); bool pdsc_is_fw_good(struct pdsc *pdsc); int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds); int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds); int pdsc_devcmd_init(struct pdsc *pdsc); int pdsc_devcmd_reset(struct pdsc *pdsc); int pdsc_dev_init(struct pdsc *pdsc); @@ -316,11 +356,23 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq); void pdsc_work_thread(struct work_struct *work); irqreturn_t pdsc_adminq_isr(int irq, void *data); -int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, +int pdsc_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, struct netlink_ext_ack *extack); +int pdsc_get_component_info(struct pdsc *pdsc); +const char *pdsc_fw_type_to_name(u8 type); +void pdsc_fw_components_invalidate(struct pdsc *pdsc); void pdsc_fw_down(struct pdsc *pdsc); void pdsc_fw_up(struct pdsc *pdsc); void pdsc_pci_reset_thread(struct work_struct *work); +void pdsc_host_mem_add(struct pdsc *pdsc); +void pdsc_host_mem_free(struct pdsc *pdsc); + +void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry, + dma_addr_t dma_addr, void *va, size_t size, + enum dma_data_direction dir); +void pdsc_deferred_dma_free(struct pdsc *pdsc); + #endif /* _PDSC_H_ */ diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c index 810a0cd9bcac..ef0a1b7d159b 100644 --- a/drivers/net/ethernet/amd/pds_core/debugfs.c +++ b/drivers/net/ethernet/amd/pds_core/debugfs.c @@ -178,3 +178,48 @@ void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq) debugfs_remove_recursive(qcq->dentry); qcq->dentry = NULL; } + +static int host_mem_show(struct seq_file *seq, void *v) +{ + struct pdsc *pdsc = seq->private; + struct pdsc_host_mem *hm; + int i; + + if (!pdsc->host_mem_reqs || pdsc->num_host_mem_reqs == 0) { + seq_puts(seq, "No host memory allocated\n"); + return 0; + } + + seq_printf(seq, "Host memory requests: %u\n\n", + pdsc->num_host_mem_reqs); + seq_puts(seq, "Tag Size Order PA\n"); + seq_puts(seq, "--- ---- ----- --\n"); + + for (i = 0; i < pdsc->num_host_mem_reqs; i++) { + hm = &pdsc->host_mem_reqs[i]; + + if (!hm->pg) + continue; + + seq_printf(seq, "%-6u %-12u %-6u %pad\n", + hm->tag, hm->size, hm->order, &hm->pa); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(host_mem); + +void pdsc_debugfs_add_host_mem(struct pdsc *pdsc) +{ + if (!(pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM))) + return; + + debugfs_create_file("host_mem", 0400, pdsc->dentry, + pdsc, &host_mem_fops); +} + +void pdsc_debugfs_del_host_mem(struct pdsc *pdsc) +{ + debugfs_lookup_and_remove("host_mem", pdsc->dentry); +} diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c index bded6b33289c..c50778175e2f 100644 --- a/drivers/net/ethernet/amd/pds_core/dev.c +++ b/drivers/net/ethernet/amd/pds_core/dev.c @@ -126,7 +126,8 @@ static const char *pdsc_devcmd_str(int opcode) } } -static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) +static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds, + const bool do_msg) { struct device *dev = pdsc->dev; unsigned long start_time; @@ -179,7 +180,7 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) status = pdsc_devcmd_status(pdsc); err = pdsc_err_to_errno(status); - if (err && err != -EAGAIN) + if (do_msg && err && err != -EAGAIN) dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n", opcode, pdsc_devcmd_str(opcode), status, err, ERR_PTR(err)); @@ -187,8 +188,9 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) return err; } -int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, - union pds_core_dev_comp *comp, int max_seconds) +static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds, + const bool do_msg) { int err; @@ -197,16 +199,63 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd)); pdsc_devcmd_dbell(pdsc); - err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds); + err = __pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds, do_msg); if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq) queue_work(pdsc->wq, &pdsc->health_work); else memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp)); + if (err != -ETIMEDOUT && err != -EAGAIN) + pdsc_deferred_dma_free(pdsc); + return err; } +void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry, + dma_addr_t dma_addr, void *va, size_t size, + enum dma_data_direction dir) +{ + entry->dma_addr = dma_addr; + entry->va = va; + entry->size = size; + entry->dir = dir; + + spin_lock(&pdsc->deferred_dma_lock); + list_add_tail(&entry->list, &pdsc->deferred_dma_list); + spin_unlock(&pdsc->deferred_dma_lock); +} + +void pdsc_deferred_dma_free(struct pdsc *pdsc) +{ + struct pdsc_deferred_dma *entry, *tmp; + LIST_HEAD(local_list); + + spin_lock(&pdsc->deferred_dma_lock); + list_splice_init(&pdsc->deferred_dma_list, &local_list); + spin_unlock(&pdsc->deferred_dma_lock); + + list_for_each_entry_safe(entry, tmp, &local_list, list) { + dma_unmap_single(pdsc->dev, entry->dma_addr, + entry->size, entry->dir); + kfree(entry->va); + list_del(&entry->list); + kfree(entry); + } +} + +int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true); +} + +int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, false); +} + int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { @@ -219,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, return err; } +static int __pdsc_devcmd_with_data(struct pdsc *pdsc, + union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, + int max_seconds, bool do_msg) +{ + int err; + + mutex_lock(&pdsc->devcmd_lock); + if (!pdsc->cmd_regs) { + err = -ENXIO; + goto unlock; + } + if (data_len > sizeof(pdsc->cmd_regs->data)) { + err = -ENOSPC; + goto unlock; + } + memcpy_toio(&pdsc->cmd_regs->data, data, data_len); + err = __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, do_msg); +unlock: + mutex_unlock(&pdsc->devcmd_lock); + + return err; +} + +int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len, + comp, max_seconds, true); +} + +int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len, + comp, max_seconds, false); +} + int pdsc_devcmd_init(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; @@ -242,15 +332,17 @@ int pdsc_devcmd_reset(struct pdsc *pdsc) return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } -static int pdsc_devcmd_identify_locked(struct pdsc *pdsc) +static int pdsc_devcmd_identify_locked(struct pdsc *pdsc, u8 drv_ident_ver, + bool do_msg) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .identify.opcode = PDS_CORE_CMD_IDENTIFY, - .identify.ver = PDS_CORE_IDENTITY_VERSION_1, + .identify.ver = drv_ident_ver, }; - return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + return __pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout, + do_msg); } static void pdsc_init_devinfo(struct pdsc *pdsc) @@ -273,8 +365,9 @@ static void pdsc_init_devinfo(struct pdsc *pdsc) dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version); } -static int pdsc_identify(struct pdsc *pdsc) +static int pdsc_identify_ver(struct pdsc *pdsc, u8 drv_ident_ver) { + bool do_msg = drv_ident_ver == PDS_CORE_IDENTITY_VERSION_1; struct pds_core_drv_identity drv = {}; size_t sz; int err; @@ -297,19 +390,22 @@ static int pdsc_identify(struct pdsc *pdsc) sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data)); memcpy_toio(&pdsc->cmd_regs->data, &drv, sz); - err = pdsc_devcmd_identify_locked(pdsc); + err = pdsc_devcmd_identify_locked(pdsc, drv_ident_ver, do_msg); if (!err) { sz = min_t(size_t, sizeof(pdsc->dev_ident), sizeof(pdsc->cmd_regs->data)); memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz); + + /* V1 firmware doesn't set capabilities, so the field may + * contain garbage from the outgoing driver identity. + */ + if (pdsc->dev_ident.version < PDS_CORE_IDENTITY_VERSION_2) + pdsc->dev_ident.capabilities = 0; } mutex_unlock(&pdsc->devcmd_lock); - if (err) { - dev_err(pdsc->dev, "Cannot identify device: %pe\n", - ERR_PTR(err)); + if (err) return err; - } if (isprint(pdsc->dev_info.fw_version[0]) && isascii(pdsc->dev_info.fw_version[0])) @@ -326,6 +422,26 @@ static int pdsc_identify(struct pdsc *pdsc) return 0; } +static int pdsc_identify(struct pdsc *pdsc) +{ + int err; + + /* Older firmware rejects anything but PDS_CORE_IDENTITY_VERSION_1 + * with PDS_RC_EVERSION (-EINVAL), so retry with V1 on version + * rejection. Don't retry on other errors like -ENXIO/-ETIMEDOUT + * which indicate firmware is not running or hung. + */ + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2); + if (err == -EINVAL) + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1); + + if (err) + dev_err(pdsc->dev, "Cannot identify device: %pe\n", + ERR_PTR(err)); + + return err; +} + void pdsc_dev_uninit(struct pdsc *pdsc) { if (pdsc->intr_info) { diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index 8adae7b18898..63fe45e91f71 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -90,13 +90,112 @@ int pdsc_dl_flash_update(struct devlink *dl, { struct pdsc *pdsc = devlink_priv(dl); - if (params->component) { - NL_SET_ERR_MSG_MOD(extack, - "Component update not supported by this device"); - return -EOPNOTSUPP; + return pdsc_firmware_update(pdsc, params, extack); +} + +static int pdsc_dl_report_component(struct devlink_info_req *req, + struct pds_core_fw_component_info *info) +{ + enum devlink_info_version_type ver_type; + u16 flags = le16_to_cpu(info->flags); + char *ver = info->version; + const char *name; + char buf[32]; + + /* Main firmware is reported as generic "fw" */ + if (info->component_type == PDS_CORE_FW_TYPE_MAIN) { + if (info->slot_id == PDS_CORE_FW_SLOT_GOLD) + snprintf(buf, sizeof(buf), "fw.gold"); + else + snprintf(buf, sizeof(buf), "fw"); + } else { + name = pdsc_fw_type_to_name(info->component_type); + if (!name) + return 0; + + if (info->slot_id == PDS_CORE_FW_SLOT_GOLD) + snprintf(buf, sizeof(buf), "fw.%s.gold", name); + else + snprintf(buf, sizeof(buf), "fw.%s", name); } - return pdsc_firmware_update(pdsc, params->fw, extack); + ver_type = DEVLINK_INFO_VERSION_TYPE_NONE; + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME) + ver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT; + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_FIXED) { + int err; + + err = devlink_info_version_fixed_put(req, buf, ver); + if (err) + return err; + } + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_RUNNING) { + int err; + + err = devlink_info_version_running_put_ext(req, buf, + ver, ver_type); + if (err) + return err; + } + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP) { + int err; + + err = devlink_info_version_stored_put_ext(req, buf, + ver, ver_type); + if (err) + return err; + } + + return 0; +} + +static int pdsc_dl_report_fw_ver(struct devlink_info_req *req, char *fw_ver) +{ + return devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, + fw_ver); +} + +static int pdsc_dl_component_info_get(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + u8 num_components; + int err; + int i; + + /* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate(). + * Use READ_ONCE to get a consistent snapshot of num_components. + * pdsc_fw_components_invalidate() can zero it concurrently during + * firmware recovery; using the local copy avoids iterating zero + * times when we already decided the cache was valid. + */ + num_components = READ_ONCE(pdsc->fw_components.num_components); + if (!num_components) { + err = pdsc_get_component_info(pdsc); + if (err) + return pdsc_dl_report_fw_ver(req, + pdsc->dev_info.fw_version); + num_components = READ_ONCE(pdsc->fw_components.num_components); + if (!num_components) + return pdsc_dl_report_fw_ver(req, + pdsc->dev_info.fw_version); + } + + num_components = min_t(u16, num_components, + le16_to_cpu(pdsc->dev_ident.max_fw_slots)); + for (i = 0; i < num_components; i++) { + err = pdsc_dl_report_component(req, + &pdsc->fw_components.info[i]); + if (err) + return err; + } + + return 0; } static char *fw_slotnames[] = { @@ -105,8 +204,9 @@ static char *fw_slotnames[] = { "fw.mainfwb", }; -int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, - struct netlink_ext_ack *extack) +static int pdsc_dl_fw_list_info_get(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) { union pds_core_dev_cmd cmd = { .fw_control.opcode = PDS_CORE_CMD_FW_CONTROL, @@ -140,12 +240,41 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, return err; } - err = devlink_info_version_running_put(req, - DEVLINK_INFO_VERSION_GENERIC_FW, - pdsc->dev_info.fw_version); + return 0; +} + +static int pdsc_dl_info_get_v1(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + int err; + + err = pdsc_dl_fw_list_info_get(dl, req, extack); if (err) return err; + /* Version 1: report fw from dev_info (running only) */ + return pdsc_dl_report_fw_ver(req, pdsc->dev_info.fw_version); +} + +int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + char buf[32]; + int err; + + if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2) { + err = pdsc_dl_component_info_get(dl, req, extack); + if (err) + return err; + } else { + err = pdsc_dl_info_get_v1(dl, req, extack); + if (err) + return err; + } + snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type); err = devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c index fa626719e68d..5ccf017f6af4 100644 --- a/drivers/net/ethernet/amd/pds_core/fw.c +++ b/drivers/net/ethernet/amd/pds_core/fw.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2023 Advanced Micro Devices, Inc */ +#include +#include + #include "core.h" /* The worst case wait for the install activity is about 25 minutes when @@ -14,6 +17,58 @@ /* Number of periodic log updates during fw file download */ #define PDSC_FW_INTERVAL_FRACTION 32 +#define PDSC_FW_COMPONENT_PREFIX "fw." +#define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \ + (sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN) + +/* Driver-defined component type to name mapping. + * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as "fw" without prefix. + */ +static const char * const pdsc_fw_type_names[] = { + [PDS_CORE_FW_TYPE_MAIN] = NULL, + [PDS_CORE_FW_TYPE_BOOT] = "bootloader", + [PDS_CORE_FW_TYPE_CPLD] = "cpld", + [PDS_CORE_FW_TYPE_SECURE] = "secure", + [PDS_CORE_FW_TYPE_FPGA] = "fpga", + [PDS_CORE_FW_TYPE_SUC_MAIN] = "suc", + [PDS_CORE_FW_TYPE_SUC_BOOT] = "suc.bootloader", + [PDS_CORE_FW_TYPE_UBOOT] = "uboot", +}; + +const char *pdsc_fw_type_to_name(u8 type) +{ + if (type < ARRAY_SIZE(pdsc_fw_type_names) && pdsc_fw_type_names[type]) + return pdsc_fw_type_names[type]; + return NULL; +} + +void pdsc_fw_components_invalidate(struct pdsc *pdsc) +{ + /* Pairs with READ_ONCE in pdsc_dl_component_info_get() */ + WRITE_ONCE(pdsc->fw_components.num_components, 0); +} + +static u8 pdsc_name_to_fw_type(const char *name) +{ + size_t prefix_len; + int i; + + /* "fw" without suffix maps to main firmware */ + if (!strcmp(name, "fw")) + return PDS_CORE_FW_TYPE_MAIN; + + prefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX); + if (prefix_len) + name += prefix_len; + + for (i = 1; i < ARRAY_SIZE(pdsc_fw_type_names); i++) { + if (pdsc_fw_type_names[i] && + !strcmp(name, pdsc_fw_type_names[i])) + return i; + } + return 0; +} + static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr, u32 offset, u32 length) { @@ -23,7 +78,7 @@ static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr, .fw_download.addr = cpu_to_le64(addr), .fw_download.length = cpu_to_le32(length), }; - union pds_core_dev_comp comp; + union pds_core_dev_comp comp = {}; return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } @@ -95,9 +150,12 @@ static int pdsc_fw_status_long_wait(struct pdsc *pdsc, return err; } -int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, - struct netlink_ext_ack *extack) +static int +pdsc_legacy_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) { + const struct firmware *fw = params->fw; u32 buf_sz, copy_sz, offset; struct devlink *dl; int next_interval; @@ -105,6 +163,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, int err = 0; int fw_slot; + if (params->component) { + NL_SET_ERR_MSG_MOD(extack, + "Component update not supported by this device"); + return -EOPNOTSUPP; + } + dev_info(pdsc->dev, "Installing firmware\n"); if (!pdsc->cmd_regs) @@ -195,3 +259,729 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, NULL, 0, 0); return err; } + +struct pdsc_component_priv { + u16 component_id; + bool skip; + struct list_head list_entry; +}; + +struct pds_core_fwu_priv { + struct pldmfw context; + struct devlink_flash_update_params *params; + struct netlink_ext_ack *extack; + struct pdsc *pdsc; + struct list_head components; + bool component_found; +}; + +static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv) +{ + struct pdsc_component_priv *component_priv, *tmp; + + list_for_each_entry_safe(component_priv, tmp, &priv->components, + list_entry) { + list_del(&component_priv->list_entry); + kfree(component_priv); + } +} + +static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type, + u16 desc_size, const u8 *desc_data, + u8 *match) +{ + union pds_core_dev_cmd cmd = { + .match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC, + .match_record_desc.ver = 1, + .match_record_desc.type = cpu_to_le16(desc_type), + .match_record_desc.size = cpu_to_le16(desc_size), + }; + union pds_core_dev_comp comp = {}; + int err; + + err = pdsc_devcmd_with_data(pdsc, &cmd, desc_data, desc_size, + &comp, pdsc->devcmd_timeout); + *match = comp.match_record_desc.match; + + return err; +} + +static bool pdsc_match_record_descs(struct pldmfw *context, + struct pldmfw_record *record) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pdsc *pdsc = priv->pdsc; + struct pldmfw_desc_tlv *desc; + + if (!pldmfw_op_pci_match_record(context, record)) + return false; + + list_for_each_entry(desc, &record->descs, entry) { + u8 match; + int err; + + switch (desc->type) { + /* skip types checked in pldmfw_op_pci_match_record */ + case PLDM_DESC_ID_PCI_VENDOR_ID: + case PLDM_DESC_ID_PCI_DEVICE_ID: + case PLDM_DESC_ID_PCI_SUBVENDOR_ID: + case PLDM_DESC_ID_PCI_SUBDEV_ID: + continue; + } + + if (!desc->size) + return false; + + err = pdsc_devcmd_match_record_desc(pdsc, desc->type, + desc->size, desc->data, + &match); + if (err) { + dev_err(pdsc->dev, + "match_record_desc failed type: 0x%04x size: %u, err %d\n", + desc->type, desc->size, err); + return false; + } + /* all record descriptors must match */ + if (!match) + return false; + } + + return true; +} + +static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr, + u16 length, u16 offset, u16 total_len) +{ + union pds_core_dev_cmd cmd = { + .send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA, + .send_pkg_data.ver = 1, + .send_pkg_data.data_pa = cpu_to_le64(addr), + .send_pkg_data.data_len = cpu_to_le16(length), + .send_pkg_data.offset = cpu_to_le16(offset), + .send_pkg_data.total_len = cpu_to_le16(total_len), + }; + union pds_core_dev_comp comp = {}; + + return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_send_package_data(struct pldmfw *context, const u8 *data, + u16 length) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pdsc_deferred_dma *deferred; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + dma_addr_t dma_addr; + u8 *package_data; + u32 offset; + int err; + + if (!length) + return 0; + + deferred = kmalloc_obj(*deferred, GFP_KERNEL); + if (!deferred) + return -ENOMEM; + + package_data = kmemdup(data, length, GFP_KERNEL); + if (!package_data) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, "Failed to dma_map package_data length 0x%x\n", + length); + kfree(package_data); + kfree(deferred); + return -ENOMEM; + } + + for (offset = 0; offset < length; offset += PDS_PAGE_SIZE) { + u32 copy_sz; + + copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset); + err = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset, + copy_sz, offset, length); + if (err) { + NL_SET_ERR_MSG_MOD(priv->extack, + "Failed to send package data"); + break; + } + } + + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, + package_data, length, DMA_TO_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE); + kfree(package_data); + return err; +} + +static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + if (pdsc->fw_components.info[i].component_type == type) + return true; + } + return false; +} + +static bool pdsc_component_id_matches_type(struct pdsc *pdsc, + u8 component_id, u8 type) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + if (info->identifier == component_id && + info->component_type == type) + return true; + } + return false; +} + +static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + if (info->identifier == component_id) + return info->component_type; + } + return 0; +} + +static bool pdsc_skip_component(struct pds_core_fwu_priv *priv, + u16 component_id) +{ + struct pdsc_component_priv *component_priv; + + list_for_each_entry(component_priv, &priv->components, list_entry) { + if (component_priv->component_id == component_id) + return component_priv->skip; + } + + return false; +} + +static int pdsc_send_component_table(struct pldmfw *context, + struct pldmfw_component *component, + u8 transfer_flag) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pds_core_component_tbl *component_tbl; + struct pdsc_component_priv *component_priv; + struct device *dev = context->dev; + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + struct pdsc *pdsc = priv->pdsc; + bool skip_component = false; + u8 requested_type = 0; + u16 buf_sz, tbl_sz; + int err = 0; + + dev_dbg(dev, + "component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\n", + priv->params->component, component->classification, + component->identifier, component->activation_method, + component->version_len, component->version_len, + component->version_string, component->index, + component->component_size, transfer_flag); + + component_priv = kzalloc_obj(*component_priv, GFP_KERNEL); + if (!component_priv) + return -ENOMEM; + + if (priv->params->component) { + requested_type = pdsc_name_to_fw_type(priv->params->component); + if (component->identifier > U8_MAX || + !pdsc_component_id_matches_type(pdsc, + component->identifier, + requested_type)) { + skip_component = true; + goto add_component_priv; + } + priv->component_found = true; + } + + buf_sz = sizeof(pdsc->cmd_regs->data); + tbl_sz = struct_size(component_tbl, version_str, + component->version_len); + if (tbl_sz > buf_sz) { + dev_err(dev, "component_tbl size %d too big, max size: %d\n", + tbl_sz, buf_sz); + err = -ENOSPC; + goto free_component_priv; + } + component_tbl = kzalloc(tbl_sz, GFP_KERNEL); + if (!component_tbl) { + err = -ENOMEM; + goto free_component_priv; + } + + component_tbl->comparison_stamp = + cpu_to_le32(component->comparison_stamp); + component_tbl->classification = cpu_to_le16(component->classification); + component_tbl->identifier = cpu_to_le16(component->identifier); + component_tbl->transfer_flag = transfer_flag; + component_tbl->version_str_type = component->version_type; + component_tbl->version_str_len = component->version_len; + memcpy(component_tbl->version_str, component->version_string, + component->version_len); + + cmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL; + cmd.send_component_tbl.ver = 1; + cmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID; + + err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz, + &comp, pdsc->devcmd_timeout); + kfree(component_tbl); + if (err) { + dev_err(dev, "Failed sending component table: %pe\n", + ERR_PTR(err)); + goto free_component_priv; + } + + skip_component = comp.send_component_tbl.response == 1; + +add_component_priv: + component_priv->skip = skip_component; + component_priv->component_id = component->identifier; + list_add(&component_priv->list_entry, &priv->components); + + return 0; + +free_component_priv: + kfree(component_priv); + return err; +} + +int pdsc_get_component_info(struct pdsc *pdsc) +{ + union pds_core_dev_cmd cmd = { + .get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO, + .get_component_info.ver = 1, + }; + struct pds_core_component_list_info *list_info; + struct pdsc_deferred_dma *deferred; + union pds_core_dev_comp comp = {}; + dma_addr_t dma_addr; + u8 num_components; + int err, i; + + deferred = kmalloc_obj(*deferred); + if (!deferred) + return -ENOMEM; + + list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL); + if (!list_info) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(pdsc->dev, dma_addr)) { + dev_err(pdsc->dev, + "Failed to dma_map component_list_info length %d\n", + PDS_PAGE_SIZE); + kfree(list_info); + kfree(deferred); + return -ENOMEM; + } + + cmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE); + cmd.get_component_info.data_pa = cpu_to_le64(dma_addr); + + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2); + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info, + PDS_PAGE_SIZE, DMA_FROM_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(pdsc->dev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE); + if (err) + goto out; + + if (comp.get_component_info.ver == 0) { + /* Don't support backward compatibility as version 0 has + * alignment issues, so give a hint to users to update + * their firmware + */ + dev_warn_once(pdsc->dev, + "Incompatible get_component_info version %u reported by firmware\n", + comp.get_component_info.ver); + err = 0; + goto out; + } + + num_components = list_info->num_components; + if (num_components > PDS_CORE_FW_COMPONENT_LIST_LEN) { + err = -ENOMEM; + goto out; + } + + pdsc->fw_components.num_components = num_components; + for (i = 0; i < num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + memcpy(info, &list_info->info[i], sizeof(*info)); + info->version[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0; + info->name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0; + } + +out: + kfree(list_info); + return err; +} + +static int pdsc_devcmd_send_component(struct pdsc *pdsc, + struct pds_core_flash_component *info, + u16 info_sz, dma_addr_t addr, u32 length, + u32 offset, u16 slot_id, + union pds_core_dev_comp *comp) +{ + union pds_core_dev_cmd cmd = { + .send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT, + .send_component.ver = 1, + .send_component.operation = PDS_CORE_SEND_COMPONENT_START, + .send_component.data_pa = cpu_to_le64(addr), + .send_component.data_len = cpu_to_le32(length), + .send_component.offset = cpu_to_le32(offset), + .send_component.slot_id = slot_id, + }; + unsigned long timeout = 300 * HZ; + unsigned long start_time; + unsigned long end_time; + int err; + + start_time = jiffies; + end_time = start_time + timeout; + do { + /* prevent noisy/benign devcmd failures */ + err = pdsc_devcmd_with_data_nomsg(pdsc, &cmd, info, info_sz, + comp, 60); + if (err != -EAGAIN) + break; + + /* if required, subsequent commands check status of + * PDS_CORE_CMD_SEND_COMPONENT command, which returns + * EAGAIN while the command is still running, + * else we get the final command status. + */ + cmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS; + msleep(20); + } while (time_before(jiffies, end_time)); + + if (err == -EAGAIN || err == -ETIMEDOUT) + dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n"); + + return err; +} + +static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev, + struct pds_core_flash_component *info, + u16 info_sz, const u8 *data, u16 copy_sz, + u32 offset, u8 slot_id, + union pds_core_dev_comp *comp) +{ + struct pdsc_deferred_dma *deferred; + dma_addr_t dma_addr; + u8 *component_data; + int err; + + deferred = kmalloc_obj(*deferred, GFP_KERNEL); + if (!deferred) + return -ENOMEM; + + component_data = kmemdup(data, copy_sz, GFP_KERNEL); + if (!component_data) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, + "Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n", + offset, copy_sz); + kfree(component_data); + kfree(deferred); + return -ENOMEM; + } + + err = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr, + copy_sz, offset, slot_id, comp); + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, + component_data, copy_sz, DMA_TO_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE); + kfree(component_data); + + return err; +} + +static int pdsc_flash_component(struct pldmfw *context, + struct pldmfw_component *component) +{ + char component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN]; + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pds_core_flash_component *component_info; + const char *component_name = NULL; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + u16 buf_sz, info_sz; + struct devlink *dl; + u8 component_type; + u32 total_len; + u32 offset; + int err; + + component_type = pdsc_get_component_type_by_id(pdsc, + component->identifier); + if (component_type) { + const char *type_name = pdsc_fw_type_to_name(component_type); + + if (component_type == PDS_CORE_FW_TYPE_MAIN) { + component_name = "fw"; + } else if (type_name) { + snprintf(component_name_buf, sizeof(component_name_buf), + "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name); + component_name = component_name_buf; + } + } + + dl = priv_to_devlink(pdsc); + + if (pdsc_skip_component(priv, component->identifier)) { + devlink_flash_update_status_notify(dl, "Skipped", + component_name, 0, 0); + return 0; + } + + total_len = component->component_size; + dev_dbg(dev, + "component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\n", + component_name ?: "(unknown)", component->classification, + component->identifier, component->activation_method, + component->version_len, component->version_string, + component->index, component->component_size); + + buf_sz = sizeof(pdsc->cmd_regs->data); + info_sz = struct_size(component_info, version_str, + component->version_len); + if (info_sz > buf_sz) { + dev_err(dev, "component_info size %d too big, max size: %d\n", + info_sz, buf_sz); + return -ENOSPC; + } + component_info = vzalloc(info_sz); + if (!component_info) + return -ENOMEM; + + component_info->comparison_stamp = + cpu_to_le32(component->comparison_stamp); + component_info->image_size = cpu_to_le32(total_len); + component_info->classification = cpu_to_le16(component->classification); + component_info->identifier = cpu_to_le16(component->identifier); + component_info->options = cpu_to_le16(component->options); + component_info->version_str_type = component->version_type; + component_info->version_str_len = component->version_len; + memcpy(component_info->version_str, component->version_string, + component->version_len); + + offset = 0; + while (offset < total_len) { + union pds_core_dev_comp comp = {}; + u16 copy_sz; + + copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, + total_len - offset); + + err = pdsc_flash_component_chunk(pdsc, dev, component_info, + info_sz, + component->component_data + + offset, copy_sz, offset, + PDS_CORE_FW_SLOT_INVALID, + &comp); + if (err && + comp.send_component.compat_response && + (comp.send_component.compat_response_code == + PDS_CORE_COMPONENT_STAMP_IDENTICAL || + comp.send_component.compat_response_code == + PDS_CORE_COMPONENT_STAMP_LOWER)) { + err = 0; + devlink_flash_update_status_notify(dl, "Skipped", + component_name, + 0, 0); + goto skip_component; + } + + if (err) { + NL_SET_ERR_MSG_MOD(priv->extack, + "Failed to flash component"); + goto err_out; + } + + offset += copy_sz; + devlink_flash_update_status_notify(dl, + "Erasing/Flashing", + component_name, offset, + total_len); + } + + vfree(component_info); + return 0; + +err_out: + devlink_flash_update_status_notify(dl, + "Erasing/Flashing Component Failed", + component_name, 0, 0); +skip_component: + vfree(component_info); + return err; +} + +static int pdsc_devcmd_finalize_update(struct pdsc *pdsc) +{ + union pds_core_dev_cmd cmd = { + .finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE, + .finalize_update.ver = 1, + }; + union pds_core_dev_comp comp = {}; + + return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_finalize_update(struct pldmfw *context) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + const char *component_name = priv->params->component; + unsigned long start_time, end_time; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + struct devlink *dl; + int err; + + dl = priv_to_devlink(pdsc); + + start_time = jiffies; + end_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ); + do { + err = pdsc_devcmd_finalize_update(pdsc); + if (err != -EAGAIN) + break; + + dev_dbg(dev, "retrying finalize_update: %pe\n", ERR_PTR(err)); + msleep(20); + } while (time_before(jiffies, end_time) && err == -EAGAIN); + + if (err) { + devlink_flash_update_status_notify(dl, "Finalize Update Failed", + component_name, 0, 0); + NL_SET_ERR_MSG_MOD(priv->extack, "Finalize update failed"); + return err; + } + + devlink_flash_update_status_notify(dl, "Finalized Update", + component_name, 0, 0); + return 0; +} + +static const struct pldmfw_ops pdsc_pldmfw_ops = { + .match_record = pdsc_match_record_descs, + .send_package_data = pdsc_send_package_data, + .send_component_table = pdsc_send_component_table, + .flash_component = pdsc_flash_component, + .finalize_update = pdsc_finalize_update +}; + +static int pdsc_pldm_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack, + const struct firmware *fw) +{ + struct pds_core_fwu_priv priv = {}; + int err; + + if (!pdsc->fw_components.num_components) { + err = pdsc_get_component_info(pdsc); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to get component info"); + return err; + } + } + + if (params->component) { + u8 type = pdsc_name_to_fw_type(params->component); + + if (!type || !pdsc_component_type_exists(pdsc, type)) { + NL_SET_ERR_MSG_MOD(extack, "Unknown component name"); + return -ENOENT; + } + } + + INIT_LIST_HEAD(&priv.components); + priv.context.ops = &pdsc_pldmfw_ops; + priv.context.dev = pdsc->dev; + priv.params = params; + priv.extack = extack; + priv.pdsc = pdsc; + + err = pldmfw_flash_image(&priv.context, fw); + if (!err && params->component && !priv.component_found) { + NL_SET_ERR_MSG_MOD(extack, + "Requested component not present in firmware package"); + err = -ENOENT; + } + pdsc_free_fwu_priv(&priv); + + return err; +} + +int pdsc_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + int err; + + if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 && + pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE)) + err = pdsc_pldm_firmware_update(pdsc, params, extack, + params->fw); + else + err = pdsc_legacy_firmware_update(pdsc, params, extack); + + /* Invalidate cached component info so next info_get refreshes */ + pdsc_fw_components_invalidate(pdsc); + + return err; +} diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 9a2c64198d03..bb79e7476370 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -21,6 +21,8 @@ static const struct pci_device_id pdsc_id_table[] = { }; MODULE_DEVICE_TABLE(pci, pdsc_id_table); +static void pdsc_stop_health_thread(struct pdsc *pdsc); + static void pdsc_wdtimer_cb(struct timer_list *t) { struct pdsc *pdsc = timer_container_of(pdsc, t, wdtimer); @@ -315,6 +317,7 @@ static int pdsc_init_pf(struct pdsc *pdsc) destroy_workqueue(pdsc->wq); mutex_destroy(&pdsc->config_lock); mutex_destroy(&pdsc->devcmd_lock); + pdsc_deferred_dma_free(pdsc); pci_free_irq_vectors(pdsc->pdev); err_out_unmap_bars: pdsc_unmap_bars(pdsc); @@ -353,6 +356,8 @@ static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pdsc->pdev = pdev; pdsc->dev = &pdev->dev; set_bit(PDSC_S_INITING_DRIVER, &pdsc->state); + INIT_LIST_HEAD(&pdsc->deferred_dma_list); + spin_lock_init(&pdsc->deferred_dma_lock); pci_set_drvdata(pdev, pdsc); pdsc_debugfs_add_dev(pdsc); @@ -458,6 +463,7 @@ static void pdsc_remove(struct pci_dev *pdev) } pci_disable_device(pdev); + pdsc_deferred_dma_free(pdsc); ida_free(&pdsc_ida, pdsc->uid); pdsc_debugfs_del_dev(pdsc); @@ -511,6 +517,7 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); + pdsc_deferred_dma_free(pdsc); } static void pdsc_reset_done(struct pci_dev *pdev) diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c index 3b2951030a38..507db46daf2b 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c @@ -7,7 +7,7 @@ * Keyur Chudgar */ -#include +#include #include "xgene_enet_main.h" #include "xgene_enet_hw.h" #include "xgene_enet_sgmac.h" diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c b/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c index 3ca072360ec7..fd4ee6212234 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c @@ -735,11 +735,7 @@ static int aq_set_rxsc(struct aq_nic_s *nic, const u32 rxsc_idx) sc_record.valid = 1; sc_record.fresh = 1; - ret = aq_mss_set_ingress_sc_record(hw, &sc_record, hw_sc_idx); - if (ret) - return ret; - - return ret; + return aq_mss_set_ingress_sc_record(hw, &sc_record, hw_sc_idx); } static int aq_mdo_add_rxsc(struct macsec_context *ctx) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index ac4c93e5b634..a4288f0258f8 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -20,6 +20,7 @@ #include #include "bnge.h" +#include "bnge_hwrm.h" #include "bnge_hwrm_lib.h" #include "bnge_ethtool.h" #include "bnge_rmem.h" @@ -2144,16 +2145,16 @@ static int bnge_hwrm_set_vnic_filter(struct bnge_net *bn, u16 vnic_id, u16 idx, return rc; } -static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask) +static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask, + const struct netdev_hw_addr_list *mc) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; - struct net_device *dev = bn->netdev; struct netdev_hw_addr *ha; int mc_count = 0, off = 0; bool update = false; u8 *haddr; - netdev_for_each_mc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, mc) { if (mc_count >= BNGE_MAX_MC_ADDRS) { *rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST; vnic->mc_list_count = 0; @@ -2177,17 +2178,17 @@ static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask) return update; } -static bool bnge_uc_list_updated(struct bnge_net *bn) +static bool bnge_uc_list_updated(struct bnge_net *bn, + const struct netdev_hw_addr_list *uc) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; - struct net_device *dev = bn->netdev; struct netdev_hw_addr *ha; int off = 0; - if (netdev_uc_count(dev) != (vnic->uc_filter_count - 1)) + if (netdev_hw_addr_list_count(uc) != (vnic->uc_filter_count - 1)) return true; - netdev_for_each_uc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, uc) { if (!ether_addr_equal(ha->addr, vnic->uc_list + off)) return true; @@ -2201,18 +2202,14 @@ static bool bnge_promisc_ok(struct bnge_net *bn) return true; } -static int bnge_cfg_def_vnic(struct bnge_net *bn) +static int bnge_cfg_rx_mode(struct bnge_net *bn, struct netdev_hw_addr_list *uc, + bool uc_update, bool snapshot) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; struct net_device *dev = bn->netdev; struct bnge_dev *bd = bn->bd; struct netdev_hw_addr *ha; int i, off = 0, rc; - bool uc_update; - - netif_addr_lock_bh(dev); - uc_update = bnge_uc_list_updated(bn); - netif_addr_unlock_bh(dev); if (!uc_update) goto skip_uc; @@ -2226,22 +2223,28 @@ static int bnge_cfg_def_vnic(struct bnge_net *bn) vnic->uc_filter_count = 1; - netif_addr_lock_bh(dev); - if (netdev_uc_count(dev) > (BNGE_MAX_UC_ADDRS - 1)) { + if (!snapshot) + netif_addr_lock_bh(dev); + if (netdev_hw_addr_list_count(uc) > (BNGE_MAX_UC_ADDRS - 1)) { vnic->rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS; } else { - netdev_for_each_uc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, uc) { memcpy(vnic->uc_list + off, ha->addr, ETH_ALEN); off += ETH_ALEN; vnic->uc_filter_count++; } } - netif_addr_unlock_bh(dev); + if (!snapshot) + netif_addr_unlock_bh(dev); for (i = 1, off = 0; i < vnic->uc_filter_count; i++, off += ETH_ALEN) { rc = bnge_hwrm_set_vnic_filter(bn, 0, i, vnic->uc_list + off); if (rc) { - netdev_err(dev, "HWRM vnic filter failure rc: %d\n", rc); + if (rc == -EAGAIN) + netdev_warn(dev, "FW busy while setting vnic filter, will retry\n"); + else + netdev_err(dev, "HWRM vnic filter failure rc: %d\n", + rc); vnic->uc_filter_count = i; return rc; } @@ -2260,13 +2263,58 @@ static int bnge_cfg_def_vnic(struct bnge_net *bn) vnic->mc_list_count = 0; rc = bnge_hwrm_cfa_l2_set_rx_mask(bd, vnic); } - if (rc) - netdev_err(dev, "HWRM cfa l2 rx mask failure rc: %d\n", - rc); + if (rc) { + if (rc == -EAGAIN) { + netdev_warn(dev, "FW busy while setting l2 rx mask in CFA, will retry\n"); + vnic->rx_mask &= ~BNGE_RX_MASK_CFG_FLAGS; + } else { + netdev_err(dev, "HWRM CFA L2 rx mask failure rc: %d\n", + rc); + } + } return rc; } +static int bnge_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct bnge_net *bn = netdev_priv(dev); + struct bnge_vnic_info *vnic; + bool mc_update = false; + bool uc_update; + u32 mask; + + if (!test_bit(BNGE_STATE_OPEN, &bn->bd->state)) + return 0; + + vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; + mask = vnic->rx_mask; + mask &= ~BNGE_RX_MASK_CFG_FLAGS; + + if (dev->flags & IFF_PROMISC) + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS; + + uc_update = bnge_uc_list_updated(bn, uc); + + if (dev->flags & IFF_BROADCAST) + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_BCAST; + if (dev->flags & IFF_ALLMULTI) { + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST; + vnic->mc_list_count = 0; + } else if (dev->flags & IFF_MULTICAST) { + mc_update = bnge_mc_list_updated(bn, &mask, mc); + } + + if (mask != vnic->rx_mask || uc_update || mc_update) { + vnic->rx_mask = mask; + return bnge_cfg_rx_mode(bn, uc, uc_update, true); + } + + return 0; +} + static void bnge_disable_int(struct bnge_net *bn) { struct bnge_dev *bd = bn->bd; @@ -2695,13 +2743,17 @@ static int bnge_init_chip(struct bnge_net *bn) } else if (bn->netdev->flags & IFF_MULTICAST) { u32 mask = 0; - bnge_mc_list_updated(bn, &mask); + bnge_mc_list_updated(bn, &mask, &bn->netdev->mc); vnic->rx_mask |= mask; } - rc = bnge_cfg_def_vnic(bn); - if (rc) + rc = bnge_cfg_rx_mode(bn, &bn->netdev->uc, true, false); + if (rc == -EAGAIN) { + netif_rx_mode_schedule_retry(bn->netdev); + rc = 0; + } else if (rc) { goto err_out; + } return 0; err_out: @@ -2811,6 +2863,24 @@ static void bnge_tx_enable(struct bnge_net *bn) netif_carrier_on(bn->netdev); } +static int bnge_hwrm_if_change(struct bnge_dev *bd, bool up) +{ + struct hwrm_func_drv_if_change_input *req; + int rc; + + if (!(bd->fw_cap & BNGE_FW_CAP_IF_CHANGE)) + return 0; + + rc = bnge_hwrm_req_init(bd, req, HWRM_FUNC_DRV_IF_CHANGE); + if (rc) + return rc; + + if (up) + req->flags = cpu_to_le32(FUNC_DRV_IF_CHANGE_REQ_FLAGS_UP); + + return bnge_hwrm_req_send(bd, req); +} + static int bnge_open_core(struct bnge_net *bn) { struct bnge_dev *bd = bn->bd; @@ -2818,16 +2888,22 @@ static int bnge_open_core(struct bnge_net *bn) netif_carrier_off(bn->netdev); + rc = bnge_hwrm_if_change(bd, true); + if (rc) { + netdev_err(bn->netdev, "bnge_hwrm_if_change err: %d\n", rc); + return rc; + } + rc = bnge_reserve_rings(bd); if (rc) { netdev_err(bn->netdev, "bnge_reserve_rings err: %d\n", rc); - return rc; + goto err_if_change; } rc = bnge_alloc_core(bn); if (rc) { netdev_err(bn->netdev, "bnge_alloc_core err: %d\n", rc); - return rc; + goto err_if_change; } bnge_init_napi(bn); @@ -2874,6 +2950,8 @@ static int bnge_open_core(struct bnge_net *bn) err_del_napi: bnge_del_napi(bn); bnge_free_core(bn); +err_if_change: + bnge_hwrm_if_change(bd, false); return rc; } @@ -3104,6 +3182,7 @@ static int bnge_close(struct net_device *dev) bnge_close_core(bn); bnge_hwrm_shutdown_link(bn->bd); + bnge_hwrm_if_change(bn->bd, false); bn->sp_event = 0; return 0; @@ -3193,6 +3272,7 @@ static const struct net_device_ops bnge_netdev_ops = { .ndo_stop = bnge_close, .ndo_start_xmit = bnge_start_xmit, .ndo_get_stats64 = bnge_get_stats64, + .ndo_set_rx_mode_async = bnge_set_rx_mode, .ndo_features_check = bnge_features_check, }; diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h index d177919c2e11..476b5bab96fe 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h @@ -561,6 +561,12 @@ struct bnge_napi { #define BNGE_VNIC_DEFAULT 0 #define BNGE_MAX_UC_ADDRS 4 +#define BNGE_RX_MASK_CFG_FLAGS \ + (CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS | \ + CFA_L2_SET_RX_MASK_REQ_MASK_MCAST | \ + CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST | \ + CFA_L2_SET_RX_MASK_REQ_MASK_BCAST) + struct bnge_vnic_info { u16 fw_vnic_id; #define BNGE_MAX_CTX_PER_VNIC 8 diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c index 5b2640bd31c3..5a9742fd3ddf 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c @@ -4742,13 +4742,13 @@ int bnx2x_alloc_mem_bp(struct bnx2x *bp) /* fp array: RSS plus CNIC related L2 queues */ fp_array_size = BNX2X_MAX_RSS_COUNT(bp) + CNIC_SUPPORT(bp); - bp->fp_array_size = fp_array_size; - BNX2X_DEV_INFO("fp_array_size %d\n", bp->fp_array_size); - - fp = kzalloc_objs(*fp, bp->fp_array_size); + BNX2X_DEV_INFO("fp_array_size %d\n", fp_array_size); + fp = kzalloc_objs(*fp, fp_array_size); if (!fp) goto alloc_err; bp->fp = fp; + bp->fp_array_size = fp_array_size; + for (i = 0; i < bp->fp_array_size; i++) { fp[i].tpa_info = kzalloc_objs(struct bnx2x_agg_info, diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c index 07a908a2c72f..d560524d317d 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "bnx2x.h" #include "bnx2x_cmn.h" #include "bnx2x_sp.h" @@ -2664,7 +2665,7 @@ static void bnx2x_free_groups(struct list_head *mcast_group_list) struct bnx2x_mcast_elem_group, mcast_group_link); list_del(¤t_mcast_group->mcast_group_link); - free_page((unsigned long)current_mcast_group); + kfree(current_mcast_group); } } @@ -2713,8 +2714,7 @@ static int bnx2x_mcast_enqueue_cmd(struct bnx2x *bp, total_elems = BNX2X_MCAST_BINS_NUM; } while (total_elems > 0) { - elem_group = (struct bnx2x_mcast_elem_group *) - __get_free_page(GFP_ATOMIC | __GFP_ZERO); + elem_group = kzalloc(PAGE_SIZE, GFP_ATOMIC); if (!elem_group) { bnx2x_free_groups(&new_cmd->group_head); kfree(new_cmd); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index bc7b37cb74a7..9377bf675981 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11592,7 +11592,7 @@ static int bnxt_get_num_msix(struct bnxt *bp) static int bnxt_init_int_mode(struct bnxt *bp) { - int i, total_vecs, max, rc = 0, min = 1, ulp_msix, tx_cp, tbl_size; + int i, total_vecs, max, rc, min = 1, ulp_msix, tx_cp, tbl_size; total_vecs = bnxt_get_num_msix(bp); max = bnxt_get_max_func_irqs(bp); @@ -11617,26 +11617,26 @@ static int bnxt_init_int_mode(struct bnxt *bp) if (pci_msix_can_alloc_dyn(bp->pdev)) tbl_size = max; bp->irq_tbl = kzalloc_objs(*bp->irq_tbl, tbl_size); - if (bp->irq_tbl) { - for (i = 0; i < total_vecs; i++) - bp->irq_tbl[i].vector = pci_irq_vector(bp->pdev, i); - - bp->total_irqs = total_vecs; - /* Trim rings based upon num of vectors allocated */ - rc = bnxt_trim_rings(bp, &bp->rx_nr_rings, &bp->tx_nr_rings, - total_vecs - ulp_msix, min == 1); - if (rc) - goto msix_setup_exit; - - tx_cp = bnxt_num_tx_to_cp(bp, bp->tx_nr_rings); - bp->cp_nr_rings = (min == 1) ? - max_t(int, tx_cp, bp->rx_nr_rings) : - tx_cp + bp->rx_nr_rings; - - } else { + if (!bp->irq_tbl) { rc = -ENOMEM; goto msix_setup_exit; } + + for (i = 0; i < total_vecs; i++) + bp->irq_tbl[i].vector = pci_irq_vector(bp->pdev, i); + + bp->total_irqs = total_vecs; + /* Trim rings based upon num of vectors allocated */ + rc = bnxt_trim_rings(bp, &bp->rx_nr_rings, &bp->tx_nr_rings, + total_vecs - ulp_msix, min == 1); + if (rc) + goto msix_setup_exit; + + tx_cp = bnxt_num_tx_to_cp(bp, bp->tx_nr_rings); + bp->cp_nr_rings = (min == 1) ? + max_t(int, tx_cp, bp->rx_nr_rings) : + tx_cp + bp->rx_nr_rings; + return 0; msix_setup_exit: @@ -11792,6 +11792,9 @@ static void bnxt_irq_affinity_notify(struct irq_affinity_notify *notify, irq = container_of(notify, struct bnxt_irq, affinity_notify); + cpumask_copy(irq->bp->ring_cpu_mask[irq->ring_nr], mask); + set_bit(irq->ring_nr, irq->bp->ring_affinity_set); + #ifdef CONFIG_RFS_ACCEL if (irq->bp->dev->rx_cpu_rmap && irq->ring_nr < irq->bp->rx_nr_rings) { int err; @@ -11807,8 +11810,6 @@ static void bnxt_irq_affinity_notify(struct irq_affinity_notify *notify, if (!irq->bp->tph_mode) return; - cpumask_copy(irq->cpu_mask, mask); - if (irq->ring_nr >= irq->bp->rx_nr_rings) return; @@ -11850,10 +11851,6 @@ static void bnxt_register_irq_notifier(struct bnxt *bp, struct bnxt_irq *irq) irq->bp = bp; - /* Nothing to do if TPH is not enabled */ - if (!bp->tph_mode) - return; - /* Register IRQ affinity notifier */ notify = &irq->affinity_notify; notify->irq = irq->vector; @@ -11863,6 +11860,41 @@ static void bnxt_register_irq_notifier(struct bnxt *bp, struct bnxt_irq *irq) irq_set_affinity_notifier(irq->vector, notify); } +static int bnxt_alloc_ring_cpu_masks(struct bnxt *bp) +{ + int i; + + bp->ring_cpu_mask = kzalloc_objs(*bp->ring_cpu_mask, bp->max_irqs); + if (!bp->ring_cpu_mask) + return -ENOMEM; + + bp->ring_affinity_set = bitmap_zalloc(bp->max_irqs, GFP_KERNEL); + if (!bp->ring_affinity_set) + return -ENOMEM; + + for (i = 0; i < bp->max_irqs; i++) + if (!zalloc_cpumask_var(&bp->ring_cpu_mask[i], GFP_KERNEL)) + return -ENOMEM; + + return 0; +} + +static void bnxt_free_ring_cpu_masks(struct bnxt *bp) +{ + int i; + + if (!bp->ring_cpu_mask) + return; + + for (i = 0; i < bp->max_irqs; i++) + free_cpumask_var(bp->ring_cpu_mask[i]); + + bitmap_free(bp->ring_affinity_set); + bp->ring_affinity_set = NULL; + kfree(bp->ring_cpu_mask); + bp->ring_cpu_mask = NULL; +} + static void bnxt_free_irq(struct bnxt *bp) { struct bnxt_irq *irq; @@ -11877,13 +11909,7 @@ static void bnxt_free_irq(struct bnxt *bp) irq = &bp->irq_tbl[map_idx]; if (irq->requested) { bnxt_release_irq_notifier(irq); - - if (irq->have_cpumask) { - irq_update_affinity_hint(irq->vector, NULL); - free_cpumask_var(irq->cpu_mask); - irq->have_cpumask = 0; - } - + irq_update_affinity_hint(irq->vector, NULL); free_irq(irq->vector, bp->bnapi[i]); } @@ -11925,9 +11951,9 @@ static int bnxt_request_irq(struct bnxt *bp) bp->tph_mode = PCI_TPH_ST_IV_MODE; for (i = 0, j = 0; i < bp->cp_nr_rings; i++) { + struct cpumask *cpu_mask = bp->ring_cpu_mask[i]; int map_idx = bnxt_cp_num_to_irq_num(bp, i); struct bnxt_irq *irq = &bp->irq_tbl[map_idx]; - unsigned int cpu_num; u16 tag; if (IS_ENABLED(CONFIG_RFS_ACCEL) && @@ -11946,19 +11972,24 @@ static int bnxt_request_irq(struct bnxt *bp) netif_napi_set_irq_locked(&bp->bnapi[i]->napi, irq->vector); irq->requested = 1; - - if (!zalloc_cpumask_var(&irq->cpu_mask, GFP_KERNEL)) - continue; - - irq->have_cpumask = 1; irq->msix_nr = map_idx; irq->ring_nr = i; - cpu_num = cpumask_local_spread(i, numa_node); - cpumask_set_cpu(cpu_num, irq->cpu_mask); + + /* Reuse the mask recorded before the IRQs were freed. Nothing + * was recorded yet on the very first request, and the mask + * may have gone stale if the CPUs went offline in between. + */ + if (!test_bit(i, bp->ring_affinity_set) || + !cpumask_intersects(cpu_mask, cpu_online_mask)) { + clear_bit(i, bp->ring_affinity_set); + cpumask_clear(cpu_mask); + cpumask_set_cpu(cpumask_local_spread(i, numa_node), + cpu_mask); + } /* Init ST table entry if we can get the mapping */ if (!pcie_tph_get_cpu_st(bp->pdev, TPH_MEM_TYPE_VM, - cpu_num, &tag)) { + cpumask_first(cpu_mask), &tag)) { pcie_tph_set_st_entry(bp->pdev, irq->msix_nr, tag); irq->tag = tag; irq->new_tag = tag; @@ -11966,10 +11997,19 @@ static int bnxt_request_irq(struct bnxt *bp) bnxt_register_irq_notifier(bp, irq); - rc = irq_update_affinity_hint(irq->vector, irq->cpu_mask); + /* Only put the IRQ back where it was configured to be, our own + * placement is just a hint, the core spreads within + * irq_default_affinity which we know nothing about. + * Set after installing the notifier, if we race with the user + * it's better to overwrite than miss the notification. + */ + if (test_bit(i, bp->ring_affinity_set)) + rc = irq_set_affinity_and_hint(irq->vector, cpu_mask); + else + rc = irq_update_affinity_hint(irq->vector, cpu_mask); if (rc) { netdev_warn(bp->dev, - "Update affinity hint failed, IRQ = %d\n", + "Setting IRQ affinity failed, IRQ = %d\n", irq->vector); break; } @@ -15025,6 +15065,7 @@ static void bnxt_unmap_bars(struct bnxt *bp, struct pci_dev *pdev) static void bnxt_cleanup_pci(struct bnxt *bp) { + pci_disable_ptm(bp->pdev); bnxt_unmap_bars(bp, bp->pdev); pci_release_regions(bp->pdev); if (pci_is_enabled(bp->pdev)) @@ -15593,6 +15634,8 @@ static int bnxt_init_board(struct pci_dev *pdev, struct net_device *dev) goto init_err_release; } + pci_enable_ptm(pdev); + INIT_WORK(&bp->sp_task, bnxt_sp_task); INIT_DELAYED_WORK(&bp->fw_reset_task, bnxt_fw_reset_task); @@ -16607,6 +16650,7 @@ static void bnxt_remove_one(struct pci_dev *pdev) bnxt_shutdown_tc(bp); bnxt_clear_int_mode(bp); + bnxt_free_ring_cpu_masks(bp); bnxt_hwrm_func_drv_unrgtr(bp); bnxt_free_hwrm_resources(bp); bnxt_hwmon_uninit(bp); @@ -17045,6 +17089,11 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp->msg_enable = BNXT_DEF_MSG_ENABLE; bnxt_set_max_func_irqs(bp, max_irqs); + bp->max_irqs = max_irqs; + rc = bnxt_alloc_ring_cpu_masks(bp); + if (rc) + goto init_err_free; + if (bnxt_vf_pciid(bp->board_idx)) bp->flags |= BNXT_FLAG_VF; @@ -17099,7 +17148,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) } dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_TSO6 | + NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_TSO_ECN | NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_GRE | NETIF_F_GSO_IPXIP4 | NETIF_F_GSO_UDP_TUNNEL_CSUM | NETIF_F_GSO_GRE_CSUM | @@ -17112,7 +17161,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) dev->hw_enc_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_TSO6 | + NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_TSO_ECN | NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_GRE | NETIF_F_GSO_UDP_TUNNEL_CSUM | NETIF_F_GSO_GRE_CSUM | NETIF_F_GSO_IPXIP4 | NETIF_F_GSO_PARTIAL; @@ -17294,6 +17343,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp->rss_indir_tbl = NULL; init_err_free: + bnxt_free_ring_cpu_masks(bp); free_netdev(dev); return rc; } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index dc8ec5e5733e..ab894f8addef 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -1261,9 +1261,7 @@ struct bnxt_irq { irq_handler_t handler; unsigned int vector; u8 requested:1; - u8 have_cpumask:1; char name[IFNAMSIZ + BNXT_IRQ_NAME_EXTRA]; - cpumask_var_t cpu_mask; struct bnxt *bp; int msix_nr; @@ -2482,6 +2480,15 @@ struct bnxt { pci_channel_offline((bp)->pdev)) struct bnxt_irq *irq_tbl; + /* IRQ affinity, indexed by completion ring. Kept across IRQ + * reallocation, the MSI-X vector index is not stable. + */ + cpumask_var_t *ring_cpu_mask; + /* Rings for which the mask above was configured from the outside, + * rather than being our own default placement. + */ + unsigned long *ring_affinity_set; + int max_irqs; int total_irqs; int ulp_num_msix_want; u8 mac_addr[ETH_ALEN]; diff --git a/drivers/net/ethernet/brocade/bna/bnad.c b/drivers/net/ethernet/brocade/bna/bnad.c index 8e19add764db..8b75004ba7c9 100644 --- a/drivers/net/ethernet/brocade/bna/bnad.c +++ b/drivers/net/ethernet/brocade/bna/bnad.c @@ -3006,7 +3006,6 @@ bnad_start_xmit(struct sk_buff *skb, struct net_device *netdev) txqent->hdr.wi.reserved = 0; txqent->hdr.wi.num_vectors = vectors; - head_unmap->skb = skb; head_unmap->nvecs = 0; /* Program the vectors */ @@ -3018,6 +3017,7 @@ bnad_start_xmit(struct sk_buff *skb, struct net_device *netdev) BNAD_UPDATE_CTR(bnad, tx_skb_map_failed); return NETDEV_TX_OK; } + head_unmap->skb = skb; BNA_SET_DMA_ADDR(dma_addr, &txqent->vector[0].host_addr); txqent->vector[0].length = htons(len); dma_unmap_addr_set(&unmap->vectors[0], dma_addr, dma_addr); diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 2de56017ee0d..1e1f52285a39 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -184,12 +184,6 @@ #define GEM_DCFG8 0x029C /* Design Config 8 */ #define GEM_DCFG10 0x02A4 /* Design Config 10 */ #define GEM_DCFG12 0x02AC /* Design Config 12 */ -#define GEM_ENST_START_TIME_Q0 0x0800 /* ENST Q0 start time */ -#define GEM_ENST_START_TIME_Q1 0x0804 /* ENST Q1 start time */ -#define GEM_ENST_ON_TIME_Q0 0x0820 /* ENST Q0 on time */ -#define GEM_ENST_ON_TIME_Q1 0x0824 /* ENST Q1 on time */ -#define GEM_ENST_OFF_TIME_Q0 0x0840 /* ENST Q0 off time */ -#define GEM_ENST_OFF_TIME_Q1 0x0844 /* ENST Q1 off time */ #define GEM_ENST_CONTROL 0x0880 /* ENST control register */ #define GEM_USX_CONTROL 0x0A80 /* High speed PCS control register */ #define GEM_USX_STATUS 0x0A88 /* High speed PCS status register */ @@ -1207,11 +1201,11 @@ struct macb_or_gem_ops { /* MACB-PTP interface: adapt to platform needs. */ struct macb_ptp_info { - void (*ptp_init)(struct net_device *ndev); - void (*ptp_remove)(struct net_device *ndev); + void (*ptp_init)(struct net_device *netdev); + void (*ptp_remove)(struct net_device *netdev); s32 (*get_ptp_max_adj)(void); unsigned int (*get_tsu_rate)(struct macb *bp); - int (*get_ts_info)(struct net_device *dev, + int (*get_ts_info)(struct net_device *netdev, struct kernel_ethtool_ts_info *info); int (*get_hwtst)(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config); @@ -1326,7 +1320,7 @@ struct macb { struct clk *tx_clk; struct clk *rx_clk; struct clk *tsu_clk; - struct net_device *dev; + struct net_device *netdev; /* Protects hw_stats and ethtool_stats */ spinlock_t stats_lock; union { @@ -1406,8 +1400,8 @@ enum macb_bd_control { TSTAMP_ALL_FRAMES, }; -void gem_ptp_init(struct net_device *ndev); -void gem_ptp_remove(struct net_device *ndev); +void gem_ptp_init(struct net_device *netdev); +void gem_ptp_remove(struct net_device *netdev); void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc); void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc); static inline void gem_ptp_do_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) @@ -1426,14 +1420,14 @@ static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, stru gem_ptp_rxstamp(bp, skb, desc); } -int gem_get_hwtst(struct net_device *dev, +int gem_get_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config); -int gem_set_hwtst(struct net_device *dev, +int gem_set_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config, struct netlink_ext_ack *extack); #else -static inline void gem_ptp_init(struct net_device *ndev) { } -static inline void gem_ptp_remove(struct net_device *ndev) { } +static inline void gem_ptp_init(struct net_device *netdev) { } +static inline void gem_ptp_remove(struct net_device *netdev) { } static inline void gem_ptp_do_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) { } static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) { } diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index d394f1f43b68..1476bce77f34 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -252,9 +252,9 @@ static void macb_set_hwaddr(struct macb *bp) u32 bottom; u16 top; - bottom = get_unaligned_le32(bp->dev->dev_addr); + bottom = get_unaligned_le32(bp->netdev->dev_addr); macb_or_gem_writel(bp, SA1B, bottom); - top = get_unaligned_le16(bp->dev->dev_addr + 4); + top = get_unaligned_le16(bp->netdev->dev_addr + 4); macb_or_gem_writel(bp, SA1T, top); if (gem_has_ptp(bp)) { @@ -291,13 +291,13 @@ static void macb_get_hwaddr(struct macb *bp) addr[5] = (top >> 8) & 0xff; if (is_valid_ether_addr(addr)) { - eth_hw_addr_set(bp->dev, addr); + eth_hw_addr_set(bp->netdev, addr); return; } } dev_info(&bp->pdev->dev, "invalid hw address, using random\n"); - eth_hw_addr_random(bp->dev); + eth_hw_addr_random(bp->netdev); } static int macb_mdio_wait_for_idle(struct macb *bp) @@ -509,12 +509,12 @@ static void macb_set_tx_clk(struct macb *bp, int speed) ferr = abs(rate_rounded - rate); ferr = DIV_ROUND_UP(ferr, rate / 100000); if (ferr > 5) - netdev_warn(bp->dev, + netdev_warn(bp->netdev, "unable to generate target frequency: %ld Hz\n", rate); if (clk_set_rate(bp->tx_clk, rate_rounded)) - netdev_err(bp->dev, "adjusting tx_clk failed.\n"); + netdev_err(bp->netdev, "adjusting tx_clk failed.\n"); } static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, @@ -697,8 +697,8 @@ static void macb_tx_lpi_wake(struct macb *bp) static void macb_mac_disable_tx_lpi(struct phylink_config *config) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; cancel_delayed_work_sync(&bp->tx_lpi_work); @@ -712,8 +712,8 @@ static void macb_mac_disable_tx_lpi(struct phylink_config *config) static int macb_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, bool tx_clk_stop) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; spin_lock_irqsave(&bp->lock, flags); @@ -732,8 +732,8 @@ static int macb_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, static void macb_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; u32 old_ctrl, ctrl; u32 old_ncr, ncr; @@ -774,8 +774,8 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode, static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int q; u32 ctrl; @@ -789,7 +789,7 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE)); macb_writel(bp, NCR, ctrl); - netif_tx_stop_all_queues(ndev); + netif_tx_stop_all_queues(netdev); } /* Use juggling algorithm to left rotate tx ring and tx skb array */ @@ -877,20 +877,20 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue) static void gem_shuffle_tx_rings(struct macb *bp) { struct macb_queue *queue; - int q; + unsigned int q; for (q = 0, queue = bp->queues; q < bp->num_queues; q++, queue++) gem_shuffle_tx_one_ring(queue); } static void macb_mac_link_up(struct phylink_config *config, - struct phy_device *phy, + struct phy_device *phydev, unsigned int mode, phy_interface_t interface, int speed, int duplex, bool tx_pause, bool rx_pause) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; @@ -946,14 +946,14 @@ static void macb_mac_link_up(struct phylink_config *config, macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE)); - netif_tx_wake_all_queues(ndev); + netif_tx_wake_all_queues(netdev); } static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config, phy_interface_t interface) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); if (interface == PHY_INTERFACE_MODE_10GBASER) return &bp->phylink_usx_pcs; @@ -982,7 +982,7 @@ static bool macb_phy_handle_exists(struct device_node *dn) static int macb_phylink_connect(struct macb *bp) { struct device_node *dn = bp->pdev->dev.of_node; - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; struct phy_device *phydev; int ret; @@ -992,7 +992,7 @@ static int macb_phylink_connect(struct macb *bp) if (!dn || (ret && !macb_phy_handle_exists(dn))) { phydev = phy_find_first(bp->mii_bus); if (!phydev) { - netdev_err(dev, "no PHY found\n"); + netdev_err(netdev, "no PHY found\n"); return -ENXIO; } @@ -1001,7 +1001,7 @@ static int macb_phylink_connect(struct macb *bp) } if (ret) { - netdev_err(dev, "Could not attach PHY (%d)\n", ret); + netdev_err(netdev, "Could not attach PHY (%d)\n", ret); return ret; } @@ -1013,21 +1013,21 @@ static int macb_phylink_connect(struct macb *bp) static void macb_get_pcs_fixed_state(struct phylink_config *config, struct phylink_link_state *state) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); state->link = (macb_readl(bp, NSR) & MACB_BIT(NSR_LINK)) != 0; } /* based on au1000_eth. c*/ -static int macb_mii_probe(struct net_device *dev) +static int macb_mii_probe(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); bp->phylink_sgmii_pcs.ops = &macb_phylink_pcs_ops; bp->phylink_usx_pcs.ops = &macb_phylink_usx_pcs_ops; - bp->phylink_config.dev = &dev->dev; + bp->phylink_config.dev = &netdev->dev; bp->phylink_config.type = PHYLINK_NETDEV; bp->phylink_config.mac_managed_pm = true; @@ -1086,7 +1086,7 @@ static int macb_mii_probe(struct net_device *dev) bp->phylink = phylink_create(&bp->phylink_config, bp->pdev->dev.fwnode, bp->phy_interface, &macb_phylink_ops); if (IS_ERR(bp->phylink)) { - netdev_err(dev, "Could not create a phylink instance (%ld)\n", + netdev_err(netdev, "Could not create a phylink instance (%ld)\n", PTR_ERR(bp->phylink)); return PTR_ERR(bp->phylink); } @@ -1133,7 +1133,7 @@ static int macb_mii_init(struct macb *bp) */ mdio_np = of_get_child_by_name(np, "mdio"); if (!mdio_np && of_phy_is_fixed_link(np)) - return macb_mii_probe(bp->dev); + return macb_mii_probe(bp->netdev); /* Enable management port */ macb_writel(bp, NCR, MACB_BIT(MPE)); @@ -1154,13 +1154,13 @@ static int macb_mii_init(struct macb *bp) bp->mii_bus->priv = bp; bp->mii_bus->parent = &bp->pdev->dev; - dev_set_drvdata(&bp->dev->dev, bp->mii_bus); + dev_set_drvdata(&bp->netdev->dev, bp->mii_bus); err = macb_mdiobus_register(bp, mdio_np); if (err) goto err_out_free_mdiobus; - err = macb_mii_probe(bp->dev); + err = macb_mii_probe(bp->netdev); if (err) goto err_out_unregister_bus; @@ -1254,22 +1254,21 @@ static dma_addr_t macb_get_addr(struct macb *bp, struct macb_dma_desc *desc) static void macb_tx_error_task(struct work_struct *work) { - struct macb_queue *queue = container_of(work, struct macb_queue, - tx_error_task); - bool halt_timeout = false; - struct macb *bp = queue->bp; - u32 queue_index; - u32 packets = 0; - u32 bytes = 0; - struct macb_tx_skb *tx_skb; - struct macb_dma_desc *desc; - struct sk_buff *skb; - unsigned int tail; - unsigned long flags; + struct macb_queue *queue = container_of(work, struct macb_queue, + tx_error_task); + unsigned int q = queue - queue->bp->queues; + struct macb *bp = queue->bp; + struct macb_tx_skb *tx_skb; + struct macb_dma_desc *desc; + bool halt_timeout = false; + struct sk_buff *skb; + unsigned long flags; + unsigned int tail; + u32 packets = 0; + u32 bytes = 0; - queue_index = queue - bp->queues; - netdev_vdbg(bp->dev, "macb_tx_error_task: q = %u, t = %u, h = %u\n", - queue_index, queue->tx_tail, queue->tx_head); + netdev_vdbg(bp->netdev, "%s: q = %u, t = %u, h = %u\n", + __func__, q, queue->tx_tail, queue->tx_head); /* Prevent the queue NAPI TX poll from running, as it calls * macb_tx_complete(), which in turn may call netif_wake_subqueue(). @@ -1281,14 +1280,14 @@ static void macb_tx_error_task(struct work_struct *work) spin_lock_irqsave(&bp->lock, flags); /* Make sure nobody is trying to queue up new packets */ - netif_tx_stop_all_queues(bp->dev); + netif_tx_stop_all_queues(bp->netdev); /* Stop transmission now * (in case we have just queued new packets) * macb/gem must be halted to write TBQP register */ if (macb_halt_tx(bp)) { - netdev_err(bp->dev, "BUG: halt tx timed out\n"); + netdev_err(bp->netdev, "BUG: halt tx timed out\n"); macb_writel(bp, NCR, macb_readl(bp, NCR) & (~MACB_BIT(TE))); halt_timeout = true; } @@ -1317,13 +1316,13 @@ static void macb_tx_error_task(struct work_struct *work) * since it's the only one written back by the hardware */ if (!(ctrl & MACB_BIT(TX_BUF_EXHAUSTED))) { - netdev_vdbg(bp->dev, "txerr skb %u (data %p) TX complete\n", + netdev_vdbg(bp->netdev, "txerr skb %u (data %p) TX complete\n", macb_tx_ring_wrap(bp, tail), skb->data); - bp->dev->stats.tx_packets++; + bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; packets++; - bp->dev->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len; queue->stats.tx_bytes += skb->len; bytes += skb->len; } @@ -1333,7 +1332,7 @@ static void macb_tx_error_task(struct work_struct *work) * those. Statistics are updated by hardware. */ if (ctrl & MACB_BIT(TX_BUF_EXHAUSTED)) - netdev_err(bp->dev, + netdev_err(bp->netdev, "BUG: TX buffers exhausted mid-frame\n"); desc->ctrl = ctrl | MACB_BIT(TX_USED); @@ -1342,7 +1341,7 @@ static void macb_tx_error_task(struct work_struct *work) macb_tx_unmap(bp, tx_skb, 0); } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, q), packets, bytes); /* Set end of TX queue */ @@ -1367,7 +1366,7 @@ static void macb_tx_error_task(struct work_struct *work) macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TE)); /* Now we are ready to start transmission again */ - netif_tx_start_all_queues(bp->dev); + netif_tx_start_all_queues(bp->netdev); macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART)); spin_unlock_irqrestore(&bp->lock, flags); @@ -1407,7 +1406,7 @@ static bool ptp_one_step_sync(struct sk_buff *skb) static int macb_tx_complete(struct macb_queue *queue, int budget) { struct macb *bp = queue->bp; - u16 queue_index = queue - bp->queues; + unsigned int q = queue - bp->queues; unsigned long flags; unsigned int tail; unsigned int head; @@ -1446,12 +1445,12 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) !ptp_one_step_sync(skb)) gem_ptp_do_txstamp(bp, skb, desc); - netdev_vdbg(bp->dev, "skb %u (data %p) TX complete\n", + netdev_vdbg(bp->netdev, "skb %u (data %p) TX complete\n", macb_tx_ring_wrap(bp, tail), skb->data); - bp->dev->stats.tx_packets++; + bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; - bp->dev->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len; queue->stats.tx_bytes += skb->len; packets++; bytes += skb->len; @@ -1469,14 +1468,14 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) } } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, q), packets, bytes); queue->tx_tail = tail; - if (__netif_subqueue_stopped(bp->dev, queue_index) && + if (__netif_subqueue_stopped(bp->netdev, q) && CIRC_CNT(queue->tx_head, queue->tx_tail, bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp)) - netif_wake_subqueue(bp->dev, queue_index); + netif_wake_subqueue(bp->netdev, q); spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); if (packets) @@ -1487,11 +1486,11 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) static void gem_rx_refill(struct macb_queue *queue) { - unsigned int entry; - struct sk_buff *skb; - dma_addr_t paddr; struct macb *bp = queue->bp; struct macb_dma_desc *desc; + struct sk_buff *skb; + unsigned int entry; + dma_addr_t paddr; while (CIRC_SPACE(queue->rx_prepared_head, queue->rx_tail, bp->rx_ring_size) > 0) { @@ -1504,9 +1503,9 @@ static void gem_rx_refill(struct macb_queue *queue) if (!queue->rx_skbuff[entry]) { /* allocate sk_buff for this free entry in ring */ - skb = netdev_alloc_skb(bp->dev, bp->rx_buffer_size); + skb = netdev_alloc_skb(bp->netdev, bp->rx_buffer_size); if (unlikely(!skb)) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "Unable to allocate sk_buff\n"); break; } @@ -1555,8 +1554,8 @@ static void gem_rx_refill(struct macb_queue *queue) /* Make descriptor updates visible to hardware */ wmb(); - netdev_vdbg(bp->dev, "rx ring: queue: %p, prepared head %d, tail %d\n", - queue, queue->rx_prepared_head, queue->rx_tail); + netdev_vdbg(bp->netdev, "rx ring: queue: %p, prepared head %d, tail %d\n", + queue, queue->rx_prepared_head, queue->rx_tail); } /* Mark DMA descriptors from begin up to and not including end as unused */ @@ -1584,11 +1583,11 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, int budget) { struct macb *bp = queue->bp; - unsigned int len; - unsigned int entry; - struct sk_buff *skb; - struct macb_dma_desc *desc; - int count = 0; + struct macb_dma_desc *desc; + struct sk_buff *skb; + unsigned int entry; + unsigned int len; + int count = 0; while (count < budget) { u32 ctrl; @@ -1616,17 +1615,17 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, count++; if (!(ctrl & MACB_BIT(RX_SOF) && ctrl & MACB_BIT(RX_EOF))) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "not whole frame pointed by descriptor\n"); - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; queue->stats.rx_dropped++; break; } skb = queue->rx_skbuff[entry]; if (unlikely(!skb)) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "inconsistent Rx descriptor chain\n"); - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; queue->stats.rx_dropped++; break; } @@ -1634,28 +1633,29 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, queue->rx_skbuff[entry] = NULL; len = ctrl & bp->rx_frm_len_mask; - netdev_vdbg(bp->dev, "gem_rx %u (len %u)\n", entry, len); + netdev_vdbg(bp->netdev, "%s %u (len %u)\n", + __func__, entry, len); skb_put(skb, len); dma_unmap_single(&bp->pdev->dev, addr, bp->rx_buffer_size, DMA_FROM_DEVICE); - skb->protocol = eth_type_trans(skb, bp->dev); + skb->protocol = eth_type_trans(skb, bp->netdev); skb_checksum_none_assert(skb); - if (bp->dev->features & NETIF_F_RXCSUM && - !(bp->dev->flags & IFF_PROMISC) && + if (bp->netdev->features & NETIF_F_RXCSUM && + !(bp->netdev->flags & IFF_PROMISC) && GEM_BFEXT(RX_CSUM, ctrl) & GEM_RX_CSUM_CHECKED_MASK) skb->ip_summed = CHECKSUM_UNNECESSARY; - bp->dev->stats.rx_packets++; + bp->netdev->stats.rx_packets++; queue->stats.rx_packets++; - bp->dev->stats.rx_bytes += skb->len; + bp->netdev->stats.rx_bytes += skb->len; queue->stats.rx_bytes += skb->len; gem_ptp_do_rxstamp(bp, skb, desc); #if defined(DEBUG) && defined(VERBOSE_DEBUG) - netdev_vdbg(bp->dev, "received skb of length %u, csum: %08x\n", + netdev_vdbg(bp->netdev, "received skb of length %u, csum: %08x\n", skb->len, skb->csum); print_hex_dump(KERN_DEBUG, " mac: ", DUMP_PREFIX_ADDRESS, 16, 1, skb_mac_header(skb), 16, true); @@ -1674,19 +1674,20 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, unsigned int first_frag, unsigned int last_frag) { - unsigned int len; - unsigned int frag; + struct macb *bp = queue->bp; + struct macb_dma_desc *desc; unsigned int offset; struct sk_buff *skb; - struct macb_dma_desc *desc; - struct macb *bp = queue->bp; + unsigned int frag; + unsigned int len; desc = macb_rx_desc(queue, last_frag); len = desc->ctrl & bp->rx_frm_len_mask; - netdev_vdbg(bp->dev, "macb_rx_frame frags %u - %u (len %u)\n", - macb_rx_ring_wrap(bp, first_frag), - macb_rx_ring_wrap(bp, last_frag), len); + netdev_vdbg(bp->netdev, "%s frags %u - %u (len %u)\n", + __func__, + macb_rx_ring_wrap(bp, first_frag), + macb_rx_ring_wrap(bp, last_frag), len); /* The ethernet header starts NET_IP_ALIGN bytes into the * first buffer. Since the header is 14 bytes, this makes the @@ -1696,9 +1697,9 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, * the two padding bytes into the skb so that we avoid hitting * the slowpath in memcpy(), and pull them off afterwards. */ - skb = netdev_alloc_skb(bp->dev, len + NET_IP_ALIGN); + skb = netdev_alloc_skb(bp->netdev, len + NET_IP_ALIGN); if (!skb) { - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; for (frag = first_frag; ; frag++) { desc = macb_rx_desc(queue, frag); desc->addr &= ~MACB_BIT(RX_USED); @@ -1742,11 +1743,11 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, wmb(); __skb_pull(skb, NET_IP_ALIGN); - skb->protocol = eth_type_trans(skb, bp->dev); + skb->protocol = eth_type_trans(skb, bp->netdev); - bp->dev->stats.rx_packets++; - bp->dev->stats.rx_bytes += skb->len; - netdev_vdbg(bp->dev, "received skb of length %u, csum: %08x\n", + bp->netdev->stats.rx_packets++; + bp->netdev->stats.rx_bytes += skb->len; + netdev_vdbg(bp->netdev, "received skb of length %u, csum: %08x\n", skb->len, skb->csum); napi_gro_receive(napi, skb); @@ -1755,9 +1756,9 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, static inline void macb_init_rx_ring(struct macb_queue *queue) { + struct macb_dma_desc *desc = NULL; struct macb *bp = queue->bp; dma_addr_t addr; - struct macb_dma_desc *desc = NULL; int i; addr = queue->rx_buffers_dma; @@ -1776,9 +1777,9 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, { struct macb *bp = queue->bp; bool reset_rx_queue = false; - int received = 0; - unsigned int tail; int first_frag = -1; + unsigned int tail; + int received = 0; for (tail = queue->rx_tail; budget > 0; tail++) { struct macb_dma_desc *desc = macb_rx_desc(queue, tail); @@ -1826,7 +1827,7 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, unsigned long flags; u32 ctrl; - netdev_err(bp->dev, "RX queue corruption: reset it\n"); + netdev_err(bp->netdev, "RX queue corruption: reset it\n"); spin_lock_irqsave(&bp->lock, flags); @@ -1853,8 +1854,8 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, static bool macb_rx_pending(struct macb_queue *queue) { struct macb *bp = queue->bp; - unsigned int entry; - struct macb_dma_desc *desc; + struct macb_dma_desc *desc; + unsigned int entry; entry = macb_rx_ring_wrap(bp, queue->rx_tail); desc = macb_rx_desc(queue, entry); @@ -1873,7 +1874,7 @@ static int macb_rx_poll(struct napi_struct *napi, int budget) work_done = bp->macbgem_ops.mog_rx(queue, napi, budget); - netdev_vdbg(bp->dev, "RX poll: queue = %u, work_done = %d, budget = %d\n", + netdev_vdbg(bp->netdev, "RX poll: queue = %u, work_done = %d, budget = %d\n", (unsigned int)(queue - bp->queues), work_done, budget); if (work_done < budget && napi_complete_done(napi, work_done)) { @@ -1892,7 +1893,7 @@ static int macb_rx_poll(struct napi_struct *napi, int budget) if (macb_rx_pending(queue)) { queue_writel(queue, IDR, bp->rx_intr_mask); macb_queue_isr_clear(bp, queue, MACB_BIT(RCOMP)); - netdev_vdbg(bp->dev, "poll: packets pending, reschedule\n"); + netdev_vdbg(bp->netdev, "poll: packets pending, reschedule\n"); napi_schedule(napi); } } @@ -1956,11 +1957,11 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) rmb(); // ensure txubr_pending is up to date if (queue->txubr_pending) { queue->txubr_pending = false; - netdev_vdbg(bp->dev, "poll: tx restart\n"); + netdev_vdbg(bp->netdev, "poll: tx restart\n"); macb_tx_restart(queue); } - netdev_vdbg(bp->dev, "TX poll: queue = %u, work_done = %d, budget = %d\n", + netdev_vdbg(bp->netdev, "TX poll: queue = %u, work_done = %d, budget = %d\n", (unsigned int)(queue - bp->queues), work_done, budget); if (work_done < budget && napi_complete_done(napi, work_done)) { @@ -1979,7 +1980,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) if (macb_tx_complete_pending(queue)) { queue_writel(queue, IDR, MACB_BIT(TCOMP)); macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP)); - netdev_vdbg(bp->dev, "TX poll: packets pending, reschedule\n"); + netdev_vdbg(bp->netdev, "TX poll: packets pending, reschedule\n"); napi_schedule(napi); } } @@ -1990,7 +1991,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) static void macb_hresp_error_task(struct work_struct *work) { struct macb *bp = from_work(bp, work, hresp_err_bh_work); - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; struct macb_queue *queue; unsigned int q; u32 ctrl; @@ -2004,8 +2005,8 @@ static void macb_hresp_error_task(struct work_struct *work) ctrl &= ~(MACB_BIT(RE) | MACB_BIT(TE)); macb_writel(bp, NCR, ctrl); - netif_tx_stop_all_queues(dev); - netif_carrier_off(dev); + netif_tx_stop_all_queues(netdev); + netif_carrier_off(netdev); bp->macbgem_ops.mog_init_rings(bp); @@ -2022,8 +2023,8 @@ static void macb_hresp_error_task(struct work_struct *work) ctrl |= MACB_BIT(RE) | MACB_BIT(TE); macb_writel(bp, NCR, ctrl); - netif_carrier_on(dev); - netif_tx_start_all_queues(dev); + netif_carrier_on(netdev); + netif_tx_start_all_queues(netdev); } static void macb_wol_interrupt(struct macb_queue *queue, u32 status) @@ -2032,7 +2033,7 @@ static void macb_wol_interrupt(struct macb_queue *queue, u32 status) queue_writel(queue, IDR, MACB_BIT(WOL)); macb_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "MACB WoL: queue = %u, isr = 0x%08lx\n", + netdev_vdbg(bp->netdev, "MACB WoL: queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); macb_queue_isr_clear(bp, queue, MACB_BIT(WOL)); @@ -2045,7 +2046,7 @@ static void gem_wol_interrupt(struct macb_queue *queue, u32 status) queue_writel(queue, IDR, GEM_BIT(WOL)); gem_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "GEM WoL: queue = %u, isr = 0x%08lx\n", + netdev_vdbg(bp->netdev, "GEM WoL: queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); macb_queue_isr_clear(bp, queue, GEM_BIT(WOL)); @@ -2055,10 +2056,10 @@ static void gem_wol_interrupt(struct macb_queue *queue, u32 status) static int macb_interrupt_misc(struct macb_queue *queue, u32 status) { struct macb *bp = queue->bp; - struct net_device *dev; + struct net_device *netdev; u32 ctrl; - dev = bp->dev; + netdev = bp->netdev; if (unlikely(status & (MACB_TX_ERR_FLAGS))) { queue_writel(queue, IDR, MACB_TX_INT_FLAGS); @@ -2099,7 +2100,7 @@ static int macb_interrupt_misc(struct macb_queue *queue, u32 status) if (status & MACB_BIT(HRESP)) { queue_work(system_bh_wq, &bp->hresp_err_bh_work); - netdev_err(dev, "DMA bus error: HRESP not OK\n"); + netdev_err(netdev, "DMA bus error: HRESP not OK\n"); macb_queue_isr_clear(bp, queue, MACB_BIT(HRESP)); } @@ -2118,7 +2119,7 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) { struct macb_queue *queue = dev_id; struct macb *bp = queue->bp; - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; u32 status; status = queue_readl(queue, ISR); @@ -2130,13 +2131,13 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) while (status) { /* close possible race with dev_close */ - if (unlikely(!netif_running(dev))) { + if (unlikely(!netif_running(netdev))) { queue_writel(queue, IDR, -1); macb_queue_isr_clear(bp, queue, -1); break; } - netdev_vdbg(bp->dev, "queue = %u, isr = 0x%08lx\n", + netdev_vdbg(netdev, "queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); @@ -2181,16 +2182,16 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) /* Polling receive - used by netconsole and other diagnostic tools * to allow network i/o with interrupts disabled. */ -static void macb_poll_controller(struct net_device *dev) +static void macb_poll_controller(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; local_irq_save(flags); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) - macb_interrupt(dev->irq, queue); + macb_interrupt(netdev->irq, queue); local_irq_restore(flags); } #endif @@ -2277,7 +2278,7 @@ static unsigned int macb_tx_map(struct macb *bp, /* Should never happen */ if (unlikely(!tx_skb)) { - netdev_err(bp->dev, "BUG! empty skb!\n"); + netdev_err(bp->netdev, "BUG! empty skb!\n"); return 0; } @@ -2328,7 +2329,7 @@ static unsigned int macb_tx_map(struct macb *bp, if (i == queue->tx_head) { ctrl |= MACB_BF(TX_LSO, lso_ctrl); ctrl |= MACB_BF(TX_TCP_SEQ_SRC, seq_ctrl); - if ((bp->dev->features & NETIF_F_HW_CSUM) && + if ((bp->netdev->features & NETIF_F_HW_CSUM) && skb->ip_summed != CHECKSUM_PARTIAL && !lso_ctrl && !ptp_one_step_sync(skb)) ctrl |= MACB_BIT(TX_NOCRC); @@ -2352,7 +2353,7 @@ static unsigned int macb_tx_map(struct macb *bp, return 0; dma_error: - netdev_err(bp->dev, "TX DMA map failed\n"); + netdev_err(bp->netdev, "TX DMA map failed\n"); for (i = queue->tx_head; i != tx_head; i++) { tx_skb = macb_tx_skb(queue, i); @@ -2364,7 +2365,7 @@ static unsigned int macb_tx_map(struct macb *bp, } static netdev_features_t macb_features_check(struct sk_buff *skb, - struct net_device *dev, + struct net_device *netdev, netdev_features_t features) { unsigned int nr_frags, f; @@ -2416,7 +2417,7 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) +static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); @@ -2425,7 +2426,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) struct sk_buff *nskb; u32 fcs; - if (!(ndev->features & NETIF_F_HW_CSUM) || + if (!(netdev->features & NETIF_F_HW_CSUM) || !((*skb)->ip_summed != CHECKSUM_PARTIAL) || skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) return 0; @@ -2467,23 +2468,24 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) return 0; } -static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) +static netdev_tx_t macb_start_xmit(struct sk_buff *skb, + struct net_device *netdev) { - u16 queue_index = skb_get_queue_mapping(skb); - struct macb *bp = netdev_priv(dev); - struct macb_queue *queue = &bp->queues[queue_index]; + struct macb *bp = netdev_priv(netdev); + unsigned int q = skb_get_queue_mapping(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 long flags; bool is_lso; - netdev_tx_t ret = NETDEV_TX_OK; if (macb_clear_csum(skb)) { dev_kfree_skb_any(skb); return ret; } - if (macb_pad_and_fcs(&skb, dev)) { + if (macb_pad_and_fcs(&skb, netdev)) { dev_kfree_skb_any(skb); return ret; } @@ -2502,7 +2504,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) else hdrlen = skb_tcp_all_headers(skb); if (skb_headlen(skb) < hdrlen) { - netdev_err(bp->dev, "Error - LSO headers fragmented!!!\n"); + netdev_err(bp->netdev, "Error - LSO headers fragmented!!!\n"); /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } @@ -2510,9 +2512,9 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) hdrlen = umin(skb_headlen(skb), bp->max_tx_length); #if defined(DEBUG) && defined(VERBOSE_DEBUG) - netdev_vdbg(bp->dev, - "start_xmit: queue %hu len %u head %p data %p tail %p end %p\n", - queue_index, skb->len, skb->head, skb->data, + netdev_vdbg(bp->netdev, + "start_xmit: queue %u len %u head %p data %p tail %p end %p\n", + q, skb->len, skb->head, skb->data, skb_tail_pointer(skb), skb_end_pointer(skb)); print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, 16, true); @@ -2538,8 +2540,8 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) /* This is a hard error, log it. */ if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < desc_cnt) { - netif_stop_subqueue(dev, queue_index); - netdev_dbg(bp->dev, "tx_head = %u, tx_tail = %u\n", + netif_stop_subqueue(netdev, q); + netdev_dbg(netdev, "tx_head = %u, tx_tail = %u\n", queue->tx_head, queue->tx_tail); ret = NETDEV_TX_BUSY; goto unlock; @@ -2554,7 +2556,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Make newly initialized descriptor visible to hardware */ wmb(); skb_tx_timestamp(skb); - netdev_tx_sent_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_sent_queue(netdev_get_tx_queue(bp->netdev, q), skb->len); spin_lock(&bp->lock); @@ -2563,7 +2565,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) spin_unlock(&bp->lock); if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < 1) - netif_stop_subqueue(dev, queue_index); + netif_stop_subqueue(netdev, q); unlock: spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); @@ -2579,7 +2581,7 @@ static void macb_init_rx_buffer_size(struct macb *bp, size_t size) bp->rx_buffer_size = MIN(size, RX_BUFFER_MAX); if (bp->rx_buffer_size % RX_BUFFER_MULTIPLE) { - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "RX buffer must be multiple of %d bytes, expanding\n", RX_BUFFER_MULTIPLE); bp->rx_buffer_size = @@ -2587,8 +2589,8 @@ static void macb_init_rx_buffer_size(struct macb *bp, size_t size) } } - netdev_dbg(bp->dev, "mtu [%u] rx_buffer_size [%zu]\n", - bp->dev->mtu, bp->rx_buffer_size); + netdev_dbg(bp->netdev, "mtu [%u] rx_buffer_size [%zu]\n", + bp->netdev->mtu, bp->rx_buffer_size); } static void gem_free_rx_buffers(struct macb *bp) @@ -2646,19 +2648,13 @@ static unsigned int macb_rx_ring_size_per_queue(struct macb *bp) return macb_dma_desc_get_size(bp) * bp->rx_ring_size + bp->rx_bd_rd_prefetch; } -static void macb_free_consistent(struct macb *bp) +static void macb_free(struct macb *bp) { struct device *dev = &bp->pdev->dev; struct macb_queue *queue; unsigned int q; size_t size; - if (bp->rx_ring_tieoff) { - dma_free_coherent(dev, macb_dma_desc_get_size(bp), - bp->rx_ring_tieoff, bp->rx_ring_tieoff_dma); - bp->rx_ring_tieoff = NULL; - } - bp->macbgem_ops.mog_free_rx_buffers(bp); size = bp->num_queues * macb_tx_ring_size_per_queue(bp); @@ -2679,7 +2675,7 @@ static void macb_free_consistent(struct macb *bp) } queue->stats.tx_dropped += dropped; - bp->dev->stats.tx_dropped += dropped; + bp->netdev->stats.tx_dropped += dropped; kfree(queue->tx_skb); queue->tx_skb = NULL; @@ -2704,7 +2700,7 @@ static int gem_alloc_rx_buffers(struct macb *bp) if (!queue->rx_skbuff) return -ENOMEM; else - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "Allocated %d RX struct sk_buff entries at %p\n", bp->rx_ring_size, queue->rx_skbuff); } @@ -2722,13 +2718,13 @@ static int macb_alloc_rx_buffers(struct macb *bp) if (!queue->rx_buffers) return -ENOMEM; - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "Allocated RX buffers of %d bytes at %08lx (mapped %p)\n", size, (unsigned long)queue->rx_buffers_dma, queue->rx_buffers); return 0; } -static int macb_alloc_consistent(struct macb *bp) +static int macb_alloc(struct macb *bp) { struct device *dev = &bp->pdev->dev; dma_addr_t tx_dma, rx_dma; @@ -2748,14 +2744,14 @@ static int macb_alloc_consistent(struct macb *bp) tx = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL); if (!tx || upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) goto out_err; - netdev_dbg(bp->dev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", + netdev_dbg(bp->netdev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)tx_dma, tx); size = bp->num_queues * macb_rx_ring_size_per_queue(bp); rx = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL); if (!rx || upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) goto out_err; - netdev_dbg(bp->dev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", + netdev_dbg(bp->netdev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)rx_dma, rx); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { @@ -2773,36 +2769,13 @@ static int macb_alloc_consistent(struct macb *bp) if (bp->macbgem_ops.mog_alloc_rx_buffers(bp)) goto out_err; - /* Required for tie off descriptor for PM cases */ - if (!(bp->caps & MACB_CAPS_QUEUE_DISABLE)) { - bp->rx_ring_tieoff = dma_alloc_coherent(&bp->pdev->dev, - macb_dma_desc_get_size(bp), - &bp->rx_ring_tieoff_dma, - GFP_KERNEL); - if (!bp->rx_ring_tieoff) - goto out_err; - } - return 0; out_err: - macb_free_consistent(bp); + macb_free(bp); return -ENOMEM; } -static void macb_init_tieoff(struct macb *bp) -{ - struct macb_dma_desc *desc = bp->rx_ring_tieoff; - - if (bp->caps & MACB_CAPS_QUEUE_DISABLE) - return; - /* Setup a wrapping descriptor with no free slots - * (WRAP and USED) to tie off/disable unused RX queues. - */ - macb_set_addr(bp, desc, MACB_BIT(RX_WRAP) | MACB_BIT(RX_USED)); - desc->ctrl = 0; -} - static void gem_init_rx_ring(struct macb_queue *queue) { queue->rx_tail = 0; @@ -2830,8 +2803,6 @@ static void gem_init_rings(struct macb *bp) gem_init_rx_ring(queue); } - - macb_init_tieoff(bp); } static void macb_init_rings(struct macb *bp) @@ -2849,8 +2820,6 @@ static void macb_init_rings(struct macb *bp) bp->queues[0].tx_head = 0; bp->queues[0].tx_tail = 0; desc->ctrl |= MACB_BIT(TX_WRAP); - - macb_init_tieoff(bp); } static void macb_reset_hw(struct macb *bp) @@ -2983,7 +2952,7 @@ static void macb_configure_dma(struct macb *bp) else dmacfg |= GEM_BIT(ENDIA_DESC); /* CPU in big endian */ - if (bp->dev->features & NETIF_F_HW_CSUM) + if (bp->netdev->features & NETIF_F_HW_CSUM) dmacfg |= GEM_BIT(TXCOEN); else dmacfg &= ~GEM_BIT(TXCOEN); @@ -2993,7 +2962,7 @@ static void macb_configure_dma(struct macb *bp) dmacfg |= GEM_BIT(ADDR64); if (macb_dma_ptp(bp)) dmacfg |= GEM_BIT(RXEXT) | GEM_BIT(TXEXT); - netdev_dbg(bp->dev, "Cadence configure DMA with 0x%08x\n", + netdev_dbg(bp->netdev, "Cadence configure DMA with 0x%08x\n", dmacfg); gem_writel(bp, DMACFG, dmacfg); } @@ -3017,11 +2986,11 @@ static void macb_init_hw(struct macb *bp) config |= MACB_BIT(JFRAME); /* Enable jumbo frames */ else config |= MACB_BIT(BIG); /* Receive oversized frames */ - if (bp->dev->flags & IFF_PROMISC) + if (bp->netdev->flags & IFF_PROMISC) config |= MACB_BIT(CAF); /* Copy All Frames */ - else if (macb_is_gem(bp) && bp->dev->features & NETIF_F_RXCSUM) + else if (macb_is_gem(bp) && bp->netdev->features & NETIF_F_RXCSUM) config |= GEM_BIT(RXCOEN); - if (!(bp->dev->flags & IFF_BROADCAST)) + if (!(bp->netdev->flags & IFF_BROADCAST)) config |= MACB_BIT(NBC); /* No BroadCast */ config |= macb_dbw(bp); macb_writel(bp, NCFGR, config); @@ -3095,17 +3064,17 @@ static int hash_get_index(__u8 *addr) } /* Add multicast addresses to the internal multicast-hash table. */ -static void macb_sethashtable(struct net_device *dev) +static void macb_sethashtable(struct net_device *netdev) { struct netdev_hw_addr *ha; unsigned long mc_filter[2]; unsigned int bitnr; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); mc_filter[0] = 0; mc_filter[1] = 0; - netdev_for_each_mc_addr(ha, dev) { + netdev_for_each_mc_addr(ha, netdev) { bitnr = hash_get_index(ha->addr); mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); } @@ -3115,14 +3084,14 @@ static void macb_sethashtable(struct net_device *dev) } /* Enable/Disable promiscuous and multicast modes. */ -static void macb_set_rx_mode(struct net_device *dev) +static void macb_set_rx_mode(struct net_device *netdev) { unsigned long cfg; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); cfg = macb_readl(bp, NCFGR); - if (dev->flags & IFF_PROMISC) { + if (netdev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ cfg |= MACB_BIT(CAF); @@ -3134,20 +3103,20 @@ static void macb_set_rx_mode(struct net_device *dev) cfg &= ~MACB_BIT(CAF); /* Enable RX checksum offload only if requested */ - if (macb_is_gem(bp) && dev->features & NETIF_F_RXCSUM) + if (macb_is_gem(bp) && netdev->features & NETIF_F_RXCSUM) cfg |= GEM_BIT(RXCOEN); } - if (dev->flags & IFF_ALLMULTI) { + if (netdev->flags & IFF_ALLMULTI) { /* Enable all multicast mode */ macb_or_gem_writel(bp, HRB, -1); macb_or_gem_writel(bp, HRT, -1); cfg |= MACB_BIT(NCFGR_MTI); - } else if (!netdev_mc_empty(dev)) { + } else if (!netdev_mc_empty(netdev)) { /* Enable specific multicasts */ - macb_sethashtable(dev); + macb_sethashtable(netdev); cfg |= MACB_BIT(NCFGR_MTI); - } else if (dev->flags & (~IFF_ALLMULTI)) { + } else if (netdev->flags & (~IFF_ALLMULTI)) { /* Disable all multicast mode */ macb_or_gem_writel(bp, HRB, 0); macb_or_gem_writel(bp, HRT, 0); @@ -3157,15 +3126,15 @@ static void macb_set_rx_mode(struct net_device *dev) macb_writel(bp, NCFGR, cfg); } -static int macb_open(struct net_device *dev) +static int macb_open(struct net_device *netdev) { - size_t bufsz = dev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; - struct macb *bp = netdev_priv(dev); + size_t bufsz = netdev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int q; int err; - netdev_dbg(bp->dev, "open\n"); + netdev_dbg(bp->netdev, "open\n"); err = pm_runtime_resume_and_get(&bp->pdev->dev); if (err < 0) @@ -3174,9 +3143,9 @@ static int macb_open(struct net_device *dev) /* RX buffers initialization */ macb_init_rx_buffer_size(bp, bufsz); - err = macb_alloc_consistent(bp); + err = macb_alloc(bp); if (err) { - netdev_err(dev, "Unable to allocate DMA memory (error %d)\n", + netdev_err(netdev, "Unable to allocate DMA memory (error %d)\n", err); goto pm_exit; } @@ -3203,10 +3172,10 @@ static int macb_open(struct net_device *dev) if (err) goto phy_off; - netif_tx_start_all_queues(dev); + netif_tx_start_all_queues(netdev); if (bp->ptp_info) - bp->ptp_info->ptp_init(dev); + bp->ptp_info->ptp_init(netdev); return 0; @@ -3219,25 +3188,25 @@ static int macb_open(struct net_device *dev) napi_disable(&queue->napi_rx); napi_disable(&queue->napi_tx); } - macb_free_consistent(bp); + macb_free(bp); pm_exit: pm_runtime_put_sync(&bp->pdev->dev); return err; } -static int macb_close(struct net_device *dev) +static int macb_close(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; - netif_tx_stop_all_queues(dev); + netif_tx_stop_all_queues(netdev); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { napi_disable(&queue->napi_rx); napi_disable(&queue->napi_tx); - netdev_tx_reset_queue(netdev_get_tx_queue(dev, q)); + netdev_tx_reset_queue(netdev_get_tx_queue(netdev, q)); } cancel_delayed_work_sync(&bp->tx_lpi_work); @@ -3249,38 +3218,38 @@ static int macb_close(struct net_device *dev) spin_lock_irqsave(&bp->lock, flags); macb_reset_hw(bp); - netif_carrier_off(dev); + netif_carrier_off(netdev); spin_unlock_irqrestore(&bp->lock, flags); - macb_free_consistent(bp); + macb_free(bp); if (bp->ptp_info) - bp->ptp_info->ptp_remove(dev); + bp->ptp_info->ptp_remove(netdev); pm_runtime_put(&bp->pdev->dev); return 0; } -static int macb_change_mtu(struct net_device *dev, int new_mtu) +static int macb_change_mtu(struct net_device *netdev, int new_mtu) { - if (netif_running(dev)) + if (netif_running(netdev)) return -EBUSY; - WRITE_ONCE(dev->mtu, new_mtu); + WRITE_ONCE(netdev->mtu, new_mtu); return 0; } -static int macb_set_mac_addr(struct net_device *dev, void *addr) +static int macb_set_mac_addr(struct net_device *netdev, void *addr) { int err; - err = eth_mac_addr(dev, addr); + err = eth_mac_addr(netdev, addr); if (err < 0) return err; - macb_set_hwaddr(netdev_priv(dev)); + macb_set_hwaddr(netdev_priv(netdev)); return 0; } @@ -3318,7 +3287,7 @@ static void gem_get_stats(struct macb *bp, struct rtnl_link_stats64 *nstat) struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); - if (netif_running(bp->dev)) + if (netif_running(bp->netdev)) gem_update_stats(bp); nstat->rx_errors = (hwstat->rx_frame_check_sequence_errors + @@ -3351,10 +3320,10 @@ static void gem_get_stats(struct macb *bp, struct rtnl_link_stats64 *nstat) spin_unlock_irq(&bp->stats_lock); } -static void gem_get_ethtool_stats(struct net_device *dev, +static void gem_get_ethtool_stats(struct net_device *netdev, struct ethtool_stats *stats, u64 *data) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); spin_lock_irq(&bp->stats_lock); gem_update_stats(bp); @@ -3363,9 +3332,9 @@ static void gem_get_ethtool_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static int gem_get_sset_count(struct net_device *dev, int sset) +static int gem_get_sset_count(struct net_device *netdev, int sset) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); switch (sset) { case ETH_SS_STATS: @@ -3375,9 +3344,9 @@ static int gem_get_sset_count(struct net_device *dev, int sset) } } -static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) +static void gem_get_ethtool_strings(struct net_device *netdev, u32 sset, u8 *p) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int i; unsigned int q; @@ -3396,13 +3365,13 @@ static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) } } -static void macb_get_stats(struct net_device *dev, +static void macb_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *nstat) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; - netdev_stats_to_stats64(nstat, &bp->dev->stats); + netdev_stats_to_stats64(nstat, &bp->netdev->stats); if (macb_is_gem(bp)) { gem_get_stats(bp, nstat); return; @@ -3446,10 +3415,10 @@ static void macb_get_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_pause_stats(struct net_device *dev, +static void macb_get_pause_stats(struct net_device *netdev, struct ethtool_pause_stats *pause_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3459,10 +3428,10 @@ static void macb_get_pause_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_pause_stats(struct net_device *dev, +static void gem_get_pause_stats(struct net_device *netdev, struct ethtool_pause_stats *pause_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3472,10 +3441,10 @@ static void gem_get_pause_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_eth_mac_stats(struct net_device *dev, +static void macb_get_eth_mac_stats(struct net_device *netdev, struct ethtool_eth_mac_stats *mac_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3497,10 +3466,10 @@ static void macb_get_eth_mac_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_eth_mac_stats(struct net_device *dev, +static void gem_get_eth_mac_stats(struct net_device *netdev, struct ethtool_eth_mac_stats *mac_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3530,10 +3499,10 @@ static void gem_get_eth_mac_stats(struct net_device *dev, } /* TODO: Report SQE test errors when added to phy_stats */ -static void macb_get_eth_phy_stats(struct net_device *dev, +static void macb_get_eth_phy_stats(struct net_device *netdev, struct ethtool_eth_phy_stats *phy_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3542,10 +3511,10 @@ static void macb_get_eth_phy_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_eth_phy_stats(struct net_device *dev, +static void gem_get_eth_phy_stats(struct net_device *netdev, struct ethtool_eth_phy_stats *phy_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3554,11 +3523,11 @@ static void gem_get_eth_phy_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_rmon_stats(struct net_device *dev, +static void macb_get_rmon_stats(struct net_device *netdev, struct ethtool_rmon_stats *rmon_stats, const struct ethtool_rmon_hist_range **ranges) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3580,11 +3549,11 @@ static const struct ethtool_rmon_hist_range gem_rmon_ranges[] = { { }, }; -static void gem_get_rmon_stats(struct net_device *dev, +static void gem_get_rmon_stats(struct net_device *netdev, struct ethtool_rmon_stats *rmon_stats, const struct ethtool_rmon_hist_range **ranges) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3615,10 +3584,10 @@ static int macb_get_regs_len(struct net_device *netdev) return MACB_GREGS_NBR * sizeof(u32); } -static void macb_get_regs(struct net_device *dev, struct ethtool_regs *regs, +static void macb_get_regs(struct net_device *netdev, struct ethtool_regs *regs, void *p) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); unsigned int tail, head; u32 *regs_buff = p; @@ -3718,6 +3687,9 @@ static int macb_set_ringparam(struct net_device *netdev, u32 new_rx_size, new_tx_size; unsigned int reset = 0; + if (bp->caps & MACB_CAPS_MACB_IS_EMAC) + return -EOPNOTSUPP; + if ((ring->rx_mini_pending) || (ring->rx_jumbo_pending)) return -EINVAL; @@ -3735,16 +3707,16 @@ static int macb_set_ringparam(struct net_device *netdev, return 0; } - if (netif_running(bp->dev)) { + if (netif_running(bp->netdev)) { reset = 1; - macb_close(bp->dev); + macb_close(bp->netdev); } bp->rx_ring_size = new_rx_size; bp->tx_ring_size = new_tx_size; if (reset) - macb_open(bp->dev); + macb_open(bp->netdev); return 0; } @@ -3771,13 +3743,13 @@ static s32 gem_get_ptp_max_adj(void) return 64000000; } -static int gem_get_ts_info(struct net_device *dev, +static int gem_get_ts_info(struct net_device *netdev, struct kernel_ethtool_ts_info *info) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); if (!macb_dma_ptp(bp)) { - ethtool_op_get_ts_info(dev, info); + ethtool_op_get_ts_info(netdev, info); return 0; } @@ -3824,7 +3796,7 @@ static int macb_get_ts_info(struct net_device *netdev, static void gem_enable_flow_filters(struct macb *bp, bool enable) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; struct ethtool_rx_fs_item *item; u32 t2_scr; int num_t2_scr; @@ -4154,16 +4126,16 @@ static const struct ethtool_ops macb_ethtool_ops = { .set_ringparam = macb_set_ringparam, }; -static int macb_get_eee(struct net_device *dev, struct ethtool_keee *eee) +static int macb_get_eee(struct net_device *netdev, struct ethtool_keee *eee) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); return phylink_ethtool_get_eee(bp->phylink, eee); } -static int macb_set_eee(struct net_device *dev, struct ethtool_keee *eee) +static int macb_set_eee(struct net_device *netdev, struct ethtool_keee *eee) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); return phylink_ethtool_set_eee(bp->phylink, eee); } @@ -4194,43 +4166,43 @@ static const struct ethtool_ops gem_ethtool_ops = { .set_eee = macb_set_eee, }; -static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +static int macb_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; return phylink_mii_ioctl(bp->phylink, rq, cmd); } -static int macb_hwtstamp_get(struct net_device *dev, +static int macb_hwtstamp_get(struct net_device *netdev, struct kernel_hwtstamp_config *cfg) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; if (!bp->ptp_info) return -EOPNOTSUPP; - return bp->ptp_info->get_hwtst(dev, cfg); + return bp->ptp_info->get_hwtst(netdev, cfg); } -static int macb_hwtstamp_set(struct net_device *dev, +static int macb_hwtstamp_set(struct net_device *netdev, struct kernel_hwtstamp_config *cfg, struct netlink_ext_ack *extack) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; if (!bp->ptp_info) return -EOPNOTSUPP; - return bp->ptp_info->set_hwtst(dev, cfg, extack); + return bp->ptp_info->set_hwtst(netdev, cfg, extack); } static inline void macb_set_txcsum_feature(struct macb *bp, @@ -4253,7 +4225,7 @@ static inline void macb_set_txcsum_feature(struct macb *bp, static inline void macb_set_rxcsum_feature(struct macb *bp, netdev_features_t features) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; u32 val; if (!macb_is_gem(bp)) @@ -4300,7 +4272,7 @@ static int macb_set_features(struct net_device *netdev, static void macb_restore_features(struct macb *bp) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; netdev_features_t features = netdev->features; struct ethtool_rx_fs_item *item; @@ -4317,14 +4289,14 @@ static void macb_restore_features(struct macb *bp) macb_set_rxflow_feature(bp, features); } -static int macb_taprio_setup_replace(struct net_device *ndev, +static int macb_taprio_setup_replace(struct net_device *netdev, struct tc_taprio_qopt_offload *conf) { u64 total_on_time = 0, start_time_sec = 0, start_time = conf->base_time; u32 configured_queues = 0, speed = 0, start_time_nsec; struct macb_queue_enst_config *enst_queue; struct tc_taprio_sched_entry *entry; - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); struct ethtool_link_ksettings kset; struct macb_queue *queue; u32 queue_mask; @@ -4333,13 +4305,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, int err; if (conf->num_entries > bp->num_queues) { - netdev_err(ndev, "Too many TAPRIO entries: %zu > %d queues\n", + netdev_err(netdev, "Too many TAPRIO entries: %zu > %d queues\n", conf->num_entries, bp->num_queues); return -EINVAL; } if (conf->base_time < 0) { - netdev_err(ndev, "Invalid base_time: must be 0 or positive, got %lld\n", + netdev_err(netdev, "Invalid base_time: must be 0 or positive, got %lld\n", conf->base_time); return -ERANGE; } @@ -4347,13 +4319,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Get the current link speed */ err = phylink_ethtool_ksettings_get(bp->phylink, &kset); if (unlikely(err)) { - netdev_err(ndev, "Failed to get link settings: %d\n", err); + netdev_err(netdev, "Failed to get link settings: %d\n", err); return err; } speed = kset.base.speed; if (unlikely(speed <= 0)) { - netdev_err(ndev, "Invalid speed: %d\n", speed); + netdev_err(netdev, "Invalid speed: %d\n", speed); return -EINVAL; } @@ -4366,7 +4338,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, entry = &conf->entries[i]; if (entry->command != TC_TAPRIO_CMD_SET_GATES) { - netdev_err(ndev, "Entry %zu: unsupported command %d\n", + netdev_err(netdev, "Entry %zu: unsupported command %d\n", i, entry->command); err = -EOPNOTSUPP; goto cleanup; @@ -4374,7 +4346,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Validate gate_mask: must be nonzero, single queue, and within range */ if (!is_power_of_2(entry->gate_mask)) { - netdev_err(ndev, "Entry %zu: gate_mask 0x%x is not a power of 2 (only one queue per entry allowed)\n", + netdev_err(netdev, "Entry %zu: gate_mask 0x%x is not a power of 2 (only one queue per entry allowed)\n", i, entry->gate_mask); err = -EINVAL; goto cleanup; @@ -4383,7 +4355,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* gate_mask must not select queues outside the valid queues */ queue_id = order_base_2(entry->gate_mask); if (queue_id >= bp->num_queues) { - netdev_err(ndev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n", + netdev_err(netdev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n", i, entry->gate_mask, bp->num_queues); err = -EINVAL; goto cleanup; @@ -4393,7 +4365,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, start_time_sec = start_time; start_time_nsec = do_div(start_time_sec, NSEC_PER_SEC); if (start_time_sec > GENMASK(GEM_START_TIME_SEC_SIZE - 1, 0)) { - netdev_err(ndev, "Entry %zu: Start time %llu s exceeds hardware limit\n", + netdev_err(netdev, "Entry %zu: Start time %llu s exceeds hardware limit\n", i, start_time_sec); err = -ERANGE; goto cleanup; @@ -4401,7 +4373,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check for on time limit */ if (entry->interval > enst_max_hw_interval(speed)) { - netdev_err(ndev, "Entry %zu: interval %u ns exceeds hardware limit %llu ns\n", + netdev_err(netdev, "Entry %zu: interval %u ns exceeds hardware limit %llu ns\n", i, entry->interval, enst_max_hw_interval(speed)); err = -ERANGE; goto cleanup; @@ -4409,7 +4381,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check for off time limit*/ if ((conf->cycle_time - entry->interval) > enst_max_hw_interval(speed)) { - netdev_err(ndev, "Entry %zu: off_time %llu ns exceeds hardware limit %llu ns\n", + netdev_err(netdev, "Entry %zu: off_time %llu ns exceeds hardware limit %llu ns\n", i, conf->cycle_time - entry->interval, enst_max_hw_interval(speed)); err = -ERANGE; @@ -4432,13 +4404,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check total interval doesn't exceed cycle time */ if (total_on_time > conf->cycle_time) { - netdev_err(ndev, "Total ON %llu ns exceeds cycle time %llu ns\n", + netdev_err(netdev, "Total ON %llu ns exceeds cycle time %llu ns\n", total_on_time, conf->cycle_time); err = -EINVAL; goto cleanup; } - netdev_dbg(ndev, "TAPRIO setup: %zu entries, base_time=%lld ns, cycle_time=%llu ns\n", + netdev_dbg(netdev, "TAPRIO setup: %zu entries, base_time=%lld ns, cycle_time=%llu ns\n", conf->num_entries, conf->base_time, conf->cycle_time); /* All validations passed - proceed with hardware configuration */ @@ -4463,7 +4435,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, gem_writel(bp, ENST_CONTROL, configured_queues); } - netdev_info(ndev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n", + netdev_info(netdev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n", conf->num_entries, hweight32(configured_queues)); cleanup: @@ -4471,14 +4443,14 @@ static int macb_taprio_setup_replace(struct net_device *ndev, return err; } -static void macb_taprio_destroy(struct net_device *ndev) +static void macb_taprio_destroy(struct net_device *netdev) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; u32 queue_mask; unsigned int q; - netdev_reset_tc(ndev); + netdev_reset_tc(netdev); queue_mask = BIT_U32(bp->num_queues) - 1; scoped_guard(spinlock_irqsave, &bp->lock) { @@ -4493,30 +4465,30 @@ static void macb_taprio_destroy(struct net_device *ndev) queue_writel(queue, ENST_OFF_TIME, 0); } } - netdev_info(ndev, "TAPRIO destroy: All gates disabled\n"); + netdev_info(netdev, "TAPRIO destroy: All gates disabled\n"); } -static int macb_setup_taprio(struct net_device *ndev, +static int macb_setup_taprio(struct net_device *netdev, struct tc_taprio_qopt_offload *taprio) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); int err = 0; - if (unlikely(!(ndev->hw_features & NETIF_F_HW_TC))) + if (unlikely(!(netdev->hw_features & NETIF_F_HW_TC))) return -EOPNOTSUPP; /* Check if Device is in runtime suspend */ if (unlikely(pm_runtime_suspended(&bp->pdev->dev))) { - netdev_err(ndev, "Device is in runtime suspend\n"); + netdev_err(netdev, "Device is in runtime suspend\n"); return -EOPNOTSUPP; } switch (taprio->cmd) { case TAPRIO_CMD_REPLACE: - err = macb_taprio_setup_replace(ndev, taprio); + err = macb_taprio_setup_replace(netdev, taprio); break; case TAPRIO_CMD_DESTROY: - macb_taprio_destroy(ndev); + macb_taprio_destroy(netdev); break; default: err = -EOPNOTSUPP; @@ -4525,23 +4497,23 @@ static int macb_setup_taprio(struct net_device *ndev, return err; } -static int macb_setup_tc(struct net_device *dev, enum tc_setup_type type, +static int macb_setup_tc(struct net_device *netdev, enum tc_setup_type type, void *type_data) { - if (!dev || !type_data) + if (!netdev || !type_data) return -EINVAL; switch (type) { case TC_SETUP_QDISC_TAPRIO: - return macb_setup_taprio(dev, type_data); + return macb_setup_taprio(netdev, type_data); default: return -EOPNOTSUPP; } } -static void macb_tx_timeout(struct net_device *dev, unsigned int q) +static void macb_tx_timeout(struct net_device *netdev, unsigned int q) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); macb_tx_restart(&bp->queues[q]); } @@ -4749,9 +4721,9 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, static int macb_init_dflt(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); + struct net_device *netdev = platform_get_drvdata(pdev); unsigned int hw_q, q; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; int err; u32 val, reg; @@ -4767,8 +4739,8 @@ static int macb_init_dflt(struct platform_device *pdev) queue = &bp->queues[q]; queue->bp = bp; spin_lock_init(&queue->tx_ptr_lock); - netif_napi_add(dev, &queue->napi_rx, macb_rx_poll); - netif_napi_add_tx(dev, &queue->napi_tx, macb_tx_poll); + netif_napi_add(netdev, &queue->napi_rx, macb_rx_poll); + netif_napi_add_tx(netdev, &queue->napi_tx, macb_tx_poll); if (hw_q) { queue->ISR = GEM_ISR(hw_q - 1); queue->IER = GEM_IER(hw_q - 1); @@ -4798,7 +4770,7 @@ static int macb_init_dflt(struct platform_device *pdev) */ queue->irq = platform_get_irq(pdev, q); err = devm_request_irq(&pdev->dev, queue->irq, macb_interrupt, - IRQF_SHARED, dev->name, queue); + IRQF_SHARED, netdev->name, queue); if (err) { dev_err(&pdev->dev, "Unable to request IRQ %d (error %d)\n", @@ -4810,7 +4782,7 @@ static int macb_init_dflt(struct platform_device *pdev) q++; } - dev->netdev_ops = &macb_netdev_ops; + netdev->netdev_ops = &macb_netdev_ops; /* setup appropriated routines according to adapter type */ if (macb_is_gem(bp)) { @@ -4818,39 +4790,39 @@ static int macb_init_dflt(struct platform_device *pdev) bp->macbgem_ops.mog_free_rx_buffers = gem_free_rx_buffers; bp->macbgem_ops.mog_init_rings = gem_init_rings; bp->macbgem_ops.mog_rx = gem_rx; - dev->ethtool_ops = &gem_ethtool_ops; + netdev->ethtool_ops = &gem_ethtool_ops; } else { bp->macbgem_ops.mog_alloc_rx_buffers = macb_alloc_rx_buffers; bp->macbgem_ops.mog_free_rx_buffers = macb_free_rx_buffers; bp->macbgem_ops.mog_init_rings = macb_init_rings; bp->macbgem_ops.mog_rx = macb_rx; - dev->ethtool_ops = &macb_ethtool_ops; + netdev->ethtool_ops = &macb_ethtool_ops; } - netdev_sw_irq_coalesce_default_on(dev); + netdev_sw_irq_coalesce_default_on(netdev); - dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE; /* Set features */ - dev->hw_features = NETIF_F_SG; + netdev->hw_features = NETIF_F_SG; /* Check LSO capability; runtime detection can be overridden by a cap * flag if the hardware is known to be buggy */ if (!(bp->caps & MACB_CAPS_NO_LSO) && GEM_BFEXT(PBUF_LSO, gem_readl(bp, DCFG6))) - dev->hw_features |= MACB_NETIF_LSO; + netdev->hw_features |= MACB_NETIF_LSO; /* Checksum offload is only available on gem with packet buffer */ if (macb_is_gem(bp) && !(bp->caps & MACB_CAPS_FIFO_MODE)) - dev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + netdev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; if (bp->caps & MACB_CAPS_SG_DISABLED) - dev->hw_features &= ~NETIF_F_SG; + netdev->hw_features &= ~NETIF_F_SG; /* Enable HW_TC if hardware supports QBV */ if (bp->caps & MACB_CAPS_QBV) - dev->hw_features |= NETIF_F_HW_TC; + netdev->hw_features |= NETIF_F_HW_TC; - dev->features = dev->hw_features; + netdev->features = netdev->hw_features; /* Check RX Flow Filters support. * Max Rx flows set by availability of screeners & compare regs: @@ -4868,7 +4840,7 @@ static int macb_init_dflt(struct platform_device *pdev) reg = GEM_BFINS(ETHTCMP, (uint16_t)ETH_P_IP, reg); gem_writel_n(bp, ETHT, SCRT2_ETHT, reg); /* Filtering is supported in hw but don't enable it in kernel now */ - dev->hw_features |= NETIF_F_NTUPLE; + netdev->hw_features |= NETIF_F_NTUPLE; /* init Rx flow definitions */ bp->rx_fs_list.count = 0; spin_lock_init(&bp->rx_fs_lock); @@ -4962,71 +4934,72 @@ static const struct macb_usrio_config at91_default_usrio = { static struct sifive_fu540_macb_mgmt *mgmt; -static int at91ether_alloc_coherent(struct macb *lp) +static int at91ether_alloc_coherent(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; - q->rx_ring = dma_alloc_coherent(&lp->pdev->dev, - (AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp)), - &q->rx_ring_dma, GFP_KERNEL); - if (!q->rx_ring) + queue->rx_ring = dma_alloc_coherent(&bp->pdev->dev, + (AT91ETHER_MAX_RX_DESCR * + macb_dma_desc_get_size(bp)), + &queue->rx_ring_dma, GFP_KERNEL); + if (!queue->rx_ring) return -ENOMEM; - q->rx_buffers = dma_alloc_coherent(&lp->pdev->dev, - AT91ETHER_MAX_RX_DESCR * - AT91ETHER_MAX_RBUFF_SZ, - &q->rx_buffers_dma, GFP_KERNEL); - if (!q->rx_buffers) { - dma_free_coherent(&lp->pdev->dev, + queue->rx_buffers = dma_alloc_coherent(&bp->pdev->dev, + AT91ETHER_MAX_RX_DESCR * + AT91ETHER_MAX_RBUFF_SZ, + &queue->rx_buffers_dma, + GFP_KERNEL); + if (!queue->rx_buffers) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp), - q->rx_ring, q->rx_ring_dma); - q->rx_ring = NULL; + macb_dma_desc_get_size(bp), + queue->rx_ring, queue->rx_ring_dma); + queue->rx_ring = NULL; return -ENOMEM; } return 0; } -static void at91ether_free_coherent(struct macb *lp) +static void at91ether_free_coherent(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; - if (q->rx_ring) { - dma_free_coherent(&lp->pdev->dev, + if (queue->rx_ring) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp), - q->rx_ring, q->rx_ring_dma); - q->rx_ring = NULL; + macb_dma_desc_get_size(bp), + queue->rx_ring, queue->rx_ring_dma); + queue->rx_ring = NULL; } - if (q->rx_buffers) { - dma_free_coherent(&lp->pdev->dev, + if (queue->rx_buffers) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * AT91ETHER_MAX_RBUFF_SZ, - q->rx_buffers, q->rx_buffers_dma); - q->rx_buffers = NULL; + queue->rx_buffers, queue->rx_buffers_dma); + queue->rx_buffers = NULL; } } /* Initialize and start the Receiver and Transmit subsystems */ -static int at91ether_start(struct macb *lp) +static int at91ether_start(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; struct macb_dma_desc *desc; dma_addr_t addr; u32 ctl; int i, ret; - ret = at91ether_alloc_coherent(lp); + ret = at91ether_alloc_coherent(bp); if (ret) return ret; - addr = q->rx_buffers_dma; + addr = queue->rx_buffers_dma; for (i = 0; i < AT91ETHER_MAX_RX_DESCR; i++) { - desc = macb_rx_desc(q, i); - macb_set_addr(lp, desc, addr); + desc = macb_rx_desc(queue, i); + macb_set_addr(bp, desc, addr); desc->ctrl = 0; addr += AT91ETHER_MAX_RBUFF_SZ; } @@ -5035,17 +5008,17 @@ static int at91ether_start(struct macb *lp) desc->addr |= MACB_BIT(RX_WRAP); /* Reset buffer index */ - q->rx_tail = 0; + queue->rx_tail = 0; /* Program address of descriptor list in Rx Buffer Queue register */ - macb_writel(lp, RBQP, q->rx_ring_dma); + macb_writel(bp, RBQP, queue->rx_ring_dma); /* Enable Receive and Transmit */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl | MACB_BIT(RE) | MACB_BIT(TE)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl | MACB_BIT(RE) | MACB_BIT(TE)); /* Enable MAC interrupts */ - macb_writel(lp, IER, MACB_BIT(RCOMP) | + macb_writel(bp, IER, MACB_BIT(RCOMP) | MACB_BIT(RXUBR) | MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE) | @@ -5056,12 +5029,12 @@ static int at91ether_start(struct macb *lp) return 0; } -static void at91ether_stop(struct macb *lp) +static void at91ether_stop(struct macb *bp) { u32 ctl; /* Disable MAC interrupts */ - macb_writel(lp, IDR, MACB_BIT(RCOMP) | + macb_writel(bp, IDR, MACB_BIT(RCOMP) | MACB_BIT(RXUBR) | MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE) | @@ -5070,96 +5043,99 @@ static void at91ether_stop(struct macb *lp) MACB_BIT(HRESP)); /* Disable Receiver and Transmitter */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl & ~(MACB_BIT(TE) | MACB_BIT(RE))); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl & ~(MACB_BIT(TE) | MACB_BIT(RE))); /* Free resources. */ - at91ether_free_coherent(lp); + at91ether_free_coherent(bp); } /* Open the ethernet interface */ -static int at91ether_open(struct net_device *dev) +static int at91ether_open(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); u32 ctl; int ret; - ret = pm_runtime_resume_and_get(&lp->pdev->dev); + ret = pm_runtime_resume_and_get(&bp->pdev->dev); if (ret < 0) return ret; /* Clear internal statistics */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl | MACB_BIT(CLRSTAT)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl | MACB_BIT(CLRSTAT)); - macb_set_hwaddr(lp); + macb_set_hwaddr(bp); - ret = at91ether_start(lp); + ret = at91ether_start(bp); if (ret) goto pm_exit; - ret = macb_phylink_connect(lp); + ret = macb_phylink_connect(bp); if (ret) goto stop; - netif_start_queue(dev); + netif_start_queue(netdev); return 0; stop: - at91ether_stop(lp); + at91ether_stop(bp); pm_exit: - pm_runtime_put_sync(&lp->pdev->dev); + pm_runtime_put_sync(&bp->pdev->dev); return ret; } /* Close the interface */ -static int at91ether_close(struct net_device *dev) +static int at91ether_close(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - netif_stop_queue(dev); + netif_stop_queue(netdev); - phylink_stop(lp->phylink); - phylink_disconnect_phy(lp->phylink); + phylink_stop(bp->phylink); + phylink_disconnect_phy(bp->phylink); - at91ether_stop(lp); + at91ether_stop(bp); - pm_runtime_put(&lp->pdev->dev); + pm_runtime_put(&bp->pdev->dev); return 0; } /* Transmit packet */ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, - struct net_device *dev) + struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); + struct device *dev = &bp->pdev->dev; - if (macb_readl(lp, TSR) & MACB_BIT(RM9200_BNQ)) { + if (macb_readl(bp, TSR) & MACB_BIT(RM9200_BNQ)) { int desc = 0; - netif_stop_queue(dev); + netif_stop_queue(netdev); /* Store packet information (to free when Tx completed) */ - lp->rm9200_txq[desc].skb = skb; - lp->rm9200_txq[desc].size = skb->len; - lp->rm9200_txq[desc].mapping = dma_map_single(&lp->pdev->dev, skb->data, - skb->len, DMA_TO_DEVICE); - if (dma_mapping_error(&lp->pdev->dev, lp->rm9200_txq[desc].mapping)) { + bp->rm9200_txq[desc].skb = skb; + bp->rm9200_txq[desc].size = skb->len; + bp->rm9200_txq[desc].mapping = dma_map_single(dev, skb->data, + skb->len, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, bp->rm9200_txq[desc].mapping)) { dev_kfree_skb_any(skb); - dev->stats.tx_dropped++; - netdev_err(dev, "%s: DMA mapping error\n", __func__); + netdev->stats.tx_dropped++; + netdev_err(netdev, "%s: DMA mapping error\n", __func__); return NETDEV_TX_OK; } /* Set address of the data in the Transmit Address register */ - macb_writel(lp, TAR, lp->rm9200_txq[desc].mapping); + macb_writel(bp, TAR, bp->rm9200_txq[desc].mapping); /* Set length of the packet in the Transmit Control register */ - macb_writel(lp, TCR, skb->len); + macb_writel(bp, TCR, skb->len); } else { - netdev_err(dev, "%s called, but device is busy!\n", __func__); + netdev_err(netdev, "%s called, but device is busy!\n", + __func__); return NETDEV_TX_BUSY; } @@ -5169,104 +5145,107 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, /* Extract received frame from buffer descriptors and sent to upper layers. * (Called from interrupt context) */ -static void at91ether_rx(struct net_device *dev) +static void at91ether_rx(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); - struct macb_queue *q = &lp->queues[0]; + struct macb *bp = netdev_priv(netdev); + struct macb_queue *queue = &bp->queues[0]; struct macb_dma_desc *desc; unsigned char *p_recv; struct sk_buff *skb; unsigned int pktlen; - desc = macb_rx_desc(q, q->rx_tail); + desc = macb_rx_desc(queue, queue->rx_tail); while (desc->addr & MACB_BIT(RX_USED)) { - p_recv = q->rx_buffers + q->rx_tail * AT91ETHER_MAX_RBUFF_SZ; + p_recv = queue->rx_buffers + + queue->rx_tail * AT91ETHER_MAX_RBUFF_SZ; pktlen = MACB_BF(RX_FRMLEN, desc->ctrl); - skb = netdev_alloc_skb(dev, pktlen + 2); + skb = netdev_alloc_skb(netdev, pktlen + 2); if (skb) { skb_reserve(skb, 2); skb_put_data(skb, p_recv, pktlen); - skb->protocol = eth_type_trans(skb, dev); - dev->stats.rx_packets++; - dev->stats.rx_bytes += pktlen; + skb->protocol = eth_type_trans(skb, netdev); + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += pktlen; netif_rx(skb); } else { - dev->stats.rx_dropped++; + netdev->stats.rx_dropped++; } if (desc->ctrl & MACB_BIT(RX_MHASH_MATCH)) - dev->stats.multicast++; + netdev->stats.multicast++; /* reset ownership bit */ desc->addr &= ~MACB_BIT(RX_USED); /* wrap after last buffer */ - if (q->rx_tail == AT91ETHER_MAX_RX_DESCR - 1) - q->rx_tail = 0; + if (queue->rx_tail == AT91ETHER_MAX_RX_DESCR - 1) + queue->rx_tail = 0; else - q->rx_tail++; + queue->rx_tail++; - desc = macb_rx_desc(q, q->rx_tail); + desc = macb_rx_desc(queue, queue->rx_tail); } } /* MAC interrupt handler */ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) { - struct net_device *dev = dev_id; - struct macb *lp = netdev_priv(dev); + struct net_device *netdev = dev_id; + struct macb *bp = netdev_priv(netdev); u32 intstatus, ctl; unsigned int desc; /* MAC Interrupt Status register indicates what interrupts are pending. * It is automatically cleared once read. */ - intstatus = macb_readl(lp, ISR); + intstatus = macb_readl(bp, ISR); /* Receive complete */ if (intstatus & MACB_BIT(RCOMP)) - at91ether_rx(dev); + at91ether_rx(netdev); /* Transmit complete */ if (intstatus & MACB_BIT(TCOMP)) { /* The TCOM bit is set even if the transmission failed */ if (intstatus & (MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE))) - dev->stats.tx_errors++; + netdev->stats.tx_errors++; desc = 0; - if (lp->rm9200_txq[desc].skb) { - dev_consume_skb_irq(lp->rm9200_txq[desc].skb); - lp->rm9200_txq[desc].skb = NULL; - dma_unmap_single(&lp->pdev->dev, lp->rm9200_txq[desc].mapping, - lp->rm9200_txq[desc].size, DMA_TO_DEVICE); - dev->stats.tx_packets++; - dev->stats.tx_bytes += lp->rm9200_txq[desc].size; + if (bp->rm9200_txq[desc].skb) { + dev_consume_skb_irq(bp->rm9200_txq[desc].skb); + bp->rm9200_txq[desc].skb = NULL; + dma_unmap_single(&bp->pdev->dev, + bp->rm9200_txq[desc].mapping, + bp->rm9200_txq[desc].size, + DMA_TO_DEVICE); + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += bp->rm9200_txq[desc].size; } - netif_wake_queue(dev); + netif_wake_queue(netdev); } /* Work-around for EMAC Errata section 41.3.1 */ if (intstatus & MACB_BIT(RXUBR)) { - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl & ~MACB_BIT(RE)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl & ~MACB_BIT(RE)); wmb(); - macb_writel(lp, NCR, ctl | MACB_BIT(RE)); + macb_writel(bp, NCR, ctl | MACB_BIT(RE)); } if (intstatus & MACB_BIT(ISR_ROVR)) - netdev_err(dev, "ROVR error\n"); + netdev_err(netdev, "ROVR error\n"); return IRQ_HANDLED; } #ifdef CONFIG_NET_POLL_CONTROLLER -static void at91ether_poll_controller(struct net_device *dev) +static void at91ether_poll_controller(struct net_device *netdev) { unsigned long flags; local_irq_save(flags); - at91ether_interrupt(dev->irq, dev); + at91ether_interrupt(netdev->irq, netdev); local_irq_restore(flags); } #endif @@ -5313,17 +5292,17 @@ static int at91ether_clk_init(struct platform_device *pdev, struct clk **pclk, static int at91ether_init(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); - struct macb *bp = netdev_priv(dev); + struct net_device *netdev = platform_get_drvdata(pdev); + struct macb *bp = netdev_priv(netdev); int err; bp->queues[0].bp = bp; - dev->netdev_ops = &at91ether_netdev_ops; - dev->ethtool_ops = &macb_ethtool_ops; + netdev->netdev_ops = &at91ether_netdev_ops; + netdev->ethtool_ops = &macb_ethtool_ops; - err = devm_request_irq(&pdev->dev, dev->irq, at91ether_interrupt, - 0, dev->name, dev); + err = devm_request_irq(&pdev->dev, netdev->irq, at91ether_interrupt, + 0, netdev->name, netdev); if (err) return err; @@ -5452,8 +5431,8 @@ static int fu540_c000_init(struct platform_device *pdev) static int init_reset_optional(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); - struct macb *bp = netdev_priv(dev); + struct net_device *netdev = platform_get_drvdata(pdev); + struct macb *bp = netdev_priv(netdev); int ret; if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { @@ -5528,6 +5507,38 @@ static int eyeq5_init(struct platform_device *pdev) return ret; } +static int macb_alloc_tieoff(struct macb *bp) +{ + /* Tieoff is a workaround in case HW cannot disable queues, for PM. */ + if (bp->caps & MACB_CAPS_QUEUE_DISABLE) + return 0; + + bp->rx_ring_tieoff = dma_alloc_coherent(&bp->pdev->dev, + macb_dma_desc_get_size(bp), + &bp->rx_ring_tieoff_dma, + GFP_KERNEL); + if (!bp->rx_ring_tieoff) + return -ENOMEM; + + macb_set_addr(bp, bp->rx_ring_tieoff, + MACB_BIT(RX_WRAP) | MACB_BIT(RX_USED)); + + bp->rx_ring_tieoff->ctrl = 0; + + return 0; +} + +static void macb_free_tieoff(struct macb *bp) +{ + if (!bp->rx_ring_tieoff) + return; + + dma_free_coherent(&bp->pdev->dev, macb_dma_desc_get_size(bp), + bp->rx_ring_tieoff, + bp->rx_ring_tieoff_dma); + bp->rx_ring_tieoff = NULL; +} + static const struct macb_usrio_config mpfs_usrio = { .tsu_source = 0, }; @@ -5761,7 +5772,7 @@ static int macb_probe(struct platform_device *pdev) const struct macb_config *macb_config; struct clk *tsu_clk = NULL; phy_interface_t interface; - struct net_device *dev; + struct net_device *netdev; struct resource *regs; u32 wtrmrk_rst_val; void __iomem *mem; @@ -5796,19 +5807,19 @@ static int macb_probe(struct platform_device *pdev) goto err_disable_clocks; } - dev = alloc_etherdev_mq(sizeof(*bp), num_queues); - if (!dev) { + netdev = alloc_etherdev_mq(sizeof(*bp), num_queues); + if (!netdev) { err = -ENOMEM; goto err_disable_clocks; } - dev->base_addr = regs->start; + netdev->base_addr = regs->start; - SET_NETDEV_DEV(dev, &pdev->dev); + SET_NETDEV_DEV(netdev, &pdev->dev); - bp = netdev_priv(dev); + bp = netdev_priv(netdev); bp->pdev = pdev; - bp->dev = dev; + bp->netdev = netdev; bp->regs = mem; bp->native_io = native_io; if (native_io) { @@ -5881,21 +5892,21 @@ static int macb_probe(struct platform_device *pdev) bp->caps |= MACB_CAPS_DMA_64B; } #endif - platform_set_drvdata(pdev, dev); + platform_set_drvdata(pdev, netdev); - dev->irq = platform_get_irq(pdev, 0); - if (dev->irq < 0) { - err = dev->irq; + netdev->irq = platform_get_irq(pdev, 0); + if (netdev->irq < 0) { + err = netdev->irq; goto err_out_free_netdev; } /* MTU range: 68 - 1518 or 10240 */ - dev->min_mtu = GEM_MTU_MIN_SIZE; + netdev->min_mtu = GEM_MTU_MIN_SIZE; if ((bp->caps & MACB_CAPS_JUMBO) && bp->jumbo_max_len) - dev->max_mtu = MIN(bp->jumbo_max_len, RX_BUFFER_MAX) - + netdev->max_mtu = MIN(bp->jumbo_max_len, RX_BUFFER_MAX) - ETH_HLEN - ETH_FCS_LEN; else - dev->max_mtu = 1536 - ETH_HLEN - ETH_FCS_LEN; + netdev->max_mtu = 1536 - ETH_HLEN - ETH_FCS_LEN; if (bp->caps & MACB_CAPS_BD_RD_PREFETCH) { val = GEM_BFEXT(RXBD_RDBUFF, gem_readl(bp, DCFG10)); @@ -5913,7 +5924,7 @@ static int macb_probe(struct platform_device *pdev) if (bp->caps & MACB_CAPS_NEEDS_RSTONUBR) bp->rx_intr_mask |= MACB_BIT(RXUBR); - err = of_get_ethdev_address(np, bp->dev); + err = of_get_ethdev_address(np, bp->netdev); if (err == -EPROBE_DEFER) goto err_out_free_netdev; else if (err) @@ -5935,25 +5946,32 @@ static int macb_probe(struct platform_device *pdev) if (err) goto err_out_phy_exit; - netif_carrier_off(dev); + netif_carrier_off(netdev); - err = register_netdev(dev); + err = macb_alloc_tieoff(bp); + if (err) + goto err_out_unregister_mdio; + + err = register_netdev(netdev); if (err) { dev_err(&pdev->dev, "Cannot register net device, aborting.\n"); - goto err_out_unregister_mdio; + goto err_out_free_tieoff; } INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task); INIT_DELAYED_WORK(&bp->tx_lpi_work, macb_tx_lpi_work_fn); - netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", + netdev_info(netdev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID), - dev->base_addr, dev->irq, dev->dev_addr); + netdev->base_addr, netdev->irq, netdev->dev_addr); pm_runtime_put_autosuspend(&bp->pdev->dev); return 0; +err_out_free_tieoff: + macb_free_tieoff(bp); + err_out_unregister_mdio: mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); @@ -5962,7 +5980,7 @@ static int macb_probe(struct platform_device *pdev) phy_exit(bp->phy); err_out_free_netdev: - free_netdev(dev); + free_netdev(netdev); err_disable_clocks: macb_clks_disable(pclk, hclk, tx_clk, rx_clk, tsu_clk); @@ -5975,14 +5993,15 @@ static int macb_probe(struct platform_device *pdev) static void macb_remove(struct platform_device *pdev) { - struct net_device *dev; + struct net_device *netdev; struct macb *bp; - dev = platform_get_drvdata(pdev); + netdev = platform_get_drvdata(pdev); - if (dev) { - bp = netdev_priv(dev); - unregister_netdev(dev); + if (netdev) { + bp = netdev_priv(netdev); + unregister_netdev(netdev); + macb_free_tieoff(bp); phy_exit(bp->phy); mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); @@ -5994,7 +6013,7 @@ static void macb_remove(struct platform_device *pdev) pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); phylink_destroy(bp->phylink); - free_netdev(dev); + free_netdev(netdev); } } @@ -6009,7 +6028,7 @@ static int __maybe_unused macb_suspend(struct device *dev) u32 tmp, ifa_local; unsigned int q; - if (!device_may_wakeup(&bp->dev->dev)) + if (!device_may_wakeup(&bp->netdev->dev)) phy_exit(bp->phy); if (!netif_running(netdev)) @@ -6019,7 +6038,7 @@ static int __maybe_unused macb_suspend(struct device *dev) if (bp->wolopts & WAKE_ARP) { /* Check for IP address in WOL ARP mode */ rcu_read_lock(); - idev = __in_dev_get_rcu(bp->dev); + idev = __in_dev_get_rcu(bp->netdev); if (idev) ifa = rcu_dereference(idev->ifa_list); if (!ifa) { @@ -6121,7 +6140,7 @@ static int __maybe_unused macb_resume(struct device *dev) unsigned long flags; unsigned int q; - if (!device_may_wakeup(&bp->dev->dev)) + if (!device_may_wakeup(&bp->netdev->dev)) phy_init(bp->phy); if (!netif_running(netdev)) diff --git a/drivers/net/ethernet/cadence/macb_pci.c b/drivers/net/ethernet/cadence/macb_pci.c index b79dec17e6b0..ac009007118f 100644 --- a/drivers/net/ethernet/cadence/macb_pci.c +++ b/drivers/net/ethernet/cadence/macb_pci.c @@ -24,48 +24,48 @@ #define GEM_PCLK_RATE 50000000 #define GEM_HCLK_RATE 50000000 -static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +static int macb_probe(struct pci_dev *pci, const struct pci_device_id *id) { int err; - struct platform_device *plat_dev; + struct platform_device *pdev; struct platform_device_info plat_info; struct macb_platform_data plat_data; struct resource res[2]; /* enable pci device */ - err = pcim_enable_device(pdev); + err = pcim_enable_device(pci); if (err < 0) { - dev_err(&pdev->dev, "Enabling PCI device has failed: %d", err); + dev_err(&pci->dev, "Enabling PCI device has failed: %d", err); return err; } - pci_set_master(pdev); + pci_set_master(pci); /* set up resources */ memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); - res[0].start = pci_resource_start(pdev, 0); - res[0].end = pci_resource_end(pdev, 0); + res[0].start = pci_resource_start(pci, 0); + res[0].end = pci_resource_end(pci, 0); res[0].name = PCI_DRIVER_NAME; res[0].flags = IORESOURCE_MEM; - res[1].start = pci_irq_vector(pdev, 0); + res[1].start = pci_irq_vector(pci, 0); res[1].name = PCI_DRIVER_NAME; res[1].flags = IORESOURCE_IRQ; - dev_info(&pdev->dev, "EMAC physical base addr: %pa\n", + dev_info(&pci->dev, "EMAC physical base addr: %pa\n", &res[0].start); /* set up macb platform data */ memset(&plat_data, 0, sizeof(plat_data)); /* initialize clocks */ - plat_data.pclk = clk_register_fixed_rate(&pdev->dev, "pclk", NULL, 0, + plat_data.pclk = clk_register_fixed_rate(&pci->dev, "pclk", NULL, 0, GEM_PCLK_RATE); if (IS_ERR(plat_data.pclk)) { err = PTR_ERR(plat_data.pclk); goto err_pclk_register; } - plat_data.hclk = clk_register_fixed_rate(&pdev->dev, "hclk", NULL, 0, + plat_data.hclk = clk_register_fixed_rate(&pci->dev, "hclk", NULL, 0, GEM_HCLK_RATE); if (IS_ERR(plat_data.hclk)) { err = PTR_ERR(plat_data.hclk); @@ -74,24 +74,24 @@ static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* set up platform device info */ memset(&plat_info, 0, sizeof(plat_info)); - plat_info.parent = &pdev->dev; - plat_info.fwnode = pdev->dev.fwnode; + plat_info.parent = &pci->dev; + plat_info.fwnode = pci->dev.fwnode; plat_info.name = PLAT_DRIVER_NAME; - plat_info.id = pdev->devfn; + plat_info.id = pci->devfn; plat_info.res = res; plat_info.num_res = ARRAY_SIZE(res); plat_info.data = &plat_data; plat_info.size_data = sizeof(plat_data); - plat_info.dma_mask = pdev->dma_mask; + plat_info.dma_mask = pci->dma_mask; /* register platform device */ - plat_dev = platform_device_register_full(&plat_info); - if (IS_ERR(plat_dev)) { - err = PTR_ERR(plat_dev); + pdev = platform_device_register_full(&plat_info); + if (IS_ERR(pdev)) { + err = PTR_ERR(pdev); goto err_plat_dev_register; } - pci_set_drvdata(pdev, plat_dev); + pci_set_drvdata(pci, pdev); return 0; @@ -105,14 +105,14 @@ static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) return err; } -static void macb_remove(struct pci_dev *pdev) +static void macb_remove(struct pci_dev *pci) { - struct platform_device *plat_dev = pci_get_drvdata(pdev); - struct macb_platform_data *plat_data = dev_get_platdata(&plat_dev->dev); + struct platform_device *pdev = pci_get_drvdata(pci); + struct macb_platform_data *plat_data = dev_get_platdata(&pdev->dev); struct clk *pclk = plat_data->pclk; struct clk *hclk = plat_data->hclk; - platform_device_unregister(plat_dev); + platform_device_unregister(pdev); clk_unregister_fixed_rate(pclk); clk_unregister_fixed_rate(hclk); } diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index d91f7b1aa39c..e5195d7dac1d 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -324,9 +324,9 @@ void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb, skb_tstamp_tx(skb, &shhwtstamps); } -void gem_ptp_init(struct net_device *dev) +void gem_ptp_init(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); bp->ptp_clock_info = gem_ptp_caps_template; @@ -334,7 +334,7 @@ void gem_ptp_init(struct net_device *dev) bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp); bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj(); gem_ptp_init_timer(bp); - bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev); + bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &netdev->dev); if (IS_ERR(bp->ptp_clock)) { pr_err("ptp clock register failed: %ld\n", PTR_ERR(bp->ptp_clock)); @@ -353,9 +353,9 @@ void gem_ptp_init(struct net_device *dev) GEM_PTP_TIMER_NAME); } -void gem_ptp_remove(struct net_device *ndev) +void gem_ptp_remove(struct net_device *netdev) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); if (bp->ptp_clock) { ptp_clock_unregister(bp->ptp_clock); @@ -378,10 +378,10 @@ static int gem_ptp_set_ts_mode(struct macb *bp, return 0; } -int gem_get_hwtst(struct net_device *dev, +int gem_get_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); *tstamp_config = bp->tstamp_config; if (!macb_dma_ptp(bp)) @@ -402,13 +402,13 @@ static void gem_ptp_set_one_step_sync(struct macb *bp, u8 enable) macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE)); } -int gem_set_hwtst(struct net_device *dev, +int gem_set_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config, struct netlink_ext_ack *extack) { enum macb_bd_control tx_bd_control = TSTAMP_DISABLED; enum macb_bd_control rx_bd_control = TSTAMP_DISABLED; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); u32 regval; if (!macb_dma_ptp(bp)) diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index 9e2c2fa16d7a..1ced6df6eac8 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -1163,7 +1163,7 @@ static u16 cxgb_select_queue(struct net_device *dev, struct sk_buff *skb, } #endif /* CONFIG_CHELSIO_T4_DCB */ - if (dev->num_tc) { + if (netdev_get_num_tc(dev)) { struct port_info *pi = netdev2pinfo(dev); u8 ver, proto; diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c index 6871127427fa..29d7b786e51e 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c @@ -33,6 +33,7 @@ */ #include +#include #include "cxgb4.h" #include "t4_regs.h" #include "t4_values.h" @@ -4867,7 +4868,7 @@ static void mem_intr_handler(struct adapter *adapter, int idx) if (printk_ratelimit()) dev_warn(adapter->pdev_dev, "%u %s correctable ECC data error%s\n", - cnt, name[idx], cnt > 1 ? "s" : ""); + cnt, name[idx], str_plural(cnt)); } if (v & ECC_UE_INT_CAUSE_F) dev_alert(adapter->pdev_dev, diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile index a96b8332e6e2..e38aaf34c148 100644 --- a/drivers/net/ethernet/cisco/enic/Makefile +++ b/drivers/net/ethernet/cisco/enic/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_ENIC) := enic.o enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \ enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \ - enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o + enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \ + enic_admin.o enic_mbox.o diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 08472420f3a1..4a67947cfb9f 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -256,10 +256,10 @@ struct enic { struct enic_rx_coal rx_coalesce_setting; u32 rx_coalesce_usecs; u32 tx_coalesce_usecs; -#ifdef CONFIG_PCI_IOV u16 num_vfs; -#endif enum enic_vf_type vf_type; + bool vf_registered; + u32 pf_cap_version; unsigned int enable_count; spinlock_t enic_api_lock; bool enic_api_busy; @@ -292,10 +292,46 @@ struct enic { /* Admin channel resources for SR-IOV MBOX */ bool has_admin_channel; + /* true only while the admin WQ/RQ/CQ are allocated and enabled; gates + * enic_admin_channel_close() so it is a no-op after a failed (re)open + * left the resources freed. + */ + bool admin_chan_up; + /* set on send timeout; cleared on channel re-open */ + bool mbox_send_disabled; struct vnic_wq admin_wq; struct vnic_rq admin_rq; struct vnic_cq admin_cq[2]; struct vnic_intr admin_intr; + struct delayed_work admin_poll_work; + unsigned int admin_intr_index; + struct work_struct link_notify_work; + struct work_struct admin_msg_work; + spinlock_t admin_msg_lock; /* protects admin_msg_list */ + struct list_head admin_msg_list; + unsigned int admin_msg_count; /* current depth of admin_msg_list */ + void (*admin_rq_handler)(struct enic *enic, void *buf, + unsigned int len); + + /* MBOX protocol state — mbox_lock serializes admin WQ sends */ + struct mutex mbox_lock; + u64 mbox_msg_num; + /* MBOX request-reply state. mbox_expected_reply is written and + * cleared by the process-context request helpers (capability/register/ + * unregister) and only read by the admin_msg_work receive handlers, so + * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked: + * only one request is in flight at a time (requesters run under RTNL or + * single-threaded probe/remove), so each request is serialized and its + * reply completes mbox_comp before the next request is issued. + */ + struct completion mbox_comp; + u8 mbox_expected_reply; + bool mbox_initialized; + + /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ + struct enic_vf_state { + bool registered; + } *vf_state; }; static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev) @@ -417,6 +453,7 @@ void enic_reset_addr_lists(struct enic *enic); int enic_sriov_enabled(struct enic *enic); int enic_is_valid_vf(struct enic *enic, int vf); int enic_is_dynamic(struct enic *enic); +int enic_is_sriov_vf_v2(struct enic *enic); void enic_set_ethtool_ops(struct net_device *netdev); int __enic_set_rsskey(struct enic *enic); void enic_ext_cq(struct enic *enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c new file mode 100644 index 000000000000..7188f1b81c04 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2025 Cisco Systems, Inc. All rights reserved. + +#include +#include +#include +#include + +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "vnic_cq.h" +#include "vnic_intr.h" +#include "vnic_resource.h" +#include "vnic_devcmd.h" +#include "enic.h" +#include "enic_admin.h" +#include "cq_desc.h" +#include "cq_enet_desc.h" +#include "wq_enet_desc.h" +#include "rq_enet_desc.h" +#include "enic_mbox.h" + +/* Retry interval for a failed admin RQ refill. Short so the control + * channel recovers quickly once memory is available again. + */ +#define ENIC_ADMIN_RQ_REFILL_RETRY_MS 100 + +/* Clean up any admin WQ buffers still held by hardware at close time. + * Normally buffers are freed inline after send completion, but a timed-out + * send intentionally leaves the buffer live until the queue is stopped. + */ +static void enic_admin_wq_buf_clean(struct vnic_wq *wq, + struct vnic_wq_buf *buf) +{ + struct enic *enic = vnic_dev_priv(wq->vdev); + + if (buf->os_buf) { + dma_unmap_single(&enic->pdev->dev, buf->dma_addr, + buf->len, DMA_TO_DEVICE); + kfree(buf->os_buf); + buf->os_buf = NULL; + } +} + +static void enic_admin_rq_buf_clean(struct vnic_rq *rq, + struct vnic_rq_buf *buf) +{ + struct enic *enic = vnic_dev_priv(rq->vdev); + + if (!buf->os_buf) + return; + + dma_unmap_single(&enic->pdev->dev, buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + kfree(buf->os_buf); + buf->os_buf = NULL; +} + +static int enic_admin_rq_post_one(struct enic *enic, gfp_t gfp) +{ + struct vnic_rq *rq = &enic->admin_rq; + struct rq_enet_desc *desc; + dma_addr_t dma_addr; + void *buf; + + buf = kzalloc(ENIC_ADMIN_BUF_SIZE, gfp); + if (!buf) + return -ENOMEM; + + dma_addr = dma_map_single(&enic->pdev->dev, buf, ENIC_ADMIN_BUF_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(&enic->pdev->dev, dma_addr)) { + kfree(buf); + return -ENOMEM; + } + + desc = vnic_rq_next_desc(rq); + rq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET, + RQ_ENET_TYPE_ONLY_SOP, ENIC_ADMIN_BUF_SIZE); + vnic_rq_post(rq, buf, 0, dma_addr, ENIC_ADMIN_BUF_SIZE, 0); + + return 0; +} + +static int enic_admin_rq_fill(struct enic *enic, gfp_t gfp) +{ + struct vnic_rq *rq = &enic->admin_rq; + int err; + + while (vnic_rq_desc_avail(rq) > 0) { + err = enic_admin_rq_post_one(enic, gfp); + if (err) + return err; + } + + return 0; +} + +static void enic_admin_rq_drain(struct enic *enic) +{ + vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); +} + +static unsigned int enic_admin_cq_color(void *cq_desc, unsigned int desc_size) +{ + u8 type_color = *((u8 *)cq_desc + desc_size - 1); + + return (type_color >> CQ_DESC_COLOR_SHIFT) & CQ_DESC_COLOR_MASK; +} + +unsigned int enic_admin_wq_cq_service(struct enic *enic) +{ + struct vnic_cq *cq = &enic->admin_cq[0]; + unsigned int work = 0; + void *desc; + + desc = vnic_cq_to_clean(cq); + while (enic_admin_cq_color(desc, cq->ring.desc_size) != + cq->last_color) { + vnic_cq_inc_to_clean(cq); + work++; + desc = vnic_cq_to_clean(cq); + } + + return work; +} + +/* Upper bound on pending admin messages. A buggy or hostile VF could flood + * the PF admin channel faster than admin_msg_work drains it; cap the backlog + * so a guest cannot drive the host out of memory. + */ +#define ENIC_ADMIN_MSG_MAX 256 + +static void enic_admin_msg_enqueue(struct enic *enic, void *buf, + unsigned int len) +{ + struct enic_admin_msg *msg; + + msg = kmalloc(struct_size(msg, data, len), GFP_KERNEL); + if (!msg) + return; + + msg->len = len; + memcpy(msg->data, buf, len); + + spin_lock(&enic->admin_msg_lock); + if (enic->admin_msg_count >= ENIC_ADMIN_MSG_MAX) { + spin_unlock(&enic->admin_msg_lock); + kfree(msg); + if (net_ratelimit()) + netdev_warn(enic->netdev, + "admin msg backlog full (%u); dropping\n", + ENIC_ADMIN_MSG_MAX); + return; + } + list_add_tail(&msg->list, &enic->admin_msg_list); + enic->admin_msg_count++; + spin_unlock(&enic->admin_msg_lock); +} + +unsigned int enic_admin_rq_cq_service(struct enic *enic) +{ + struct vnic_cq *cq = &enic->admin_cq[1]; + struct vnic_rq *rq = &enic->admin_rq; + struct cq_enet_rq_desc *rq_desc; + struct vnic_rq_buf *buf; + u16 bwf, bytes_written; + unsigned int work = 0; + void *desc; + + /* The admin RQ and its CQ form a single in-order channel: firmware + * posts exactly one CQE per consumed RQ descriptor, in submission + * order. Each CQE therefore pairs with rq->to_clean below without a + * completed_index cross-check, mirroring the in-order assumption of + * the main enic RX path. + */ + desc = vnic_cq_to_clean(cq); + while (enic_admin_cq_color(desc, cq->ring.desc_size) != + cq->last_color) { + /* Ensure DMA descriptor fields are read after + * the color/valid check. dma_rmb() is the + * correct barrier for DMA-written descriptors. + */ + dma_rmb(); + buf = rq->to_clean; + + /* Decode the actual number of bytes hardware wrote into + * the RX buffer. buf->len is the static allocation size + * (ENIC_ADMIN_BUF_SIZE); copying that many bytes would read + * beyond the actual DMA payload. bytes_written_flags is at + * the same offset in every cq_enet_rq_desc[_32|_64] variant. + */ + rq_desc = desc; + bwf = le16_to_cpu(rq_desc->bytes_written_flags); + bytes_written = bwf & CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK; + if (bytes_written > buf->len) + goto next_desc; + + dma_sync_single_for_cpu(&enic->pdev->dev, + buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + + /* Drop on hardware error indications. Admin messages + * are internal to the VIC, not received over the wire. + * Firmware sets TRUNCATED when the message does not fit + * in the posted buffer, and FCS_OK is always set on + * healthy admin completions. + */ + if (bwf & CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) { + netdev_warn_once(enic->netdev, + "admin RQ: truncated message dropped\n"); + goto next_desc; + } + if (!(rq_desc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) { + netdev_warn_once(enic->netdev, + "admin RQ: bad FCS, dropping message\n"); + goto next_desc; + } + + if (enic->admin_rq_handler) { + u16 sender_vlan; + + /* Firmware sets the CQ VLAN field to identify the + * sender: 0 = PF, 1-based = VF index. Overwrite + * the untrusted src_vnic_id in the MBOX header with + * the hardware-verified value. + */ + sender_vlan = le16_to_cpu(rq_desc->vlan); + if (bytes_written >= sizeof(struct enic_mbox_hdr)) { + struct enic_mbox_hdr *hdr = buf->os_buf; + + hdr->src_vnic_id = (sender_vlan == 0) ? + cpu_to_le16(ENIC_MBOX_DST_PF) : + cpu_to_le16(sender_vlan - 1); + } + + enic_admin_msg_enqueue(enic, buf->os_buf, + bytes_written); + } + +next_desc: + enic_admin_rq_buf_clean(rq, rq->to_clean); + rq->to_clean = rq->to_clean->next; + rq->ring.desc_avail++; + + vnic_cq_inc_to_clean(cq); + work++; + desc = vnic_cq_to_clean(cq); + } + + if (enic_admin_rq_fill(enic, GFP_KERNEL)) { + /* Some RX buffers could not be reposted (transient memory + * pressure). If the ring is left empty the channel would stall + * with no completion to drive the next refill, so arm a delayed + * re-run of this poll work (the sole owner of the admin RQ ring) + * to repost buffers and re-arm the RQ from a single context. + */ + if (net_ratelimit()) + netdev_warn(enic->netdev, + "admin RQ refill failed; scheduling retry\n"); + schedule_delayed_work(&enic->admin_poll_work, + msecs_to_jiffies(ENIC_ADMIN_RQ_REFILL_RETRY_MS)); + } + + return work; +} + +static irqreturn_t enic_admin_isr_msix(int irq, void *data) +{ + struct enic *enic = data; + + schedule_delayed_work(&enic->admin_poll_work, 0); + + return IRQ_HANDLED; +} + +static void enic_admin_msg_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(work, struct enic, admin_msg_work); + struct enic_admin_msg *msg, *tmp; + LIST_HEAD(local_list); + + spin_lock_bh(&enic->admin_msg_lock); + list_splice_init(&enic->admin_msg_list, &local_list); + enic->admin_msg_count = 0; + spin_unlock_bh(&enic->admin_msg_lock); + + list_for_each_entry_safe(msg, tmp, &local_list, list) { + if (enic->admin_rq_handler) + enic->admin_rq_handler(enic, msg->data, msg->len); + list_del(&msg->list); + kfree(msg); + } +} + +static void enic_admin_poll_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(to_delayed_work(work), struct enic, + admin_poll_work); + unsigned int credits; + unsigned int rq_work; + + /* Snapshot the pending credit count before draining so we acknowledge + * exactly what the hardware reported for this interrupt. Credits that + * accrue while enic_admin_rq_cq_service() runs are left for the next + * interrupt, which is harmless on this low-rate control path. + */ + credits = vnic_intr_credits(&enic->admin_intr); + + rq_work = enic_admin_rq_cq_service(enic); + + if (rq_work > 0) + schedule_work(&enic->admin_msg_work); + + /* Acknowledge the snapshotted credits and unmask the vector. Unlike + * the NAPI data path, the admin channel is not re-polled, so the vector + * must be re-armed here to receive the next completion. The unmask is + * applied through the interrupt mask register independently of the + * credit count, so returning zero credits on a spurious wakeup still + * re-arms the vector. + */ + vnic_intr_return_credits(&enic->admin_intr, + credits, + 1 /* unmask */, 0); +} + +static int enic_admin_setup_intr(struct enic *enic) +{ + unsigned int intr_index = enic->intr_count; + int err; + + if (vnic_dev_get_intr_mode(enic->vdev) != VNIC_DEV_INTR_MODE_MSIX || + intr_index >= enic->intr_avail) + return -ENODEV; + + /* The admin INTR uses a slot in the same RES_TYPE_INTR_CTRL + * strided array of per-vector control blocks (mask, coalescing + * timer, credit return) that the data-path IRQs occupy in BAR0. + * vnic_intr_alloc() defaults to RES_TYPE_INTR_CTRL, which is what + * we want here. + */ + err = vnic_intr_alloc(enic->vdev, &enic->admin_intr, intr_index); + if (err) { + netdev_warn(enic->netdev, + "Failed to alloc admin intr at index %u: %d\n", + intr_index, err); + return err; + } + + enic->admin_intr_index = intr_index; + + /* Mask the admin vector before requesting the IRQ so an early or + * spurious completion cannot run the poll handler against the + * not-yet-initialised admin rings. enic_admin_channel_open() unmasks + * it only after the rings are initialised and filled. + */ + vnic_intr_mask(&enic->admin_intr); + + /* A V2 VF opens the admin channel during probe, before + * register_netdev() resolves the "eth%d" name template, so using + * netdev->name here would register the literal "eth%d-admin" in + * /proc/interrupts. Use the already-stable PCI device name instead. + */ + snprintf(enic->msix[intr_index].devname, + sizeof(enic->msix[intr_index].devname), + "%s-admin", pci_name(enic->pdev)); + enic->msix[intr_index].isr = enic_admin_isr_msix; + enic->msix[intr_index].devid = enic; + + err = request_irq(enic->msix_entry[intr_index].vector, + enic->msix[intr_index].isr, 0, + enic->msix[intr_index].devname, + enic->msix[intr_index].devid); + if (err) { + netdev_warn(enic->netdev, + "Failed to request admin MSI-X irq: %d\n", err); + vnic_intr_free(&enic->admin_intr); + return err; + } + + enic->msix[intr_index].requested = 1; + + netdev_dbg(enic->netdev, + "admin channel using MSI-X interrupt (index %u)\n", + intr_index); + + return 0; +} + +static void enic_admin_teardown_intr(struct enic *enic) +{ + unsigned int intr_index = enic->admin_intr_index; + + free_irq(enic->msix_entry[intr_index].vector, + enic->msix[intr_index].devid); + cancel_delayed_work_sync(&enic->admin_poll_work); + enic->msix[intr_index].requested = 0; +} + +static int enic_admin_qp_type_set(struct enic *enic, u32 enable) +{ + u64 a0 = QP_TYPE_ADMIN, a1 = enable; + int wait = 1000; + int err; + + spin_lock_bh(&enic->devcmd_lock); + err = vnic_dev_cmd(enic->vdev, CMD_QP_TYPE_SET, &a0, &a1, wait); + spin_unlock_bh(&enic->devcmd_lock); + + return err; +} + +static int enic_admin_alloc_resources(struct enic *enic) +{ + int err; + + err = vnic_wq_alloc_with_type(enic->vdev, &enic->admin_wq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct wq_enet_desc), + RES_TYPE_ADMIN_WQ); + if (err) + return err; + + err = vnic_rq_alloc_with_type(enic->vdev, &enic->admin_rq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct rq_enet_desc), + RES_TYPE_ADMIN_RQ); + if (err) + goto free_wq; + + /* admin_cq[0] is the WQ completion queue. WQ CQEs are always + * 16 bytes wide; firmware always writes 16-byte CQEs for WQ + * completions on every WQ, including the admin channel WQ. + * Use sizeof(struct cq_desc) accordingly. + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[0], 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct cq_desc), + RES_TYPE_ADMIN_CQ); + if (err) + goto free_rq; + + /* admin_cq[1] is the RQ completion queue. Its descriptor size + * must match what firmware writes. enic_ext_cq() called earlier + * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL, + * programming firmware to write CQ entries of (16 << enic->ext_cq) + * bytes for every RQ CQ on the vNIC, including the admin RQ CQ. + * Allocating with the same size keeps the host poller and + * firmware in lockstep: + * + * - The color/valid bit lives at byte (desc_size - 1) of every + * cq_enet_rq_desc[_32|_64] variant, so enic_admin_cq_color() + * reads it from the correct offset. + * - Only the first 15 bytes of the descriptor (vlan, + * bytes_written_flags, ...) are accessed by the admin path; + * these fields are identical across all three variants (see + * comment in enic_rq.c above cq_enet_rq_desc_dec()). + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[1], 1, + ENIC_ADMIN_DESC_COUNT, + 16 << enic->ext_cq, + RES_TYPE_ADMIN_CQ); + if (err) + goto free_cq0; + + return 0; + +free_cq0: + vnic_cq_free(&enic->admin_cq[0]); +free_rq: + vnic_rq_free(&enic->admin_rq); +free_wq: + vnic_wq_free(&enic->admin_wq); + return err; +} + +static void enic_admin_free_resources(struct enic *enic) +{ + vnic_intr_free(&enic->admin_intr); + vnic_cq_free(&enic->admin_cq[1]); + vnic_cq_free(&enic->admin_cq[0]); + vnic_rq_free(&enic->admin_rq); + vnic_wq_free(&enic->admin_wq); +} + +static void enic_admin_init_resources(struct enic *enic) +{ + unsigned int intr_offset = enic->admin_intr_index; + + vnic_wq_init(&enic->admin_wq, + 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_rq_init(&enic->admin_rq, + 1, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_cq_init(&enic->admin_cq[0], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_DISABLE, /* polled synchronously by mbox send */ + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + intr_offset, + 0 /* cq_message_addr */); + vnic_cq_init(&enic->admin_cq[1], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_ENABLE, + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + intr_offset, + 0 /* cq_message_addr */); + /* coalescing_timer, coalescing_type, mask_on_assertion */ + vnic_intr_init(&enic->admin_intr, + 0, 0, 1); +} + +static void enic_admin_msg_drain(struct enic *enic) +{ + struct enic_admin_msg *msg, *tmp; + + spin_lock_bh(&enic->admin_msg_lock); + list_for_each_entry_safe(msg, tmp, &enic->admin_msg_list, list) { + list_del(&msg->list); + kfree(msg); + } + enic->admin_msg_count = 0; + spin_unlock_bh(&enic->admin_msg_lock); +} + +int enic_admin_channel_open(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return -ENODEV; + + /* Keep MBOX sends disabled for the entire open sequence. It is + * cleared only after every resource is allocated and enabled below, + * so any early error return here leaves sends disabled and a + * concurrent sender cannot touch a half-open or freed admin_wq. + */ + WRITE_ONCE(enic->mbox_send_disabled, true); + + err = enic_admin_alloc_resources(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to alloc admin channel resources: %d\n", + err); + return err; + } + + spin_lock_init(&enic->admin_msg_lock); + INIT_LIST_HEAD(&enic->admin_msg_list); + INIT_WORK(&enic->admin_msg_work, enic_admin_msg_work_handler); + INIT_DELAYED_WORK(&enic->admin_poll_work, enic_admin_poll_work_handler); + + err = enic_admin_setup_intr(enic); + if (err) { + netdev_err(enic->netdev, + "Admin channel requires MSI-X, SR-IOV unavailable: %d\n", + err); + goto free_resources; + } + + enic_admin_init_resources(enic); + + vnic_wq_enable(&enic->admin_wq); + vnic_rq_enable(&enic->admin_rq); + + err = enic_admin_rq_fill(enic, GFP_KERNEL); + if (err) { + netdev_err(enic->netdev, + "Failed to fill admin RQ buffers: %d\n", err); + goto disable_queues; + } + + err = enic_admin_qp_type_set(enic, QP_ENABLE); + if (err) { + netdev_err(enic->netdev, + "Failed to set admin QP type: %d\n", err); + goto disable_queues; + } + + vnic_intr_unmask(&enic->admin_intr); + + /* Only now that the admin WQ/RQ/CQ and interrupt are fully allocated, + * programmed and enabled is it safe to allow MBOX sends. Clearing this + * earlier opened a window where a concurrent sender (e.g. link-notify + * work scheduled by a post-reset link-up) could call enic_mbox_send_msg() + * against a not-yet-allocated admin_wq and crash. + */ + WRITE_ONCE(enic->mbox_send_disabled, false); + + netdev_dbg(enic->netdev, + "admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\n", + enic->admin_intr_index, + vnic_wq_desc_avail(&enic->admin_wq), + vnic_rq_desc_avail(&enic->admin_rq), + enic->admin_cq[0].last_color, + enic->admin_cq[1].last_color); + + enic->admin_chan_up = true; + + return 0; + +disable_queues: + enic_admin_teardown_intr(enic); + enic_admin_qp_type_set(enic, QP_DISABLE); + if (vnic_wq_disable(&enic->admin_wq)) + netdev_warn(enic->netdev, "Failed to disable admin WQ\n"); + if (vnic_rq_disable(&enic->admin_rq)) + netdev_warn(enic->netdev, "Failed to disable admin RQ\n"); + cancel_work_sync(&enic->admin_msg_work); + enic_admin_msg_drain(enic); + enic_admin_rq_drain(enic); +free_resources: + enic_admin_free_resources(enic); + return err; +} + +void enic_admin_channel_close(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return; + + /* Nothing to tear down if the channel was never (re)opened, e.g. a + * failed enic_admin_channel_open() in probe or in the reset path; + * otherwise the disable/clean calls below dereference freed resources. + */ + if (!enic->admin_chan_up) + return; + + WRITE_ONCE(enic->mbox_send_disabled, true); + + netdev_dbg(enic->netdev, "admin channel close\n"); + + vnic_intr_mask(&enic->admin_intr); + enic_admin_teardown_intr(enic); + cancel_work_sync(&enic->link_notify_work); + cancel_work_sync(&enic->admin_msg_work); + enic_admin_msg_drain(enic); + + enic_admin_qp_type_set(enic, QP_DISABLE); + + err = vnic_wq_disable(&enic->admin_wq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin WQ: %d\n", err); + err = vnic_rq_disable(&enic->admin_rq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin RQ: %d\n", err); + + vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean); + enic_admin_rq_drain(enic); + vnic_cq_clean(&enic->admin_cq[0]); + vnic_cq_clean(&enic->admin_cq[1]); + vnic_intr_clean(&enic->admin_intr); + + enic->admin_rq_handler = NULL; + enic_admin_free_resources(enic); + + enic->admin_chan_up = false; +} diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h new file mode 100644 index 000000000000..62c80220b0ca --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */ + +#ifndef _ENIC_ADMIN_H_ +#define _ENIC_ADMIN_H_ + +#define ENIC_ADMIN_DESC_COUNT 64 +#define ENIC_ADMIN_BUF_SIZE 2048 + +struct enic; + +/* Wrapper for received admin messages queued for deferred processing. + * The admin CQ poll work handler enqueues these; a separate work handler + * processes them where sleeping (mutex, GFP_KERNEL) is safe. + */ +struct enic_admin_msg { + struct list_head list; + unsigned int len; + u8 data[] __aligned(8); +}; + +int enic_admin_channel_open(struct enic *enic); +void enic_admin_channel_close(struct enic *enic); +unsigned int enic_admin_wq_cq_service(struct enic *enic); +unsigned int enic_admin_rq_cq_service(struct enic *enic); + +#endif /* _ENIC_ADMIN_H_ */ diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index d98f7e7ccab9..0baef7a120ec 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -60,6 +60,8 @@ #include "enic_clsf.h" #include "enic_rq.h" #include "enic_wq.h" +#include "enic_admin.h" +#include "enic_mbox.h" #define ENIC_NOTIFY_TIMER_PERIOD (2 * HZ) @@ -314,6 +316,11 @@ static int enic_is_sriov_vf(struct enic *enic) enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2; } +int enic_is_sriov_vf_v2(struct enic *enic) +{ + return enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2; +} + int enic_is_valid_vf(struct enic *enic, int vf) { #ifdef CONFIG_PCI_IOV @@ -411,18 +418,50 @@ static void enic_set_rx_coal_setting(struct enic *enic) rx_coal->use_adaptive_rx_coalesce = 1; } +static void enic_link_notify_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(work, struct enic, + link_notify_work); + u32 state; + u16 i; + + if (!enic_sriov_enabled(enic) || !enic->vf_state) + return; + + state = netif_carrier_ok(enic->netdev) ? + ENIC_MBOX_LINK_STATE_ENABLE : + ENIC_MBOX_LINK_STATE_DISABLE; + + for (i = 0; i < enic->num_vfs; i++) + enic_mbox_send_link_state(enic, i, state); +} + static void enic_link_check(struct enic *enic) { - int link_status = vnic_dev_link_status(enic->vdev); - int carrier_ok = netif_carrier_ok(enic->netdev); + int link_status; + int carrier_ok; + + /* A V2 SR-IOV VF's carrier is driven by PF link-state MBOX + * notifications, not by its own vnic link status; skip the + * autonomous check so it cannot flap the VF carrier. + */ + if (enic_is_sriov_vf_v2(enic)) + return; + + link_status = vnic_dev_link_status(enic->vdev); + carrier_ok = netif_carrier_ok(enic->netdev); if (link_status && !carrier_ok) { netdev_info(enic->netdev, "Link UP\n"); netif_carrier_on(enic->netdev); enic_set_rx_coal_setting(enic); + if (enic_sriov_enabled(enic) && enic->vf_state) + schedule_work(&enic->link_notify_work); } else if (!link_status && carrier_ok) { netdev_info(enic->netdev, "Link DOWN\n"); netif_carrier_off(enic->netdev); + if (enic_sriov_enabled(enic) && enic->vf_state) + schedule_work(&enic->link_notify_work); } } @@ -2142,6 +2181,74 @@ static void enic_set_api_busy(struct enic *enic, bool busy) spin_unlock(&enic->enic_api_lock); } +/* The admin/MBOX channel exists on a V2 PF while SR-IOV is enabled and on + * every V2 VF. A reset wipes the admin WQ/RQ/CQ, so such devices must tear + * the channel down before the reset and re-establish it afterwards. + */ +static bool enic_has_admin_chan(struct enic *enic) +{ + return enic_is_sriov_vf_v2(enic) || + (enic_sriov_enabled(enic) && enic->vf_type == ENIC_VF_TYPE_V2); +} + +/* Re-establish the admin/MBOX channel after a reset has re-created the data + * path. Mirrors the relevant part of the probe / SR-IOV-enable sequence: + * reinitialise MBOX and reopen the channel, then for a VF re-run the PF + * handshake (the reset wiped the VF's admin QP, so the VF must register + * again), or for a PF re-push the current link state to registered VFs. + */ +static void enic_admin_chan_reopen(struct enic *enic) +{ + int err; + + /* Install the MBOX receive handler and reset the sequence number + * before opening the channel, so the handler is in place before the + * admin interrupt is unmasked and no early completion is dropped. + */ + enic_mbox_init(enic); + + /* A reset destroys the VF's local admin QP, so the VF can no longer + * rely on its previous registration. The PF may retain stale software + * registration state until the VF successfully registers again. + * Clear the local flag before reopening so a failed reopen or + * re-handshake cannot leave the VF believing it has a usable PF + * registration over a dead channel. + */ + if (enic_is_sriov_vf_v2(enic)) + enic->vf_registered = false; + + err = enic_admin_channel_open(enic); + if (err) { + netdev_err(enic->netdev, + "admin channel reopen after reset failed: %d\n", err); + return; + } + + if (enic_is_sriov_vf_v2(enic)) { + err = enic_mbox_vf_capability_check(enic); + if (err) { + netdev_err(enic->netdev, + "MBOX capability check after reset failed: %d\n", + err); + enic_admin_channel_close(enic); + return; + } + err = enic_mbox_vf_register(enic); + if (err) { + netdev_err(enic->netdev, + "MBOX VF re-registration after reset failed: %d\n", + err); + enic_admin_channel_close(enic); + } + } else { + /* The link came back up during enic_open() above while MBOX + * sends were still disabled (channel not yet reopened), so that + * link-notify was dropped. Re-push current link state now. + */ + schedule_work(&enic->link_notify_work); + } +} + static void enic_reset(struct work_struct *work) { struct enic *enic = container_of(work, struct enic, reset); @@ -2154,15 +2261,34 @@ static void enic_reset(struct work_struct *work) /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); + /* Fully tear down the V2 admin/MBOX channel before the soft reset. + * The reset wipes all hardware queues including the admin WQ/RQ; + * closing first tells firmware to stop the admin QP (so it no longer + * DMAs from the about-to-be-reset rings) and frees the admin resources + * so they are cleanly re-allocated afterwards. + */ + if (enic_has_admin_chan(enic)) + enic_admin_channel_close(enic); + enic_stop(enic->netdev); + enic_dev_soft_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); enic_set_rss_nic_cfg(enic); enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); + enic_open(enic->netdev); + /* Re-establish the admin/MBOX channel after the data path is back up. + * It was fully torn down by enic_admin_channel_close() above; + * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link + * state, or for a VF re-runs the probe-time PF handshake. + */ + if (enic_has_admin_chan(enic)) + enic_admin_chan_reopen(enic); + /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); @@ -2180,16 +2306,33 @@ static void enic_tx_hang_reset(struct work_struct *work) /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); + /* Fully tear down the V2 admin/MBOX channel before the hang reset, for + * the same reason as the soft reset path: stop the admin QP and free + * the admin resources before the hardware queues are wiped. + */ + if (enic_has_admin_chan(enic)) + enic_admin_channel_close(enic); + enic_dev_hang_notify(enic); enic_stop(enic->netdev); + enic_dev_hang_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); enic_set_rss_nic_cfg(enic); enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); + enic_open(enic->netdev); + /* Re-establish the admin/MBOX channel after the data path is back up. + * It was fully torn down by enic_admin_channel_close() above; + * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link + * state, or for a VF re-runs the probe-time PF handshake. + */ + if (enic_has_admin_chan(enic)) + enic_admin_chan_reopen(enic); + /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); @@ -2200,6 +2343,8 @@ static void enic_tx_hang_reset(struct work_struct *work) static int enic_set_intr_mode(struct enic *enic) { + unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0; + unsigned int min_intr = ENIC_MSIX_MIN_INTR + admin_reserve; unsigned int i; int num_intr; @@ -2210,12 +2355,12 @@ static int enic_set_intr_mode(struct enic *enic) */ if (enic->config.intr_mode < 1 && - enic->intr_avail >= ENIC_MSIX_MIN_INTR) { + enic->intr_avail >= min_intr) { for (i = 0; i < enic->intr_avail; i++) enic->msix_entry[i].entry = i; num_intr = pci_enable_msix_range(enic->pdev, enic->msix_entry, - ENIC_MSIX_MIN_INTR, + min_intr, enic->intr_avail); if (num_intr > 0) { vnic_dev_set_intr_mode(enic->vdev, @@ -2310,16 +2455,27 @@ static int enic_adjust_resources(struct enic *enic) enic->cq_count = 2; enic->intr_count = enic->intr_avail; break; - case VNIC_DEV_INTR_MODE_MSIX: + case VNIC_DEV_INTR_MODE_MSIX: { /* Adjust the number of wqs/rqs/cqs/interrupts that will be - * used based on which resource is the most constrained + * used based on which resource is the most constrained. + * Reserve one extra MSI-X slot for the admin channel INTR + * when has_admin_channel is set so that + * enic_admin_setup_intr() can allocate at intr_count + * within the intr_avail bounds even when the data queue + * count is maxed out. intr_count counts only the data-path + * IRQs (registered by enic_request_intr()); the admin INTR + * lives at msix index intr_count and is set up later by + * enic_admin_setup_intr(). */ + unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0; + wq_avail = min(enic->wq_avail, ENIC_WQ_MAX); rq_default = max(netif_get_num_default_rss_queues(), ENIC_RQ_MIN_DEFAULT); rq_avail = min3(enic->rq_avail, ENIC_RQ_MAX, rq_default); max_queues = min(enic->cq_avail, - enic->intr_avail - ENIC_MSIX_RESERVED_INTR); + enic->intr_avail - ENIC_MSIX_RESERVED_INTR - + admin_reserve); if (wq_avail + rq_avail <= max_queues) { enic->rq_count = rq_avail; enic->wq_count = wq_avail; @@ -2337,6 +2493,7 @@ static int enic_adjust_resources(struct enic *enic) enic->intr_count = enic->cq_count + ENIC_MSIX_RESERVED_INTR; break; + } default: dev_err(enic_get_dev(enic), "Unknown interrupt mode\n"); return -EINVAL; @@ -2641,8 +2798,10 @@ static void enic_iounmap(struct enic *enic) static void enic_sriov_detect_vf_type(struct enic *enic) { struct pci_dev *pdev = enic->pdev; - int pos; + u64 supported_versions, a1 = 0; u16 vf_dev_id; + int pos; + int err; if (enic_is_sriov_vf(enic) || enic_is_dynamic(enic)) return; @@ -2669,6 +2828,161 @@ static void enic_sriov_detect_vf_type(struct enic *enic) enic->vf_type = ENIC_VF_TYPE_NONE; break; } + + if (enic->vf_type != ENIC_VF_TYPE_V2) + return; + + /* A successful command means firmware recognizes + * VIC_FEATURE_SRIOV; supported_versions is available + * for sub-feature versioning in the future. + */ + err = vnic_dev_get_supported_feature_ver(enic->vdev, + VIC_FEATURE_SRIOV, + &supported_versions, + &a1); + if (err) { + dev_warn(&pdev->dev, + "SR-IOV V2 not supported by current firmware. Upgrade to VIC FW 5.3(4.72) or higher.\n"); + enic->vf_type = ENIC_VF_TYPE_NONE; + } +} + +static int __maybe_unused +enic_sriov_v2_enable(struct enic *enic, int num_vfs) +{ + int err; + + if (!enic->has_admin_channel) { + netdev_err(enic->netdev, + "V2 SR-IOV requires admin channel resources\n"); + return -EOPNOTSUPP; + } + + enic->vf_state = kcalloc(num_vfs, sizeof(*enic->vf_state), GFP_KERNEL); + if (!enic->vf_state) + return -ENOMEM; + + /* Install the MBOX receive handler before the admin interrupt is + * unmasked in enic_admin_channel_open(), so no early completion is + * dropped. + */ + enic_mbox_init(enic); + + err = enic_admin_channel_open(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to open admin channel: %d\n", err); + goto free_vf_state; + } + + enic->num_vfs = num_vfs; + + err = pci_enable_sriov(enic->pdev, num_vfs); + if (err) { + netdev_err(enic->netdev, + "pci_enable_sriov failed: %d\n", err); + goto close_admin; + } + + enic->priv_flags |= ENIC_SRIOV_ENABLED; + return num_vfs; + +close_admin: + enic->num_vfs = 0; + enic_admin_channel_close(enic); +free_vf_state: + kfree(enic->vf_state); + enic->vf_state = NULL; + return err; +} + +static void enic_sriov_v2_disable(struct enic *enic) +{ + /* Stop new VF link-state broadcasts before tearing down vf_state. + * Clearing ENIC_SRIOV_ENABLED makes enic_link_check() (called from + * the notify timer/ISR) skip the VF notify path, and cancelling + * link_notify_work ensures any already-queued broadcast has finished + * before vf_state is freed, closing a use-after-free window. + */ + enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + cancel_work_sync(&enic->link_notify_work); + + pci_disable_sriov(enic->pdev); + enic_admin_channel_close(enic); + kfree(enic->vf_state); + enic->vf_state = NULL; + enic->num_vfs = 0; +} + +/* + * enic_sriov_configure() and its V2 helpers are defined but not yet wired + * into enic_driver via .sriov_configure (see the __maybe_unused annotations); + * V2 enable/disable is activated in a follow-up series. Because the callback + * is not registered, it cannot run concurrently with the rtnl-protected reset + * paths (enic_reset(), enic_tx_hang_reset()) yet. Serialization against those + * paths is added together with the .sriov_configure wiring in that series. + */ +static int __maybe_unused +enic_sriov_configure(struct pci_dev *pdev, int num_vfs) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct enic *enic = netdev_priv(netdev); + struct enic_port_profile *pp; + int err; + + if (num_vfs > 0) { + if (enic->config.mq_subvnic_count) { + netdev_err(netdev, + "SR-IOV not supported with multi-queue sub-vnics\n"); + return -EOPNOTSUPP; + } + + if (enic->vf_type == ENIC_VF_TYPE_NONE) { + netdev_err(netdev, + "SR-IOV not supported on this firmware version\n"); + return -EOPNOTSUPP; + } + + if (enic->vf_type == ENIC_VF_TYPE_V2) + return enic_sriov_v2_enable(enic, num_vfs); + + pp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL); + if (!pp) + return -ENOMEM; + + err = pci_enable_sriov(pdev, num_vfs); + if (err) { + kfree(pp); + return err; + } + + kfree(enic->pp); + enic->pp = pp; + enic->num_vfs = num_vfs; + enic->priv_flags |= ENIC_SRIOV_ENABLED; + return num_vfs; + } + + if (!enic_sriov_enabled(enic)) + return 0; + + if (enic->vf_type == ENIC_VF_TYPE_V2) { + enic_sriov_v2_disable(enic); + return 0; + } + + pp = kzalloc_obj(*enic->pp, GFP_KERNEL); + if (!pp) + return -ENOMEM; + + pci_disable_sriov(pdev); + enic->num_vfs = 0; + enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + + kfree(enic->pp); + enic->pp = pp; + + return 0; } #endif @@ -2768,12 +3082,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_vnic_unregister; #ifdef CONFIG_PCI_IOV - /* Get number of subvnics */ + enic_sriov_detect_vf_type(enic); + + /* Auto-enable SR-IOV only for the legacy VF types. V2 VFs require + * the admin channel, which is not yet set up at probe time (V2 SR-IOV + * will be enabled through the sysfs .sriov_configure callback once a + * follow-up series wires it up); and a V2-capable device whose + * firmware lacks V2 support is downgraded to ENIC_VF_TYPE_NONE by + * enic_sriov_detect_vf_type() and must not be brought up through the + * legacy pci_enable_sriov() path either. + */ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV); if (pos) { pci_read_config_word(pdev, pos + PCI_SRIOV_TOTAL_VF, &enic->num_vfs); - if (enic->num_vfs) { + if (enic->num_vfs && + (enic->vf_type == ENIC_VF_TYPE_V1 || + enic->vf_type == ENIC_VF_TYPE_USNIC)) { err = pci_enable_sriov(pdev, enic->num_vfs); if (err) { dev_err(dev, "SRIOV enable failed, aborting." @@ -2785,7 +3110,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) num_pps = enic->num_vfs; } } - enic_sriov_detect_vf_type(enic); #endif /* Allocate structure for port profiles */ @@ -2850,6 +3174,42 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_dev_close; } + /* Initialise link_notify_work before the V2-VF admin-open block below: + * its error path (err_out_admin_close -> enic_admin_channel_close() -> + * cancel_work_sync()) would otherwise act on an uninitialised work. + */ + INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler); + + /* V2 VF: open admin channel and register with PF. + * Must happen before register_netdev so the VF is fully + * initialized before the interface is visible to userspace. + * + * enic_mbox_init() installs the receive handler and resets the + * sequence number; it must run before enic_admin_channel_open() + * unmasks the admin interrupt so an early completion is not dropped. + */ + if (enic_is_sriov_vf_v2(enic)) { + enic_mbox_init(enic); + err = enic_admin_channel_open(enic); + if (err) { + dev_err(dev, + "Failed to open admin channel: %d\n", err); + goto err_out_dev_deinit; + } + err = enic_mbox_vf_capability_check(enic); + if (err) { + dev_err(dev, + "MBOX capability check failed: %d\n", err); + goto err_out_admin_close; + } + err = enic_mbox_vf_register(enic); + if (err) { + dev_err(dev, + "MBOX VF registration failed: %d\n", err); + goto err_out_admin_close; + } + } + netif_set_real_num_tx_queues(netdev, enic->wq_count); netif_set_real_num_rx_queues(netdev, enic->rq_count); @@ -2874,7 +3234,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = enic_set_mac_addr(netdev, enic->mac_addr); if (err) { dev_err(dev, "Invalid MAC address, aborting\n"); - goto err_out_dev_deinit; + goto err_out_admin_close; } enic->tx_coalesce_usecs = enic->config.intr_timer_usec; @@ -2972,11 +3332,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = register_netdev(netdev); if (err) { dev_err(dev, "Cannot register net device, aborting\n"); - goto err_out_dev_deinit; + goto err_out_admin_close; } return 0; +err_out_admin_close: + if (enic_is_sriov_vf_v2(enic)) { + if (enic->vf_registered) { + int unreg_err = enic_mbox_vf_unregister(enic); + + if (unreg_err) + netdev_warn(netdev, + "Failed to unregister from PF: %d\n", + unreg_err); + } + enic_admin_channel_close(enic); + } err_out_dev_deinit: enic_dev_deinit(enic); err_out_dev_close: @@ -3014,15 +3386,40 @@ static void enic_remove(struct pci_dev *pdev) disable_work_sync(&enic->reset); disable_work_sync(&enic->tx_hang_reset); disable_work_sync(&enic->change_mtu_work); + + /* Close the admin channel and unregister from the PF before + * unregister_netdev() to prevent a late PF notification from + * touching a netdev that is being torn down. + */ + if (enic_is_sriov_vf_v2(enic)) { + if (enic->vf_registered) { + int unreg_err = enic_mbox_vf_unregister(enic); + + if (unreg_err) + netdev_warn(netdev, + "Failed to unregister from PF: %d\n", + unreg_err); + } + enic_admin_channel_close(enic); + } + unregister_netdev(netdev); - enic_dev_deinit(enic); - vnic_dev_close(enic->vdev); + /* unregister_netdev() -> enic_stop() stops the notify timer, so + * no new link_notify_work can be queued past this point. Cancel + * unconditionally to cover the narrow window where + * enic_link_check() scheduled it just as SR-IOV was disabled. + */ + cancel_work_sync(&enic->link_notify_work); #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { - pci_disable_sriov(pdev); - enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + if (enic->vf_type == ENIC_VF_TYPE_V2) + enic_sriov_v2_disable(enic); + else + pci_disable_sriov(pdev); } #endif + enic_dev_deinit(enic); + vnic_dev_close(enic->vdev); kfree(enic->pp); vnic_dev_unregister(enic->vdev); enic_iounmap(enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c new file mode 100644 index 000000000000..2fb0f1e2ff50 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2025 Cisco Systems, Inc. All rights reserved. + +#include +#include +#include +#include +#include + +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_cq.h" +#include "enic.h" +#include "enic_admin.h" +#include "enic_mbox.h" +#include "wq_enet_desc.h" + +#define ENIC_MBOX_POLL_TIMEOUT_US 5000000 +#define ENIC_MBOX_POLL_INTERVAL_US 100 + +static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr, + u8 msg_type, u16 dst_vnic_id, u16 msg_len) +{ + memset(hdr, 0, sizeof(*hdr)); + hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id); + hdr->msg_type = msg_type; + hdr->msg_len = cpu_to_le16(msg_len); + hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num); +} + +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len) +{ + size_t total_len = sizeof(struct enic_mbox_hdr) + payload_len; + struct vnic_wq *wq = &enic->admin_wq; + struct wq_enet_desc *desc; + unsigned long timeout; + dma_addr_t dma_addr; + u16 vlan_tag; + void *buf; + int err; + + /* Reject payloads that cannot fit in a single admin buffer. Checked + * before taking mbox_lock; total_len is computed as size_t so the + * sizeof() + payload_len sum cannot wrap. + */ + if (payload_len > ENIC_ADMIN_BUF_SIZE - sizeof(struct enic_mbox_hdr)) + return -EINVAL; + + /* Serialize MBOX sends. The admin channel is a low-frequency + * control path; holding the mutex across the poll is acceptable. + */ + mutex_lock(&enic->mbox_lock); + + if (!enic->has_admin_channel || READ_ONCE(enic->mbox_send_disabled)) { + err = -ENODEV; + goto unlock; + } + + if (vnic_wq_desc_avail(wq) == 0) { + err = -ENOSPC; + goto unlock; + } + + buf = kmalloc(total_len, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto unlock; + } + + enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len); + if (payload_len) { + void *dst = buf + sizeof(struct enic_mbox_hdr); + + memcpy(dst, payload, payload_len); + } + + dma_addr = dma_map_single(&enic->pdev->dev, buf, total_len, + DMA_TO_DEVICE); + if (dma_mapping_error(&enic->pdev->dev, dma_addr)) { + kfree(buf); + err = -ENOMEM; + goto unlock; + } + + /* Firmware uses vlan field for routing: 0 = PF, 1-based = VF index */ + if (dst_vnic_id == ENIC_MBOX_DST_PF) + vlan_tag = 0; + else + vlan_tag = dst_vnic_id + 1; + + desc = vnic_wq_next_desc(wq); + wq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET, + total_len, + 0, 0, 0, /* mss, hdr_len, offload_mode */ + 1, 1, /* eop, cq_entry */ + 0, /* fcoe_encap */ + 1, vlan_tag, /* vlan_tag_insert, vlan_tag */ + 0); /* loopback */ + vnic_wq_post(wq, buf, dma_addr, total_len, + 1, 1, /* sop, eop */ + 1, 1, /* desc_skip_cnt, cq_entry */ + 0, 0); /* compressed_send, wrid */ + vnic_wq_doorbell(wq); + + timeout = jiffies + usecs_to_jiffies(ENIC_MBOX_POLL_TIMEOUT_US); + err = -ETIMEDOUT; + while (time_before(jiffies, timeout)) { + if (enic_admin_wq_cq_service(enic)) { + err = 0; + break; + } + usleep_range(ENIC_MBOX_POLL_INTERVAL_US, + ENIC_MBOX_POLL_INTERVAL_US + 50); + } + /* Final check in case completion arrived during the last sleep */ + if (err && enic_admin_wq_cq_service(enic)) + err = 0; + + if (!err) { + wq->to_clean = wq->to_clean->next; + wq->ring.desc_avail++; + dma_unmap_single(&enic->pdev->dev, dma_addr, total_len, + DMA_TO_DEVICE); + kfree(buf); + } else { + netdev_err(enic->netdev, + "MBOX send timed out (type %u dst %u), disabling channel\n", + msg_type, dst_vnic_id); + /* + * The WQ descriptor is still live in hardware. Do not unmap + * or free the buffer: the device may still DMA from dma_addr. + * Mark the channel unusable so no further sends are attempted. + */ + WRITE_ONCE(enic->mbox_send_disabled, true); + } + + netdev_dbg(enic->netdev, + "MBOX send msg_type %u dst %u vlan %u err %d\n", + msg_type, dst_vnic_id, vlan_tag, err); +unlock: + mutex_unlock(&enic->mbox_lock); + return err; +} + +static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms) +{ + unsigned long left; + + left = wait_for_completion_timeout(&enic->mbox_comp, + msecs_to_jiffies(timeout_ms)); + + return left ? 0 : -ETIMEDOUT; +} + +int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) +{ + struct enic_mbox_pf_link_state_notif_msg notif = {}; + + if (!enic->vf_state || vf_id >= enic->num_vfs || + !enic->vf_state[vf_id].registered) { + netdev_dbg(enic->netdev, + "MBOX: skip link state to unregistered VF %u\n", + vf_id); + return 0; + } + + notif.link_state = cpu_to_le32(link_state); + return enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_NOTIF, vf_id, + ¬if, sizeof(notif)); +} + +static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_capability_reply_msg reply = {}; + + reply.reply.ret_major = cpu_to_le16(0); + reply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); + + return enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, + &reply, sizeof(reply)); +} + +static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_register_reply_msg reply = {}; + u32 link_state; + int err; + + if (!enic->vf_state || vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: register from invalid VF %u\n", + vf_id); + return -EINVAL; + } + + /* VF re-registering (e.g. guest reboot without clean unregister): + * mark the previous registration inactive before accepting the new one. + */ + if (enic->vf_state[vf_id].registered) { + netdev_dbg(enic->netdev, + "MBOX: VF %u re-register, cleaning previous state\n", + vf_id); + enic->vf_state[vf_id].registered = false; + } + + reply.reply.ret_major = cpu_to_le16(0); + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, + &reply, sizeof(reply)); + if (err) + return err; + + enic->vf_state[vf_id].registered = true; + if (net_ratelimit()) + netdev_info(enic->netdev, "VF %u registered via MBOX\n", vf_id); + + link_state = netif_carrier_ok(enic->netdev) ? + ENIC_MBOX_LINK_STATE_ENABLE : + ENIC_MBOX_LINK_STATE_DISABLE; + err = enic_mbox_send_link_state(enic, vf_id, link_state); + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "VF %u: failed to send initial link state: %d\n", + vf_id, err); + /* Registration succeeded; initial link state notification attempted + * above. Subsequent link state changes are sent from the PF + * when enic_link_check() detects carrier changes. + */ + return 0; +} + +static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_register_reply_msg reply = {}; + int err; + + if (!enic->vf_state || vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: unregister from invalid VF %u\n", + vf_id); + return -EINVAL; + } + + /* VF is unloading; clear local state regardless of whether + * the reply is successfully delivered to avoid the PF treating + * a dead VF as still registered. + */ + enic->vf_state[vf_id].registered = false; + + reply.reply.ret_major = cpu_to_le16(0); + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, + &reply, sizeof(reply)); + + if (net_ratelimit()) + netdev_info(enic->netdev, + "VF %u unregistered via MBOX\n", vf_id); + + return err; +} + +static void enic_mbox_pf_process_msg(struct enic *enic, + struct enic_mbox_hdr *hdr, void *payload) +{ + u16 vf_id = le16_to_cpu(hdr->src_vnic_id); + u16 msg_len = le16_to_cpu(hdr->msg_len); + int err = 0; + + if (!enic->vf_state) { + netdev_dbg(enic->netdev, + "MBOX: PF received msg but SRIOV not active\n"); + return; + } + + if (vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: PF received msg from invalid VF %u\n", + vf_id); + return; + } + + switch (hdr->msg_type) { + case ENIC_MBOX_VF_CAPABILITY_REQUEST: + err = enic_mbox_pf_handle_capability(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_VF_REGISTER_REQUEST: + err = enic_mbox_pf_handle_register(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_VF_UNREGISTER_REQUEST: + err = enic_mbox_pf_handle_unregister(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_PF_LINK_STATE_ACK: { + struct enic_mbox_pf_link_state_ack_msg *ack = payload; + + if (msg_len < sizeof(*hdr) + sizeof(*ack)) + break; + if (le16_to_cpu(ack->ack.ret_major) && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: VF %u link state ACK error %u/%u\n", + vf_id, + le16_to_cpu(ack->ack.ret_major), + le16_to_cpu(ack->ack.ret_minor)); + break; + } + default: + netdev_dbg(enic->netdev, + "MBOX: PF unhandled msg type %u from VF %u\n", + hdr->msg_type, vf_id); + err = -EOPNOTSUPP; + break; + } + + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: PF handler for msg type %u from VF %u failed: %d\n", + hdr->msg_type, vf_id, err); +} + +static void enic_mbox_vf_handle_capability_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_capability_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale capability reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major) == 0) + enic->pf_cap_version = le32_to_cpu(reply->version); + else + netdev_warn(enic->netdev, + "MBOX: PF rejected capability request: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_register_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_register_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale register reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major)) { + netdev_warn(enic->netdev, + "MBOX: VF register rejected by PF: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + } else { + enic->vf_registered = true; + } + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_unregister_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_register_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale unregister reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major)) { + netdev_warn(enic->netdev, + "MBOX: VF unregister rejected by PF: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + } else { + enic->vf_registered = false; + } + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) +{ + struct enic_mbox_pf_link_state_notif_msg *notif = payload; + struct enic_mbox_pf_link_state_ack_msg ack = {}; + int err; + + switch (le32_to_cpu(notif->link_state)) { + case ENIC_MBOX_LINK_STATE_ENABLE: + if (!netif_carrier_ok(enic->netdev)) + netif_carrier_on(enic->netdev); + netdev_dbg(enic->netdev, "MBOX: link state -> UP\n"); + break; + case ENIC_MBOX_LINK_STATE_DISABLE: + if (netif_carrier_ok(enic->netdev)) + netif_carrier_off(enic->netdev); + netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n"); + break; + default: + netdev_warn(enic->netdev, "MBOX: unknown link state %u\n", + le32_to_cpu(notif->link_state)); + ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC); + break; + } + + err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, + ENIC_MBOX_DST_PF, &ack, sizeof(ack)); + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: failed to send link state ACK: %d\n", err); +} + +static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type, + u16 payload_len, size_t min_len) +{ + if (payload_len < min_len) { + netdev_warn(enic->netdev, + "MBOX: short payload for type %u (%u < %zu)\n", + msg_type, payload_len, min_len); + return false; + } + return true; +} + +static void enic_mbox_vf_process_msg(struct enic *enic, + struct enic_mbox_hdr *hdr, void *payload, + u16 payload_len) +{ + switch (hdr->msg_type) { + case ENIC_MBOX_VF_CAPABILITY_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_capability_reply(enic, payload); + break; + } + case ENIC_MBOX_VF_REGISTER_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_register_reply(enic, payload); + break; + } + case ENIC_MBOX_VF_UNREGISTER_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_unregister_reply(enic, payload); + break; + } + case ENIC_MBOX_PF_LINK_STATE_NOTIF: { + size_t exp = sizeof(struct enic_mbox_pf_link_state_notif_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_link_state(enic, payload); + break; + } + default: + netdev_dbg(enic->netdev, + "MBOX: VF unhandled msg type %u\n", + hdr->msg_type); + break; + } +} + +static void enic_mbox_recv_handler(struct enic *enic, void *buf, + unsigned int len) +{ + struct enic_mbox_hdr *hdr = buf; + void *payload; + u16 msg_len; + + if (len < sizeof(*hdr)) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: truncated message (len %u < %zu)\n", + len, sizeof(*hdr)); + return; + } + + if (hdr->msg_type >= ENIC_MBOX_MAX) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: unknown msg type %u\n", + hdr->msg_type); + return; + } + + msg_len = le16_to_cpu(hdr->msg_len); + if (msg_len < sizeof(*hdr) || msg_len > len) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: invalid msg_len %u (buf len %u)\n", + msg_len, len); + return; + } + + netdev_dbg(enic->netdev, + "MBOX recv: type %u from vnic %u len %u\n", + hdr->msg_type, le16_to_cpu(hdr->src_vnic_id), msg_len); + + payload = buf + sizeof(*hdr); + + if (enic->vf_state) { + enic_mbox_pf_process_msg(enic, hdr, payload); + } else if (le16_to_cpu(hdr->src_vnic_id) == ENIC_MBOX_DST_PF) { + /* src_vnic_id was overwritten from the hardware-verified CQ + * VLAN sender field, so a VF only accepts messages that the + * adapter attributes to the PF. Its sole admin-channel peer is + * the PF; drop anything else as a spoofed notification. + */ + enic_mbox_vf_process_msg(enic, hdr, payload, + msg_len - (u16)sizeof(*hdr)); + } else if (net_ratelimit()) { + netdev_warn(enic->netdev, + "MBOX: VF dropping non-PF message from vnic %u\n", + le16_to_cpu(hdr->src_vnic_id)); + } +} + +int enic_mbox_vf_capability_check(struct enic *enic) +{ + struct enic_mbox_vf_capability_msg req = {}; + int err; + + enic->pf_cap_version = 0; + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_CAPABILITY_REPLY); + req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST, + ENIC_MBOX_DST_PF, &req, sizeof(req)); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) { + netdev_warn(enic->netdev, + "MBOX: no capability reply from PF\n"); + return err; + } + + if (enic->pf_cap_version < ENIC_MBOX_CAP_VERSION_1) { + netdev_warn(enic->netdev, + "MBOX: PF rejected capability request or reported unsupported version %u\n", + enic->pf_cap_version); + return -EOPNOTSUPP; + } + + return 0; +} + +int enic_mbox_vf_register(struct enic *enic) +{ + int err; + + enic->vf_registered = false; + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_REGISTER_REPLY); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST, + ENIC_MBOX_DST_PF, NULL, 0); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) { + netdev_warn(enic->netdev, + "MBOX: VF registration with PF timed out\n"); + return err; + } + + if (!enic->vf_registered) + return -ENODEV; + + return 0; +} + +int enic_mbox_vf_unregister(struct enic *enic) +{ + int err; + + if (!enic->vf_registered) + return 0; + + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_UNREGISTER_REPLY); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, + ENIC_MBOX_DST_PF, NULL, 0); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) + return err; + if (enic->vf_registered) + return -EACCES; + return 0; +} + +void enic_mbox_init(struct enic *enic) +{ + /* mbox_lock and mbox_comp must be initialized exactly once per + * device lifetime; the PF sriov_configure path can re-enter this + * on each enable cycle where these primitives are already set up. + */ + if (!enic->mbox_initialized) { + mutex_init(&enic->mbox_lock); + init_completion(&enic->mbox_comp); + enic->mbox_initialized = true; + } else { + reinit_completion(&enic->mbox_comp); + } + enic->mbox_msg_num = 0; + enic->admin_rq_handler = enic_mbox_recv_handler; +} diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h new file mode 100644 index 000000000000..15e30ee2b0ed --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */ + +#ifndef _ENIC_MBOX_H_ +#define _ENIC_MBOX_H_ + +#include +#include + +/* + * Mailbox protocol for PF-VF communication over the admin channel. + * + * Even numbers are requests, odd numbers are replies/acks. + * The prefix indicates the initiator: VF_ = VF-initiated, PF_ = PF-initiated. + */ +enum enic_mbox_msg_type { + ENIC_MBOX_VF_CAPABILITY_REQUEST = 0, + ENIC_MBOX_VF_CAPABILITY_REPLY = 1, + ENIC_MBOX_VF_REGISTER_REQUEST = 2, + ENIC_MBOX_VF_REGISTER_REPLY = 3, + ENIC_MBOX_VF_UNREGISTER_REQUEST = 4, + ENIC_MBOX_VF_UNREGISTER_REPLY = 5, + ENIC_MBOX_PF_LINK_STATE_NOTIF = 6, + ENIC_MBOX_PF_LINK_STATE_ACK = 7, + ENIC_MBOX_MAX +}; + +struct enic_mbox_hdr { + __le16 src_vnic_id; + __le16 dst_vnic_id; + u8 msg_type; + u8 flags; + __le16 msg_len; + __le64 msg_num; +}; + +struct enic_mbox_generic_reply { + __le16 ret_major; + __le16 ret_minor; +}; + +#define ENIC_MBOX_ERR_GENERIC BIT(0) +#define ENIC_MBOX_ERR_VF_NOT_REGISTERED BIT(1) +#define ENIC_MBOX_ERR_MSG_NOT_SUPPORTED BIT(2) + +/* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */ +#define ENIC_MBOX_CAP_VERSION_0 0 +#define ENIC_MBOX_CAP_VERSION_1 1 + +struct enic_mbox_vf_capability_msg { + __le32 version; + __le32 reserved[32]; +}; + +/* The embedded enic_mbox_generic_reply has 2-byte alignment, but the + * __le32 members give this struct 4-byte natural alignment. Receive + * buffers come from kmalloc (>= 8-byte aligned), so there is no + * misaligned access risk when casting from the receive buffer. + */ +struct enic_mbox_vf_capability_reply_msg { + struct enic_mbox_generic_reply reply; + __le32 version; + __le32 reserved[32]; +}; + +/* ENIC_MBOX_VF_REGISTER / _UNREGISTER */ +struct enic_mbox_vf_register_reply_msg { + struct enic_mbox_generic_reply reply; +}; + +/* ENIC_MBOX_PF_LINK_STATE_NOTIF / _ACK */ +#define ENIC_MBOX_LINK_STATE_DISABLE 0 +#define ENIC_MBOX_LINK_STATE_ENABLE 1 + +struct enic_mbox_pf_link_state_notif_msg { + __le32 link_state; +}; + +struct enic_mbox_pf_link_state_ack_msg { + struct enic_mbox_generic_reply ack; +}; + +#define ENIC_MBOX_DST_PF 0xFFFF + +struct enic; + +void enic_mbox_init(struct enic *enic); +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len); +int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state); +int enic_mbox_vf_capability_check(struct enic *enic); +int enic_mbox_vf_register(struct enic *enic); +int enic_mbox_vf_unregister(struct enic *enic); + +#endif /* _ENIC_MBOX_H_ */ diff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c index 4720a952725d..3f611e240c25 100644 --- a/drivers/net/ethernet/cisco/enic/enic_pp.c +++ b/drivers/net/ethernet/cisco/enic/enic_pp.c @@ -25,6 +25,11 @@ int enic_is_valid_pp_vf(struct enic *enic, int vf, int *err) if (vf != PORT_SELF_VF) { #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { + /* V2 SR-IOV uses MBOX, not port profiles */ + if (enic->vf_type == ENIC_VF_TYPE_V2) { + *err = -EOPNOTSUPP; + goto err_out; + } if (vf < 0 || vf >= enic->num_vfs) { *err = -EINVAL; goto err_out; diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c index 2b7545d6a67f..74cd2ee3af5c 100644 --- a/drivers/net/ethernet/cisco/enic/enic_res.c +++ b/drivers/net/ethernet/cisco/enic/enic_res.c @@ -59,6 +59,7 @@ int enic_get_vnic_config(struct enic *enic) GET_CONFIG(intr_timer_usec); GET_CONFIG(loop_tag); GET_CONFIG(num_arfs); + GET_CONFIG(mq_subvnic_count); GET_CONFIG(max_rq_ring); GET_CONFIG(max_wq_ring); GET_CONFIG(max_cq_ring); @@ -210,7 +211,8 @@ void enic_get_res_counts(struct enic *enic) vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_RQ) >= 1 && vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_CQ) >= ARRAY_SIZE(enic->admin_cq) && - vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1; + (enic_is_sriov_vf_v2(enic) || + vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1); dev_info(enic_get_dev(enic), "vNIC resources avail: wq %d rq %d cq %d intr %d admin %s\n", diff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h index d46d4d2ef6bb..35ffa3230713 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_cq.h +++ b/drivers/net/ethernet/cisco/enic/vnic_cq.h @@ -76,6 +76,15 @@ int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, int vnic_cq_alloc_with_type(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, unsigned int desc_count, unsigned int desc_size, unsigned int res_type); +#define VNIC_CQ_FC_ENABLE 1 +#define VNIC_CQ_FC_DISABLE 0 +#define VNIC_CQ_COLOR_ENABLE 1 +#define VNIC_CQ_INTR_ENABLE 1 +#define VNIC_CQ_INTR_DISABLE 0 +#define VNIC_CQ_ENTRY_ENABLE 1 +#define VNIC_CQ_MSG_ENABLE 1 +#define VNIC_CQ_MSG_DISABLE 0 + void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable, unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail, unsigned int cq_tail_color, unsigned int interrupt_enable, diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h index 605ef17f967e..90ca06691ebd 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h +++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h @@ -455,8 +455,19 @@ enum vnic_devcmd_cmd { */ CMD_CQ_ENTRY_SIZE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 90), + /* + * Set queue pair type (admin or data) + * in: (u32) a0 = queue pair type (0 = admin, 1 = data) + * in: (u32) a1 = enable (1) / disable (0) + */ + CMD_QP_TYPE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 97), }; +#define QP_TYPE_ADMIN 0 +#define QP_TYPE_DATA 1 +#define QP_ENABLE 1 +#define QP_DISABLE 0 + /* CMD_ENABLE2 flags */ #define CMD_ENABLE2_STANDBY 0x0 #define CMD_ENABLE2_ACTIVE 0x1 @@ -734,6 +745,8 @@ enum vic_feature_t { VIC_FEATURE_VXLAN, VIC_FEATURE_RDMA, VIC_FEATURE_VXLAN_PATCH, + /* slot 3 reserved for firmware VIC_FEATURE_PTP */ + VIC_FEATURE_SRIOV = 4, VIC_FEATURE_MAX, }; diff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h index 9e8e86262a3f..519d2969990b 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_enet.h +++ b/drivers/net/ethernet/cisco/enic/vnic_enet.h @@ -21,7 +21,9 @@ struct vnic_enet_config { u16 loop_tag; u16 vf_rq_count; u16 num_arfs; - u8 reserved[66]; + u8 reserved1[32]; + u16 mq_subvnic_count; + u8 reserved2[32]; u32 max_rq_ring; // MAX RQ ring size u32 max_wq_ring; // MAX WQ ring size u32 max_cq_ring; // MAX CQ ring size diff --git a/drivers/net/ethernet/dec/tulip/Kconfig b/drivers/net/ethernet/dec/tulip/Kconfig index 078a12f07e96..324608611a63 100644 --- a/drivers/net/ethernet/dec/tulip/Kconfig +++ b/drivers/net/ethernet/dec/tulip/Kconfig @@ -5,9 +5,9 @@ config NET_TULIP bool "DEC - Tulip devices" - depends on (PCI || EISA || CARDBUS) + depends on PCI help - This selects the "Tulip" family of EISA/PCI network cards. + This selects the "Tulip" family of PCI network cards. if NET_TULIP @@ -139,16 +139,4 @@ config ULI526X To compile this driver as a module, choose M here. The module will be called uli526x. -config PCMCIA_XIRCOM - tristate "Xircom CardBus support" - depends on CARDBUS - help - This driver is for the Digital "Tulip" Ethernet CardBus adapters. - It should work with most DEC 21*4*-based chips/ethercards, as well - as with work-alike chips from Lite-On (PNIC) and Macronix (MXIC) and - ASIX. - - To compile this driver as a module, choose M here. The module will - be called xircom_cb. If unsure, say N. - endif # NET_TULIP diff --git a/drivers/net/ethernet/dec/tulip/Makefile b/drivers/net/ethernet/dec/tulip/Makefile index d4f1d21d29a0..5d999d72840f 100644 --- a/drivers/net/ethernet/dec/tulip/Makefile +++ b/drivers/net/ethernet/dec/tulip/Makefile @@ -5,7 +5,6 @@ ccflags-$(CONFIG_NET_TULIP) := -DDEBUG -obj-$(CONFIG_PCMCIA_XIRCOM) += xircom_cb.o obj-$(CONFIG_DM9102) += dmfe.o obj-$(CONFIG_WINBOND_840) += winbond-840.o obj-$(CONFIG_DE2104X) += de2104x.o diff --git a/drivers/net/ethernet/dec/tulip/xircom_cb.c b/drivers/net/ethernet/dec/tulip/xircom_cb.c deleted file mode 100644 index e5d2ede13845..000000000000 --- a/drivers/net/ethernet/dec/tulip/xircom_cb.c +++ /dev/null @@ -1,1172 +0,0 @@ -/* - * xircom_cb: A driver for the (tulip-like) Xircom Cardbus ethernet cards - * - * This software is (C) by the respective authors, and licensed under the GPL - * License. - * - * Written by Arjan van de Ven for Red Hat, Inc. - * Based on work by Jeff Garzik, Doug Ledford and Donald Becker - * - * This software may be used and distributed according to the terms - * of the GNU General Public License, incorporated herein by reference. - * - * - * $Id: xircom_cb.c,v 1.33 2001/03/19 14:02:07 arjanv Exp $ - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef CONFIG_NET_POLL_CONTROLLER -#include -#endif - -MODULE_DESCRIPTION("Xircom Cardbus ethernet driver"); -MODULE_AUTHOR("Arjan van de Ven "); -MODULE_LICENSE("GPL"); - -#define xw32(reg, val) iowrite32(val, ioaddr + (reg)) -#define xr32(reg) ioread32(ioaddr + (reg)) -#define xr8(reg) ioread8(ioaddr + (reg)) - -/* IO registers on the card, offsets */ -#define CSR0 0x00 -#define CSR1 0x08 -#define CSR2 0x10 -#define CSR3 0x18 -#define CSR4 0x20 -#define CSR5 0x28 -#define CSR6 0x30 -#define CSR7 0x38 -#define CSR8 0x40 -#define CSR9 0x48 -#define CSR10 0x50 -#define CSR11 0x58 -#define CSR12 0x60 -#define CSR13 0x68 -#define CSR14 0x70 -#define CSR15 0x78 -#define CSR16 0x80 - -/* PCI registers */ -#define PCI_POWERMGMT 0x40 - -/* Offsets of the buffers within the descriptor pages, in bytes */ - -#define NUMDESCRIPTORS 4 - -static int bufferoffsets[NUMDESCRIPTORS] = {128,2048,4096,6144}; - - -struct xircom_private { - /* Send and receive buffers, kernel-addressable and dma addressable forms */ - - __le32 *rx_buffer; - __le32 *tx_buffer; - - dma_addr_t rx_dma_handle; - dma_addr_t tx_dma_handle; - - struct sk_buff *tx_skb[4]; - - void __iomem *ioaddr; - int open; - - /* transmit_used is the rotating counter that indicates which transmit - descriptor has to be used next */ - int transmit_used; - - /* Spinlock to serialize register operations. - It must be helt while manipulating the following registers: - CSR0, CSR6, CSR7, CSR9, CSR10, CSR15 - */ - spinlock_t lock; - - struct pci_dev *pdev; - struct net_device *dev; -}; - - -/* Function prototypes */ -static int xircom_probe(struct pci_dev *pdev, const struct pci_device_id *id); -static void xircom_remove(struct pci_dev *pdev); -static irqreturn_t xircom_interrupt(int irq, void *dev_instance); -static netdev_tx_t xircom_start_xmit(struct sk_buff *skb, - struct net_device *dev); -static int xircom_open(struct net_device *dev); -static int xircom_close(struct net_device *dev); -static void xircom_up(struct xircom_private *card); -#ifdef CONFIG_NET_POLL_CONTROLLER -static void xircom_poll_controller(struct net_device *dev); -#endif - -static void investigate_read_descriptor(struct net_device *dev,struct xircom_private *card, int descnr, unsigned int bufferoffset); -static void investigate_write_descriptor(struct net_device *dev, struct xircom_private *card, int descnr, unsigned int bufferoffset); -static void read_mac_address(struct xircom_private *card); -static void transceiver_voodoo(struct xircom_private *card); -static void initialize_card(struct xircom_private *card); -static void trigger_transmit(struct xircom_private *card); -static void trigger_receive(struct xircom_private *card); -static void setup_descriptors(struct xircom_private *card); -static void remove_descriptors(struct xircom_private *card); -static int link_status_changed(struct xircom_private *card); -static void activate_receiver(struct xircom_private *card); -static void deactivate_receiver(struct xircom_private *card); -static void activate_transmitter(struct xircom_private *card); -static void deactivate_transmitter(struct xircom_private *card); -static void enable_transmit_interrupt(struct xircom_private *card); -static void enable_receive_interrupt(struct xircom_private *card); -static void enable_link_interrupt(struct xircom_private *card); -static void disable_all_interrupts(struct xircom_private *card); -static int link_status(struct xircom_private *card); - - - -static const struct pci_device_id xircom_pci_table[] = { - { PCI_VDEVICE(XIRCOM, 0x0003), }, - {0,}, -}; -MODULE_DEVICE_TABLE(pci, xircom_pci_table); - -static struct pci_driver xircom_driver = { - .name = "xircom_cb", - .id_table = xircom_pci_table, - .probe = xircom_probe, - .remove = xircom_remove, -}; - - -#if defined DEBUG && DEBUG > 1 -static void print_binary(unsigned int number) -{ - int i,i2; - char buffer[64]; - memset(buffer,0,64); - i2=0; - for (i=31;i>=0;i--) { - if (number & (1<dev; - struct net_device *dev = NULL; - struct xircom_private *private; - unsigned long flags; - unsigned short tmp16; - int rc; - - /* First do the PCI initialisation */ - - rc = pci_enable_device(pdev); - if (rc < 0) - goto out; - - /* disable all powermanagement */ - pci_write_config_dword(pdev, PCI_POWERMGMT, 0x0000); - - pci_set_master(pdev); /* Why isn't this done by pci_enable_device ?*/ - - /* clear PCI status, if any */ - pci_read_config_word (pdev,PCI_STATUS, &tmp16); - pci_write_config_word (pdev, PCI_STATUS,tmp16); - - rc = pci_request_regions(pdev, "xircom_cb"); - if (rc < 0) { - pr_err("%s: failed to allocate io-region\n", __func__); - goto err_disable; - } - - rc = -ENOMEM; - /* - Before changing the hardware, allocate the memory. - This way, we can fail gracefully if not enough memory - is available. - */ - dev = alloc_etherdev(sizeof(struct xircom_private)); - if (!dev) - goto err_release; - - private = netdev_priv(dev); - - /* Allocate the send/receive buffers */ - private->rx_buffer = dma_alloc_coherent(d, 8192, - &private->rx_dma_handle, - GFP_KERNEL); - if (private->rx_buffer == NULL) - goto rx_buf_fail; - - private->tx_buffer = dma_alloc_coherent(d, 8192, - &private->tx_dma_handle, - GFP_KERNEL); - if (private->tx_buffer == NULL) - goto tx_buf_fail; - - SET_NETDEV_DEV(dev, &pdev->dev); - - - private->dev = dev; - private->pdev = pdev; - - /* IO range. */ - private->ioaddr = pci_iomap(pdev, 0, 0); - if (!private->ioaddr) - goto reg_fail; - - spin_lock_init(&private->lock); - - initialize_card(private); - read_mac_address(private); - setup_descriptors(private); - - dev->netdev_ops = &netdev_ops; - pci_set_drvdata(pdev, dev); - - rc = register_netdev(dev); - if (rc < 0) { - pr_err("%s: netdevice registration failed\n", __func__); - goto err_unmap; - } - - netdev_info(dev, "Xircom cardbus revision %i at irq %i\n", - pdev->revision, pdev->irq); - /* start the transmitter to get a heartbeat */ - /* TODO: send 2 dummy packets here */ - transceiver_voodoo(private); - - spin_lock_irqsave(&private->lock,flags); - activate_transmitter(private); - activate_receiver(private); - spin_unlock_irqrestore(&private->lock,flags); - - trigger_receive(private); -out: - return rc; - -err_unmap: - pci_iounmap(pdev, private->ioaddr); -reg_fail: - dma_free_coherent(d, 8192, private->tx_buffer, private->tx_dma_handle); -tx_buf_fail: - dma_free_coherent(d, 8192, private->rx_buffer, private->rx_dma_handle); -rx_buf_fail: - free_netdev(dev); -err_release: - pci_release_regions(pdev); -err_disable: - pci_disable_device(pdev); - goto out; -} - - -/* - xircom_remove is called on module-unload or on device-eject. - it unregisters the irq, io-region and network device. - Interrupts and such are already stopped in the "ifconfig ethX down" - code. - */ -static void xircom_remove(struct pci_dev *pdev) -{ - struct net_device *dev = pci_get_drvdata(pdev); - struct xircom_private *card = netdev_priv(dev); - struct device *d = &pdev->dev; - - unregister_netdev(dev); - pci_iounmap(pdev, card->ioaddr); - dma_free_coherent(d, 8192, card->tx_buffer, card->tx_dma_handle); - dma_free_coherent(d, 8192, card->rx_buffer, card->rx_dma_handle); - free_netdev(dev); - pci_release_regions(pdev); - pci_disable_device(pdev); -} - -static irqreturn_t xircom_interrupt(int irq, void *dev_instance) -{ - struct net_device *dev = (struct net_device *) dev_instance; - struct xircom_private *card = netdev_priv(dev); - void __iomem *ioaddr = card->ioaddr; - unsigned int status; - int i; - - spin_lock(&card->lock); - status = xr32(CSR5); - -#if defined DEBUG && DEBUG > 1 - print_binary(status); - pr_debug("tx status 0x%08x 0x%08x\n", - card->tx_buffer[0], card->tx_buffer[4]); - pr_debug("rx status 0x%08x 0x%08x\n", - card->rx_buffer[0], card->rx_buffer[4]); -#endif - /* Handle shared irq and hotplug */ - if (status == 0 || status == 0xffffffff) { - spin_unlock(&card->lock); - return IRQ_NONE; - } - - if (link_status_changed(card)) { - int newlink; - netdev_dbg(dev, "Link status has changed\n"); - newlink = link_status(card); - netdev_info(dev, "Link is %d mbit\n", newlink); - if (newlink) - netif_carrier_on(dev); - else - netif_carrier_off(dev); - - } - - /* Clear all remaining interrupts */ - status |= 0xffffffff; /* FIXME: make this clear only the - real existing bits */ - xw32(CSR5, status); - - - for (i=0;ilock); - return IRQ_HANDLED; -} - -static netdev_tx_t xircom_start_xmit(struct sk_buff *skb, - struct net_device *dev) -{ - struct xircom_private *card; - unsigned long flags; - int nextdescriptor; - int desc; - - card = netdev_priv(dev); - spin_lock_irqsave(&card->lock,flags); - - /* First see if we can free some descriptors */ - for (desc=0;desctransmit_used +1) % (NUMDESCRIPTORS); - desc = card->transmit_used; - - /* only send the packet if the descriptor is free */ - if (card->tx_buffer[4*desc]==0) { - /* Copy the packet data; zero the memory first as the card - sometimes sends more than you ask it to. */ - - memset(&card->tx_buffer[bufferoffsets[desc]/4],0,1536); - skb_copy_from_linear_data(skb, - &(card->tx_buffer[bufferoffsets[desc] / 4]), - skb->len); - /* FIXME: The specification tells us that the length we send HAS to be a multiple of - 4 bytes. */ - - card->tx_buffer[4*desc+1] = cpu_to_le32(skb->len); - if (desc == NUMDESCRIPTORS - 1) /* bit 25: last descriptor of the ring */ - card->tx_buffer[4*desc+1] |= cpu_to_le32(1<<25); - - card->tx_buffer[4*desc+1] |= cpu_to_le32(0xF0000000); - /* 0xF0... means want interrupts*/ - card->tx_skb[desc] = skb; - - wmb(); - /* This gives the descriptor to the card */ - card->tx_buffer[4*desc] = cpu_to_le32(0x80000000); - trigger_transmit(card); - if (card->tx_buffer[nextdescriptor*4] & cpu_to_le32(0x8000000)) { - /* next descriptor is occupied... */ - netif_stop_queue(dev); - } - card->transmit_used = nextdescriptor; - spin_unlock_irqrestore(&card->lock,flags); - return NETDEV_TX_OK; - } - - /* Uh oh... no free descriptor... drop the packet */ - netif_stop_queue(dev); - spin_unlock_irqrestore(&card->lock,flags); - trigger_transmit(card); - - return NETDEV_TX_BUSY; -} - - - - -static int xircom_open(struct net_device *dev) -{ - struct xircom_private *xp = netdev_priv(dev); - const int irq = xp->pdev->irq; - int retval; - - netdev_info(dev, "xircom cardbus adaptor found, using irq %i\n", irq); - retval = request_irq(irq, xircom_interrupt, IRQF_SHARED, dev->name, dev); - if (retval) - return retval; - - xircom_up(xp); - xp->open = 1; - - return 0; -} - -static int xircom_close(struct net_device *dev) -{ - struct xircom_private *card; - unsigned long flags; - - card = netdev_priv(dev); - netif_stop_queue(dev); /* we don't want new packets */ - - - spin_lock_irqsave(&card->lock,flags); - - disable_all_interrupts(card); -#if 0 - /* We can enable this again once we send dummy packets on ifconfig ethX up */ - deactivate_receiver(card); - deactivate_transmitter(card); -#endif - remove_descriptors(card); - - spin_unlock_irqrestore(&card->lock,flags); - - card->open = 0; - free_irq(card->pdev->irq, dev); - - return 0; - -} - - -#ifdef CONFIG_NET_POLL_CONTROLLER -static void xircom_poll_controller(struct net_device *dev) -{ - struct xircom_private *xp = netdev_priv(dev); - const int irq = xp->pdev->irq; - - disable_irq(irq); - xircom_interrupt(irq, dev); - enable_irq(irq); -} -#endif - - -static void initialize_card(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - u32 val; - - spin_lock_irqsave(&card->lock, flags); - - /* First: reset the card */ - val = xr32(CSR0); - val |= 0x01; /* Software reset */ - xw32(CSR0, val); - - udelay(100); /* give the card some time to reset */ - - val = xr32(CSR0); - val &= ~0x01; /* disable Software reset */ - xw32(CSR0, val); - - - val = 0; /* Value 0x00 is a safe and conservative value - for the PCI configuration settings */ - xw32(CSR0, val); - - - disable_all_interrupts(card); - deactivate_receiver(card); - deactivate_transmitter(card); - - spin_unlock_irqrestore(&card->lock, flags); -} - -/* -trigger_transmit causes the card to check for frames to be transmitted. -This is accomplished by writing to the CSR1 port. The documentation -claims that the act of writing is sufficient and that the value is -ignored; I chose zero. -*/ -static void trigger_transmit(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR1, 0); -} - -/* -trigger_receive causes the card to check for empty frames in the -descriptor list in which packets can be received. -This is accomplished by writing to the CSR2 port. The documentation -claims that the act of writing is sufficient and that the value is -ignored; I chose zero. -*/ -static void trigger_receive(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR2, 0); -} - -/* -setup_descriptors initializes the send and receive buffers to be valid -descriptors and programs the addresses into the card. -*/ -static void setup_descriptors(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - u32 address; - int i; - - BUG_ON(card->rx_buffer == NULL); - BUG_ON(card->tx_buffer == NULL); - - /* Receive descriptors */ - memset(card->rx_buffer, 0, 128); /* clear the descriptors */ - for (i=0;i 0x80000000 */ - card->rx_buffer[i*4 + 0] = cpu_to_le32(0x80000000); - /* Rx Descr1: buffer 1 is 1536 bytes, buffer 2 is 0 bytes */ - card->rx_buffer[i*4 + 1] = cpu_to_le32(1536); - if (i == NUMDESCRIPTORS - 1) /* bit 25 is "last descriptor" */ - card->rx_buffer[i*4 + 1] |= cpu_to_le32(1 << 25); - - /* Rx Descr2: address of the buffer - we store the buffer at the 2nd half of the page */ - - address = card->rx_dma_handle; - card->rx_buffer[i*4 + 2] = cpu_to_le32(address + bufferoffsets[i]); - /* Rx Desc3: address of 2nd buffer -> 0 */ - card->rx_buffer[i*4 + 3] = 0; - } - - wmb(); - /* Write the receive descriptor ring address to the card */ - address = card->rx_dma_handle; - xw32(CSR3, address); /* Receive descr list address */ - - - /* transmit descriptors */ - memset(card->tx_buffer, 0, 128); /* clear the descriptors */ - - for (i=0;i 0x00000000 */ - card->tx_buffer[i*4 + 0] = 0x00000000; - /* Tx Descr1: buffer 1 is 1536 bytes, buffer 2 is 0 bytes */ - card->tx_buffer[i*4 + 1] = cpu_to_le32(1536); - if (i == NUMDESCRIPTORS - 1) /* bit 25 is "last descriptor" */ - card->tx_buffer[i*4 + 1] |= cpu_to_le32(1 << 25); - - /* Tx Descr2: address of the buffer - we store the buffer at the 2nd half of the page */ - address = card->tx_dma_handle; - card->tx_buffer[i*4 + 2] = cpu_to_le32(address + bufferoffsets[i]); - /* Tx Desc3: address of 2nd buffer -> 0 */ - card->tx_buffer[i*4 + 3] = 0; - } - - wmb(); - /* wite the transmit descriptor ring to the card */ - address = card->tx_dma_handle; - xw32(CSR4, address); /* xmit descr list address */ -} - -/* -remove_descriptors informs the card the descriptors are no longer -valid by setting the address in the card to 0x00. -*/ -static void remove_descriptors(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = 0; - xw32(CSR3, val); /* Receive descriptor address */ - xw32(CSR4, val); /* Send descriptor address */ -} - -/* -link_status_changed returns 1 if the card has indicated that -the link status has changed. The new link status has to be read from CSR12. - -This function also clears the status-bit. -*/ -static int link_status_changed(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR5); /* Status register */ - if (!(val & (1 << 27))) /* no change */ - return 0; - - /* clear the event by writing a 1 to the bit in the - status register. */ - val = (1 << 27); - xw32(CSR5, val); - - return 1; -} - - -/* -transmit_active returns 1 if the transmitter on the card is -in a non-stopped state. -*/ -static int transmit_active(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - if (!(xr32(CSR5) & (7 << 20))) /* transmitter disabled */ - return 0; - - return 1; -} - -/* -receive_active returns 1 if the receiver on the card is -in a non-stopped state. -*/ -static int receive_active(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - if (!(xr32(CSR5) & (7 << 17))) /* receiver disabled */ - return 0; - - return 1; -} - -/* -activate_receiver enables the receiver on the card. -Before being allowed to active the receiver, the receiver -must be completely de-activated. To achieve this, -this code actually disables the receiver first; then it waits for the -receiver to become inactive, then it activates the receiver and then -it waits for the receiver to be active. - -must be called with the lock held and interrupts disabled. -*/ -static void activate_receiver(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - - /* If the "active" bit is set and the receiver is already - active, no need to do the expensive thing */ - if ((val&2) && (receive_active(card))) - return; - - - val = val & ~2; /* disable the receiver */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, "Receiver failed to deactivate\n"); - } - - /* enable the receiver */ - val = xr32(CSR6); /* Operation mode */ - val = val | 2; /* enable the receiver */ - xw32(CSR6, val); - - /* now wait for the card to activate again */ - counter = 10; - while (counter > 0) { - if (receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Receiver failed to re-activate\n"); - } -} - -/* -deactivate_receiver disables the receiver on the card. -To achieve this this code disables the receiver first; -then it waits for the receiver to become inactive. - -must be called with the lock held and interrupts disabled. -*/ -static void deactivate_receiver(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - val = val & ~2; /* disable the receiver */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, "Receiver failed to deactivate\n"); - } -} - - -/* -activate_transmitter enables the transmitter on the card. -Before being allowed to active the transmitter, the transmitter -must be completely de-activated. To achieve this, -this code actually disables the transmitter first; then it waits for the -transmitter to become inactive, then it activates the transmitter and then -it waits for the transmitter to be active again. - -must be called with the lock held and interrupts disabled. -*/ -static void activate_transmitter(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - - /* If the "active" bit is set and the receiver is already - active, no need to do the expensive thing */ - if ((val&(1<<13)) && (transmit_active(card))) - return; - - val = val & ~(1 << 13); /* disable the transmitter */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to deactivate\n"); - } - - /* enable the transmitter */ - val = xr32(CSR6); /* Operation mode */ - val = val | (1 << 13); /* enable the transmitter */ - xw32(CSR6, val); - - /* now wait for the card to activate again */ - counter = 10; - while (counter > 0) { - if (transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to re-activate\n"); - } -} - -/* -deactivate_transmitter disables the transmitter on the card. -To achieve this this code disables the transmitter first; -then it waits for the transmitter to become inactive. - -must be called with the lock held and interrupts disabled. -*/ -static void deactivate_transmitter(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - val = val & ~2; /* disable the transmitter */ - xw32(CSR6, val); - - counter = 20; - while (counter > 0) { - if (!transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to deactivate\n"); - } -} - - -/* -enable_transmit_interrupt enables the transmit interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_transmit_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val |= 1; /* enable the transmit interrupt */ - xw32(CSR7, val); -} - - -/* -enable_receive_interrupt enables the receive interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_receive_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val = val | (1 << 6); /* enable the receive interrupt */ - xw32(CSR7, val); -} - -/* -enable_link_interrupt enables the link status change interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_link_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val = val | (1 << 27); /* enable the link status chage interrupt */ - xw32(CSR7, val); -} - - - -/* -disable_all_interrupts disables all interrupts - -must be called with the lock held and interrupts disabled. -*/ -static void disable_all_interrupts(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR7, 0); -} - -/* -enable_common_interrupts enables several weird interrupts - -must be called with the lock held and interrupts disabled. -*/ -static void enable_common_interrupts(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val |= (1<<16); /* Normal Interrupt Summary */ - val |= (1<<15); /* Abnormal Interrupt Summary */ - val |= (1<<13); /* Fatal bus error */ - val |= (1<<8); /* Receive Process Stopped */ - val |= (1<<7); /* Receive Buffer Unavailable */ - val |= (1<<5); /* Transmit Underflow */ - val |= (1<<2); /* Transmit Buffer Unavailable */ - val |= (1<<1); /* Transmit Process Stopped */ - xw32(CSR7, val); -} - -/* -enable_promisc starts promisc mode - -must be called with the lock held and interrupts disabled. -*/ -static int enable_promisc(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR6); - val = val | (1 << 6); - xw32(CSR6, val); - - return 1; -} - - - - -/* -link_status() checks the links status and will return 0 for no link, 10 for 10mbit link and 100 for.. guess what. - -Must be called in locked state with interrupts disabled -*/ -static int link_status(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - u8 val; - - val = xr8(CSR12); - - /* bit 2 is 0 for 10mbit link, 1 for not an 10mbit link */ - if (!(val & (1 << 2))) - return 10; - /* bit 1 is 0 for 100mbit link, 1 for not an 100mbit link */ - if (!(val & (1 << 1))) - return 100; - - /* If we get here -> no link at all */ - - return 0; -} - - - - - -/* - read_mac_address() reads the MAC address from the NIC and stores it in the "dev" structure. - - This function will take the spinlock itself and can, as a result, not be called with the lock helt. - */ -static void read_mac_address(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - u8 link; - int i; - - spin_lock_irqsave(&card->lock, flags); - - xw32(CSR9, 1 << 12); /* enable boot rom access */ - for (i = 0x100; i < 0x1f7; i += link + 2) { - u8 tuple, data_id, data_count; - - xw32(CSR10, i); - tuple = xr32(CSR9); - xw32(CSR10, i + 1); - link = xr32(CSR9); - xw32(CSR10, i + 2); - data_id = xr32(CSR9); - xw32(CSR10, i + 3); - data_count = xr32(CSR9); - if ((tuple == 0x22) && (data_id == 0x04) && (data_count == 0x06)) { - u8 addr[ETH_ALEN]; - int j; - - for (j = 0; j < 6; j++) { - xw32(CSR10, i + j + 4); - addr[j] = xr32(CSR9) & 0xff; - } - eth_hw_addr_set(card->dev, addr); - break; - } else if (link == 0) { - break; - } - } - spin_unlock_irqrestore(&card->lock, flags); - pr_debug(" %pM\n", card->dev->dev_addr); -} - - -/* - transceiver_voodoo() enables the external UTP plug thingy. - it's called voodoo as I stole this code and cannot cross-reference - it with the specification. - */ -static void transceiver_voodoo(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - - /* disable all powermanagement */ - pci_write_config_dword(card->pdev, PCI_POWERMGMT, 0x0000); - - setup_descriptors(card); - - spin_lock_irqsave(&card->lock, flags); - - xw32(CSR15, 0x0008); - udelay(25); - xw32(CSR15, 0xa8050000); - udelay(25); - xw32(CSR15, 0xa00f0000); - udelay(25); - - spin_unlock_irqrestore(&card->lock, flags); - - netif_start_queue(card->dev); -} - - -static void xircom_up(struct xircom_private *card) -{ - unsigned long flags; - int i; - - /* disable all powermanagement */ - pci_write_config_dword(card->pdev, PCI_POWERMGMT, 0x0000); - - setup_descriptors(card); - - spin_lock_irqsave(&card->lock, flags); - - - enable_link_interrupt(card); - enable_transmit_interrupt(card); - enable_receive_interrupt(card); - enable_common_interrupts(card); - enable_promisc(card); - - /* The card can have received packets already, read them away now */ - for (i=0;idev,card,i,bufferoffsets[i]); - - - spin_unlock_irqrestore(&card->lock, flags); - trigger_receive(card); - trigger_transmit(card); - netif_start_queue(card->dev); -} - -/* Bufferoffset is in BYTES */ -static void -investigate_read_descriptor(struct net_device *dev, struct xircom_private *card, - int descnr, unsigned int bufferoffset) -{ - int status; - - status = le32_to_cpu(card->rx_buffer[4*descnr]); - - if (status > 0) { /* packet received */ - - /* TODO: discard error packets */ - - short pkt_len = ((status >> 16) & 0x7ff) - 4; - /* minus 4, we don't want the CRC */ - struct sk_buff *skb; - - if (pkt_len > 1518) { - netdev_err(dev, "Packet length %i is bogus\n", pkt_len); - pkt_len = 1518; - } - - skb = netdev_alloc_skb(dev, pkt_len + 2); - if (skb == NULL) { - dev->stats.rx_dropped++; - goto out; - } - skb_reserve(skb, 2); - skb_copy_to_linear_data(skb, - &card->rx_buffer[bufferoffset / 4], - pkt_len); - skb_put(skb, pkt_len); - skb->protocol = eth_type_trans(skb, dev); - netif_rx(skb); - dev->stats.rx_packets++; - dev->stats.rx_bytes += pkt_len; - -out: - /* give the buffer back to the card */ - card->rx_buffer[4*descnr] = cpu_to_le32(0x80000000); - trigger_receive(card); - } -} - - -/* Bufferoffset is in BYTES */ -static void -investigate_write_descriptor(struct net_device *dev, - struct xircom_private *card, - int descnr, unsigned int bufferoffset) -{ - int status; - - status = le32_to_cpu(card->tx_buffer[4*descnr]); -#if 0 - if (status & 0x8000) { /* Major error */ - pr_err("Major transmit error status %x\n", status); - card->tx_buffer[4*descnr] = 0; - netif_wake_queue (dev); - } -#endif - if (status > 0) { /* bit 31 is 0 when done */ - if (card->tx_skb[descnr]!=NULL) { - dev->stats.tx_bytes += card->tx_skb[descnr]->len; - dev_kfree_skb_irq(card->tx_skb[descnr]); - } - card->tx_skb[descnr] = NULL; - /* Bit 8 in the status field is 1 if there was a collision */ - if (status & (1 << 8)) - dev->stats.collisions++; - card->tx_buffer[4*descnr] = 0; /* descriptor is free again */ - netif_wake_queue (dev); - dev->stats.tx_packets++; - } -} - -module_pci_driver(xircom_driver); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c index 764d2a09668f..6f1046c9cc51 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c @@ -1403,10 +1403,10 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb, struct dpaa2_eth_fq *fq; struct netdev_queue *nq; struct dpaa2_fd *fd; + int err, i, num_tc; u16 queue_mapping; void *swa = NULL; u8 prio = 0; - int err, i; u32 fd_len; percpu_stats = this_cpu_ptr(priv->percpu_stats); @@ -1468,12 +1468,14 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb, */ queue_mapping = skb_get_queue_mapping(skb); - if (net_dev->num_tc) { + num_tc = netdev_get_num_tc(net_dev); + + if (num_tc) { prio = netdev_txq_to_tc(net_dev, queue_mapping); /* Hardware interprets priority level 0 as being the highest, * so we need to do a reverse mapping to the netdev tc index */ - prio = net_dev->num_tc - prio - 1; + prio = num_tc - prio - 1; /* We have only one FQ array entry for all Tx hardware queues * with the same flow id (but different priority levels) */ @@ -2913,7 +2915,7 @@ static int update_xps(struct dpaa2_eth_priv *priv) return -ENOMEM; num_queues = dpaa2_eth_queue_count(priv); - netdev_queues = (net_dev->num_tc ? : 1) * num_queues; + netdev_queues = (netdev_get_num_tc(net_dev) ? : 1) * num_queues; /* The first entries in priv->fq array are Tx/Tx conf * queues, so only process those @@ -2946,7 +2948,7 @@ static int dpaa2_eth_setup_mqprio(struct net_device *net_dev, num_queues = dpaa2_eth_queue_count(priv); num_tc = mqprio->num_tc; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; if (num_tc > dpaa2_eth_tc_count(priv)) { diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index dd4f60031d0c..79d4a77f72bd 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -25,6 +25,9 @@ #define DEFAULT_VLAN_ID 1 +static struct notifier_block dpaa2_switch_port_switchdev_nb; +static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb; + static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv) { return port_priv->fdb->fdb_id; @@ -51,6 +54,17 @@ dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct dpaa2_switch_lag * +dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) +{ + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + if (!ethsw->lags[i].in_use) + return ðsw->lags[i]; + return NULL; +} + static bool dpaa2_switch_fdb_in_use_by_others(struct ethsw_core *ethsw, struct dpaa2_switch_fdb *fdb, struct ethsw_port_priv *except) @@ -71,9 +85,9 @@ static struct dpaa2_switch_fdb * dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, struct net_device *upper_dev) { - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct ethsw_port_priv *other_port_priv = NULL; + struct net_device *other_dev, *other_dev2; + struct list_head *iter, *iter2; /* The below call to netdev_for_each_lower_dev() demands the RTNL lock * being held. Assert on it so that it's easier to catch new code @@ -82,17 +96,32 @@ dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, ASSERT_RTNL(); /* If part of a bridge, use the FDB of the first dpaa2 switch interface - * to be present in that bridge + * to be present in that bridge. The search descends one level through + * a bridged bond's lowers as well. */ netdev_for_each_lower_dev(upper_dev, other_dev, iter) { - if (!dpaa2_switch_port_dev_check(other_dev)) - continue; + if (netif_is_lag_master(other_dev)) { + netdev_for_each_lower_dev(other_dev, other_dev2, iter2) { + if (!dpaa2_switch_port_dev_check(other_dev2)) + continue; - if (other_dev == port_priv->netdev) - continue; + if (other_dev2 == port_priv->netdev) + continue; - other_port_priv = netdev_priv(other_dev); - return other_port_priv->fdb; + other_port_priv = netdev_priv(other_dev2); + break; + } + } else { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + if (other_dev == port_priv->netdev) + continue; + + other_port_priv = netdev_priv(other_dev); + } + if (other_port_priv) + return other_port_priv->fdb; } return port_priv->fdb; @@ -537,6 +566,103 @@ static int dpaa2_switch_port_fdb_del_mc(struct ethsw_port_priv *port_priv, return err; } +static int dpaa2_switch_port_fdb_add(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + int err; + + if (is_unicast_ether_addr(addr)) + err = dpaa2_switch_port_fdb_add_uc(port_priv, addr); + else + err = dpaa2_switch_port_fdb_add_mc(port_priv, addr); + + return err; +} + +static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + if (is_unicast_ether_addr(addr)) + return dpaa2_switch_port_fdb_del_uc(port_priv, addr); + else + return dpaa2_switch_port_fdb_del_mc(port_priv, addr); +} + +static struct dpaa2_mac_addr * +dpaa2_switch_mac_addr_find(struct list_head *addr_list, + const unsigned char *addr, u16 vid) +{ + struct dpaa2_mac_addr *a; + + list_for_each_entry(a, addr_list, list) + if (ether_addr_equal(a->addr, addr) && a->vid == vid) + return a; + + return NULL; +} + +static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + int err = 0; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (a) { + refcount_inc(&a->refcount); + goto out; + } + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) { + err = -ENOMEM; + goto out; + } + + err = dpaa2_switch_port_fdb_add(port_priv, addr); + if (err) { + kfree(a); + goto out; + } + + ether_addr_copy(a->addr, addr); + a->vid = vid; + refcount_set(&a->refcount, 1); + list_add_tail(&a->list, &lag->fdbs); + +out: + mutex_unlock(&lag->fdb_lock); + + return err; +} + +static void dpaa2_switch_lag_fdb_del(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + list_del(&a->list); + kfree(a); + + dpaa2_switch_port_fdb_del(port_priv, addr); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1485,6 +1611,33 @@ bool dpaa2_switch_port_dev_check(const struct net_device *netdev) return netdev->netdev_ops == &dpaa2_switch_port_ops; } +static bool dpaa2_switch_foreign_dev_check(const struct net_device *dev, + const struct net_device *foreign_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(dev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port; + int i; + + if (netif_is_bridge_master(foreign_dev)) + if (port_priv->fdb->bridge_dev == foreign_dev) + return false; + + if (netif_is_bridge_port(foreign_dev)) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port = ethsw->ports[i]; + + if (!other_port) + continue; + if (dpaa2_switch_port_offloads_bridge_port(other_port, + foreign_dev)) + return false; + } + } + + return true; +} + static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv) { struct fsl_mc_device *dpsw_port_dev, *dpmac_dev; @@ -1861,51 +2014,32 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, vlan->changed); } -static int dpaa2_switch_port_lookup_address(struct net_device *netdev, int is_uc, - const unsigned char *addr) -{ - struct netdev_hw_addr_list *list = (is_uc) ? &netdev->uc : &netdev->mc; - struct netdev_hw_addr *ha; - - netif_addr_lock_bh(netdev); - list_for_each_entry(ha, &list->list, list) { - if (ether_addr_equal(ha->addr, addr)) { - netif_addr_unlock_bh(netdev); - return 1; - } - } - netif_addr_unlock_bh(netdev); - return 0; -} - static int dpaa2_switch_port_mdb_add(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; + struct dpaa2_switch_lag *lag; - /* Check if address is already set on this port */ - if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -EEXIST; - - err = dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); - if (err) - return err; - - err = dev_mc_add(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_add err %d\n", err); - dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - } - - return err; + lag = rtnl_dereference(port_priv->lag); + if (lag) + return dpaa2_switch_lag_fdb_add(lag, mdb->addr, mdb->vid); + else + return dpaa2_switch_port_fdb_add(port_priv, mdb->addr); } -static int dpaa2_switch_port_obj_add(struct net_device *netdev, - const struct switchdev_obj *obj) +static int dpaa2_switch_port_obj_add(struct net_device *netdev, const void *ctx, + const struct switchdev_obj *obj, + struct netlink_ext_ack *extack) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_add(netdev, @@ -2001,29 +2135,29 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; + struct dpaa2_switch_lag *lag; - if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -ENOENT; + lag = rtnl_dereference(port_priv->lag); + if (lag) + dpaa2_switch_lag_fdb_del(lag, mdb->addr, mdb->vid); + else + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); - err = dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - if (err) - return err; - - err = dev_mc_del(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_del err %d\n", err); - return err; - } - - return err; + return 0; } -static int dpaa2_switch_port_obj_del(struct net_device *netdev, +static int dpaa2_switch_port_obj_del(struct net_device *netdev, const void *ctx, const struct switchdev_obj *obj) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_del(netdev, SWITCHDEV_OBJ_PORT_VLAN(obj)); @@ -2049,6 +2183,21 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev, return notifier_from_errno(err); } +static struct net_device * +dpaa2_switch_port_to_bridge_port(struct ethsw_port_priv *port_priv) +{ + struct dpaa2_switch_lag *lag; + + if (!port_priv->fdb->bridge_dev) + return NULL; + + lag = rtnl_dereference(port_priv->lag); + if (lag) + return lag->bond_dev; + + return port_priv->netdev; +} + static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) @@ -2056,6 +2205,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; bool learn_ena; int err; @@ -2067,7 +2217,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, dpaa2_switch_port_set_fdb(port_priv, upper_dev, true); /* Inherit the initial bridge port learning state */ - learn_ena = br_port_flag_is_set(netdev, BR_LEARNING); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + learn_ena = br_port_flag_is_set(brport_dev, BR_LEARNING); err = dpaa2_switch_port_set_learning(port_priv, learn_ena); port_priv->learn_ena = learn_ena; @@ -2081,8 +2232,11 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, if (err) goto err_egress_flood; - err = switchdev_bridge_port_offload(netdev, netdev, NULL, - NULL, NULL, false, extack); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + err = switchdev_bridge_port_offload(brport_dev, netdev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb, + false, extack); if (err) goto err_switchdev_offload; @@ -2116,7 +2270,22 @@ static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, vo static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) { - switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; + + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + if (!brport_dev) + return; + + switchdev_bridge_port_unoffload(brport_dev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb); + + /* Make sure that any FDB add/del operations are completed before the + * bridge layout changes + */ + flush_workqueue(ethsw->workqueue); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) @@ -2178,30 +2347,53 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) false); } +static int +dpaa2_switch_have_vlan_upper(struct net_device *upper_dev, + __always_unused struct netdev_nested_priv *priv) +{ + return is_vlan_dev(upper_dev); +} + static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *netdev) { - struct net_device *upper_dev; - struct list_head *iter; + struct netdev_nested_priv priv = {}; /* RCU read lock not necessary because we have write-side protection - * (rtnl_mutex), however a non-rcu iterator does not exist. + * (rtnl_mutex), however a non-rcu iterator does not exist. Walk the + * entire upper chain so that a VLAN device stacked on a intermediate + * bond is caught too. */ - netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) - if (is_vlan_dev(upper_dev)) - return -EOPNOTSUPP; + if (netdev_walk_all_upper_dev_rcu(netdev, dpaa2_switch_have_vlan_upper, + &priv)) + return -EOPNOTSUPP; return 0; } +static int dpaa2_switch_check_dpsw_instance(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + struct ethsw_port_priv *port_priv = (struct ethsw_port_priv *)priv->data; + struct ethsw_port_priv *other_priv = netdev_priv(dev); + + if (!dpaa2_switch_port_dev_check(dev)) + return 0; + + if (other_priv->ethsw_data == port_priv->ethsw_data) + return 0; + + return 1; +} + static int dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct netdev_nested_priv data = { + .data = (void *)port_priv, + }; int err; if (!br_vlan_enabled(upper_dev)) { @@ -2216,6 +2408,70 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, return err; } + err = netdev_walk_all_lower_dev(upper_dev, + dpaa2_switch_check_dpsw_instance, + &data); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Interface from a different DPSW is in the bridge already"); + return -EINVAL; + } + + return 0; +} + +static int dpaa2_switch_pre_lag_join(struct net_device *netdev, + struct net_device *upper_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv; + struct dpaa2_switch_lag *lag = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct net_device *other_dev; + int i, num_ifs = 0, err; + struct list_head *iter; + + if (!(ethsw->features & ETHSW_FEATURE_LAG_OFFLOAD)) { + NL_SET_ERR_MSG_MOD(extack, + "LAG offload is supported only for DPSW >= v8.13"); + return -EOPNOTSUPP; + } + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload LAG using hash TX type"); + return -EOPNOTSUPP; + } + + if (info->hash_type != NETDEV_LAG_HASH_L23) { + NL_SET_ERR_MSG_MOD(extack, "Can only offload L2+L3 Tx hash"); + return -EOPNOTSUPP; + } + + if (!dpaa2_switch_port_has_mac(port_priv)) { + NL_SET_ERR_MSG_MOD(extack, + "Only switch interfaces connected to MACs can be under a LAG"); + return -EINVAL; + } + + if (vlan_uses_dev(upper_dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot join a LAG upper that has a VLAN"); + return -EOPNOTSUPP; + } + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (!ethsw->lags[i].in_use) + continue; + if (ethsw->lags[i].bond_dev != upper_dev) + continue; + lag = ðsw->lags[i]; + break; + } + netdev_for_each_lower_dev(upper_dev, other_dev, iter) { if (!dpaa2_switch_port_dev_check(other_dev)) continue; @@ -2223,9 +2479,248 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, other_port_priv = netdev_priv(other_dev); if (other_port_priv->ethsw_data != port_priv->ethsw_data) { NL_SET_ERR_MSG_MOD(extack, - "Interface from a different DPSW is in the bridge already"); + "Interface from a different DPSW is in the bond already"); return -EINVAL; } + + cfg.if_id[num_ifs++] = other_port_priv->idx; + + if (num_ifs >= DPSW_MAX_LAG_IFS) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add more than 8 DPAA2 switch ports under the same bond"); + return -EINVAL; + } + } + + if (lag) { + cfg.group_id = lag->id; + cfg.if_id[num_ifs++] = port_priv->idx; + cfg.num_ifs = num_ifs; + cfg.phase = DPSW_LAG_SET_PHASE_CHECK; + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload LAG configuration"); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static void dpaa2_switch_port_set_lag_group(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv = NULL; + struct dpaa2_switch_lag *lag = NULL; + struct dpaa2_switch_lag *other_lag; + struct net_device *other_dev; + struct list_head *iter; + + netdev_for_each_lower_dev(bond_dev, other_dev, iter) { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + other_port_priv = netdev_priv(other_dev); + other_lag = rtnl_dereference(other_port_priv->lag); + if (!other_lag) + continue; + + if (other_lag->bond_dev == bond_dev) { + rcu_assign_pointer(port_priv->lag, other_lag); + return; + } + } + + /* This is the first interface to be added under a bond device. Find an + * unused LAG group. No need to check for NULL since there are the same + * amount of DPSW ports as LAG groups, meaning that each port can have + * its own LAG group. + */ + lag = dpaa2_switch_lag_get_unused(ethsw); + lag->in_use = true; + lag->bond_dev = bond_dev; + lag->primary = port_priv; + rcu_assign_pointer(port_priv->lag, lag); +} + +static bool dpaa2_switch_port_in_lag(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct dpaa2_switch_lag *lag; + + if (!port_priv) + return false; + + lag = rtnl_dereference(port_priv->lag); + return lag && lag->bond_dev == bond_dev; +} + +static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, + struct ethsw_core *ethsw) +{ + struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; + struct ethsw_port_priv *primary, *port_priv; + struct ethsw_port_priv *new_primary = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct dpaa2_mac_addr *a; + u8 num_ifs = 0; + int err, i; + + cfg.group_id = lag_id; + + /* Determine the primary port. The caller clears ->lag on the port that + * is leaving, so a NULL ->lag on the current primary means it is the + * one leaving: elect the first remaining member as the new primary. + * Otherwise keep the current primary. + */ + if (rtnl_dereference(lag->primary->lag)) { + primary = lag->primary; + } else { + primary = NULL; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (dpaa2_switch_port_in_lag(ethsw->ports[i], bond_dev)) { + new_primary = ethsw->ports[i]; + primary = new_primary; + break; + } + } + } + + /* Build the interface list, always placing the primary first */ + if (primary) + cfg.if_id[num_ifs++] = primary->idx; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + port_priv = ethsw->ports[i]; + if (port_priv == primary) + continue; + if (!dpaa2_switch_port_in_lag(port_priv, bond_dev)) + continue; + + cfg.if_id[num_ifs++] = port_priv->idx; + } + cfg.num_ifs = num_ifs; + + /* No more interfaces under this LAG group, mark it as not in use. Wait + * for a grace period so that any readers of the lag structure finished. + */ + if (!num_ifs) { + synchronize_net(); + + lag->bond_dev = NULL; + lag->primary = NULL; + lag->in_use = false; + } + + /* When the primary changes, migrate the FDB entries from the old + * primary to the new one: remove them before reconfiguring the LAG in + * hardware and re-add them on the new primary afterwards. We do not + * touch any refcounting since the intention is to change the HW entry, + * not the parallel software tracking. + */ + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) + dpaa2_switch_port_fdb_del(lag->primary, a->addr); + mutex_unlock(&lag->fdb_lock); + } + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) + return err; + + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) { + err = dpaa2_switch_port_fdb_add(new_primary, a->addr); + if (err) + netdev_err(new_primary->netdev, "Unable to migrate FDB\n"); + } + mutex_unlock(&lag->fdb_lock); + + synchronize_net(); + lag->primary = new_primary; + } + + return 0; +} + +static int dpaa2_switch_port_bond_join(struct net_device *netdev, + struct net_device *bond_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *bridge_dev; + struct dpaa2_switch_lag *lag; + int err = 0; + u8 lag_id; + + /* Setup the port_priv->lag pointer for this switch port */ + dpaa2_switch_port_set_lag_group(port_priv, bond_dev); + + /* Create the LAG configuration and apply it in MC */ + lag = rtnl_dereference(port_priv->lag); + lag_id = lag->id; + err = dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + if (err) + goto err_lag_cfg; + + /* If the bond device is a switch port, join the bridge as well */ + bridge_dev = netdev_master_upper_dev_get(bond_dev); + if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) + return 0; + + err = dpaa2_switch_port_bridge_join(netdev, bridge_dev, extack); + if (err) + goto err_lag_cfg; + + return err; + +err_lag_cfg: + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + + return err; +} + +static int dpaa2_switch_port_bond_leave(struct net_device *netdev, + struct net_device *bond_dev) +{ + struct net_device *bridge_dev = netdev_master_upper_dev_get(bond_dev); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_lag *lag = rtnl_dereference(port_priv->lag); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brpdev; + bool learn_ena; + int err; + + if (!lag) + return 0; + + /* Recreate the LAG configuration for the LAG group that we left. */ + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw); + + if (bridge_dev && netif_is_bridge_master(bridge_dev)) { + /* Make sure that the new primary inherits the learning state */ + if (lag->primary) { + brpdev = dpaa2_switch_port_to_bridge_port(lag->primary); + learn_ena = br_port_flag_is_set(brpdev, BR_LEARNING); + err = dpaa2_switch_port_set_learning(lag->primary, + learn_ena); + if (err) + return err; + lag->primary->learn_ena = learn_ena; + } + + /* In case the bond is a bridge port, leave the upper bridge as + * well. + */ + return dpaa2_switch_port_bridge_leave(netdev); } return 0; @@ -2235,8 +2730,8 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, struct netdev_notifier_changeupper_info *info) { struct ethsw_port_priv *port_priv; + struct net_device *upper_dev, *br; struct netlink_ext_ack *extack; - struct net_device *upper_dev; int err; if (!dpaa2_switch_port_dev_check(netdev)) @@ -2253,6 +2748,24 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, if (!info->linking) dpaa2_switch_port_pre_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (!info->linking) { + if (netif_is_bridge_port(upper_dev)) + dpaa2_switch_port_pre_bridge_leave(netdev); + return 0; + } + + if (netif_is_bridge_port(upper_dev)) { + br = netdev_master_upper_dev_get(upper_dev); + err = dpaa2_switch_prechangeupper_sanity_checks(netdev, + br, + extack); + if (err) + return err; + } + + return dpaa2_switch_pre_lag_join(netdev, upper_dev, + info->upper_info, extack); } else if (is_vlan_dev(upper_dev)) { port_priv = netdev_priv(netdev); if (port_priv->fdb->bridge_dev) { @@ -2284,6 +2797,80 @@ static int dpaa2_switch_port_changeupper(struct net_device *netdev, extack); else return dpaa2_switch_port_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (info->linking) + return dpaa2_switch_port_bond_join(netdev, upper_dev, + info->upper_info, + extack); + else + return dpaa2_switch_port_bond_leave(netdev, upper_dev); + } + + return 0; +} + +static int +dpaa2_switch_lag_prechangeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_prechangeupper(lower, info); + if (err) + return err; + } + + return err; +} + +static int +dpaa2_switch_lag_changeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_changeupper(lower, info); + if (err) + return err; + } + + return 0; +} + +static int +dpaa2_switch_port_changelowerstate(struct net_device *netdev, + struct netdev_lag_lower_state_info *linfo) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + + if (!rtnl_dereference(port_priv->lag)) + return 0; + + err = dpsw_if_set_lag_state(ethsw->mc_io, 0, ethsw->dpsw_handle, + port_priv->idx, linfo->tx_enabled ? 1 : 0); + if (err) { + netdev_err(netdev, "dpsw_if_set_lag_state() = %d\n", err); + return err; } return 0; @@ -2293,6 +2880,7 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changelowerstate_info *info; int err = 0; switch (event) { @@ -2301,13 +2889,29 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_prechangeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; case NETDEV_CHANGEUPPER: err = dpaa2_switch_port_changeupper(netdev, ptr); if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_changeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; + case NETDEV_CHANGELOWERSTATE: + info = ptr; + if (!dpaa2_switch_port_dev_check(netdev)) + break; + + err = dpaa2_switch_port_changelowerstate(netdev, + info->lower_state_info); + return notifier_from_errno(err); } return NOTIFY_DONE; @@ -2317,80 +2921,97 @@ struct ethsw_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; + struct net_device *orig_dev; unsigned long event; + u16 vid; }; static void dpaa2_switch_event_work(struct work_struct *work) { struct ethsw_switchdev_event_work *switchdev_work = container_of(work, struct ethsw_switchdev_event_work, work); + struct net_device *orig_dev = switchdev_work->orig_dev; struct net_device *dev = switchdev_work->dev; + struct ethsw_port_priv *port_priv = netdev_priv(dev); struct switchdev_notifier_fdb_info *fdb_info; + struct dpaa2_switch_lag *lag; int err; - rtnl_lock(); fdb_info = &switchdev_work->fdb_info; + /* The lag structures are freed only from dpaa2_switch_remove(), which + * first flushes this workqueue, so the pointer stays valid for the + * lifetime of the work item. Only the dereference needs the RCU + * read-side lock; the FDB helpers below can sleep and must run outside + * of it. + */ + rcu_read_lock(); + lag = rcu_dereference(port_priv->lag); + rcu_read_unlock(); + switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; - if (is_unicast_ether_addr(fdb_info->addr)) - err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), - fdb_info->addr); + if (lag) + err = dpaa2_switch_lag_fdb_add(lag, fdb_info->addr, + switchdev_work->vid); else - err = dpaa2_switch_port_fdb_add_mc(netdev_priv(dev), - fdb_info->addr); + err = dpaa2_switch_port_fdb_add(port_priv, + fdb_info->addr); if (err) break; fdb_info->offloaded = true; - call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, orig_dev, &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; - if (is_unicast_ether_addr(fdb_info->addr)) - dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); + if (lag) + dpaa2_switch_lag_fdb_del(lag, fdb_info->addr, + switchdev_work->vid); else - dpaa2_switch_port_fdb_del_mc(netdev_priv(dev), fdb_info->addr); + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } - rtnl_unlock(); kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); + dev_put(orig_dev); } -/* Called under rcu_read_lock() */ -static int dpaa2_switch_port_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int +dpaa2_switch_port_fdb_event(struct net_device *dev, + struct net_device *orig_dev, + unsigned long event, const void *ctx, + const struct switchdev_notifier_fdb_info *fdb_info) { - struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; - struct switchdev_notifier_fdb_info *fdb_info = ptr; struct ethsw_core *ethsw = port_priv->ethsw_data; - if (event == SWITCHDEV_PORT_ATTR_SET) - return dpaa2_switch_port_attr_set_event(dev, ptr); + if (ctx && ctx != port_priv) + return 0; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; + /* For the moment, do nothing with entries towards foreign devices */ + if (dpaa2_switch_foreign_dev_check(dev, orig_dev)) + return 0; + + if (!fdb_info->added_by_user || fdb_info->is_local) + return 0; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) - return NOTIFY_BAD; + return -ENOMEM; INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work); switchdev_work->dev = dev; switchdev_work->event = event; + switchdev_work->orig_dev = orig_dev; + switchdev_work->vid = fdb_info->vid; switch (event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - memcpy(&switchdev_work->fdb_info, ptr, + memcpy(&switchdev_work->fdb_info, fdb_info, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) @@ -2401,52 +3022,61 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, /* Take a reference on the device to avoid being freed. */ dev_hold(dev); + dev_hold(orig_dev); break; default: kfree(switchdev_work); - return NOTIFY_DONE; + return 0; } queue_work(ethsw->workqueue, &switchdev_work->work); - return NOTIFY_DONE; + return 0; err_addr_alloc: kfree(switchdev_work); - return NOTIFY_BAD; + return -ENOMEM; } -static int dpaa2_switch_port_obj_event(unsigned long event, - struct net_device *netdev, - struct switchdev_notifier_port_obj_info *port_obj_info) +/* Called under rcu_read_lock() */ +static int dpaa2_switch_port_event(struct notifier_block *nb, + unsigned long event, void *ptr) { - int err = -EOPNOTSUPP; - - if (!dpaa2_switch_port_dev_check(netdev)) - return NOTIFY_DONE; + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { - case SWITCHDEV_PORT_OBJ_ADD: - err = dpaa2_switch_port_obj_add(netdev, port_obj_info->obj); - break; - case SWITCHDEV_PORT_OBJ_DEL: - err = dpaa2_switch_port_obj_del(netdev, port_obj_info->obj); - break; + case SWITCHDEV_PORT_ATTR_SET: + return dpaa2_switch_port_attr_set_event(dev, ptr); + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + err = switchdev_handle_fdb_event_to_device(dev, event, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_foreign_dev_check, + dpaa2_switch_port_fdb_event); + return notifier_from_errno(err); + default: + return NOTIFY_DONE; } - - port_obj_info->handled = true; - return notifier_from_errno(err); } static int dpaa2_switch_port_blocking_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_OBJ_ADD: + err = switchdev_handle_port_obj_add(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_add); + return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: - return dpaa2_switch_port_obj_event(event, dev, ptr); + err = switchdev_handle_port_obj_del(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_del); + return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); } @@ -2491,19 +3121,22 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, dma_addr_t addr = dpaa2_fd_get_addr(fd); struct ethsw_core *ethsw = fq->ethsw; struct ethsw_port_priv *port_priv; + struct dpaa2_switch_lag *lag; struct net_device *netdev; struct vlan_ethhdr *hdr; struct sk_buff *skb; u16 vlan_tci, vid; int if_id, err; void *vaddr; + u64 flc; vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr); dma_unmap_page(ethsw->dev, addr, DPAA2_SWITCH_RX_BUF_SIZE, DMA_FROM_DEVICE); /* get switch ingress interface ID */ - if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF; + flc = dpaa2_fd_get_flc(fd); + if_id = DPAA2_ETHSW_FLC_IF_ID(flc); if (if_id >= ethsw->sw_attr.num_ifs) { dev_err(ethsw->dev, "Frame received from unknown interface!\n"); goto err_free_fd; @@ -2542,12 +3175,20 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, } } - skb->dev = netdev; + rcu_read_lock(); + + lag = rcu_dereference(port_priv->lag); + if (DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) && lag) + skb->dev = lag->bond_dev; + else + skb->dev = netdev; skb->protocol = eth_type_trans(skb, skb->dev); /* Setup the offload_fwd_mark only if the port is under a bridge */ skb->offload_fwd_mark = !!(port_priv->fdb->bridge_dev); + rcu_read_unlock(); + netif_receive_skb(skb); return; @@ -2562,6 +3203,9 @@ static void dpaa2_switch_detect_features(struct ethsw_core *ethsw) if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 6)) ethsw->features |= ETHSW_FEATURE_MAC_ADDR; + + if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 13)) + ethsw->features |= ETHSW_FEATURE_LAG_OFFLOAD; } static int dpaa2_switch_setup_fqs(struct ethsw_core *ethsw) @@ -3196,17 +3840,15 @@ static int dpaa2_switch_init(struct fsl_mc_device *sw_dev) return err; } -/* Add an ACL to redirect frames with specific destination MAC address to - * control interface - */ +/* Add an ACL to redirect frames to control interface based on the dst MAC */ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, - const char *mac) + const u8 *mac, const u8 *mask) { struct dpaa2_switch_acl_entry acl_entry = {0}; /* Match on the destination MAC address */ ether_addr_copy(acl_entry.key.match.l2_dest_mac, mac); - eth_broadcast_addr(acl_entry.key.mask.l2_dest_mac); + ether_addr_copy(acl_entry.key.mask.l2_dest_mac, mask); /* Trap to CPU */ acl_entry.cfg.precedence = 0; @@ -3217,7 +3859,8 @@ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) { - const char stpa[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mac[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mask[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xf0}; struct switchdev_obj_port_vlan vlan = { .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .vid = DEFAULT_VLAN_ID, @@ -3292,11 +3935,7 @@ static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) if (err) return err; - err = dpaa2_switch_port_trap_mac_addr(port_priv, stpa); - if (err) - return err; - - return err; + return dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); } static void dpaa2_switch_ctrl_if_teardown(struct ethsw_core *ethsw) @@ -3332,6 +3971,9 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) dev = &sw_dev->dev; ethsw = dev_get_drvdata(dev); + /* Make sure that all events were handled before we kfree anything */ + flush_workqueue(ethsw->workqueue); + dpaa2_switch_teardown_irqs(sw_dev); dpsw_disable(ethsw->mc_io, 0, ethsw->dpsw_handle); @@ -3345,12 +3987,15 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++) netif_napi_del(ðsw->fq[i].napi); - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { dpaa2_switch_remove_port(ethsw, i); + mutex_destroy(ðsw->lags[i].fdb_lock); + } kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); kfree(ethsw->ports); + kfree(ethsw->lags); dpaa2_switch_teardown(sw_dev); @@ -3378,6 +4023,7 @@ static int dpaa2_switch_probe_port(struct ethsw_core *ethsw, port_priv = netdev_priv(port_netdev); port_priv->netdev = port_netdev; port_priv->ethsw_data = ethsw; + rcu_assign_pointer(port_priv->lag, NULL); mutex_init(&port_priv->mac_lock); @@ -3485,6 +4131,21 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) goto err_free_fdbs; } + ethsw->lags = kcalloc(ethsw->sw_attr.num_ifs, sizeof(*ethsw->lags), + GFP_KERNEL); + if (!ethsw->lags) { + err = -ENOMEM; + goto err_free_filter; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + ethsw->lags[i].bond_dev = NULL; + ethsw->lags[i].ethsw = ethsw; + ethsw->lags[i].id = i + 1; + ethsw->lags[i].in_use = 0; + mutex_init(ðsw->lags[i].fdb_lock); + INIT_LIST_HEAD(ðsw->lags[i].fdbs); + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { err = dpaa2_switch_probe_port(ethsw, i); if (err) @@ -3531,6 +4192,10 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + mutex_destroy(ðsw->lags[i].fdb_lock); + kfree(ethsw->lags); +err_free_filter: kfree(ethsw->filter_blocks); err_free_fdbs: kfree(ethsw->fdbs); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 42b3ca73f55d..63b702b0000c 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -41,7 +41,8 @@ #define ETHSW_MAX_FRAME_LENGTH (DPAA2_MFL - VLAN_ETH_HLEN - ETH_FCS_LEN) #define ETHSW_L2_MAX_FRM(mtu) ((mtu) + VLAN_ETH_HLEN + ETH_FCS_LEN) -#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_LAG_OFFLOAD BIT(1) /* Number of receive queues (one RX and one TX_CONF) */ #define DPAA2_SWITCH_RX_NUM_FQS 2 @@ -86,6 +87,9 @@ #define DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE 256 +#define DPAA2_ETHSW_FLC_IF_ID(flc) (((flc) >> 32) & GENMASK(15, 0)) +#define DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) ((flc) & BIT_ULL(63)) + extern const struct ethtool_ops dpaa2_switch_port_ethtool_ops; struct ethsw_core; @@ -99,12 +103,30 @@ struct dpaa2_switch_fq { u32 fqid; }; +struct dpaa2_mac_addr { + unsigned char addr[ETH_ALEN]; + u16 vid; + refcount_t refcount; + struct list_head list; +}; + struct dpaa2_switch_fdb { struct net_device *bridge_dev; u16 fdb_id; bool in_use; }; +struct dpaa2_switch_lag { + struct ethsw_core *ethsw; + struct net_device *bond_dev; + bool in_use; + u8 id; + struct ethsw_port_priv *primary; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; +}; + struct dpaa2_switch_acl_entry { struct list_head list; u16 prio; @@ -163,6 +185,8 @@ struct ethsw_port_priv { struct dpaa2_mac *mac; /* Protects against changes to port_priv->mac */ struct mutex mac_lock; + + struct dpaa2_switch_lag __rcu *lag; }; /* Switch data */ @@ -190,6 +214,8 @@ struct ethsw_core { struct dpaa2_switch_fdb *fdbs; struct dpaa2_switch_filter_block *filter_blocks; u16 mirror_port; + + struct dpaa2_switch_lag *lags; }; static inline int dpaa2_switch_get_index(struct ethsw_core *ethsw, @@ -274,4 +300,18 @@ int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, struct ethsw_port_priv *port_priv); + +static inline bool +dpaa2_switch_port_offloads_bridge_port(struct ethsw_port_priv *port_priv, + const struct net_device *dev) +{ + struct dpaa2_switch_lag *lag = rcu_dereference_rtnl(port_priv->lag); + + if (lag && lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h index 397d55f2bd99..9a2055c64983 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h @@ -12,7 +12,7 @@ /* DPSW Version */ #define DPSW_VER_MAJOR 8 -#define DPSW_VER_MINOR 9 +#define DPSW_VER_MINOR 13 #define DPSW_CMD_BASE_VERSION 1 #define DPSW_CMD_VERSION_2 2 @@ -92,11 +92,14 @@ #define DPSW_CMDID_CTRL_IF_SET_POOLS DPSW_CMD_ID(0x0A1) #define DPSW_CMDID_CTRL_IF_ENABLE DPSW_CMD_ID(0x0A2) #define DPSW_CMDID_CTRL_IF_DISABLE DPSW_CMD_ID(0x0A3) +#define DPSW_CMDID_SET_LAG DPSW_CMD_V2(0x0A4) #define DPSW_CMDID_CTRL_IF_SET_QUEUE DPSW_CMD_ID(0x0A6) #define DPSW_CMDID_SET_EGRESS_FLOOD DPSW_CMD_ID(0x0AC) #define DPSW_CMDID_IF_SET_LEARNING_MODE DPSW_CMD_ID(0x0AD) +#define DPSW_CMDID_IF_SET_LAG_STATE DPSW_CMD_ID(0x0B0) + /* Macros for accessing command fields smaller than 1byte */ #define DPSW_MASK(field) \ GENMASK(DPSW_##field##_SHIFT + DPSW_##field##_SIZE - 1, \ @@ -552,5 +555,18 @@ struct dpsw_cmd_if_reflection { /* only 2 bits from the LSB */ u8 filter; }; + +struct dpsw_cmd_lag { + u8 group_id; + u8 num_ifs; + u8 pad[6]; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +struct dpsw_cmd_if_set_lag_state { + __le16 if_id; + u8 tx_enabled; +}; #pragma pack(pop) #endif /* __FSL_DPSW_CMD_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.c b/drivers/net/ethernet/freescale/dpaa2/dpsw.c index ab921d75deb2..f75cbdce42ba 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.c @@ -1659,3 +1659,63 @@ int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, return mc_send_command(mc_io, &cmd); } + +/** + * dpsw_lag_set() - Set LAG configuration + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @cfg: pointer to LAG configuration + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg) +{ + struct fsl_mc_command cmd = { 0 }; + struct dpsw_cmd_lag *cmd_params; + int i = 0; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_SET_LAG, cmd_flags, token); + + if (cfg->num_ifs > DPSW_MAX_LAG_IFS) + return -EOPNOTSUPP; + + cmd_params = (struct dpsw_cmd_lag *)cmd.params; + cmd_params->group_id = cfg->group_id; + cmd_params->num_ifs = cfg->num_ifs; + cmd_params->phase = cfg->phase; + + for (i = 0; i < cfg->num_ifs; i++) + cmd_params->if_id[i] = cfg->if_id[i]; + + return mc_send_command(mc_io, &cmd); +} + +/** + * dpsw_if_set_lag_state() - Change per port LAG state + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @if_id: ID of the switch interface + * @tx_enabled: Value of the per port LAG state + * - 0 if the interface will not be active as part of the LAG group + * - 1 if the interface will be active in the LAG group + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled) +{ + struct dpsw_cmd_if_set_lag_state *cmd_params; + struct fsl_mc_command cmd = { 0 }; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_IF_SET_LAG_STATE, + cmd_flags, token); + + cmd_params = (struct dpsw_cmd_if_set_lag_state *)cmd.params; + cmd_params->if_id = cpu_to_le16(if_id); + cmd_params->tx_enabled = tx_enabled; + + return mc_send_command(mc_io, &cmd); +} diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.h b/drivers/net/ethernet/freescale/dpaa2/dpsw.h index b90bd363f47a..89f0267de8e9 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.h @@ -20,6 +20,8 @@ struct fsl_mc_io; #define DPSW_MAX_IF 64 +#define DPSW_MAX_LAG_IFS 8 + int dpsw_open(struct fsl_mc_io *mc_io, u32 cmd_flags, int dpsw_id, u16 *token); int dpsw_close(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token); @@ -788,4 +790,32 @@ int dpsw_if_add_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, u16 if_id, const struct dpsw_reflection_cfg *cfg); + +/* Link Aggregation Group configuration */ + +#define DPSW_LAG_SET_PHASE_APPLY 0 +#define DPSW_LAG_SET_PHASE_CHECK 1 + +/** + * struct dpsw_lag_cfg - Configuration structure for a LAG group + * @group_id: Link aggregation group ID. Valid values are in the + * [1, DPSW_MAX_LAG_IFS] range. + * @num_ifs: Number of interfaces in this LAG group, valid range is + * [0, DPSW_MAX_LAG_IFS]. + * @if_id: Array containing the interface IDs of the ports part of a LAG group + * @phase: Use DPSW_LAG_SET_PHASE_APPLY for LAG configuration processing or + * DPSW_LAG_SET_PHASE_CHECK for LAG configuration validation. + */ +struct dpsw_lag_cfg { + u8 group_id; + u8 num_ifs; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg); + +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled); #endif /* __FSL_DPSW_H */ diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index 04a5dd5ea6c7..8839cfb49bcf 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ -/* Copyright 2017-2019 NXP */ +/* Copyright 2017-2019, 2025-2026 NXP */ #include #include @@ -324,8 +324,6 @@ struct enetc_si { const struct enetc_drvdata *drvdata; const struct enetc_si_ops *ops; - struct workqueue_struct *workqueue; - struct work_struct rx_mode_task; struct dentry *debugfs_root; struct enetc_msg_swbd msg; /* Only valid for VSI */ }; diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c index 1b1591dce73d..5029038bf99f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c @@ -28,15 +28,14 @@ static void enetc_show_si_mac_hash_filter(struct seq_file *s, int i) static int enetc_mac_filter_show(struct seq_file *s, void *data) { - struct enetc_si *si = s->private; - struct enetc_hw *hw = &si->hw; + struct enetc_pf *pf = enetc_si_priv(s->private); + struct enetc_hw *hw = &pf->si->hw; + int num_si = pf->total_vfs + 1; struct maft_entry_data maft; - struct enetc_pf *pf; - int i, err, num_si; - u32 val; - - pf = enetc_si_priv(si); - num_si = pf->caps.num_vsi + 1; + struct ntmp_user *user; + u32 val, entry_id; + int err = 0; + int i; val = enetc_port_rd(hw, ENETC4_PSIPMMR); for (i = 0; i < num_si; i++) { @@ -50,31 +49,38 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) for (i = 0; i < num_si; i++) enetc_show_si_mac_hash_filter(s, i); - if (!pf->num_mfe) - return 0; + user = &pf->si->ntmp_user; + rtnl_lock(); + + if (bitmap_empty(user->maft_eid_bitmap, user->maft_num_entries)) + goto unlock_rtnl; /* MAC address filter table */ seq_puts(s, "MAC address filter table\n"); - for (i = 0; i < pf->num_mfe; i++) { + for_each_set_bit(entry_id, user->maft_eid_bitmap, + user->maft_num_entries) { memset(&maft, 0, sizeof(maft)); - err = ntmp_maft_query_entry(&si->ntmp_user, i, &maft); + err = ntmp_maft_query_entry(user, entry_id, &maft); if (err) - return err; + goto unlock_rtnl; - seq_printf(s, "Entry %d, MAC: %pM, SI bitmap: 0x%04x\n", i, - maft.keye.mac_addr, le16_to_cpu(maft.cfge.si_bitmap)); + seq_printf(s, "Entry %d, MAC: %pM, SI bitmap: 0x%04x\n", + entry_id, maft.keye.mac_addr, + le16_to_cpu(maft.cfge.si_bitmap)); } - return 0; +unlock_rtnl: + rtnl_unlock(); + + return err; } DEFINE_SHOW_ATTRIBUTE(enetc_mac_filter); void enetc_create_debugfs(struct enetc_si *si) { - struct net_device *ndev = si->ndev; struct dentry *root; - root = debugfs_create_dir(netdev_name(ndev), NULL); + root = debugfs_create_dir(pci_name(si->pdev), NULL); if (IS_ERR(root)) return; diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h index f18437556a0e..09025e7a2a3a 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h @@ -69,8 +69,6 @@ /* Port Station interface promiscuous MAC mode register */ #define ENETC4_PSIPMMR 0x200 -#define PSIPMMR_SI_MAC_UP(a) BIT(a) /* a = SI index */ -#define PSIPMMR_SI_MAC_MP(a) BIT((a) + 16) /* Port Station interface promiscuous VLAN mode register */ #define ENETC4_PSIPVMR 0x204 @@ -137,7 +135,6 @@ #define ENETC4_PSIVHFR1(a) ((a) * 0x80 + 0x2064) #define ENETC4_PMCAPR 0x4004 -#define PMCAPR_HD BIT(8) #define PMCAPR_FP GENMASK(10, 9) /* Port capability register */ @@ -150,7 +147,7 @@ #define PCR_L2DOSE BIT(4) #define PCR_TIMER_CS BIT(8) #define PCR_PSPEED GENMASK(29, 16) -#define PCR_PSPEED_VAL(speed) (((speed) / 10 - 1) << 16) +#define PCR_PSPEED_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1)) /* Port MAC address register 0/1 */ #define ENETC4_PMAR0 0x4020 @@ -200,7 +197,6 @@ #define PM_CMD_CFG_CNT_FRM_EN BIT(13) #define PM_CMD_CFG_TXP BIT(15) #define PM_CMD_CFG_SEND_IDLE BIT(16) -#define PM_CMD_CFG_HD_FCEN BIT(18) #define PM_CMD_CFG_SFD BIT(21) #define PM_CMD_CFG_TX_FLUSH BIT(22) #define PM_CMD_CFG_TX_LOWP_EN BIT(23) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 437a15bbb47b..fcfbabb29d22 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -23,18 +23,11 @@ static void enetc4_get_port_caps(struct enetc_pf *pf) u32 val; val = enetc_port_rd(hw, ENETC4_ECAPR1); - pf->caps.num_vsi = (val & ECAPR1_NUM_VSI) >> 24; pf->caps.num_msix = ((val & ECAPR1_NUM_MSIX) >> 12) + 1; val = enetc_port_rd(hw, ENETC4_ECAPR2); pf->caps.num_rx_bdr = (val & ECAPR2_NUM_RX_BDR) >> 16; pf->caps.num_tx_bdr = val & ECAPR2_NUM_TX_BDR; - - val = enetc_port_rd(hw, ENETC4_PMCAPR); - pf->caps.half_duplex = (val & PMCAPR_HD) ? 1 : 0; - - val = enetc_port_rd(hw, ENETC4_PSIMAFCAPR); - pf->caps.mac_filter_num = val & PSIMAFCAPR_NUM_MAC_AFTE; } static void enetc4_get_psi_hw_features(struct enetc_si *si) @@ -75,38 +68,6 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si, put_unaligned_le16(lower, addr + 4); } -static void enetc4_pf_set_si_mac_promisc(struct enetc_hw *hw, int si, - bool uc_promisc, bool mc_promisc) -{ - u32 val = enetc_port_rd(hw, ENETC4_PSIPMMR); - - if (uc_promisc) - val |= PSIPMMR_SI_MAC_UP(si); - else - val &= ~PSIPMMR_SI_MAC_UP(si); - - if (mc_promisc) - val |= PSIPMMR_SI_MAC_MP(si); - else - val &= ~PSIPMMR_SI_MAC_MP(si); - - enetc_port_wr(hw, ENETC4_PSIPMMR, val); -} - -static void enetc4_pf_set_si_uc_hash_filter(struct enetc_hw *hw, int si, - u64 hash) -{ - enetc_port_wr(hw, ENETC4_PSIUMHFR0(si), lower_32_bits(hash)); - enetc_port_wr(hw, ENETC4_PSIUMHFR1(si), upper_32_bits(hash)); -} - -static void enetc4_pf_set_si_mc_hash_filter(struct enetc_hw *hw, int si, - u64 hash) -{ - enetc_port_wr(hw, ENETC4_PSIMMHFR0(si), lower_32_bits(hash)); - enetc_port_wr(hw, ENETC4_PSIMMHFR1(si), upper_32_bits(hash)); -} - static void enetc4_pf_set_loopback(struct net_device *ndev, bool en) { struct enetc_ndev_priv *priv = netdev_priv(ndev); @@ -124,34 +85,47 @@ static void enetc4_pf_set_loopback(struct net_device *ndev, bool en) static void enetc4_pf_clear_maft_entries(struct enetc_pf *pf) { - int i; + struct ntmp_user *user = &pf->si->ntmp_user; + u32 entry_id; - for (i = 0; i < pf->num_mfe; i++) - ntmp_maft_delete_entry(&pf->si->ntmp_user, i); - - pf->num_mfe = 0; + for_each_set_bit(entry_id, user->maft_eid_bitmap, + user->maft_num_entries) { + if (!ntmp_maft_delete_entry(user, entry_id)) + ntmp_clear_eid_bitmap(user->maft_eid_bitmap, entry_id); + } } static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, - struct enetc_mac_addr *mac, - int mac_cnt) + struct netdev_hw_addr_list *uc) { + struct ntmp_user *user = &pf->si->ntmp_user; + int mac_cnt = netdev_hw_addr_list_count(uc); struct maft_entry_data maft = {}; + struct netdev_hw_addr *ha; + u32 available_entries; u16 si_bit = BIT(0); - int i, err; + u32 entry_id; + int err; + + available_entries = user->maft_num_entries - + bitmap_weight(user->maft_eid_bitmap, + user->maft_num_entries); + + if (mac_cnt > available_entries) + return -ENOSPC; maft.cfge.si_bitmap = cpu_to_le16(si_bit); - for (i = 0; i < mac_cnt; i++) { - ether_addr_copy(maft.keye.mac_addr, mac[i].addr); - err = ntmp_maft_add_entry(&pf->si->ntmp_user, i, &maft); + netdev_hw_addr_list_for_each(ha, uc) { + entry_id = ntmp_lookup_free_eid(user->maft_eid_bitmap, + user->maft_num_entries); + ether_addr_copy(maft.keye.mac_addr, ha->addr); + err = ntmp_maft_add_entry(user, entry_id, &maft); if (unlikely(err)) { - pf->num_mfe = i; + ntmp_clear_eid_bitmap(user->maft_eid_bitmap, entry_id); goto clear_maft_entries; } } - pf->num_mfe = mac_cnt; - return 0; clear_maft_entries: @@ -160,97 +134,70 @@ static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, return err; } -static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) +static void enetc4_pf_set_uc_hash_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *uc) { - int max_num_mfe = pf->caps.mac_filter_num; - struct enetc_mac_filter mac_filter = {}; - struct net_device *ndev = pf->si->ndev; - struct enetc_hw *hw = &pf->si->hw; - struct enetc_mac_addr *mac_tbl; + struct enetc_mac_filter *mac_filter = &pf->mac_filter[UC]; struct netdev_hw_addr *ha; - int i = 0, err; - int mac_cnt; + u64 hash; - netif_addr_lock_bh(ndev); + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, uc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - mac_cnt = netdev_uc_count(ndev); - if (!mac_cnt) { - netif_addr_unlock_bh(ndev); + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_uc_hash_filter(pf->si, 0, hash); +} + +static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *uc) +{ + struct enetc_si *si = pf->si; + int err; + + if (netdev_hw_addr_list_empty(uc)) { /* clear both MAC hash and exact filters */ - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); + enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); return 0; } - if (mac_cnt > max_num_mfe) { - err = -ENOSPC; - goto unlock_netif_addr; - } - - mac_tbl = kzalloc_objs(*mac_tbl, mac_cnt, GFP_ATOMIC); - if (!mac_tbl) { - err = -ENOMEM; - goto unlock_netif_addr; - } - - netdev_for_each_uc_addr(ha, ndev) { - enetc_add_mac_addr_ht_filter(&mac_filter, ha->addr); - ether_addr_copy(mac_tbl[i++].addr, ha->addr); - } - - netif_addr_unlock_bh(ndev); - - /* Set temporary unicast hash filters in case of Rx loss when + /* Set temporary unicast hash filter in case of Rx loss when * updating MAC address filter table */ - enetc4_pf_set_si_uc_hash_filter(hw, 0, *mac_filter.mac_hash_table); + enetc4_pf_set_uc_hash_filter(pf, uc); enetc4_pf_clear_maft_entries(pf); - if (!enetc4_pf_add_maft_entries(pf, mac_tbl, i)) - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); - - kfree(mac_tbl); - - return 0; - -unlock_netif_addr: - netif_addr_unlock_bh(ndev); + err = enetc4_pf_add_maft_entries(pf, uc); + if (!err) { + enetc_reset_mac_addr_filter(&pf->mac_filter[UC]); + enetc_set_si_uc_hash_filter(si, 0, 0); + } return err; } -static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type) +static void enetc4_pf_set_mc_hash_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *mc) { - struct net_device *ndev = pf->si->ndev; - struct enetc_mac_filter *mac_filter; - struct enetc_hw *hw = &pf->si->hw; + struct enetc_mac_filter *mac_filter = &pf->mac_filter[MC]; struct netdev_hw_addr *ha; + u64 hash; - netif_addr_lock_bh(ndev); - if (type & ENETC_MAC_FILTER_TYPE_UC) { - mac_filter = &pf->mac_filter[UC]; - enetc_reset_mac_addr_filter(mac_filter); - netdev_for_each_uc_addr(ha, ndev) - enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, mc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - enetc4_pf_set_si_uc_hash_filter(hw, 0, - *mac_filter->mac_hash_table); - } - - if (type & ENETC_MAC_FILTER_TYPE_MC) { - mac_filter = &pf->mac_filter[MC]; - enetc_reset_mac_addr_filter(mac_filter); - netdev_for_each_mc_addr(ha, ndev) - enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - - enetc4_pf_set_si_mc_hash_filter(hw, 0, - *mac_filter->mac_hash_table); - } - netif_addr_unlock_bh(ndev); + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_mc_hash_filter(pf->si, 0, hash); } -static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type) +static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { /* Currently, the MAC address filter table (MAFT) only has 4 entries, * and multiple multicast addresses for filtering will be configured @@ -258,15 +205,16 @@ static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type) * unicast filtering. If the number of unicast addresses exceeds the * table capacity, the MAC hash filter will be used. */ - if (type & ENETC_MAC_FILTER_TYPE_UC && enetc4_pf_set_uc_exact_filter(pf)) { + if (type & ENETC_MAC_FILTER_TYPE_UC && + enetc4_pf_set_uc_exact_filter(pf, uc)) { /* Fall back to the MAC hash filter */ - enetc4_pf_set_mac_hash_filter(pf, ENETC_MAC_FILTER_TYPE_UC); + enetc4_pf_set_uc_hash_filter(pf, uc); /* Clear the old MAC exact filter */ enetc4_pf_clear_maft_entries(pf); } if (type & ENETC_MAC_FILTER_TYPE_MC) - enetc4_pf_set_mac_hash_filter(pf, ENETC_MAC_FILTER_TYPE_MC); + enetc4_pf_set_mc_hash_filter(pf, mc); } static const struct enetc_pf_ops enetc4_pf_ops = { @@ -306,34 +254,35 @@ static void enetc4_default_rings_allocation(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; u32 num_rx_bdr, num_tx_bdr, val; + int num_vfs = pf->total_vfs; u32 vf_tx_bdr, vf_rx_bdr; int i, rx_rem, tx_rem; - if (pf->caps.num_rx_bdr < ENETC_SI_MAX_RING_NUM + pf->caps.num_vsi) - num_rx_bdr = pf->caps.num_rx_bdr - pf->caps.num_vsi; + if (pf->caps.num_rx_bdr < ENETC_SI_MAX_RING_NUM + num_vfs) + num_rx_bdr = pf->caps.num_rx_bdr - num_vfs; else num_rx_bdr = ENETC_SI_MAX_RING_NUM; - if (pf->caps.num_tx_bdr < ENETC_SI_MAX_RING_NUM + pf->caps.num_vsi) - num_tx_bdr = pf->caps.num_tx_bdr - pf->caps.num_vsi; + if (pf->caps.num_tx_bdr < ENETC_SI_MAX_RING_NUM + num_vfs) + num_tx_bdr = pf->caps.num_tx_bdr - num_vfs; else num_tx_bdr = ENETC_SI_MAX_RING_NUM; val = enetc4_psicfgr0_val_construct(false, num_tx_bdr, num_rx_bdr); enetc_port_wr(hw, ENETC4_PSICFGR0(0), val); - if (!pf->caps.num_vsi) + if (!num_vfs) return; num_rx_bdr = pf->caps.num_rx_bdr - num_rx_bdr; - rx_rem = num_rx_bdr % pf->caps.num_vsi; - num_rx_bdr = num_rx_bdr / pf->caps.num_vsi; + rx_rem = num_rx_bdr % num_vfs; + num_rx_bdr = num_rx_bdr / num_vfs; num_tx_bdr = pf->caps.num_tx_bdr - num_tx_bdr; - tx_rem = num_tx_bdr % pf->caps.num_vsi; - num_tx_bdr = num_tx_bdr / pf->caps.num_vsi; + tx_rem = num_tx_bdr % num_vfs; + num_tx_bdr = num_tx_bdr / num_vfs; - for (i = 0; i < pf->caps.num_vsi; i++) { + for (i = 0; i < num_vfs; i++) { vf_tx_bdr = (i < tx_rem) ? num_tx_bdr + 1 : num_tx_bdr; vf_rx_bdr = (i < rx_rem) ? num_rx_bdr + 1 : num_rx_bdr; val = enetc4_psicfgr0_val_construct(true, vf_tx_bdr, vf_rx_bdr); @@ -346,53 +295,70 @@ static void enetc4_allocate_si_rings(struct enetc_pf *pf) enetc4_default_rings_allocation(pf); } -static void enetc4_pf_set_si_vlan_promisc(struct enetc_hw *hw, int si, bool en) -{ - u32 val = enetc_port_rd(hw, ENETC4_PSIPVMR); - - if (en) - val |= BIT(si); - else - val &= ~BIT(si); - - enetc_port_wr(hw, ENETC4_PSIPVMR, val); -} - -static void enetc4_set_default_si_vlan_promisc(struct enetc_pf *pf) -{ - struct enetc_hw *hw = &pf->si->hw; - int num_si = pf->caps.num_vsi + 1; - int i; - - /* enforce VLAN promiscuous mode for all SIs */ - for (i = 0; i < num_si; i++) - enetc4_pf_set_si_vlan_promisc(hw, i, true); -} - /* Allocate the number of MSI-X vectors for per SI. */ static void enetc4_set_si_msix_num(struct enetc_pf *pf) { + int valid_num_si = pf->total_vfs + 1; struct enetc_hw *hw = &pf->si->hw; - int i, num_msix, total_si; + int i, num_msix, num_vsi; u32 val; - total_si = pf->caps.num_vsi + 1; + val = enetc_port_rd(hw, ENETC4_ECAPR1); + num_vsi = FIELD_GET(ECAPR1_NUM_VSI, val); - num_msix = pf->caps.num_msix / total_si + - pf->caps.num_msix % total_si - 1; - val = num_msix & PSICFGR2_NUM_MSIX; - enetc_port_wr(hw, ENETC4_PSICFGR2(0), val); + /* The PSIaCFGR2[NUM_MSIX] indicates the number of MSI-X allocated to + * the SI is NUM_MSIX + 1, so the minimum number of MSI-X allocated to + * each SI is 1. The total number of MSI-X allocated to PSI and VSIs + * cannot exceed the total number of MSI-X owned by this ENETC, which + * is ECAPR1[NUM_MSIX]. Otherwise, when multiple ENETC instances exist, + * it will affect other ENETCs whose MSI-X interrupts cannot be + * generated. This is similar to out-of-bounds array access: the array + * itself is not affected, but adjacent arrays will be corrupted. + * + * pf->total_vfs is 0 if CONFIG_PCI_IOV is disabled. If the hardware + * itself supports SR-IOV, then when allocating the number of MSIXs to + * the SI, it must be taken into account that the VSI has at least 1 + * MSIX, and the total number of MSIXs of all SIs cannot exceed + * ECAPR1[NUM_MSIX]. + */ + if (!pf->total_vfs && num_vsi) { + /* Because each SI has at least one MSIX, and from the hardware + * perspective, pf->caps.num_msix will always be greater than + * num_vsi. So num_msix is always greater than or equal to 0. + */ + num_msix = pf->caps.num_msix - num_vsi - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + enetc_port_wr(hw, ENETC4_PSICFGR2(0), num_msix); - num_msix = pf->caps.num_msix / total_si - 1; - val = num_msix & PSICFGR2_NUM_MSIX; - for (i = 0; i < pf->caps.num_vsi; i++) - enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), val); + for (i = 0; i < num_vsi; i++) + enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), 0); + + return; + } + + /* Likewise, from the hardware perspective pf->caps.num_msix is always + * greater than valid_num_si. So num_msix is always greater than or + * equal to 0. + */ + num_msix = pf->caps.num_msix / valid_num_si + + pf->caps.num_msix % valid_num_si - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + enetc_port_wr(hw, ENETC4_PSICFGR2(0), num_msix); + + num_msix = pf->caps.num_msix / valid_num_si - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + + for (i = 0; i < pf->total_vfs; i++) + enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), num_msix); } static void enetc4_enable_all_si(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; - int num_si = pf->caps.num_vsi + 1; + int num_si = pf->total_vfs + 1; u32 si_bitmap = 0; int i; @@ -412,7 +378,9 @@ static void enetc4_configure_port_si(struct enetc_pf *pf) /* Outer VLAN tag will be used for VLAN filtering */ enetc_port_wr(hw, ENETC4_PSIVLANFMR, PSIVLANFMR_VS); - enetc4_set_default_si_vlan_promisc(pf); + /* Enforce VLAN promiscuous mode for all SIs */ + for (int i = 0; i < pf->total_vfs + 1; i++) + enetc_set_si_vlan_promisc(pf->si, i, true); /* Disable SI MAC multicast & unicast promiscuous */ enetc_port_wr(hw, ENETC4_PSIPMMR, 0); @@ -450,18 +418,60 @@ static void enetc4_configure_port(struct enetc_pf *pf) enetc_set_default_rss_key(pf); } +static void enetc4_get_ntmp_caps(struct enetc_si *si) +{ + struct ntmp_user *user = &si->ntmp_user; + struct enetc_hw *hw = &si->hw; + u32 val; + + val = enetc_port_rd(hw, ENETC4_PSIMAFCAPR); + user->maft_num_entries = FIELD_GET(PSIMAFCAPR_NUM_MAC_AFTE, val); +} + +static int enetc4_ntmp_bitmap_init(struct ntmp_user *user) +{ + user->maft_eid_bitmap = bitmap_zalloc(user->maft_num_entries, + GFP_KERNEL); + if (!user->maft_eid_bitmap) + return -ENOMEM; + + return 0; +} + +static void enetc4_ntmp_bitmap_free(struct ntmp_user *user) +{ + bitmap_free(user->maft_eid_bitmap); + user->maft_eid_bitmap = NULL; +} + static int enetc4_init_ntmp_user(struct enetc_si *si) { struct ntmp_user *user = &si->ntmp_user; + int err; /* For ENETC 4.1, all table versions are 0 */ memset(&user->tbl, 0, sizeof(user->tbl)); - return enetc4_setup_cbdr(si); + err = enetc4_setup_cbdr(si); + if (err) + return err; + + enetc4_get_ntmp_caps(si); + err = enetc4_ntmp_bitmap_init(user); + if (err) + goto teardown_cbdr; + + return 0; + +teardown_cbdr: + enetc4_teardown_cbdr(si); + + return err; } static void enetc4_free_ntmp_user(struct enetc_si *si) { + enetc4_ntmp_bitmap_free(&si->ntmp_user); enetc4_teardown_cbdr(si); } @@ -479,7 +489,7 @@ static int enetc4_pf_init(struct enetc_pf *pf) err = enetc4_init_ntmp_user(pf->si); if (err) { - dev_err(dev, "Failed to init CBDR\n"); + dev_err(dev, "Failed to init NTMP user\n"); return err; } @@ -493,18 +503,17 @@ static void enetc4_pf_free(struct enetc_pf *pf) enetc4_free_ntmp_user(pf->si); } -static void enetc4_psi_do_set_rx_mode(struct work_struct *work) +static int enetc4_pf_set_rx_mode(struct net_device *ndev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { - struct enetc_si *si = container_of(work, struct enetc_si, rx_mode_task); - struct enetc_pf *pf = enetc_si_priv(si); - struct net_device *ndev = si->ndev; - struct enetc_hw *hw = &si->hw; + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_si *si = priv->si; bool uc_promisc = false; bool mc_promisc = false; int type = 0; - rtnl_lock(); - if (ndev->flags & IFF_PROMISC) { uc_promisc = true; mc_promisc = true; @@ -515,28 +524,21 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work) type = ENETC_MAC_FILTER_TYPE_ALL; } - enetc4_pf_set_si_mac_promisc(hw, 0, uc_promisc, mc_promisc); + enetc_set_si_uc_promisc(si, 0, uc_promisc); + enetc_set_si_mc_promisc(si, 0, mc_promisc); if (uc_promisc) { - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); + enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); } if (mc_promisc) - enetc4_pf_set_si_mc_hash_filter(hw, 0, 0); + enetc_set_si_mc_hash_filter(si, 0, 0); /* Set new MAC filter */ - enetc4_pf_set_mac_filter(pf, type); + enetc4_pf_set_mac_filter(pf, type, uc, mc); - rtnl_unlock(); -} - -static void enetc4_pf_set_rx_mode(struct net_device *ndev) -{ - struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_si *si = priv->si; - - queue_work(si->workqueue, &si->rx_mode_task); + return 0; } static int enetc4_pf_set_features(struct net_device *ndev, @@ -544,12 +546,11 @@ static int enetc4_pf_set_features(struct net_device *ndev, { netdev_features_t changed = ndev->features ^ features; struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_hw *hw = &priv->si->hw; if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) { bool promisc_en = !(features & NETIF_F_HW_VLAN_CTAG_FILTER); - enetc4_pf_set_si_vlan_promisc(hw, 0, promisc_en); + enetc_set_si_vlan_promisc(priv->si, 0, promisc_en); } if (changed & NETIF_F_LOOPBACK) @@ -566,7 +567,7 @@ static const struct net_device_ops enetc4_ndev_ops = { .ndo_start_xmit = enetc_xmit, .ndo_get_stats = enetc_get_stats, .ndo_set_mac_address = enetc_pf_set_mac_addr, - .ndo_set_rx_mode = enetc4_pf_set_rx_mode, + .ndo_set_rx_mode_async = enetc4_pf_set_rx_mode, .ndo_set_features = enetc4_pf_set_features, .ndo_vlan_rx_add_vid = enetc_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = enetc_vlan_rx_del_vid, @@ -602,11 +603,6 @@ static void enetc4_mac_config(struct enetc_pf *pf, unsigned int mode, case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_TXID: val |= IFMODE_RGMII; - /* We need to enable auto-negotiation for the MAC - * if its RGMII interface support In-Band status. - */ - if (phylink_autoneg_inband(mode)) - val |= PM_IF_MODE_ENA; break; case PHY_INTERFACE_MODE_RMII: val |= IFMODE_RMII; @@ -616,7 +612,6 @@ static void enetc4_mac_config(struct enetc_pf *pf, unsigned int mode, val |= IFMODE_SGMII; break; case PHY_INTERFACE_MODE_10GBASER: - case PHY_INTERFACE_MODE_XGMII: case PHY_INTERFACE_MODE_USXGMII: val |= IFMODE_XGMII; break; @@ -642,26 +637,19 @@ static void enetc4_set_port_speed(struct enetc_ndev_priv *priv, int speed) u32 old_speed = priv->speed; u32 val; + /* If the speed is unknown, use the minimum value */ + if (speed == SPEED_UNKNOWN) { + speed = SPEED_10; + dev_warn(priv->dev, "Speed unknown, default is 10Mbps\n"); + } + if (speed == old_speed) return; - val = enetc_port_rd(&priv->si->hw, ENETC4_PCR); - val &= ~PCR_PSPEED; - - switch (speed) { - case SPEED_100: - case SPEED_1000: - case SPEED_2500: - case SPEED_10000: - val |= (PCR_PSPEED & PCR_PSPEED_VAL(speed)); - break; - case SPEED_10: - default: - val |= (PCR_PSPEED & PCR_PSPEED_VAL(SPEED_10)); - } - - priv->speed = speed; + val = enetc_port_rd(&priv->si->hw, ENETC4_PCR) & (~PCR_PSPEED); + val |= PCR_PSPEED_VAL(speed); enetc_port_wr(&priv->si->hw, ENETC4_PCR, val); + priv->speed = speed; } static void enetc4_set_rgmii_mac(struct enetc_pf *pf, int speed, int duplex) @@ -717,22 +705,6 @@ static void enetc4_set_rmii_mac(struct enetc_pf *pf, int speed, int duplex) enetc_port_mac_wr(si, ENETC4_PM_IF_MODE(0), val); } -static void enetc4_set_hd_flow_control(struct enetc_pf *pf, bool enable) -{ - struct enetc_si *si = pf->si; - u32 old_val, val; - - if (!pf->caps.half_duplex) - return; - - old_val = enetc_port_mac_rd(si, ENETC4_PM_CMD_CFG(0)); - val = u32_replace_bits(old_val, enable ? 1 : 0, PM_CMD_CFG_HD_FCEN); - if (val == old_val) - return; - - enetc_port_mac_wr(si, ENETC4_PM_CMD_CFG(0), val); -} - static void enetc4_set_rx_pause(struct enetc_pf *pf, bool rx_pause) { struct enetc_si *si = pf->si; @@ -908,13 +880,11 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config, struct enetc_pf *pf = phylink_to_enetc_pf(config); struct enetc_si *si = pf->si; struct enetc_ndev_priv *priv; - bool hd_fc = false; priv = netdev_priv(si->ndev); enetc4_set_port_speed(priv, speed); - if (!phylink_autoneg_inband(mode) && - phy_interface_mode_is_rgmii(interface)) + if (phy_interface_mode_is_rgmii(interface)) enetc4_set_rgmii_mac(pf, speed, duplex); if (interface == PHY_INTERFACE_MODE_RMII) @@ -926,18 +896,8 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config, */ if (priv->active_offloads & ENETC_F_QBU) tx_pause = false; - } else { /* DUPLEX_HALF */ - if (tx_pause || rx_pause) - hd_fc = true; - - /* As per 802.3 annex 31B, PAUSE frames are only supported - * when the link is configured for full duplex operation. - */ - tx_pause = false; - rx_pause = false; } - enetc4_set_hd_flow_control(pf, hd_fc); enetc4_set_tx_pause(pf, priv->num_rx_rings, tx_pause); enetc4_set_rx_pause(pf, rx_pause); enetc4_mac_tx_enable(pf); @@ -1009,19 +969,6 @@ static void enetc4_link_deinit(struct enetc_ndev_priv *priv) enetc_mdiobus_destroy(pf); } -static int enetc4_psi_wq_task_init(struct enetc_si *si) -{ - char wq_name[24]; - - INIT_WORK(&si->rx_mode_task, enetc4_psi_do_set_rx_mode); - snprintf(wq_name, sizeof(wq_name), "enetc-%s", pci_name(si->pdev)); - si->workqueue = create_singlethread_workqueue(wq_name); - if (!si->workqueue) - return -ENOMEM; - - return 0; -} - static int enetc4_pf_netdev_create(struct enetc_si *si) { struct device *dev = &si->pdev->dev; @@ -1062,12 +1009,6 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) if (err) goto err_link_init; - err = enetc4_psi_wq_task_init(si); - if (err) { - dev_err(dev, "Failed to init workqueue\n"); - goto err_wq_init; - } - err = register_netdev(ndev); if (err) { dev_err(dev, "Failed to register netdev\n"); @@ -1077,8 +1018,6 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) return 0; err_reg_netdev: - destroy_workqueue(si->workqueue); -err_wq_init: enetc4_link_deinit(priv); err_link_init: enetc_free_msix(priv); @@ -1096,8 +1035,6 @@ static void enetc4_pf_netdev_destroy(struct enetc_si *si) struct net_device *ndev = si->ndev; unregister_netdev(ndev); - cancel_work(&si->rx_mode_task); - destroy_workqueue(si->workqueue); enetc4_link_deinit(priv); enetc_free_msix(priv); free_netdev(ndev); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c index 71f376ef1be1..07b7832f2427 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c @@ -29,7 +29,7 @@ static const u32 enetc_rxbdr_regs[] = { }; static const u32 enetc_port_regs[] = { - ENETC_PMR, ENETC_PSR, ENETC_PSIPMR, ENETC_PSIPMAR0(0), + ENETC_PMR, ENETC_PSR, ENETC_PSIPMMR, ENETC_PSIPMAR0(0), ENETC_PSIPMAR1(0), ENETC_PTXMBAR, ENETC_PCAPR0, ENETC_PCAPR1, ENETC_PSICFGR0(0), ENETC_PRFSCAPR, ENETC_PTCMSDUR(0), ENETC_PM0_CMD_CFG, ENETC_PM0_MAXFRM, ENETC_PM0_IF_MODE diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index bf99b65d7598..16da732dc5de 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -180,13 +180,13 @@ enum enetc_bdr_type {TX, RX}; #define ENETC_PMR_PSPEED_1000M BIT(9) #define ENETC_PMR_PSPEED_2500M BIT(10) #define ENETC_PSR 0x0004 /* RO */ -#define ENETC_PSIPMR 0x0018 -#define ENETC_PSIPMR_SET_UP(n) BIT(n) /* n = SI index */ -#define ENETC_PSIPMR_SET_MP(n) BIT((n) + 16) +#define ENETC_PSIPMMR 0x0018 +#define PSIPMMR_SI_MAC_UP(n) BIT(n) /* n = SI index */ +#define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16) + #define ENETC_PSIPVMR 0x001c -#define ENETC_VLAN_PROMISC_MAP_ALL 0x7 -#define ENETC_PSIPVMR_SET_VP(simap) ((simap) & 0x7) -#define ENETC_PSIPVMR_SET_VUTA(simap) (((simap) & 0x7) << 16) +#define PSIPVMR_SI_VLAN_P(n) BIT(n) /* n = SI index */ + #define ENETC_PSIPMAR0(n) (0x0100 + (n) * 0x8) /* n = SI index */ #define ENETC_PSIPMAR1(n) (0x0104 + (n) * 0x8) #define ENETC_PVCLCTR 0x0208 diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 2d687bb8c3a0..a509929f89f2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -42,26 +42,6 @@ static void enetc_pf_destroy_pcs(struct phylink_pcs *pcs) lynx_pcs_destroy(pcs); } -static void enetc_set_vlan_promisc(struct enetc_hw *hw, char si_map) -{ - u32 val = enetc_port_rd(hw, ENETC_PSIPVMR); - - val &= ~ENETC_PSIPVMR_SET_VP(ENETC_VLAN_PROMISC_MAP_ALL); - enetc_port_wr(hw, ENETC_PSIPVMR, ENETC_PSIPVMR_SET_VP(si_map) | val); -} - -static void enetc_enable_si_vlan_promisc(struct enetc_pf *pf, int si_idx) -{ - pf->vlan_promisc_simap |= BIT(si_idx); - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap); -} - -static void enetc_disable_si_vlan_promisc(struct enetc_pf *pf, int si_idx) -{ - pf->vlan_promisc_simap &= ~BIT(si_idx); - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap); -} - static void enetc_set_isol_vlan(struct enetc_hw *hw, int si, u16 vlan, u8 qos) { u32 val = 0; @@ -80,37 +60,6 @@ static void enetc_add_mac_addr_em_filter(struct enetc_mac_filter *filter, filter->mac_addr_cnt++; } -static void enetc_clear_mac_ht_flt(struct enetc_si *si, int si_idx, int type) -{ - bool err = si->errata & ENETC_ERR_UCMCSWP; - - if (type == UC) { - enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err), 0); - enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx), 0); - } else { /* MC */ - enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err), 0); - enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx), 0); - } -} - -static void enetc_set_mac_ht_flt(struct enetc_si *si, int si_idx, int type, - unsigned long hash) -{ - bool err = si->errata & ENETC_ERR_UCMCSWP; - - if (type == UC) { - enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err), - lower_32_bits(hash)); - enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx), - upper_32_bits(hash)); - } else { /* MC */ - enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err), - lower_32_bits(hash)); - enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx), - upper_32_bits(hash)); - } -} - static void enetc_sync_mac_filters(struct enetc_pf *pf) { struct enetc_mac_filter *f = pf->mac_filter; @@ -122,12 +71,16 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) for (i = 0; i < MADDR_TYPE; i++, f++) { bool em = (f->mac_addr_cnt == 1) && (i == UC); bool clear = !f->mac_addr_cnt; + u64 hash; if (clear) { - if (i == UC) + if (i == UC) { enetc_clear_mac_flt_entry(si, pos); + enetc_set_si_uc_hash_filter(si, 0, 0); + } else { + enetc_set_si_mc_hash_filter(si, 0, 0); + } - enetc_clear_mac_ht_flt(si, 0, i); continue; } @@ -135,7 +88,7 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) if (em) { int err; - enetc_clear_mac_ht_flt(si, 0, UC); + enetc_set_si_uc_hash_filter(si, 0, 0); err = enetc_set_mac_flt_entry(si, pos, f->mac_addr, BIT(0)); @@ -147,11 +100,15 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) err); } + bitmap_to_arr64(&hash, f->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); /* hash table filter, clear EM filter for UC entries */ - if (i == UC) + if (i == UC) { enetc_clear_mac_flt_entry(si, pos); - - enetc_set_mac_ht_flt(si, 0, i, *f->mac_hash_table); + enetc_set_si_uc_hash_filter(si, 0, hash); + } else { + enetc_set_si_mc_hash_filter(si, 0, hash); + } } } @@ -159,21 +116,17 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) { struct enetc_ndev_priv *priv = netdev_priv(ndev); struct enetc_pf *pf = enetc_si_priv(priv->si); - struct enetc_hw *hw = &priv->si->hw; bool uprom = false, mprom = false; struct enetc_mac_filter *filter; struct netdev_hw_addr *ha; - u32 psipmr = 0; bool em; if (ndev->flags & IFF_PROMISC) { /* enable promisc mode for SI0 (PF) */ - psipmr = ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0); uprom = true; mprom = true; } else if (ndev->flags & IFF_ALLMULTI) { /* enable multi cast promisc mode for SI0 (PF) */ - psipmr = ENETC_PSIPMR_SET_MP(0); mprom = true; } @@ -211,9 +164,8 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) /* update PF entries */ enetc_sync_mac_filters(pf); - psipmr |= enetc_port_rd(hw, ENETC_PSIPMR) & - ~(ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0)); - enetc_port_wr(hw, ENETC_PSIPMR, psipmr); + enetc_set_si_uc_promisc(priv->si, 0, uprom); + enetc_set_si_mc_promisc(priv->si, 0, mprom); } static void enetc_set_loopback(struct net_device *ndev, bool en) @@ -469,12 +421,11 @@ static void enetc_configure_port(struct enetc_pf *pf) /* split up RFS entries */ enetc_port_assign_rfs_entries(pf->si); - /* enforce VLAN promisc mode for all SIs */ - pf->vlan_promisc_simap = ENETC_VLAN_PROMISC_MAP_ALL; - enetc_set_vlan_promisc(hw, pf->vlan_promisc_simap); + for (int i = 0; i < pf->total_vfs + 1; i++) + enetc_set_si_vlan_promisc(pf->si, i, true); - enetc_port_wr(hw, ENETC_PSIPMR, 0); + enetc_port_wr(hw, ENETC_PSIPMMR, 0); /* enable port */ enetc_port_wr(hw, ENETC_PMR, ENETC_PMR_EN); @@ -494,12 +445,9 @@ static int enetc_pf_set_features(struct net_device *ndev, } if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) { - struct enetc_pf *pf = enetc_si_priv(priv->si); + bool promisc = !(features & NETIF_F_HW_VLAN_CTAG_FILTER); - if (!!(features & NETIF_F_HW_VLAN_CTAG_FILTER)) - enetc_disable_si_vlan_promisc(pf, 0); - else - enetc_enable_si_vlan_promisc(pf, 0); + enetc_set_si_vlan_promisc(priv->si, 0, promisc); } if (changed & NETIF_F_LOOPBACK) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 285b7e5c48fd..56d23a8a11a0 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -17,12 +17,9 @@ struct enetc_vf_state { }; struct enetc_port_caps { - u32 half_duplex:1; - int num_vsi; int num_msix; int num_rx_bdr; int num_tx_bdr; - int mac_filter_num; }; struct enetc_pf; @@ -47,7 +44,6 @@ struct enetc_pf { struct work_struct msg_task; char msg_int_name[ENETC_INT_NAME_MAX]; - char vlan_promisc_simap; /* bitmap of SIs in VLAN promisc mode */ DECLARE_BITMAP(vlan_ht_filter, ENETC_VLAN_HT_SIZE); DECLARE_BITMAP(active_vlans, VLAN_N_VID); @@ -60,8 +56,6 @@ struct enetc_pf { struct enetc_port_caps caps; const struct enetc_pf_ops *ops; - - int num_mfe; /* number of mac address filter table entries */ }; #define phylink_to_enetc_pf(config) \ diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 6e5d2f869915..d32a195a04c9 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) -/* Copyright 2024 NXP */ +/* Copyright 2024-2026 NXP */ #include #include @@ -87,6 +87,112 @@ int enetc_setup_mac_addresses(struct device_node *np, struct enetc_pf *pf) } EXPORT_SYMBOL_GPL(enetc_setup_mac_addresses); +void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipmmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipmmr_off = ENETC_PSIPMMR; + else + psipmmr_off = ENETC4_PSIPMMR; + + val = enetc_port_rd(hw, psipmmr_off); + + if (promisc) + val |= PSIPMMR_SI_MAC_UP(si_id); + else + val &= ~PSIPMMR_SI_MAC_UP(si_id); + + enetc_port_wr(hw, psipmmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_uc_promisc); + +void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipmmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipmmr_off = ENETC_PSIPMMR; + else + psipmmr_off = ENETC4_PSIPMMR; + + val = enetc_port_rd(hw, psipmmr_off); + + if (promisc) + val |= PSIPMMR_SI_MAC_MP(si_id); + else + val &= ~PSIPMMR_SI_MAC_MP(si_id); + + enetc_port_wr(hw, psipmmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_mc_promisc); + +void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +{ + int psiumhfr0_off, psiumhfr1_off; + struct enetc_hw *hw = &si->hw; + + if (is_enetc_rev1(si)) { + bool err = si->errata & ENETC_ERR_UCMCSWP; + + psiumhfr0_off = ENETC_PSIUMHFR0(si_id, err); + psiumhfr1_off = ENETC_PSIUMHFR1(si_id); + } else { + psiumhfr0_off = ENETC4_PSIUMHFR0(si_id); + psiumhfr1_off = ENETC4_PSIUMHFR1(si_id); + } + + enetc_port_wr(hw, psiumhfr0_off, lower_32_bits(hash)); + enetc_port_wr(hw, psiumhfr1_off, upper_32_bits(hash)); +} +EXPORT_SYMBOL_GPL(enetc_set_si_uc_hash_filter); + +void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +{ + int psimmhfr0_off, psimmhfr1_off; + struct enetc_hw *hw = &si->hw; + + if (is_enetc_rev1(si)) { + bool err = si->errata & ENETC_ERR_UCMCSWP; + + psimmhfr0_off = ENETC_PSIMMHFR0(si_id, err); + psimmhfr1_off = ENETC_PSIMMHFR1(si_id); + } else { + psimmhfr0_off = ENETC4_PSIMMHFR0(si_id); + psimmhfr1_off = ENETC4_PSIMMHFR1(si_id); + } + + enetc_port_wr(hw, psimmhfr0_off, lower_32_bits(hash)); + enetc_port_wr(hw, psimmhfr1_off, upper_32_bits(hash)); +} +EXPORT_SYMBOL_GPL(enetc_set_si_mc_hash_filter); + +void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipvmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipvmr_off = ENETC_PSIPVMR; + else + psipvmr_off = ENETC4_PSIPVMR; + + val = enetc_port_rd(hw, psipvmr_off); + + if (promisc) + val |= PSIPVMR_SI_VLAN_P(si_id); + else + val &= ~PSIPVMR_SI_VLAN_P(si_id); + + enetc_port_wr(hw, psipvmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_vlan_promisc); + void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev, const struct net_device_ops *ndev_ops) { @@ -275,7 +381,8 @@ static bool enetc_port_has_pcs(struct enetc_pf *pf) return (pf->if_mode == PHY_INTERFACE_MODE_SGMII || pf->if_mode == PHY_INTERFACE_MODE_1000BASEX || pf->if_mode == PHY_INTERFACE_MODE_2500BASEX || - pf->if_mode == PHY_INTERFACE_MODE_USXGMII); + pf->if_mode == PHY_INTERFACE_MODE_USXGMII || + pf->if_mode == PHY_INTERFACE_MODE_10GBASER); } int enetc_mdiobus_create(struct enetc_pf *pf, struct device_node *node) @@ -316,25 +423,42 @@ int enetc_phylink_create(struct enetc_ndev_priv *priv, struct device_node *node, { struct enetc_pf *pf = enetc_si_priv(priv->si); struct phylink *phylink; + unsigned long mac_caps; int err; pf->phylink_config.dev = &priv->ndev->dev; pf->phylink_config.type = PHYLINK_NETDEV; - pf->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | - MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD; __set_bit(PHY_INTERFACE_MODE_INTERNAL, pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_SGMII, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_1000BASEX, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_2500BASEX, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_USXGMII, - pf->phylink_config.supported_interfaces); - phy_interface_set_rgmii(pf->phylink_config.supported_interfaces); + mac_caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; + if (!enetc_is_pseudo_mac(priv->si)) { + mac_caps |= MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD; + + __set_bit(PHY_INTERFACE_MODE_SGMII, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_USXGMII, + pf->phylink_config.supported_interfaces); + + if (!is_enetc_rev1(priv->si)) { + mac_caps |= MAC_5000FD | MAC_10000FD; + __set_bit(PHY_INTERFACE_MODE_10GBASER, + pf->phylink_config.supported_interfaces); + } + + phy_interface_set_rgmii(pf->phylink_config.supported_interfaces); + } else { + mac_caps |= MAC_10FD | MAC_100FD | MAC_1000FD | MAC_2500FD | + MAC_5000FD | MAC_10000FD | MAC_20000FD | + MAC_25000FD; + } + + pf->phylink_config.mac_capabilities = mac_caps; phylink = phylink_create(&pf->phylink_config, of_fwnode_handle(node), pf->if_mode, ops); if (IS_ERR(phylink)) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 57d2e0ebd2b0..8243ce0de57f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -17,6 +17,11 @@ void enetc_set_default_rss_key(struct enetc_pf *pf); int enetc_vlan_rx_add_vid(struct net_device *ndev, __be16 prot, u16 vid); int enetc_vlan_rx_del_vid(struct net_device *ndev, __be16 prot, u16 vid); int enetc_init_sriov_resources(struct enetc_pf *pf); +void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc); +void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc); +void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash); +void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); +void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_qos.c b/drivers/net/ethernet/freescale/enetc/enetc_qos.c index 7b17bca24f26..2aa0fcaafcd2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_qos.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_qos.c @@ -1135,7 +1135,6 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv, struct flow_action_entry *entry; struct action_gate_entry *e; u8 sfi_overwrite = 0; - int entries_size; int i, err; if (f->common.chain_index >= priv->psfp_cap.max_streamid) { @@ -1242,8 +1241,7 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv, goto free_filter; } - entries_size = struct_size(sgi, entries, entryg->gate.num_entries); - sgi = kzalloc(entries_size, GFP_KERNEL); + sgi = kzalloc_flex(*sgi, entries, entryg->gate.num_entries); if (!sgi) { err = -ENOMEM; goto free_filter; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c index 9cdb0a4d6baf..7dcb4a0246f5 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c @@ -317,7 +317,14 @@ static int enetc_vf_probe(struct pci_dev *pdev, enetc_get_si_caps(si); - ndev = alloc_etherdev_mq(sizeof(*priv), ENETC_MAX_NUM_TXQS); + /* Currently, the supported SoCs have a max of 6 CPUs and the VFs + * have less than 6 RX/TX rings. So no issues for these supported + * SoCs, but for future SoCs which have more CPUs or more TX/RX + * rings, all the related logic needs to be improved. + */ + ndev = alloc_etherdev_mqs(sizeof(*priv), + min(si->num_tx_rings, ENETC_MAX_NUM_TXQS), + min(si->num_rx_rings, ENETC_MAX_NUM_TXQS)); if (!ndev) { err = -ENOMEM; dev_err(&pdev->dev, "netdev creation failed\n"); diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c index 89215e1ddc2d..cf636fc5aafa 100644 --- a/drivers/net/ethernet/freescale/gianfar.c +++ b/drivers/net/ethernet/freescale/gianfar.c @@ -2877,11 +2877,7 @@ static int gfar_enet_open(struct net_device *dev) if (err) return err; - err = startup_gfar(dev); - if (err) - return err; - - return err; + return startup_gfar(dev); } /* Stops the kernel queue, and halts the controller */ diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c index 08587bf40ed4..f05f4895f4c7 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.c +++ b/drivers/net/ethernet/google/gve/gve_adminq.c @@ -385,7 +385,7 @@ void gve_adminq_release(struct gve_priv *priv) gve_clear_admin_queue_ok(priv); } -void gve_adminq_free(struct device *dev, struct gve_priv *priv) +void gve_adminq_free(struct gve_priv *priv) { if (!gve_get_admin_queue_ok(priv)) return; @@ -920,19 +920,6 @@ int gve_adminq_destroy_rx_queues(struct gve_priv *priv, u32 num_queues) return err; } -static void gve_set_default_desc_cnt(struct gve_priv *priv, - const struct gve_device_descriptor *descriptor) -{ - priv->tx_desc_cnt = be16_to_cpu(descriptor->tx_queue_entries); - priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries); - - /* set default ranges */ - priv->max_tx_desc_cnt = priv->tx_desc_cnt; - priv->max_rx_desc_cnt = priv->rx_desc_cnt; - priv->min_tx_desc_cnt = priv->tx_desc_cnt; - priv->min_rx_desc_cnt = priv->rx_desc_cnt; -} - static void gve_set_default_rss_sizes(struct gve_priv *priv) { if (!gve_is_gqi(priv)) { @@ -1049,8 +1036,6 @@ int gve_adminq_describe_device(struct gve_priv *priv) union gve_adminq_command cmd; dma_addr_t descriptor_bus; int err = 0; - u8 *mac; - u16 mtu; memset(&cmd, 0, sizeof(cmd)); descriptor = dma_pool_alloc(priv->adminq_pool, GFP_KERNEL, @@ -1112,34 +1097,17 @@ int gve_adminq_describe_device(struct gve_priv *priv) "Driver is running with GQI QPL queue format.\n"); } - /* set default descriptor counts */ - gve_set_default_desc_cnt(priv, descriptor); - gve_set_default_rss_sizes(priv); - /* DQO supports HW-GRO and UDP_GSO */ - if (gve_is_dqo(priv)) { - u64 additional_features = NETIF_F_GRO_HW | NETIF_F_GSO_UDP_L4; - - priv->dev->hw_features |= additional_features; - priv->dev->features |= additional_features; - } - - priv->max_registered_pages = - be64_to_cpu(descriptor->max_registered_pages); - mtu = be16_to_cpu(descriptor->mtu); - if (mtu < ETH_MIN_MTU) { - dev_err(&priv->pdev->dev, "MTU %d below minimum MTU\n", mtu); - err = -EINVAL; + err = gve_set_mtu(priv, descriptor); + if (err) goto free_device_descriptor; - } - priv->dev->max_mtu = mtu; + priv->num_event_counters = be16_to_cpu(descriptor->counters); - eth_hw_addr_set(priv->dev, descriptor->mac); - mac = descriptor->mac; - dev_info(&priv->pdev->dev, "MAC addr: %pM\n", mac); - priv->tx_pages_per_qpl = be16_to_cpu(descriptor->tx_pages_per_qpl); - priv->default_num_queues = be16_to_cpu(descriptor->default_num_queues); + + gve_set_mac(priv, descriptor); + + gve_set_queue_properties(priv, descriptor); gve_enable_supported_features(priv, supported_features_mask, dev_op_jumbo_frames, dev_op_dqo_qpl, @@ -1600,3 +1568,44 @@ int gve_adminq_query_rss_config(struct gve_priv *priv, struct ethtool_rxfh_param dma_pool_free(priv->adminq_pool, descriptor, descriptor_bus); return err; } + +int gve_set_num_ntfy_blks(struct gve_priv *priv) +{ + int num_ntfy; + + num_ntfy = pci_msix_vec_count(priv->pdev); + if (num_ntfy <= 0) { + dev_err(&priv->pdev->dev, + "could not count MSI-x vectors: err=%d\n", num_ntfy); + return num_ntfy; + } else if (num_ntfy < GVE_MIN_MSIX) { + dev_err(&priv->pdev->dev, "gve needs at least %d MSI-x vectors, but only has %d\n", + GVE_MIN_MSIX, num_ntfy); + return -EINVAL; + } + + /* gvnic has one Notification Block per MSI-x vector, except for the + * management vector + */ + priv->num_ntfy_blks = (num_ntfy - 1) & ~0x1; + priv->mgmt_msix_idx = priv->num_ntfy_blks; + + return 0; +} + +void gve_set_num_queues(struct gve_priv *priv) +{ + priv->tx_cfg.max_queues = + min_t(int, priv->tx_cfg.max_queues, priv->num_ntfy_blks / 2); + priv->rx_cfg.max_queues = + min_t(int, priv->rx_cfg.max_queues, priv->num_ntfy_blks / 2); + + priv->tx_cfg.num_queues = priv->tx_cfg.max_queues; + priv->rx_cfg.num_queues = priv->rx_cfg.max_queues; + if (priv->default_num_queues > 0) { + priv->tx_cfg.num_queues = min_t(int, priv->default_num_queues, + priv->tx_cfg.num_queues); + priv->rx_cfg.num_queues = min_t(int, priv->default_num_queues, + priv->rx_cfg.num_queues); + } +} diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h index 22a74b6aa17e..68c63ce75505 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.h +++ b/drivers/net/ethernet/google/gve/gve_adminq.h @@ -620,7 +620,7 @@ union gve_adminq_command { static_assert(sizeof(union gve_adminq_command) == 64); int gve_adminq_alloc(struct device *dev, struct gve_priv *priv); -void gve_adminq_free(struct device *dev, struct gve_priv *priv); +void gve_adminq_free(struct gve_priv *priv); void gve_adminq_release(struct gve_priv *priv); int gve_adminq_describe_device(struct gve_priv *priv); int gve_adminq_configure_device_resources(struct gve_priv *priv, @@ -656,5 +656,12 @@ int gve_adminq_report_nic_ts(struct gve_priv *priv, struct gve_ptype_lut; int gve_adminq_get_ptype_map_dqo(struct gve_priv *priv, struct gve_ptype_lut *ptype_lut); - +int gve_set_num_ntfy_blks(struct gve_priv *priv); +void gve_set_num_queues(struct gve_priv *priv); +void gve_set_queue_properties(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); +int gve_set_mtu(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); +void gve_set_mac(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); #endif /* _GVE_ADMINQ_H */ diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index e4d78ae52daf..9cc343a16271 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -2398,9 +2398,57 @@ static const struct xdp_metadata_ops gve_xdp_metadata_ops = { .xmo_rx_timestamp = gve_xdp_rx_timestamp, }; +static void gve_set_default_desc_cnt(struct gve_priv *priv, + const struct gve_device_descriptor *descriptor) +{ + priv->tx_desc_cnt = be16_to_cpu(descriptor->tx_queue_entries); + priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries); + + /* set default ranges */ + priv->max_tx_desc_cnt = priv->tx_desc_cnt; + priv->max_rx_desc_cnt = priv->rx_desc_cnt; + priv->min_tx_desc_cnt = priv->tx_desc_cnt; + priv->min_rx_desc_cnt = priv->rx_desc_cnt; +} + +void gve_set_queue_properties(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + /* set default descriptor counts */ + gve_set_default_desc_cnt(priv, descriptor); + + priv->max_registered_pages = be64_to_cpu(descriptor->max_registered_pages); + priv->tx_pages_per_qpl = be16_to_cpu(descriptor->tx_pages_per_qpl); + priv->default_num_queues = be16_to_cpu(descriptor->default_num_queues); +} + +int gve_set_mtu(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + u16 mtu; + + mtu = be16_to_cpu(descriptor->mtu); + if (mtu < ETH_MIN_MTU) { + dev_err(&priv->pdev->dev, "MTU %d below minimum MTU\n", mtu); + return -EINVAL; + } + priv->dev->max_mtu = mtu; + + return 0; +} + +void gve_set_mac(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + u8 *mac; + + mac = descriptor->mac; + eth_hw_addr_set(priv->dev, mac); + dev_info(&priv->pdev->dev, "MAC addr: %pM\n", mac); +} + static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) { - int num_ntfy; int err; /* Set up the adminq */ @@ -2431,57 +2479,38 @@ static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) "Could not get device information: err=%d\n", err); goto err; } - priv->dev->mtu = priv->dev->max_mtu; - num_ntfy = pci_msix_vec_count(priv->pdev); - if (num_ntfy <= 0) { + + err = gve_set_num_ntfy_blks(priv); + if (err) { dev_err(&priv->pdev->dev, - "could not count MSI-x vectors: err=%d\n", num_ntfy); - err = num_ntfy; - goto err; - } else if (num_ntfy < GVE_MIN_MSIX) { - dev_err(&priv->pdev->dev, "gve needs at least %d MSI-x vectors, but only has %d\n", - GVE_MIN_MSIX, num_ntfy); - err = -EINVAL; + "Could not setup notify blocks: err=%d\n", err); goto err; } - /* Big TCP is only supported on DQO */ - if (!gve_is_gqi(priv)) - netif_set_tso_max_size(priv->dev, GVE_DQO_TX_MAX); - - priv->rx_copybreak = GVE_DEFAULT_RX_COPYBREAK; - /* gvnic has one Notification Block per MSI-x vector, except for the - * management vector - */ - priv->num_ntfy_blks = (num_ntfy - 1) & ~0x1; - priv->mgmt_msix_idx = priv->num_ntfy_blks; - priv->numa_node = dev_to_node(&priv->pdev->dev); - - priv->tx_cfg.max_queues = - min_t(int, priv->tx_cfg.max_queues, priv->num_ntfy_blks / 2); - priv->rx_cfg.max_queues = - min_t(int, priv->rx_cfg.max_queues, priv->num_ntfy_blks / 2); - - priv->tx_cfg.num_queues = priv->tx_cfg.max_queues; - priv->rx_cfg.num_queues = priv->rx_cfg.max_queues; - if (priv->default_num_queues > 0) { - priv->tx_cfg.num_queues = min_t(int, priv->default_num_queues, - priv->tx_cfg.num_queues); - priv->rx_cfg.num_queues = min_t(int, priv->default_num_queues, - priv->rx_cfg.num_queues); - } - priv->tx_cfg.num_xdp_queues = 0; - + gve_set_num_queues(priv); dev_info(&priv->pdev->dev, "TX queues %d, RX queues %d\n", priv->tx_cfg.num_queues, priv->rx_cfg.num_queues); dev_info(&priv->pdev->dev, "Max TX queues %d, Max RX queues %d\n", priv->tx_cfg.max_queues, priv->rx_cfg.max_queues); - if (!gve_is_gqi(priv)) { + if (gve_is_dqo(priv)) { + /* DQO supports HW-GRO and UDP_GSO */ + u64 additional_features = NETIF_F_GRO_HW | NETIF_F_GSO_UDP_L4; + + priv->dev->hw_features |= additional_features; + priv->dev->features |= additional_features; + priv->tx_coalesce_usecs = GVE_TX_IRQ_RATELIMIT_US_DQO; priv->rx_coalesce_usecs = GVE_RX_IRQ_RATELIMIT_US_DQO; + + /* Big TCP is only supported on DQO */ + netif_set_tso_max_size(priv->dev, GVE_DQO_TX_MAX); } + priv->dev->mtu = priv->dev->max_mtu; + priv->numa_node = dev_to_node(&priv->pdev->dev); + priv->tx_cfg.num_xdp_queues = 0; + priv->rx_copybreak = GVE_DEFAULT_RX_COPYBREAK; priv->ts_config.tx_type = HWTSTAMP_TX_OFF; priv->ts_config.rx_filter = HWTSTAMP_FILTER_NONE; @@ -2506,14 +2535,14 @@ static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) bitmap_free(priv->xsk_pools); priv->xsk_pools = NULL; err: - gve_adminq_free(&priv->pdev->dev, priv); + gve_adminq_free(priv); return err; } static void gve_teardown_priv_resources(struct gve_priv *priv) { gve_teardown_device_resources(priv); - gve_adminq_free(&priv->pdev->dev, priv); + gve_adminq_free(priv); bitmap_free(priv->xsk_pools); priv->xsk_pools = NULL; } diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 0ece2f6fdffb..5cf242b28557 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -770,8 +770,7 @@ static int gve_rx_xsk_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } /* Copy the data to skb */ - rx->ctx.skb_head = gve_rx_copy_data(priv->dev, napi, - xdp->data, buf_len); + rx->ctx.skb_head = xdp_build_skb_from_zc(xdp); if (unlikely(!rx->ctx.skb_head)) { xsk_buff_free(xdp); gve_free_buf_state(rx, buf_state); @@ -779,8 +778,6 @@ static int gve_rx_xsk_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } rx->ctx.skb_tail = rx->ctx.skb_head; - /* Free XSK buffer and Buffer state */ - xsk_buff_free(xdp); gve_free_buf_state(rx, buf_state); /* Update Stats */ @@ -924,7 +921,7 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, buf_state->page_info.page_address + buf_state->page_info.page_offset, buf_state->page_info.pad, - buf_len, false); + buf_len, true); gve_xdp.gve = priv; gve_xdp.compl_desc = compl_desc; @@ -938,9 +935,17 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, return 0; } + rx->ctx.skb_head = xdp_build_skb_from_buff(&gve_xdp.xdp); + if (unlikely(!rx->ctx.skb_head)) + goto error; + rx->ctx.skb_tail = rx->ctx.skb_head; + + gve_reuse_buffer(rx, buf_state); + u64_stats_update_begin(&rx->statss); rx->xdp_actions[XDP_PASS]++; u64_stats_update_end(&rx->statss); + return 0; } if (eop && buf_len <= priv->rx_copybreak && diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c index 0ae314994676..4382af937e2e 100644 --- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c +++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c @@ -155,6 +155,7 @@ netdev_tx_t hbg_net_start_xmit(struct sk_buff *skb, struct net_device *netdev) buffer->skb = skb; buffer->skb_len = skb->len; if (unlikely(hbg_dma_map(buffer))) { + buffer->skb = NULL; dev_kfree_skb_any(skb); return NETDEV_TX_OK; } diff --git a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c index 635b3a95dd82..3c4e4e8ca140 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c @@ -563,7 +563,7 @@ u32 hns_rcb_get_rx_coalesced_frames( u32 hns_rcb_get_tx_coalesced_frames( struct rcb_common_cb *rcb_common, u32 port_idx) { - u64 reg; + u32 reg; reg = RCB_CFG_PKTLINE_REG + (port_idx + HNS_RCB_TX_PKTLINE_OFFSET) * 4; return dsaf_read_dev(rcb_common, reg); @@ -634,7 +634,7 @@ int hns_rcb_set_tx_coalesced_frames( { u32 old_waterline = hns_rcb_get_tx_coalesced_frames(rcb_common, port_idx); - u64 reg; + u32 reg; if (coalesced_frames == old_waterline) return 0; diff --git a/drivers/net/ethernet/hisilicon/hns3/hnae3.h b/drivers/net/ethernet/hisilicon/hns3/hnae3.h index a8798eecd9fb..4286af9239b0 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hnae3.h +++ b/drivers/net/ethernet/hisilicon/hns3/hnae3.h @@ -602,6 +602,10 @@ typedef int (*read_func)(struct seq_file *s, void *data); * Config wake on lan * dbg_get_read_func * Return the read func for debugfs seq file + * set_pfc_prevention_tout + * Set PFC storm prevention timeout + * get_pfc_prevention_tout + * Get PFC storm prevention timeout */ struct hnae3_ae_ops { int (*init_ae_dev)(struct hnae3_ae_dev *ae_dev); @@ -810,6 +814,8 @@ struct hnae3_ae_ops { int (*hwtstamp_set)(struct hnae3_handle *handle, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); + int (*set_pfc_prevention_tout)(struct hnae3_handle *handle, u16 times); + int (*get_pfc_prevention_tout)(struct hnae3_handle *handle, u16 *times); }; struct hnae3_dcb_ops { @@ -891,6 +897,14 @@ struct hnae3_roce_private_info { unsigned long state; }; +struct hnae3_pfc_storm_para { + u32 dir; + u32 enable; + u32 period_ms; + u32 times; + u32 recovery_period_ms; +}; + #define HNAE3_SUPPORT_APP_LOOPBACK BIT(0) #define HNAE3_SUPPORT_PHY_LOOPBACK BIT(1) #define HNAE3_SUPPORT_SERDES_SERIAL_LOOPBACK BIT(2) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h index 2c2a2f1e0d7a..6dde07dde1e8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h @@ -314,6 +314,9 @@ enum hclge_opcode_type { /* Query link diagnosis info command */ HCLGE_OPC_QUERY_LINK_DIAGNOSIS = 0x702A, + + /* Config pause storm param command */ + HCLGE_OPC_CFG_PAUSE_STORM_PARA = 0x7019, }; enum hclge_comm_cmd_return_status { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c index 6ecb32e28e79..47788be64be6 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c @@ -2825,32 +2825,28 @@ static int hns3_nic_change_mtu(struct net_device *netdev, int new_mtu) return ret; } -static int hns3_get_timeout_queue(struct net_device *ndev) +static bool hns3_dump_timeout_queue(struct net_device *ndev, + unsigned int txqueue) { - unsigned int i; + unsigned int timedout_ms; + struct netdev_queue *q; - /* Find the stopped queue the same way the stack does */ - for (i = 0; i < ndev->num_tx_queues; i++) { - unsigned int timedout_ms; - struct netdev_queue *q; - - q = netdev_get_tx_queue(ndev, i); - timedout_ms = netif_xmit_timeout_ms(q); - if (timedout_ms) { + q = netdev_get_tx_queue(ndev, txqueue); + timedout_ms = netif_xmit_timeout_ms(q); + if (timedout_ms) { #ifdef CONFIG_BQL - struct dql *dql = &q->dql; + struct dql *dql = &q->dql; - netdev_info(ndev, "DQL info last_cnt: %u, queued: %u, adj_limit: %u, completed: %u\n", - dql->last_obj_cnt, dql->num_queued, - dql->adj_limit, dql->num_completed); + netdev_info(ndev, "DQL info last_cnt: %u, queued: %u, adj_limit: %u, completed: %u\n", + dql->last_obj_cnt, dql->num_queued, + dql->adj_limit, dql->num_completed); #endif - netdev_info(ndev, "queue state: 0x%lx, delta msecs: %u\n", - q->state, timedout_ms); - break; - } + netdev_info(ndev, "queue state: 0x%lx, delta msecs: %u\n", + q->state, timedout_ms); + return true; } - return i; + return false; } static void hns3_dump_queue_stats(struct net_device *ndev, @@ -2900,15 +2896,15 @@ static void hns3_dump_queue_reg(struct net_device *ndev, HNS3_RING_TX_RING_EBD_OFFSET_REG)); } -static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev) +static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev, + unsigned int txqueue) { struct hns3_nic_priv *priv = netdev_priv(ndev); struct hnae3_handle *h = hns3_get_handle(ndev); struct hns3_enet_ring *tx_ring; - u32 timeout_queue; - timeout_queue = hns3_get_timeout_queue(ndev); - if (timeout_queue >= ndev->num_tx_queues) { + if (txqueue >= h->kinfo.num_tqps || + !hns3_dump_timeout_queue(ndev, txqueue)) { netdev_info(ndev, "no netdev TX timeout queue found, timeout count: %llu\n", priv->tx_timeout_count); @@ -2917,8 +2913,8 @@ static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev) priv->tx_timeout_count++; - tx_ring = &priv->ring[timeout_queue]; - hns3_dump_queue_stats(ndev, tx_ring, timeout_queue); + tx_ring = &priv->ring[txqueue]; + hns3_dump_queue_stats(ndev, tx_ring, txqueue); /* When mac received many pause frames continuous, it's unable to send * packets, which may cause tx timeout @@ -2941,7 +2937,7 @@ static void hns3_nic_net_timeout(struct net_device *ndev, unsigned int txqueue) struct hns3_nic_priv *priv = netdev_priv(ndev); struct hnae3_handle *h = priv->ae_handle; - if (!hns3_get_tx_timeo_queue_info(ndev)) + if (!hns3_get_tx_timeo_queue_info(ndev, txqueue)) return; /* request the reset, and let the hclge to determine diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index 442f15476af3..e7318f236315 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -1895,6 +1895,12 @@ static int hns3_get_tunable(struct net_device *netdev, case ETHTOOL_TX_COPYBREAK_BUF_SIZE: *(u32 *)data = h->kinfo.tx_spare_buf_size; break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->get_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->get_pfc_prevention_tout(h, (u16 *)data); + break; default: ret = -EOPNOTSUPP; break; @@ -2020,6 +2026,12 @@ static int hns3_set_tunable(struct net_device *netdev, netdev_info(netdev, "the active tx spare buf size is %u, due to page order\n", priv->ring->tx_spare->len); + break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->set_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->set_pfc_prevention_tout(h, *(u16 *)data); break; default: ret = -EOPNOTSUPP; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h index 4ce92ddefcde..1c029dc32ab8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h @@ -890,6 +890,15 @@ struct hclge_query_wol_supported_cmd { u8 rsv[20]; }; +struct hclge_pfc_storm_para_cmd { + __le32 dir; + __le32 enable; + __le32 period_ms; + __le32 times; + __le32 recovery_period_ms; + __le32 rsv; +}; + struct hclge_hw; int hclge_cmd_send(struct hclge_hw *hw, struct hclge_desc *desc, int num); #endif diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c index dac051e798da..6093a60d257b 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c @@ -1592,6 +1592,7 @@ hclge_query_reg_info(struct hclge_dev *hdev, } kfree(msg->desc); + msg->desc = NULL; } static void hclge_query_reg_info_of_ssu(struct hclge_dev *hdev) @@ -1761,7 +1762,7 @@ static const struct hclge_hw_type_id hclge_hw_type_id_st[] = { }, }; -static void hclge_log_error(struct device *dev, char *reg, +static void hclge_log_error(struct device *dev, const char *reg, const struct hclge_hw_error *err, u32 err_sts, unsigned long *reset_requests) { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 164c3ecf195c..e6af939c1c19 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -3524,6 +3524,122 @@ static int hclge_set_vf_link_state(struct hnae3_handle *handle, int vf, return ret; } +static int hclge_set_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, + false); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + para_cmd->enable = cpu_to_le32(para->enable); + para_cmd->period_ms = cpu_to_le32(para->period_ms); + para_cmd->times = cpu_to_le32(para->times); + para_cmd->recovery_period_ms = cpu_to_le32(para->recovery_period_ms); + + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) + dev_err(&hdev->pdev->dev, + "failed to set pfc storm para, ret = %d\n", ret); + return ret; +} + +static int hclge_get_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, true); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) { + dev_err(&hdev->pdev->dev, + "failed to get pfc storm para, ret = %d\n", ret); + return ret; + } + + para->enable = le32_to_cpu(para_cmd->enable); + para->period_ms = le32_to_cpu(para_cmd->period_ms); + para->times = le32_to_cpu(para_cmd->times); + para->recovery_period_ms = le32_to_cpu(para_cmd->recovery_period_ms); + + return 0; +} + +static int hclge_enable_pfc_storm_prevent(struct hclge_dev *hdev, + int dir, bool enable) +{ + struct hnae3_pfc_storm_para para = {0}; + int ret; + + para.dir = dir; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = enable; + return hclge_set_pfc_storm_para(hdev, ¶); +} + +static int hclge_set_pfc_prevention_tout(struct hnae3_handle *h, u16 times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + if (times > HCLGE_MAX_PFC_PREVENTION_TOUT) { + dev_err(&hdev->pdev->dev, + "times %u should be no more than %u!\n", + times, HCLGE_MAX_PFC_PREVENTION_TOUT); + return -EINVAL; + } + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = times ? 1 : 0; + para.times = (u32)times; + ret = hclge_set_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + hdev->pfc_prevention_tout = times; + + return 0; +} + +static int hclge_get_pfc_prevention_tout(struct hnae3_handle *h, u16 *times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + *times = para.enable ? (u16)para.times : 0; + + return 0; +} + static void hclge_set_reset_pending(struct hclge_dev *hdev, enum hnae3_reset_type reset_type) { @@ -4317,6 +4433,26 @@ static int hclge_reset_prepare(struct hclge_dev *hdev) return hclge_reset_prepare_wait(hdev); } +static void hclge_restore_pfc_storm_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_set_pfc_prevention_tout(handle, hdev->pfc_prevention_tout); + if (ret) + dev_warn(&hdev->pdev->dev, + "failed to set tx pfc storm prevent, ret = %d\n", + ret); +} + static int hclge_reset_rebuild(struct hclge_dev *hdev) { int ret; @@ -9278,6 +9414,32 @@ static int hclge_init_wol(struct hclge_dev *hdev) return hclge_update_wol(hdev); } +static void hclge_init_pfc_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + u16 times; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_get_pfc_prevention_tout(handle, ×); + if (ret) { + dev_warn(&hdev->pdev->dev, + "failed to get tx pfc prevention timeout, ret = %d\n", + ret); + times = HCLGE_DEFAULT_PFC_PREVENTION_TOUT; + } + + hdev->pfc_prevention_tout = times; + hdev->pfc_prevention_tout_default = times; +} + static void hclge_get_wol(struct hnae3_handle *handle, struct ethtool_wolinfo *wol) { @@ -9543,6 +9705,8 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to wake on lan init, ret = %d\n", ret); + hclge_init_pfc_prevention_tout(hdev); + ret = hclge_devlink_init(hdev); if (ret) goto err_ptp_uninit; @@ -9942,6 +10106,8 @@ static int hclge_reset_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to update wol config, ret = %d\n", ret); + hclge_restore_pfc_storm_prevention_tout(hdev); + dev_info(&pdev->dev, "Reset done, %s driver initialization finished.\n", HCLGE_DRIVER_NAME); @@ -9973,6 +10139,10 @@ static void hclge_uninit_ae_dev(struct hnae3_ae_dev *ae_dev) hclge_config_nic_hw_error(hdev, false); hclge_config_rocee_ras_interrupt(hdev, false); + /* Restore hw default values for the next initialization */ + hclge_set_pfc_prevention_tout(&hdev->vport->nic, + hdev->pfc_prevention_tout_default); + hclge_comm_cmd_uninit(hdev->ae_dev, &hdev->hw.hw); hclge_misc_irq_uninit(hdev); hclge_devlink_uninit(hdev); @@ -10535,6 +10705,8 @@ static const struct hnae3_ae_ops hclge_ops = { .set_wol = hclge_set_wol, .hwtstamp_get = hclge_ptp_get_cfg, .hwtstamp_set = hclge_ptp_set_cfg, + .set_pfc_prevention_tout = hclge_set_pfc_prevention_tout, + .get_pfc_prevention_tout = hclge_get_pfc_prevention_tout, }; static struct hnae3_ae_algo ae_algo = { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h index 7419481422c3..0cee8947f6b4 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h @@ -346,6 +346,11 @@ enum hclge_link_fail_code { #define HCLGE_LINK_STATUS_DOWN 0 #define HCLGE_LINK_STATUS_UP 1 +#define HCLGE_DIR_RX 0 +#define HCLGE_DIR_TX 1 +#define HCLGE_MAX_PFC_PREVENTION_TOUT 2000 +#define HCLGE_DEFAULT_PFC_PREVENTION_TOUT 1000 + #define HCLGE_PG_NUM 4 #define HCLGE_SCH_MODE_SP 0 #define HCLGE_SCH_MODE_DWRR 1 @@ -898,6 +903,8 @@ struct hclge_dev { u16 vf_rss_size_max; /* HW defined VF max RSS task queue */ u16 pf_rss_size_max; /* HW defined PF max RSS task queue */ u32 tx_spare_buf_size; /* HW defined TX spare buffer size */ + u16 pfc_prevention_tout; /* User config, restored after reset */ + u16 pfc_prevention_tout_default; /* HW default, to avoid stale state */ u16 fdir_pf_filter_count; /* Num of guaranteed filters for this PF */ u16 num_alloc_vport; /* Num vports this driver supports */ diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c index 9306bf0020ca..d19cfbe3ed3b 100644 --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c @@ -261,8 +261,7 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task, ((struct udphdr *)skb_transport_header(skb))->dest != VXLAN_OFFLOAD_PORT_LE) { /* Unsupported tunnel packet, disable csum offload */ - skb_checksum_help(skb); - return 0; + return skb_checksum_help(skb); } } @@ -412,6 +411,10 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task, offload |= HINIC3_TX_OFFLOAD_TSO; } else { tso_cs_en = hinic3_tx_csum(txq, task, skb); + if (tso_cs_en < 0) { + offload = HINIC3_TX_OFFLOAD_INVALID; + return offload; + } if (tso_cs_en) offload |= HINIC3_TX_OFFLOAD_CSUM; } @@ -545,6 +548,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, skb->len = MIN_SKB_LEN; } + offload = hinic3_tx_offload(skb, &task, &queue_info, txq); num_sge = skb_shinfo(skb)->nr_frags + 1; /* assume normal wqe format + 1 wqebb for task info */ wqebb_cnt = num_sge + 1; @@ -560,7 +564,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, return NETDEV_TX_BUSY; } - offload = hinic3_tx_offload(skb, &task, &queue_info, txq); if (unlikely(offload == HINIC3_TX_OFFLOAD_INVALID)) { goto err_drop_pkt; } else if (!offload) { @@ -578,8 +581,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, *wqe_combo.task = task; tx_info = &txq->tx_info[pi]; - tx_info->skb = skb; - tx_info->wqebb_cnt = wqebb_cnt; err = hinic3_tx_map_skb(netdev, skb, txq, tx_info, &wqe_combo); if (err) { @@ -589,6 +590,9 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, goto err_drop_pkt; } + tx_info->skb = skb; + tx_info->wqebb_cnt = wqebb_cnt; + netif_subqueue_sent(netdev, txq->sq->q_id, skb->len); netif_subqueue_maybe_stop(netdev, txq->sq->q_id, hinic3_wq_free_wqebbs(&txq->sq->wq), diff --git a/drivers/net/ethernet/ibm/Kconfig b/drivers/net/ethernet/ibm/Kconfig index 4f4b23465c47..8e55faac2035 100644 --- a/drivers/net/ethernet/ibm/Kconfig +++ b/drivers/net/ethernet/ibm/Kconfig @@ -42,15 +42,6 @@ config IBMVETH_KUNIT_TEST source "drivers/net/ethernet/ibm/emac/Kconfig" -config EHEA - tristate "eHEA Ethernet support" - depends on IBMEBUS && SPARSEMEM - help - This driver supports the IBM pSeries eHEA ethernet adapter. - - To compile the driver as a module, choose M here. The module - will be called ehea. - config IBMVNIC tristate "IBM Virtual NIC support" depends on PPC_PSERIES diff --git a/drivers/net/ethernet/ibm/Makefile b/drivers/net/ethernet/ibm/Makefile index 1d17d0c33d4d..c7e5d891c946 100644 --- a/drivers/net/ethernet/ibm/Makefile +++ b/drivers/net/ethernet/ibm/Makefile @@ -6,4 +6,3 @@ obj-$(CONFIG_IBMVETH) += ibmveth.o obj-$(CONFIG_IBMVNIC) += ibmvnic.o obj-$(CONFIG_IBM_EMAC) += emac/ -obj-$(CONFIG_EHEA) += ehea/ diff --git a/drivers/net/ethernet/ibm/ehea/Makefile b/drivers/net/ethernet/ibm/ehea/Makefile deleted file mode 100644 index 9e1e5c7aafe2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the eHEA ethernet device driver for IBM eServer System p -# -ehea-y = ehea_main.o ehea_phyp.o ehea_qmr.o ehea_ethtool.o -obj-$(CONFIG_EHEA) += ehea.o - diff --git a/drivers/net/ethernet/ibm/ehea/ehea.h b/drivers/net/ethernet/ibm/ehea/ehea.h deleted file mode 100644 index 208c440a602b..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea.h +++ /dev/null @@ -1,477 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_H__ -#define __EHEA_H__ - -#include -#include -#include -#include -#include - -#include -#include - -#define DRV_NAME "ehea" -#define DRV_VERSION "EHEA_0107" - -/* eHEA capability flags */ -#define DLPAR_PORT_ADD_REM 1 -#define DLPAR_MEM_ADD 2 -#define DLPAR_MEM_REM 4 -#define EHEA_CAPABILITIES (DLPAR_PORT_ADD_REM | DLPAR_MEM_ADD | DLPAR_MEM_REM) - -#define EHEA_MSG_DEFAULT (NETIF_MSG_LINK | NETIF_MSG_TIMER \ - | NETIF_MSG_RX_ERR | NETIF_MSG_TX_ERR) - -#define EHEA_MAX_ENTRIES_RQ1 32767 -#define EHEA_MAX_ENTRIES_RQ2 16383 -#define EHEA_MAX_ENTRIES_RQ3 16383 -#define EHEA_MAX_ENTRIES_SQ 32767 -#define EHEA_MIN_ENTRIES_QP 127 - -#define EHEA_SMALL_QUEUES - -#ifdef EHEA_SMALL_QUEUES -#define EHEA_MAX_CQE_COUNT 1023 -#define EHEA_DEF_ENTRIES_SQ 1023 -#define EHEA_DEF_ENTRIES_RQ1 1023 -#define EHEA_DEF_ENTRIES_RQ2 1023 -#define EHEA_DEF_ENTRIES_RQ3 511 -#else -#define EHEA_MAX_CQE_COUNT 4080 -#define EHEA_DEF_ENTRIES_SQ 4080 -#define EHEA_DEF_ENTRIES_RQ1 8160 -#define EHEA_DEF_ENTRIES_RQ2 2040 -#define EHEA_DEF_ENTRIES_RQ3 2040 -#endif - -#define EHEA_MAX_ENTRIES_EQ 20 - -#define EHEA_SG_SQ 2 -#define EHEA_SG_RQ1 1 -#define EHEA_SG_RQ2 0 -#define EHEA_SG_RQ3 0 - -#define EHEA_MAX_PACKET_SIZE 9022 /* for jumbo frames */ -#define EHEA_RQ2_PKT_SIZE 2048 -#define EHEA_L_PKT_SIZE 256 /* low latency */ - -/* Send completion signaling */ - -/* Protection Domain Identifier */ -#define EHEA_PD_ID 0xaabcdeff - -#define EHEA_RQ2_THRESHOLD 1 -#define EHEA_RQ3_THRESHOLD 4 /* use RQ3 threshold of 2048 bytes */ - -#define EHEA_SPEED_10G 10000 -#define EHEA_SPEED_1G 1000 -#define EHEA_SPEED_100M 100 -#define EHEA_SPEED_10M 10 -#define EHEA_SPEED_AUTONEG 0 - -/* Broadcast/Multicast registration types */ -#define EHEA_BCMC_SCOPE_ALL 0x08 -#define EHEA_BCMC_SCOPE_SINGLE 0x00 -#define EHEA_BCMC_MULTICAST 0x04 -#define EHEA_BCMC_BROADCAST 0x00 -#define EHEA_BCMC_UNTAGGED 0x02 -#define EHEA_BCMC_TAGGED 0x00 -#define EHEA_BCMC_VLANID_ALL 0x01 -#define EHEA_BCMC_VLANID_SINGLE 0x00 - -#define EHEA_CACHE_LINE 128 - -/* Memory Regions */ -#define EHEA_MR_ACC_CTRL 0x00800000 - -#define EHEA_BUSMAP_START 0x8000000000000000ULL -#define EHEA_INVAL_ADDR 0xFFFFFFFFFFFFFFFFULL -#define EHEA_DIR_INDEX_SHIFT 13 /* 8k Entries in 64k block */ -#define EHEA_TOP_INDEX_SHIFT (EHEA_DIR_INDEX_SHIFT * 2) -#define EHEA_MAP_ENTRIES (1 << EHEA_DIR_INDEX_SHIFT) -#define EHEA_MAP_SIZE (0x10000) /* currently fixed map size */ -#define EHEA_INDEX_MASK (EHEA_MAP_ENTRIES - 1) - - -#define EHEA_WATCH_DOG_TIMEOUT 10*HZ - -/* utility functions */ - -void ehea_dump(void *adr, int len, char *msg); - -#define EHEA_BMASK(pos, length) (((pos) << 16) + (length)) - -#define EHEA_BMASK_IBM(from, to) (((63 - to) << 16) + ((to) - (from) + 1)) - -#define EHEA_BMASK_SHIFTPOS(mask) (((mask) >> 16) & 0xffff) - -#define EHEA_BMASK_MASK(mask) \ - (0xffffffffffffffffULL >> ((64 - (mask)) & 0xffff)) - -#define EHEA_BMASK_SET(mask, value) \ - ((EHEA_BMASK_MASK(mask) & ((u64)(value))) << EHEA_BMASK_SHIFTPOS(mask)) - -#define EHEA_BMASK_GET(mask, value) \ - (EHEA_BMASK_MASK(mask) & (((u64)(value)) >> EHEA_BMASK_SHIFTPOS(mask))) - -/* - * Generic ehea page - */ -struct ehea_page { - u8 entries[PAGE_SIZE]; -}; - -/* - * Generic queue in linux kernel virtual memory - */ -struct hw_queue { - u64 current_q_offset; /* current queue entry */ - struct ehea_page **queue_pages; /* array of pages belonging to queue */ - u32 qe_size; /* queue entry size */ - u32 queue_length; /* queue length allocated in bytes */ - u32 pagesize; - u32 toggle_state; /* toggle flag - per page */ - u32 reserved; /* 64 bit alignment */ -}; - -/* - * For pSeries this is a 64bit memory address where - * I/O memory is mapped into CPU address space - */ -struct h_epa { - void __iomem *addr; -}; - -struct h_epa_user { - u64 addr; -}; - -struct h_epas { - struct h_epa kernel; /* kernel space accessible resource, - set to 0 if unused */ - struct h_epa_user user; /* user space accessible resource - set to 0 if unused */ -}; - -/* - * Memory map data structures - */ -struct ehea_dir_bmap -{ - u64 ent[EHEA_MAP_ENTRIES]; -}; -struct ehea_top_bmap -{ - struct ehea_dir_bmap *dir[EHEA_MAP_ENTRIES]; -}; -struct ehea_bmap -{ - struct ehea_top_bmap *top[EHEA_MAP_ENTRIES]; -}; - -struct ehea_qp; -struct ehea_cq; -struct ehea_eq; -struct ehea_port; -struct ehea_av; - -/* - * Queue attributes passed to ehea_create_qp() - */ -struct ehea_qp_init_attr { - /* input parameter */ - u32 qp_token; /* queue token */ - u8 low_lat_rq1; - u8 signalingtype; /* cqe generation flag */ - u8 rq_count; /* num of receive queues */ - u8 eqe_gen; /* eqe generation flag */ - u16 max_nr_send_wqes; /* max number of send wqes */ - u16 max_nr_rwqes_rq1; /* max number of receive wqes */ - u16 max_nr_rwqes_rq2; - u16 max_nr_rwqes_rq3; - u8 wqe_size_enc_sq; - u8 wqe_size_enc_rq1; - u8 wqe_size_enc_rq2; - u8 wqe_size_enc_rq3; - u8 swqe_imm_data_len; /* immediate data length for swqes */ - u16 port_nr; - u16 rq2_threshold; - u16 rq3_threshold; - u64 send_cq_handle; - u64 recv_cq_handle; - u64 aff_eq_handle; - - /* output parameter */ - u32 qp_nr; - u16 act_nr_send_wqes; - u16 act_nr_rwqes_rq1; - u16 act_nr_rwqes_rq2; - u16 act_nr_rwqes_rq3; - u8 act_wqe_size_enc_sq; - u8 act_wqe_size_enc_rq1; - u8 act_wqe_size_enc_rq2; - u8 act_wqe_size_enc_rq3; - u32 nr_sq_pages; - u32 nr_rq1_pages; - u32 nr_rq2_pages; - u32 nr_rq3_pages; - u32 liobn_sq; - u32 liobn_rq1; - u32 liobn_rq2; - u32 liobn_rq3; -}; - -/* - * Event Queue attributes, passed as parameter - */ -struct ehea_eq_attr { - u32 type; - u32 max_nr_of_eqes; - u8 eqe_gen; /* generate eqe flag */ - u64 eq_handle; - u32 act_nr_of_eqes; - u32 nr_pages; - u32 ist1; /* Interrupt service token */ - u32 ist2; - u32 ist3; - u32 ist4; -}; - - -/* - * Event Queue - */ -struct ehea_eq { - struct ehea_adapter *adapter; - struct hw_queue hw_queue; - u64 fw_handle; - struct h_epas epas; - spinlock_t spinlock; - struct ehea_eq_attr attr; -}; - -/* - * HEA Queues - */ -struct ehea_qp { - struct ehea_adapter *adapter; - u64 fw_handle; /* QP handle for firmware calls */ - struct hw_queue hw_squeue; - struct hw_queue hw_rqueue1; - struct hw_queue hw_rqueue2; - struct hw_queue hw_rqueue3; - struct h_epas epas; - struct ehea_qp_init_attr init_attr; -}; - -/* - * Completion Queue attributes - */ -struct ehea_cq_attr { - /* input parameter */ - u32 max_nr_of_cqes; - u32 cq_token; - u64 eq_handle; - - /* output parameter */ - u32 act_nr_of_cqes; - u32 nr_pages; -}; - -/* - * Completion Queue - */ -struct ehea_cq { - struct ehea_adapter *adapter; - u64 fw_handle; - struct hw_queue hw_queue; - struct h_epas epas; - struct ehea_cq_attr attr; -}; - -/* - * Memory Region - */ -struct ehea_mr { - struct ehea_adapter *adapter; - u64 handle; - u64 vaddr; - u32 lkey; -}; - -/* - * Port state information - */ -struct port_stats { - int poll_receive_errors; - int queue_stopped; - int err_tcp_cksum; - int err_ip_cksum; - int err_frame_crc; -}; - -#define EHEA_IRQ_NAME_SIZE 20 - -/* - * Queue SKB Array - */ -struct ehea_q_skb_arr { - struct sk_buff **arr; /* skb array for queue */ - int len; /* array length */ - int index; /* array index */ - int os_skbs; /* rq2/rq3 only: outstanding skbs */ -}; - -/* - * Port resources - */ -struct ehea_port_res { - struct napi_struct napi; - struct port_stats p_stats; - struct ehea_mr send_mr; /* send memory region */ - struct ehea_mr recv_mr; /* receive memory region */ - struct ehea_port *port; - char int_recv_name[EHEA_IRQ_NAME_SIZE]; - char int_send_name[EHEA_IRQ_NAME_SIZE]; - struct ehea_qp *qp; - struct ehea_cq *send_cq; - struct ehea_cq *recv_cq; - struct ehea_eq *eq; - struct ehea_q_skb_arr rq1_skba; - struct ehea_q_skb_arr rq2_skba; - struct ehea_q_skb_arr rq3_skba; - struct ehea_q_skb_arr sq_skba; - int sq_skba_size; - int swqe_refill_th; - atomic_t swqe_avail; - int swqe_ll_count; - u32 swqe_id_counter; - u64 tx_packets; - u64 tx_bytes; - u64 rx_packets; - u64 rx_bytes; - int sq_restart_flag; -}; - - -#define EHEA_MAX_PORTS 16 - -#define EHEA_NUM_PORTRES_FW_HANDLES 6 /* QP handle, SendCQ handle, - RecvCQ handle, EQ handle, - SendMR handle, RecvMR handle */ -#define EHEA_NUM_PORT_FW_HANDLES 1 /* EQ handle */ -#define EHEA_NUM_ADAPTER_FW_HANDLES 2 /* MR handle, NEQ handle */ - -struct ehea_adapter { - u64 handle; - struct platform_device *ofdev; - struct ehea_port *port[EHEA_MAX_PORTS]; - struct ehea_eq *neq; /* notification event queue */ - struct tasklet_struct neq_tasklet; - struct ehea_mr mr; - u32 pd; /* protection domain */ - u64 max_mc_mac; /* max number of multicast mac addresses */ - int active_ports; - struct list_head list; -}; - - -struct ehea_mc_list { - struct list_head list; - u64 macaddr; -}; - -/* kdump support */ -struct ehea_fw_handle_entry { - u64 adh; /* Adapter Handle */ - u64 fwh; /* Firmware Handle */ -}; - -struct ehea_fw_handle_array { - struct ehea_fw_handle_entry *arr; - int num_entries; - struct mutex lock; -}; - -struct ehea_bcmc_reg_entry { - u64 adh; /* Adapter Handle */ - u32 port_id; /* Logical Port Id */ - u8 reg_type; /* Registration Type */ - u64 macaddr; -}; - -struct ehea_bcmc_reg_array { - struct ehea_bcmc_reg_entry *arr; - int num_entries; - spinlock_t lock; -}; - -#define EHEA_PORT_UP 1 -#define EHEA_PORT_DOWN 0 -#define EHEA_PHY_LINK_UP 1 -#define EHEA_PHY_LINK_DOWN 0 -#define EHEA_MAX_PORT_RES 16 -struct ehea_port { - struct ehea_adapter *adapter; /* adapter that owns this port */ - struct net_device *netdev; - struct rtnl_link_stats64 stats; - struct ehea_port_res port_res[EHEA_MAX_PORT_RES]; - struct platform_device ofdev; /* Open Firmware Device */ - struct ehea_mc_list *mc_list; /* Multicast MAC addresses */ - struct ehea_eq *qp_eq; - struct work_struct reset_task; - struct delayed_work stats_work; - struct mutex port_lock; - char int_aff_name[EHEA_IRQ_NAME_SIZE]; - int allmulti; /* Indicates IFF_ALLMULTI state */ - int promisc; /* Indicates IFF_PROMISC state */ - int num_mcs; - int resets; - unsigned long flags; - u64 mac_addr; - u32 logical_port_id; - u32 port_speed; - u32 msg_enable; - u32 sig_comp_iv; - u32 state; - u8 phy_link; - u8 full_duplex; - u8 autoneg; - u8 num_def_qps; - wait_queue_head_t swqe_avail_wq; - wait_queue_head_t restart_wq; -}; - -struct port_res_cfg { - int max_entries_rcq; - int max_entries_scq; - int max_entries_sq; - int max_entries_rq1; - int max_entries_rq2; - int max_entries_rq3; -}; - -enum ehea_flag_bits { - __EHEA_STOP_XFER, - __EHEA_DISABLE_PORT_RESET -}; - -void ehea_set_ethtool_ops(struct net_device *netdev); -int ehea_sense_port_attr(struct ehea_port *port); -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed); - -#endif /* __EHEA_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c b/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c deleted file mode 100644 index 1db5b6790a41..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea.h" -#include "ehea_phyp.h" - -static int ehea_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - u32 supported, advertising; - u32 speed; - int ret; - - ret = ehea_sense_port_attr(port); - - if (ret) - return ret; - - if (netif_carrier_ok(dev)) { - switch (port->port_speed) { - case EHEA_SPEED_10M: - speed = SPEED_10; - break; - case EHEA_SPEED_100M: - speed = SPEED_100; - break; - case EHEA_SPEED_1G: - speed = SPEED_1000; - break; - case EHEA_SPEED_10G: - speed = SPEED_10000; - break; - default: - speed = -1; - break; /* BUG */ - } - cmd->base.duplex = port->full_duplex == 1 ? - DUPLEX_FULL : DUPLEX_HALF; - } else { - speed = SPEED_UNKNOWN; - cmd->base.duplex = DUPLEX_UNKNOWN; - } - cmd->base.speed = speed; - - if (cmd->base.speed == SPEED_10000) { - supported = (SUPPORTED_10000baseT_Full | SUPPORTED_FIBRE); - advertising = (ADVERTISED_10000baseT_Full | ADVERTISED_FIBRE); - cmd->base.port = PORT_FIBRE; - } else { - supported = (SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full - | SUPPORTED_100baseT_Half | SUPPORTED_10baseT_Full - | SUPPORTED_10baseT_Half | SUPPORTED_Autoneg - | SUPPORTED_TP); - advertising = (ADVERTISED_1000baseT_Full | ADVERTISED_Autoneg - | ADVERTISED_TP); - cmd->base.port = PORT_TP; - } - - cmd->base.autoneg = port->autoneg == 1 ? - AUTONEG_ENABLE : AUTONEG_DISABLE; - - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, - supported); - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, - advertising); - - return 0; -} - -static int ehea_set_link_ksettings(struct net_device *dev, - const struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - int ret = 0; - u32 sp; - - if (cmd->base.autoneg == AUTONEG_ENABLE) { - sp = EHEA_SPEED_AUTONEG; - goto doit; - } - - switch (cmd->base.speed) { - case SPEED_10: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10M_F; - else - sp = H_SPEED_10M_H; - break; - - case SPEED_100: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_100M_F; - else - sp = H_SPEED_100M_H; - break; - - case SPEED_1000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_1G_F; - else - ret = -EINVAL; - break; - - case SPEED_10000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10G_F; - else - ret = -EINVAL; - break; - - default: - ret = -EINVAL; - break; - } - - if (ret) - goto out; -doit: - ret = ehea_set_portspeed(port, sp); - - if (!ret) - netdev_info(dev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); -out: - return ret; -} - -static int ehea_nway_reset(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - int ret; - - ret = ehea_set_portspeed(port, EHEA_SPEED_AUTONEG); - - if (!ret) - netdev_info(port->netdev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); - return ret; -} - -static void ehea_get_drvinfo(struct net_device *dev, - struct ethtool_drvinfo *info) -{ - strscpy(info->driver, DRV_NAME, sizeof(info->driver)); - strscpy(info->version, DRV_VERSION, sizeof(info->version)); -} - -static u32 ehea_get_msglevel(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - return port->msg_enable; -} - -static void ehea_set_msglevel(struct net_device *dev, u32 value) -{ - struct ehea_port *port = netdev_priv(dev); - port->msg_enable = value; -} - -static const char ehea_ethtool_stats_keys[][ETH_GSTRING_LEN] = { - {"sig_comp_iv"}, - {"swqe_refill_th"}, - {"port resets"}, - {"Receive errors"}, - {"TCP cksum errors"}, - {"IP cksum errors"}, - {"Frame cksum errors"}, - {"num SQ stopped"}, - {"PR0 free_swqes"}, - {"PR1 free_swqes"}, - {"PR2 free_swqes"}, - {"PR3 free_swqes"}, - {"PR4 free_swqes"}, - {"PR5 free_swqes"}, - {"PR6 free_swqes"}, - {"PR7 free_swqes"}, - {"PR8 free_swqes"}, - {"PR9 free_swqes"}, - {"PR10 free_swqes"}, - {"PR11 free_swqes"}, - {"PR12 free_swqes"}, - {"PR13 free_swqes"}, - {"PR14 free_swqes"}, - {"PR15 free_swqes"}, -}; - -static void ehea_get_strings(struct net_device *dev, u32 stringset, u8 *data) -{ - if (stringset == ETH_SS_STATS) { - memcpy(data, &ehea_ethtool_stats_keys, - sizeof(ehea_ethtool_stats_keys)); - } -} - -static int ehea_get_sset_count(struct net_device *dev, int sset) -{ - switch (sset) { - case ETH_SS_STATS: - return ARRAY_SIZE(ehea_ethtool_stats_keys); - default: - return -EOPNOTSUPP; - } -} - -static void ehea_get_ethtool_stats(struct net_device *dev, - struct ethtool_stats *stats, u64 *data) -{ - int i, k, tmp; - struct ehea_port *port = netdev_priv(dev); - - for (i = 0; i < ehea_get_sset_count(dev, ETH_SS_STATS); i++) - data[i] = 0; - i = 0; - - data[i++] = port->sig_comp_iv; - data[i++] = port->port_res[0].swqe_refill_th; - data[i++] = port->resets; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.poll_receive_errors; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_tcp_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_ip_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_frame_crc; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.queue_stopped; - data[i++] = tmp; - - for (k = 0; k < 16; k++) - data[i++] = atomic_read(&port->port_res[k].swqe_avail); -} - -static const struct ethtool_ops ehea_ethtool_ops = { - .get_drvinfo = ehea_get_drvinfo, - .get_msglevel = ehea_get_msglevel, - .set_msglevel = ehea_set_msglevel, - .get_link = ethtool_op_get_link, - .get_strings = ehea_get_strings, - .get_sset_count = ehea_get_sset_count, - .get_ethtool_stats = ehea_get_ethtool_stats, - .nway_reset = ehea_nway_reset, /* Restart autonegotiation */ - .get_link_ksettings = ehea_get_link_ksettings, - .set_link_ksettings = ehea_set_link_ksettings, -}; - -void ehea_set_ethtool_ops(struct net_device *netdev) -{ - netdev->ethtool_ops = &ehea_ethtool_ops; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_hw.h b/drivers/net/ethernet/ibm/ehea/ehea_hw.h deleted file mode 100644 index 590933a45d65..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_hw.h +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_hw.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_HW_H__ -#define __EHEA_HW_H__ - -#define QPX_SQA_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ1A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ2A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ3A_VALUE EHEA_BMASK_IBM(48, 63) - -#define QPTEMM_OFFSET(x) offsetof(struct ehea_qptemm, x) - -struct ehea_qptemm { - u64 qpx_hcr; - u64 qpx_c; - u64 qpx_herr; - u64 qpx_aer; - u64 qpx_sqa; - u64 qpx_sqc; - u64 qpx_rq1a; - u64 qpx_rq1c; - u64 qpx_st; - u64 qpx_aerr; - u64 qpx_tenure; - u64 qpx_reserved1[(0x098 - 0x058) / 8]; - u64 qpx_portp; - u64 qpx_reserved2[(0x100 - 0x0A0) / 8]; - u64 qpx_t; - u64 qpx_sqhp; - u64 qpx_sqptp; - u64 qpx_reserved3[(0x140 - 0x118) / 8]; - u64 qpx_sqwsize; - u64 qpx_reserved4[(0x170 - 0x148) / 8]; - u64 qpx_sqsize; - u64 qpx_reserved5[(0x1B0 - 0x178) / 8]; - u64 qpx_sigt; - u64 qpx_wqecnt; - u64 qpx_rq1hp; - u64 qpx_rq1ptp; - u64 qpx_rq1size; - u64 qpx_reserved6[(0x220 - 0x1D8) / 8]; - u64 qpx_rq1wsize; - u64 qpx_reserved7[(0x240 - 0x228) / 8]; - u64 qpx_pd; - u64 qpx_scqn; - u64 qpx_rcqn; - u64 qpx_aeqn; - u64 reserved49; - u64 qpx_ram; - u64 qpx_reserved8[(0x300 - 0x270) / 8]; - u64 qpx_rq2a; - u64 qpx_rq2c; - u64 qpx_rq2hp; - u64 qpx_rq2ptp; - u64 qpx_rq2size; - u64 qpx_rq2wsize; - u64 qpx_rq2th; - u64 qpx_rq3a; - u64 qpx_rq3c; - u64 qpx_rq3hp; - u64 qpx_rq3ptp; - u64 qpx_rq3size; - u64 qpx_rq3wsize; - u64 qpx_rq3th; - u64 qpx_lpn; - u64 qpx_reserved9[(0x400 - 0x378) / 8]; - u64 reserved_ext[(0x500 - 0x400) / 8]; - u64 reserved2[(0x1000 - 0x500) / 8]; -}; - -#define MRx_HCR_LPARID_VALID EHEA_BMASK_IBM(0, 0) - -#define MRMWMM_OFFSET(x) offsetof(struct ehea_mrmwmm, x) - -struct ehea_mrmwmm { - u64 mrx_hcr; - u64 mrx_c; - u64 mrx_herr; - u64 mrx_aer; - u64 mrx_pp; - u64 reserved1; - u64 reserved2; - u64 reserved3; - u64 reserved4[(0x200 - 0x40) / 8]; - u64 mrx_ctl[64]; -}; - -#define QPEDMM_OFFSET(x) offsetof(struct ehea_qpedmm, x) - -struct ehea_qpedmm { - - u64 reserved0[(0x400) / 8]; - u64 qpedx_phh; - u64 qpedx_ppsgp; - u64 qpedx_ppsgu; - u64 qpedx_ppdgp; - u64 qpedx_ppdgu; - u64 qpedx_aph; - u64 qpedx_apsgp; - u64 qpedx_apsgu; - u64 qpedx_apdgp; - u64 qpedx_apdgu; - u64 qpedx_apav; - u64 qpedx_apsav; - u64 qpedx_hcr; - u64 reserved1[4]; - u64 qpedx_rrl0; - u64 qpedx_rrrkey0; - u64 qpedx_rrva0; - u64 reserved2; - u64 qpedx_rrl1; - u64 qpedx_rrrkey1; - u64 qpedx_rrva1; - u64 reserved3; - u64 qpedx_rrl2; - u64 qpedx_rrrkey2; - u64 qpedx_rrva2; - u64 reserved4; - u64 qpedx_rrl3; - u64 qpedx_rrrkey3; - u64 qpedx_rrva3; -}; - -#define CQX_FECADDER EHEA_BMASK_IBM(32, 63) -#define CQX_FEC_CQE_CNT EHEA_BMASK_IBM(32, 63) -#define CQX_N1_GENERATE_COMP_EVENT EHEA_BMASK_IBM(0, 0) -#define CQX_EP_EVENT_PENDING EHEA_BMASK_IBM(0, 0) - -#define CQTEMM_OFFSET(x) offsetof(struct ehea_cqtemm, x) - -struct ehea_cqtemm { - u64 cqx_hcr; - u64 cqx_c; - u64 cqx_herr; - u64 cqx_aer; - u64 cqx_ptp; - u64 cqx_tp; - u64 cqx_fec; - u64 cqx_feca; - u64 cqx_ep; - u64 cqx_eq; - u64 reserved1; - u64 cqx_n0; - u64 cqx_n1; - u64 reserved2[(0x1000 - 0x60) / 8]; -}; - -#define EQTEMM_OFFSET(x) offsetof(struct ehea_eqtemm, x) - -struct ehea_eqtemm { - u64 eqx_hcr; - u64 eqx_c; - u64 eqx_herr; - u64 eqx_aer; - u64 eqx_ptp; - u64 eqx_tp; - u64 eqx_ssba; - u64 eqx_psba; - u64 eqx_cec; - u64 eqx_meql; - u64 eqx_xisbi; - u64 eqx_xisc; - u64 eqx_it; -}; - -/* - * These access functions will be changed when the dissuccsion about - * the new access methods for POWER has settled. - */ - -static inline u64 epa_load(struct h_epa epa, u32 offset) -{ - return __raw_readq((void __iomem *)(epa.addr + offset)); -} - -static inline void epa_store(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); - epa_load(epa, offset); /* synchronize explicitly to eHEA */ -} - -static inline void epa_store_acc(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); -} - -#define epa_store_cq(epa, offset, value)\ - epa_store(epa, CQTEMM_OFFSET(offset), value) -#define epa_load_cq(epa, offset)\ - epa_load(epa, CQTEMM_OFFSET(offset)) - -static inline void ehea_update_sqa(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_sqa), - EHEA_BMASK_SET(QPX_SQA_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq3a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq3a), - EHEA_BMASK_SET(QPX_RQ1A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq2a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq2a), - EHEA_BMASK_SET(QPX_RQ2A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq1a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq1a), - EHEA_BMASK_SET(QPX_RQ3A_VALUE, nr_wqes)); -} - -static inline void ehea_update_feca(struct ehea_cq *cq, u32 nr_cqes) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_feca), - EHEA_BMASK_SET(CQX_FECADDER, nr_cqes)); -} - -static inline void ehea_reset_cq_n1(struct ehea_cq *cq) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_cq(epa, cqx_n1, - EHEA_BMASK_SET(CQX_N1_GENERATE_COMP_EVENT, 1)); -} - -static inline void ehea_reset_cq_ep(struct ehea_cq *my_cq) -{ - struct h_epa epa = my_cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_ep), - EHEA_BMASK_SET(CQX_EP_EVENT_PENDING, 0)); -} - -#endif /* __EHEA_HW_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_main.c b/drivers/net/ethernet/ibm/ehea/ehea_main.c deleted file mode 100644 index bfc8699a05b9..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_main.c +++ /dev/null @@ -1,3581 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_main.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ehea.h" -#include "ehea_qmr.h" -#include "ehea_phyp.h" - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Christoph Raisch "); -MODULE_DESCRIPTION("IBM eServer HEA Driver"); -MODULE_VERSION(DRV_VERSION); - - -static int msg_level = -1; -static int rq1_entries = EHEA_DEF_ENTRIES_RQ1; -static int rq2_entries = EHEA_DEF_ENTRIES_RQ2; -static int rq3_entries = EHEA_DEF_ENTRIES_RQ3; -static int sq_entries = EHEA_DEF_ENTRIES_SQ; -static int use_mcs = 1; -static int prop_carrier_state; - -module_param(msg_level, int, 0); -module_param(rq1_entries, int, 0); -module_param(rq2_entries, int, 0); -module_param(rq3_entries, int, 0); -module_param(sq_entries, int, 0); -module_param(prop_carrier_state, int, 0); -module_param(use_mcs, int, 0); - -MODULE_PARM_DESC(msg_level, "msg_level"); -MODULE_PARM_DESC(prop_carrier_state, "Propagate carrier state of physical " - "port to stack. 1:yes, 0:no. Default = 0 "); -MODULE_PARM_DESC(rq3_entries, "Number of entries for Receive Queue 3 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ3) ")"); -MODULE_PARM_DESC(rq2_entries, "Number of entries for Receive Queue 2 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ2) ")"); -MODULE_PARM_DESC(rq1_entries, "Number of entries for Receive Queue 1 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ1) ")"); -MODULE_PARM_DESC(sq_entries, " Number of entries for the Send Queue " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_SQ) ")"); -MODULE_PARM_DESC(use_mcs, " Multiple receive queues, 1: enable, 0: disable, " - "Default = 1"); - -static int port_name_cnt; -static LIST_HEAD(adapter_list); -static unsigned long ehea_driver_flags; -static DEFINE_MUTEX(dlpar_mem_lock); -static struct ehea_fw_handle_array ehea_fw_handles; -static struct ehea_bcmc_reg_array ehea_bcmc_regs; - - -static int ehea_probe_adapter(struct platform_device *dev); - -static void ehea_remove(struct platform_device *dev); - -static const struct of_device_id ehea_module_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - { - .type = "network", - .compatible = "IBM,lhea-ethernet", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_module_device_table); - -static const struct of_device_id ehea_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_device_table); - -static struct platform_driver ehea_driver = { - .driver = { - .name = "ehea", - .owner = THIS_MODULE, - .of_match_table = ehea_device_table, - }, - .probe = ehea_probe_adapter, - .remove = ehea_remove, -}; - -void ehea_dump(void *adr, int len, char *msg) -{ - int x; - unsigned char *deb = adr; - for (x = 0; x < len; x += 16) { - pr_info("%s adr=%p ofs=%04x %016llx %016llx\n", - msg, deb, x, *((u64 *)&deb[0]), *((u64 *)&deb[8])); - deb += 16; - } -} - -static void ehea_schedule_port_reset(struct ehea_port *port) -{ - if (!test_bit(__EHEA_DISABLE_PORT_RESET, &port->flags)) - schedule_work(&port->reset_task); -} - -static void ehea_update_firmware_handles(void) -{ - struct ehea_fw_handle_entry *arr = NULL; - struct ehea_adapter *adapter; - int num_adapters = 0; - int num_ports = 0; - int num_portres = 0; - int i = 0; - int num_fw_handles, k, l; - - /* Determine number of handles */ - mutex_lock(&ehea_fw_handles.lock); - - list_for_each_entry(adapter, &adapter_list, list) { - num_adapters++; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_ports++; - num_portres += port->num_def_qps; - } - } - - num_fw_handles = num_adapters * EHEA_NUM_ADAPTER_FW_HANDLES + - num_ports * EHEA_NUM_PORT_FW_HANDLES + - num_portres * EHEA_NUM_PORTRES_FW_HANDLES; - - if (num_fw_handles) { - arr = kzalloc_objs(*arr, num_fw_handles); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - if (num_adapters == 0) - break; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP) || - (num_ports == 0)) - continue; - - for (l = 0; l < port->num_def_qps; l++) { - struct ehea_port_res *pr = &port->port_res[l]; - - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->qp->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->eq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_mr.handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_mr.handle; - } - arr[i].adh = adapter->handle; - arr[i++].fwh = port->qp_eq->fw_handle; - num_ports--; - } - - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->neq->fw_handle; - - if (adapter->mr.handle) { - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->mr.handle; - } - num_adapters--; - } - -out_update: - kfree(ehea_fw_handles.arr); - ehea_fw_handles.arr = arr; - ehea_fw_handles.num_entries = i; -out: - mutex_unlock(&ehea_fw_handles.lock); -} - -static void ehea_update_bcmc_registrations(void) -{ - unsigned long flags; - struct ehea_bcmc_reg_entry *arr = NULL; - struct ehea_adapter *adapter; - struct ehea_mc_list *mc_entry; - int num_registrations = 0; - int i = 0; - int k; - - spin_lock_irqsave(&ehea_bcmc_regs.lock, flags); - - /* Determine number of registrations */ - list_for_each_entry(adapter, &adapter_list, list) - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_registrations += 2; /* Broadcast registrations */ - - list_for_each_entry(mc_entry, &port->mc_list->list,list) - num_registrations += 2; - } - - if (num_registrations) { - arr = kzalloc_objs(*arr, num_registrations, GFP_ATOMIC); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_UNTAGGED; - arr[i++].macaddr = port->mac_addr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_VLANID_ALL; - arr[i++].macaddr = port->mac_addr; - num_registrations -= 2; - - list_for_each_entry(mc_entry, - &port->mc_list->list, list) { - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_UNTAGGED; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_VLANID_ALL; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - num_registrations -= 2; - } - } - } - -out_update: - kfree(ehea_bcmc_regs.arr); - ehea_bcmc_regs.arr = arr; - ehea_bcmc_regs.num_entries = i; -out: - spin_unlock_irqrestore(&ehea_bcmc_regs.lock, flags); -} - -static void ehea_get_stats64(struct net_device *dev, - struct rtnl_link_stats64 *stats) -{ - struct ehea_port *port = netdev_priv(dev); - u64 rx_packets = 0, tx_packets = 0, rx_bytes = 0, tx_bytes = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - rx_packets += port->port_res[i].rx_packets; - rx_bytes += port->port_res[i].rx_bytes; - } - - for (i = 0; i < port->num_def_qps; i++) { - tx_packets += port->port_res[i].tx_packets; - tx_bytes += port->port_res[i].tx_bytes; - } - - stats->tx_packets = tx_packets; - stats->rx_bytes = rx_bytes; - stats->tx_bytes = tx_bytes; - stats->rx_packets = rx_packets; - - stats->multicast = port->stats.multicast; - stats->rx_errors = port->stats.rx_errors; -} - -static void ehea_update_stats(struct work_struct *work) -{ - struct ehea_port *port = - container_of(work, struct ehea_port, stats_work.work); - struct net_device *dev = port->netdev; - struct rtnl_link_stats64 *stats = &port->stats; - struct hcp_ehea_port_cb2 *cb2; - u64 hret; - - cb2 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb2) { - netdev_err(dev, "No mem for cb2. Some interface statistics were not updated\n"); - goto resched; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB2, H_PORT_CB2_ALL, cb2); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_port failed\n"); - goto out_herr; - } - - if (netif_msg_hw(port)) - ehea_dump(cb2, sizeof(*cb2), "net_device_stats"); - - stats->multicast = cb2->rxmcp; - stats->rx_errors = cb2->rxuerr; - -out_herr: - free_page((unsigned long)cb2); -resched: - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); -} - -static void ehea_refill_rq1(struct ehea_port_res *pr, int index, int nr_of_wqes) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int max_index_mask = pr->rq1_skba.len - 1; - int fill_wqes = pr->rq1_skba.os_skbs + nr_of_wqes; - int adder = 0; - int i; - - pr->rq1_skba.os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - if (nr_of_wqes > 0) - pr->rq1_skba.index = index; - pr->rq1_skba.os_skbs = fill_wqes; - return; - } - - for (i = 0; i < fill_wqes; i++) { - if (!skb_arr_rq1[index]) { - skb_arr_rq1[index] = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[index]) { - pr->rq1_skba.os_skbs = fill_wqes - i; - break; - } - } - index--; - index &= max_index_mask; - adder++; - } - - if (adder == 0) - return; - - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, adder); -} - -static void ehea_init_fill_rq1(struct ehea_port_res *pr, int nr_rq1a) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int i; - - if (nr_rq1a > pr->rq1_skba.len) { - netdev_err(dev, "NR_RQ1A bigger than skb array len\n"); - return; - } - - for (i = 0; i < nr_rq1a; i++) { - skb_arr_rq1[i] = netdev_alloc_skb(dev, EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[i]) - break; - } - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, i - 1); -} - -static int ehea_refill_rq_def(struct ehea_port_res *pr, - struct ehea_q_skb_arr *q_skba, int rq_nr, - int num_wqes, int wqe_type, int packet_size) -{ - struct net_device *dev = pr->port->netdev; - struct ehea_qp *qp = pr->qp; - struct sk_buff **skb_arr = q_skba->arr; - struct ehea_rwqe *rwqe; - int i, index, max_index_mask, fill_wqes; - int adder = 0; - int ret = 0; - - fill_wqes = q_skba->os_skbs + num_wqes; - q_skba->os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - q_skba->os_skbs = fill_wqes; - return ret; - } - - index = q_skba->index; - max_index_mask = q_skba->len - 1; - for (i = 0; i < fill_wqes; i++) { - u64 tmp_addr; - struct sk_buff *skb; - - skb = netdev_alloc_skb_ip_align(dev, packet_size); - if (!skb) { - q_skba->os_skbs = fill_wqes - i; - if (q_skba->os_skbs == q_skba->len - 2) { - netdev_info(pr->port->netdev, - "rq%i ran dry - no mem for skb\n", - rq_nr); - ret = -ENOMEM; - } - break; - } - - skb_arr[index] = skb; - tmp_addr = ehea_map_vaddr(skb->data); - if (tmp_addr == -1) { - dev_consume_skb_any(skb); - q_skba->os_skbs = fill_wqes - i; - ret = 0; - break; - } - - rwqe = ehea_get_next_rwqe(qp, rq_nr); - rwqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, wqe_type) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, index); - rwqe->sg_list[0].l_key = pr->recv_mr.lkey; - rwqe->sg_list[0].vaddr = tmp_addr; - rwqe->sg_list[0].len = packet_size; - rwqe->data_segments = 1; - - index++; - index &= max_index_mask; - adder++; - } - - q_skba->index = index; - if (adder == 0) - goto out; - - /* Ring doorbell */ - iosync(); - if (rq_nr == 2) - ehea_update_rq2a(pr->qp, adder); - else - ehea_update_rq3a(pr->qp, adder); -out: - return ret; -} - - -static int ehea_refill_rq2(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq2_skba, 2, - nr_of_wqes, EHEA_RWQE2_TYPE, - EHEA_RQ2_PKT_SIZE); -} - - -static int ehea_refill_rq3(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq3_skba, 3, - nr_of_wqes, EHEA_RWQE3_TYPE, - EHEA_MAX_PACKET_SIZE); -} - -static inline int ehea_check_cqe(struct ehea_cqe *cqe, int *rq_num) -{ - *rq_num = (cqe->type & EHEA_CQE_TYPE_RQ) >> 5; - if ((cqe->status & EHEA_CQE_STAT_ERR_MASK) == 0) - return 0; - if (((cqe->status & EHEA_CQE_STAT_ERR_TCP) != 0) && - (cqe->header_length == 0)) - return 0; - return -EINVAL; -} - -static inline void ehea_fill_skb(struct net_device *dev, - struct sk_buff *skb, struct ehea_cqe *cqe, - struct ehea_port_res *pr) -{ - int length = cqe->num_bytes_transfered - 4; /*remove CRC */ - - skb_put(skb, length); - skb->protocol = eth_type_trans(skb, dev); - - /* The packet was not an IPV4 packet so a complemented checksum was - calculated. The value is found in the Internet Checksum field. */ - if (cqe->status & EHEA_CQE_BLIND_CKSUM) { - skb->ip_summed = CHECKSUM_COMPLETE; - skb->csum = csum_unfold(~cqe->inet_checksum_value); - } else - skb->ip_summed = CHECKSUM_UNNECESSARY; - - skb_record_rx_queue(skb, pr - &pr->port->port_res[0]); -} - -static inline struct sk_buff *get_skb_by_index(struct sk_buff **skb_array, - int arr_len, - struct ehea_cqe *cqe) -{ - int skb_index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - struct sk_buff *skb; - void *pref; - int x; - - x = skb_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetch(pref); - prefetch(pref + EHEA_CACHE_LINE); - prefetch(pref + EHEA_CACHE_LINE * 2); - prefetch(pref + EHEA_CACHE_LINE * 3); - } - - skb = skb_array[skb_index]; - skb_array[skb_index] = NULL; - return skb; -} - -static inline struct sk_buff *get_skb_by_index_ll(struct sk_buff **skb_array, - int arr_len, int wqe_index) -{ - struct sk_buff *skb; - void *pref; - int x; - - x = wqe_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - } - - skb = skb_array[wqe_index]; - skb_array[wqe_index] = NULL; - return skb; -} - -static int ehea_treat_poll_error(struct ehea_port_res *pr, int rq, - struct ehea_cqe *cqe, int *processed_rq2, - int *processed_rq3) -{ - struct sk_buff *skb; - - if (cqe->status & EHEA_CQE_STAT_ERR_TCP) - pr->p_stats.err_tcp_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_IP) - pr->p_stats.err_ip_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_CRC) - pr->p_stats.err_frame_crc++; - - if (rq == 2) { - *processed_rq2 += 1; - skb = get_skb_by_index(pr->rq2_skba.arr, pr->rq2_skba.len, cqe); - dev_kfree_skb(skb); - } else if (rq == 3) { - *processed_rq3 += 1; - skb = get_skb_by_index(pr->rq3_skba.arr, pr->rq3_skba.len, cqe); - dev_kfree_skb(skb); - } - - if (cqe->status & EHEA_CQE_STAT_FAT_ERR_MASK) { - if (netif_msg_rx_err(pr->port)) { - pr_err("Critical receive error for QP %d. Resetting port.\n", - pr->qp->init_attr.qp_nr); - ehea_dump(cqe, sizeof(*cqe), "CQE"); - } - ehea_schedule_port_reset(pr->port); - return 1; - } - - return 0; -} - -static int ehea_proc_rwqes(struct net_device *dev, - struct ehea_port_res *pr, - int budget) -{ - struct ehea_port *port = pr->port; - struct ehea_qp *qp = pr->qp; - struct ehea_cqe *cqe; - struct sk_buff *skb; - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct sk_buff **skb_arr_rq2 = pr->rq2_skba.arr; - struct sk_buff **skb_arr_rq3 = pr->rq3_skba.arr; - int skb_arr_rq1_len = pr->rq1_skba.len; - int skb_arr_rq2_len = pr->rq2_skba.len; - int skb_arr_rq3_len = pr->rq3_skba.len; - int processed, processed_rq1, processed_rq2, processed_rq3; - u64 processed_bytes = 0; - int wqe_index, last_wqe_index, rq, port_reset; - - processed = processed_rq1 = processed_rq2 = processed_rq3 = 0; - last_wqe_index = 0; - - cqe = ehea_poll_rq1(qp, &wqe_index); - while ((processed < budget) && cqe) { - ehea_inc_rq1(qp); - processed_rq1++; - processed++; - if (netif_msg_rx_status(port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - last_wqe_index = wqe_index; - rmb(); - if (!ehea_check_cqe(cqe, &rq)) { - if (rq == 1) { - /* LL RQ1 */ - skb = get_skb_by_index_ll(skb_arr_rq1, - skb_arr_rq1_len, - wqe_index); - if (unlikely(!skb)) { - netif_info(port, rx_err, dev, - "LL rq1: skb=NULL\n"); - - skb = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb) - break; - } - skb_copy_to_linear_data(skb, ((char *)cqe) + 64, - cqe->num_bytes_transfered - 4); - ehea_fill_skb(dev, skb, cqe, pr); - } else if (rq == 2) { - /* RQ2 */ - skb = get_skb_by_index(skb_arr_rq2, - skb_arr_rq2_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq2: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq2++; - } else { - /* RQ3 */ - skb = get_skb_by_index(skb_arr_rq3, - skb_arr_rq3_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq3: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq3++; - } - - processed_bytes += skb->len; - - if (cqe->status & EHEA_CQE_VLAN_TAG_XTRACT) - __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), - cqe->vlan_tag); - - napi_gro_receive(&pr->napi, skb); - } else { - pr->p_stats.poll_receive_errors++; - port_reset = ehea_treat_poll_error(pr, rq, cqe, - &processed_rq2, - &processed_rq3); - if (port_reset) - break; - } - cqe = ehea_poll_rq1(qp, &wqe_index); - } - - pr->rx_packets += processed; - pr->rx_bytes += processed_bytes; - - ehea_refill_rq1(pr, last_wqe_index, processed_rq1); - ehea_refill_rq2(pr, processed_rq2); - ehea_refill_rq3(pr, processed_rq3); - - return processed; -} - -#define SWQE_RESTART_CHECK 0xdeadbeaff00d0000ull - -static void reset_sq_restart_flag(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - pr->sq_restart_flag = 0; - } - wake_up(&port->restart_wq); -} - -static void check_sqs(struct ehea_port *port) -{ - struct ehea_swqe *swqe; - int swqe_index; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int ret; - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - swqe->tx_control |= EHEA_SWQE_PURGE; - swqe->wr_id = SWQE_RESTART_CHECK; - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT; - swqe->immediate_data_length = 80; - - ehea_post_swqe(pr->qp, swqe); - - ret = wait_event_timeout(port->restart_wq, - pr->sq_restart_flag == 0, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("HW/SW queues out of sync\n"); - ehea_schedule_port_reset(pr->port); - return; - } - } -} - - -static struct ehea_cqe *ehea_proc_cqes(struct ehea_port_res *pr, int my_quota) -{ - struct sk_buff *skb; - struct ehea_cq *send_cq = pr->send_cq; - struct ehea_cqe *cqe; - int quota = my_quota; - int cqe_counter = 0; - int swqe_av = 0; - int index; - struct netdev_queue *txq = netdev_get_tx_queue(pr->port->netdev, - pr - &pr->port->port_res[0]); - - cqe = ehea_poll_cq(send_cq); - while (cqe && (quota > 0)) { - ehea_inc_cq(send_cq); - - cqe_counter++; - rmb(); - - if (cqe->wr_id == SWQE_RESTART_CHECK) { - pr->sq_restart_flag = 1; - swqe_av++; - break; - } - - if (cqe->status & EHEA_CQE_STAT_ERR_MASK) { - pr_err("Bad send completion status=0x%04X\n", - cqe->status); - - if (netif_msg_tx_err(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "Send CQE"); - - if (cqe->status & EHEA_CQE_STAT_RESET_MASK) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(pr->port); - break; - } - } - - if (netif_msg_tx_done(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - if (likely(EHEA_BMASK_GET(EHEA_WR_ID_TYPE, cqe->wr_id) - == EHEA_SWQE2_TYPE)) { - - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - skb = pr->sq_skba.arr[index]; - dev_consume_skb_any(skb); - pr->sq_skba.arr[index] = NULL; - } - - swqe_av += EHEA_BMASK_GET(EHEA_WR_ID_REFILL, cqe->wr_id); - quota--; - - cqe = ehea_poll_cq(send_cq); - } - - ehea_update_feca(send_cq, cqe_counter); - atomic_add(swqe_av, &pr->swqe_avail); - - if (unlikely(netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th))) { - __netif_tx_lock(txq, smp_processor_id()); - if (netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th)) - netif_tx_wake_queue(txq); - __netif_tx_unlock(txq); - } - - wake_up(&pr->port->swqe_avail_wq); - - return cqe; -} - -#define EHEA_POLL_MAX_CQES 65535 - -static int ehea_poll(struct napi_struct *napi, int budget) -{ - struct ehea_port_res *pr = container_of(napi, struct ehea_port_res, - napi); - struct net_device *dev = pr->port->netdev; - struct ehea_cqe *cqe; - struct ehea_cqe *cqe_skb = NULL; - int wqe_index; - int rx = 0; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - - while (rx != budget) { - napi_complete(napi); - ehea_reset_cq_ep(pr->recv_cq); - ehea_reset_cq_ep(pr->send_cq); - ehea_reset_cq_n1(pr->recv_cq); - ehea_reset_cq_n1(pr->send_cq); - rmb(); - cqe = ehea_poll_rq1(pr->qp, &wqe_index); - cqe_skb = ehea_poll_cq(pr->send_cq); - - if (!cqe && !cqe_skb) - return rx; - - if (!napi_schedule(napi)) - return rx; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - } - - return rx; -} - -static irqreturn_t ehea_recv_irq_handler(int irq, void *param) -{ - struct ehea_port_res *pr = param; - - napi_schedule(&pr->napi); - - return IRQ_HANDLED; -} - -static irqreturn_t ehea_qp_aff_irq_handler(int irq, void *param) -{ - struct ehea_port *port = param; - struct ehea_eqe *eqe; - struct ehea_qp *qp; - u32 qp_token; - u64 resource_type, aer, aerr; - int reset_port = 0; - - eqe = ehea_poll_eq(port->qp_eq); - - while (eqe) { - qp_token = EHEA_BMASK_GET(EHEA_EQE_QP_TOKEN, eqe->entry); - pr_err("QP aff_err: entry=0x%llx, token=0x%x\n", - eqe->entry, qp_token); - - qp = port->port_res[qp_token].qp; - - resource_type = ehea_error_data(port->adapter, qp->fw_handle, - &aer, &aerr); - - if (resource_type == EHEA_AER_RESTYPE_QP) { - if ((aer & EHEA_AER_RESET_MASK) || - (aerr & EHEA_AERR_RESET_MASK)) - reset_port = 1; - } else - reset_port = 1; /* Reset in case of CQ or EQ error */ - - eqe = ehea_poll_eq(port->qp_eq); - } - - if (reset_port) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(port); - } - - return IRQ_HANDLED; -} - -static struct ehea_port *ehea_get_port(struct ehea_adapter *adapter, - int logical_port) -{ - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) - if (adapter->port[i]->logical_port_id == logical_port) - return adapter->port[i]; - return NULL; -} - -int ehea_sense_port_attr(struct ehea_port *port) -{ - int ret; - u64 hret; - struct hcp_ehea_port_cb0 *cb0; - - /* may be called via ehea_neq_tasklet() */ - cb0 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - /* MAC address */ - port->mac_addr = cb0->port_mac_addr << 16; - - if (!is_valid_ether_addr((u8 *)&port->mac_addr)) { - ret = -EADDRNOTAVAIL; - goto out_free; - } - - /* Port speed */ - switch (cb0->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - - port->autoneg = 1; - port->num_mcs = cb0->num_default_qps; - - /* Number of default QPs */ - if (use_mcs) - port->num_def_qps = cb0->num_default_qps; - else - port->num_def_qps = 1; - - if (!port->num_def_qps) { - ret = -EINVAL; - goto out_free; - } - - ret = 0; -out_free: - if (ret || netif_msg_probe(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_sense_port_attr"); - free_page((unsigned long)cb0); -out: - return ret; -} - -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } - - cb4->port_speed = port_speed; - - netif_carrier_off(port->netdev); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, cb4); - if (hret == H_SUCCESS) { - port->autoneg = port_speed == EHEA_SPEED_AUTONEG ? 1 : 0; - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, - cb4); - if (hret == H_SUCCESS) { - switch (cb4->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - } else { - pr_err("Failed sensing port speed\n"); - ret = -EIO; - } - } else { - if (hret == H_AUTHORITY) { - pr_info("Hypervisor denied setting port speed\n"); - ret = -EPERM; - } else { - ret = -EIO; - pr_err("Failed setting port speed\n"); - } - } - if (!prop_carrier_state || (port->phy_link == EHEA_PHY_LINK_UP)) - netif_carrier_on(port->netdev); - - free_page((unsigned long)cb4); -out: - return ret; -} - -static void ehea_parse_eqe(struct ehea_adapter *adapter, u64 eqe) -{ - int ret; - u8 ec; - u8 portnum; - struct ehea_port *port; - struct net_device *dev; - - ec = EHEA_BMASK_GET(NEQE_EVENT_CODE, eqe); - portnum = EHEA_BMASK_GET(NEQE_PORTNUM, eqe); - port = ehea_get_port(adapter, portnum); - if (!port) { - netdev_err(NULL, "unknown portnum %x\n", portnum); - return; - } - dev = port->netdev; - - switch (ec) { - case EHEA_EC_PORTSTATE_CHG: /* port state change */ - - if (EHEA_BMASK_GET(NEQE_PORT_UP, eqe)) { - if (!netif_carrier_ok(dev)) { - ret = ehea_sense_port_attr(port); - if (ret) { - netdev_err(dev, "failed resensing port attributes\n"); - break; - } - - netif_info(port, link, dev, - "Logical port up: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? - "Full" : "Half"); - - netif_carrier_on(dev); - netif_wake_queue(dev); - } - } else - if (netif_carrier_ok(dev)) { - netif_info(port, link, dev, - "Logical port down\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PORT_UP, eqe)) { - port->phy_link = EHEA_PHY_LINK_UP; - netif_info(port, link, dev, - "Physical port up\n"); - if (prop_carrier_state) - netif_carrier_on(dev); - } else { - port->phy_link = EHEA_PHY_LINK_DOWN; - netif_info(port, link, dev, - "Physical port down\n"); - if (prop_carrier_state) - netif_carrier_off(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PRIMARY, eqe)) - netdev_info(dev, - "External switch port is primary port\n"); - else - netdev_info(dev, - "External switch port is backup port\n"); - - break; - case EHEA_EC_ADAPTER_MALFUNC: - netdev_err(dev, "Adapter malfunction\n"); - break; - case EHEA_EC_PORT_MALFUNC: - netdev_info(dev, "Port malfunction\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - break; - default: - netdev_err(dev, "unknown event code %x, eqe=0x%llX\n", ec, eqe); - break; - } -} - -static void ehea_neq_tasklet(struct tasklet_struct *t) -{ - struct ehea_adapter *adapter = from_tasklet(adapter, t, neq_tasklet); - struct ehea_eqe *eqe; - u64 event_mask; - - eqe = ehea_poll_eq(adapter->neq); - pr_debug("eqe=%p\n", eqe); - - while (eqe) { - pr_debug("*eqe=%lx\n", (unsigned long) eqe->entry); - ehea_parse_eqe(adapter, eqe->entry); - eqe = ehea_poll_eq(adapter->neq); - pr_debug("next eqe=%p\n", eqe); - } - - event_mask = EHEA_BMASK_SET(NELR_PORTSTATE_CHG, 1) - | EHEA_BMASK_SET(NELR_ADAPTER_MALFUNC, 1) - | EHEA_BMASK_SET(NELR_PORT_MALFUNC, 1); - - ehea_h_reset_events(adapter->handle, - adapter->neq->fw_handle, event_mask); -} - -static irqreturn_t ehea_interrupt_neq(int irq, void *param) -{ - struct ehea_adapter *adapter = param; - tasklet_hi_schedule(&adapter->neq_tasklet); - return IRQ_HANDLED; -} - - -static int ehea_fill_port_res(struct ehea_port_res *pr) -{ - int ret; - struct ehea_qp_init_attr *init_attr = &pr->qp->init_attr; - - ehea_init_fill_rq1(pr, pr->rq1_skba.len); - - ret = ehea_refill_rq2(pr, init_attr->act_nr_rwqes_rq2 - 1); - - ret |= ehea_refill_rq3(pr, init_attr->act_nr_rwqes_rq3 - 1); - - return ret; -} - -static int ehea_reg_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i, ret; - - - snprintf(port->int_aff_name, EHEA_IRQ_NAME_SIZE - 1, "%s-aff", - dev->name); - - ret = ibmebus_request_irq(port->qp_eq->attr.ist1, - ehea_qp_aff_irq_handler, - 0, port->int_aff_name, port); - if (ret) { - netdev_err(dev, "failed registering irq for qp_aff_irq_handler:ist=%X\n", - port->qp_eq->attr.ist1); - goto out_free_qpeq; - } - - netif_info(port, ifup, dev, - "irq_handle 0x%X for function qp_aff_irq_handler registered\n", - port->qp_eq->attr.ist1); - - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - snprintf(pr->int_send_name, EHEA_IRQ_NAME_SIZE - 1, - "%s-queue%d", dev->name, i); - ret = ibmebus_request_irq(pr->eq->attr.ist1, - ehea_recv_irq_handler, - 0, pr->int_send_name, pr); - if (ret) { - netdev_err(dev, "failed registering irq for ehea_queue port_res_nr:%d, ist=%X\n", - i, pr->eq->attr.ist1); - goto out_free_req; - } - netif_info(port, ifup, dev, - "irq_handle 0x%X for function ehea_queue_int %d registered\n", - pr->eq->attr.ist1, i); - } -out: - return ret; - - -out_free_req: - while (--i >= 0) { - u32 ist = port->port_res[i].eq->attr.ist1; - ibmebus_free_irq(ist, &port->port_res[i]); - } - -out_free_qpeq: - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - i = port->num_def_qps; - - goto out; - -} - -static void ehea_free_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i; - - /* send */ - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - ibmebus_free_irq(pr->eq->attr.ist1, pr); - netif_info(port, intr, dev, - "free send irq for res %d with handle 0x%X\n", - i, pr->eq->attr.ist1); - } - - /* associated events */ - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - netif_info(port, intr, dev, - "associated event interrupt for handle 0x%X freed\n", - port->qp_eq->attr.ist1); -} - -static int ehea_configure_port(struct ehea_port *port) -{ - int ret, i; - u64 hret, mask; - struct hcp_ehea_port_cb0 *cb0; - - ret = -ENOMEM; - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - goto out; - - cb0->port_rc = EHEA_BMASK_SET(PXLY_RC_VALID, 1) - | EHEA_BMASK_SET(PXLY_RC_IP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_TCP_UDP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_XTRACT, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_TAG_FILTER, - PXLY_RC_VLAN_FILTER) - | EHEA_BMASK_SET(PXLY_RC_JUMBO_FRAME, 1); - - for (i = 0; i < port->num_mcs; i++) - if (use_mcs) - cb0->default_qpn_arr[i] = - port->port_res[i].qp->init_attr.qp_nr; - else - cb0->default_qpn_arr[i] = - port->port_res[0].qp->init_attr.qp_nr; - - if (netif_msg_ifup(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_configure_port"); - - mask = EHEA_BMASK_SET(H_PORT_CB0_PRC, 1) - | EHEA_BMASK_SET(H_PORT_CB0_DEFQPNARRAY, 1); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB0, mask, cb0); - ret = -EIO; - if (hret != H_SUCCESS) - goto out_free; - - ret = 0; - -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static int ehea_gen_smrs(struct ehea_port_res *pr) -{ - int ret; - struct ehea_adapter *adapter = pr->port->adapter; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->send_mr); - if (ret) - goto out; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->recv_mr); - if (ret) - goto out_free; - - return 0; - -out_free: - ehea_rem_mr(&pr->send_mr); -out: - pr_err("Generating SMRS failed\n"); - return -EIO; -} - -static int ehea_rem_smrs(struct ehea_port_res *pr) -{ - if ((ehea_rem_mr(&pr->send_mr)) || - (ehea_rem_mr(&pr->recv_mr))) - return -EIO; - else - return 0; -} - -static int ehea_init_q_skba(struct ehea_q_skb_arr *q_skba, int max_q_entries) -{ - int arr_size = sizeof(void *) * max_q_entries; - - q_skba->arr = vzalloc(arr_size); - if (!q_skba->arr) - return -ENOMEM; - - q_skba->len = max_q_entries; - q_skba->index = 0; - q_skba->os_skbs = 0; - - return 0; -} - -static int ehea_init_port_res(struct ehea_port *port, struct ehea_port_res *pr, - struct port_res_cfg *pr_cfg, int queue_token) -{ - struct ehea_adapter *adapter = port->adapter; - enum ehea_eq_type eq_type = EHEA_EQ; - struct ehea_qp_init_attr *init_attr = NULL; - int ret = -EIO; - u64 tx_bytes, rx_bytes, tx_packets, rx_packets; - - tx_bytes = pr->tx_bytes; - tx_packets = pr->tx_packets; - rx_bytes = pr->rx_bytes; - rx_packets = pr->rx_packets; - - memset(pr, 0, sizeof(struct ehea_port_res)); - - pr->tx_bytes = tx_bytes; - pr->tx_packets = tx_packets; - pr->rx_bytes = rx_bytes; - pr->rx_packets = rx_packets; - - pr->port = port; - - pr->eq = ehea_create_eq(adapter, eq_type, EHEA_MAX_ENTRIES_EQ, 0); - if (!pr->eq) { - pr_err("create_eq failed (eq)\n"); - goto out_free; - } - - pr->recv_cq = ehea_create_cq(adapter, pr_cfg->max_entries_rcq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->recv_cq) { - pr_err("create_cq failed (cq_recv)\n"); - goto out_free; - } - - pr->send_cq = ehea_create_cq(adapter, pr_cfg->max_entries_scq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->send_cq) { - pr_err("create_cq failed (cq_send)\n"); - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("Send CQ: act_nr_cqes=%d, Recv CQ: act_nr_cqes=%d\n", - pr->send_cq->attr.act_nr_of_cqes, - pr->recv_cq->attr.act_nr_of_cqes); - - init_attr = kzalloc_obj(*init_attr); - if (!init_attr) { - ret = -ENOMEM; - pr_err("no mem for ehea_qp_init_attr\n"); - goto out_free; - } - - init_attr->low_lat_rq1 = 1; - init_attr->signalingtype = 1; /* generate CQE if specified in WQE */ - init_attr->rq_count = 3; - init_attr->qp_token = queue_token; - init_attr->max_nr_send_wqes = pr_cfg->max_entries_sq; - init_attr->max_nr_rwqes_rq1 = pr_cfg->max_entries_rq1; - init_attr->max_nr_rwqes_rq2 = pr_cfg->max_entries_rq2; - init_attr->max_nr_rwqes_rq3 = pr_cfg->max_entries_rq3; - init_attr->wqe_size_enc_sq = EHEA_SG_SQ; - init_attr->wqe_size_enc_rq1 = EHEA_SG_RQ1; - init_attr->wqe_size_enc_rq2 = EHEA_SG_RQ2; - init_attr->wqe_size_enc_rq3 = EHEA_SG_RQ3; - init_attr->rq2_threshold = EHEA_RQ2_THRESHOLD; - init_attr->rq3_threshold = EHEA_RQ3_THRESHOLD; - init_attr->port_nr = port->logical_port_id; - init_attr->send_cq_handle = pr->send_cq->fw_handle; - init_attr->recv_cq_handle = pr->recv_cq->fw_handle; - init_attr->aff_eq_handle = port->qp_eq->fw_handle; - - pr->qp = ehea_create_qp(adapter, adapter->pd, init_attr); - if (!pr->qp) { - pr_err("create_qp failed\n"); - ret = -EIO; - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("QP: qp_nr=%d\n act_nr_snd_wqe=%d\n nr_rwqe_rq1=%d\n nr_rwqe_rq2=%d\n nr_rwqe_rq3=%d\n", - init_attr->qp_nr, - init_attr->act_nr_send_wqes, - init_attr->act_nr_rwqes_rq1, - init_attr->act_nr_rwqes_rq2, - init_attr->act_nr_rwqes_rq3); - - pr->sq_skba_size = init_attr->act_nr_send_wqes + 1; - - ret = ehea_init_q_skba(&pr->sq_skba, pr->sq_skba_size); - ret |= ehea_init_q_skba(&pr->rq1_skba, init_attr->act_nr_rwqes_rq1 + 1); - ret |= ehea_init_q_skba(&pr->rq2_skba, init_attr->act_nr_rwqes_rq2 + 1); - ret |= ehea_init_q_skba(&pr->rq3_skba, init_attr->act_nr_rwqes_rq3 + 1); - if (ret) - goto out_free; - - pr->swqe_refill_th = init_attr->act_nr_send_wqes / 10; - if (ehea_gen_smrs(pr) != 0) { - ret = -EIO; - goto out_free; - } - - atomic_set(&pr->swqe_avail, init_attr->act_nr_send_wqes - 1); - - kfree(init_attr); - - netif_napi_add(pr->port->netdev, &pr->napi, ehea_poll); - - ret = 0; - goto out; - -out_free: - kfree(init_attr); - vfree(pr->sq_skba.arr); - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - ehea_destroy_qp(pr->qp); - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); -out: - return ret; -} - -static int ehea_clean_portres(struct ehea_port *port, struct ehea_port_res *pr) -{ - int ret, i; - - if (pr->qp) - netif_napi_del(&pr->napi); - - ret = ehea_destroy_qp(pr->qp); - - if (!ret) { - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); - - for (i = 0; i < pr->rq1_skba.len; i++) - dev_kfree_skb(pr->rq1_skba.arr[i]); - - for (i = 0; i < pr->rq2_skba.len; i++) - dev_kfree_skb(pr->rq2_skba.arr[i]); - - for (i = 0; i < pr->rq3_skba.len; i++) - dev_kfree_skb(pr->rq3_skba.arr[i]); - - for (i = 0; i < pr->sq_skba.len; i++) - dev_kfree_skb(pr->sq_skba.arr[i]); - - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - vfree(pr->sq_skba.arr); - ret = ehea_rem_smrs(pr); - } - return ret; -} - -static void write_swqe2_immediate(struct sk_buff *skb, struct ehea_swqe *swqe, - u32 lkey) -{ - int skb_data_size = skb_headlen(skb); - u8 *imm_data = &swqe->u.immdata_desc.immediate_data[0]; - struct ehea_vsgentry *sg1entry = &swqe->u.immdata_desc.sg_entry; - unsigned int immediate_len = SWQE2_MAX_IMM; - - swqe->descriptors = 0; - - if (skb_is_gso(skb)) { - swqe->tx_control |= EHEA_SWQE_TSO; - swqe->mss = skb_shinfo(skb)->gso_size; - /* - * For TSO packets we only copy the headers into the - * immediate area. - */ - immediate_len = skb_tcp_all_headers(skb); - } - - if (skb_is_gso(skb) || skb_data_size >= SWQE2_MAX_IMM) { - skb_copy_from_linear_data(skb, imm_data, immediate_len); - swqe->immediate_data_length = immediate_len; - - if (skb_data_size > immediate_len) { - sg1entry->l_key = lkey; - sg1entry->len = skb_data_size - immediate_len; - sg1entry->vaddr = - ehea_map_vaddr(skb->data + immediate_len); - swqe->descriptors++; - } - } else { - skb_copy_from_linear_data(skb, imm_data, skb_data_size); - swqe->immediate_data_length = skb_data_size; - } -} - -static inline void write_swqe2_data(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - struct ehea_vsgentry *sg_list, *sg1entry, *sgentry; - skb_frag_t *frag; - int nfrags, sg1entry_contains_frag_data, i; - - nfrags = skb_shinfo(skb)->nr_frags; - sg1entry = &swqe->u.immdata_desc.sg_entry; - sg_list = (struct ehea_vsgentry *)&swqe->u.immdata_desc.sg_list; - sg1entry_contains_frag_data = 0; - - write_swqe2_immediate(skb, swqe, lkey); - - /* write descriptors */ - if (nfrags > 0) { - if (swqe->descriptors == 0) { - /* sg1entry not yet used */ - frag = &skb_shinfo(skb)->frags[0]; - - /* copy sg1entry data */ - sg1entry->l_key = lkey; - sg1entry->len = skb_frag_size(frag); - sg1entry->vaddr = - ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - sg1entry_contains_frag_data = 1; - } - - for (i = sg1entry_contains_frag_data; i < nfrags; i++) { - - frag = &skb_shinfo(skb)->frags[i]; - sgentry = &sg_list[i - sg1entry_contains_frag_data]; - - sgentry->l_key = lkey; - sgentry->len = skb_frag_size(frag); - sgentry->vaddr = ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - } - } -} - -static int ehea_broadcast_reg_helper(struct ehea_port *port, u32 hcallid) -{ - int ret = 0; - u64 hret; - u8 reg_type; - - /* De/Register untagged packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_UNTAGGED; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (tagged)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - goto out_herr; - } - - /* De/Register VLAN packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_VLANID_ALL; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (vlan)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - } -out_herr: - return ret; -} - -static int ehea_set_mac_addr(struct net_device *dev, void *sa) -{ - struct ehea_port *port = netdev_priv(dev); - struct sockaddr *mac_addr = sa; - struct hcp_ehea_port_cb0 *cb0; - int ret; - u64 hret; - - if (!is_valid_ether_addr(mac_addr->sa_data)) { - ret = -EADDRNOTAVAIL; - goto out; - } - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - memcpy(&(cb0->port_mac_addr), &(mac_addr->sa_data[0]), ETH_ALEN); - - cb0->port_mac_addr = cb0->port_mac_addr >> 16; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_MAC, 1), cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - eth_hw_addr_set(dev, mac_addr->sa_data); - - /* Deregister old MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - if (ret) - goto out_upregs; - } - - port->mac_addr = cb0->port_mac_addr << 16; - - /* Register new MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) - goto out_upregs; - } - - ret = 0; - -out_upregs: - ehea_update_bcmc_registrations(); -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static void ehea_promiscuous_error(u64 hret, int enable) -{ - if (hret == H_AUTHORITY) - pr_info("Hypervisor denied %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); - else - pr_err("failed %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); -} - -static void ehea_promiscuous(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - struct hcp_ehea_port_cb7 *cb7; - u64 hret; - - if (enable == port->promisc) - return; - - cb7 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb7) { - pr_err("no mem for cb7\n"); - goto out; - } - - /* Modify Pxs_DUCQPN in CB7 */ - cb7->def_uc_qpn = enable == 1 ? port->port_res[0].qp->fw_handle : 0; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB7, H_PORT_CB7_DUCQPN, cb7); - if (hret) { - ehea_promiscuous_error(hret, enable); - goto out; - } - - port->promisc = enable; -out: - free_page((unsigned long)cb7); -} - -static u64 ehea_multicast_reg_helper(struct ehea_port *port, u64 mc_mac_addr, - u32 hcallid) -{ - u64 hret; - u8 reg_type; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_UNTAGGED; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); - if (hret) - goto out; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_VLANID_ALL; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); -out: - return hret; -} - -static int ehea_drop_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_mc_list *mc_entry = port->mc_list; - struct list_head *pos; - struct list_head *temp; - int ret = 0; - u64 hret; - - list_for_each_safe(pos, temp, &(port->mc_list->list)) { - mc_entry = list_entry(pos, struct ehea_mc_list, list); - - hret = ehea_multicast_reg_helper(port, mc_entry->macaddr, - H_DEREG_BCMC); - if (hret) { - pr_err("failed deregistering mcast MAC\n"); - ret = -EIO; - } - - list_del(pos); - kfree(mc_entry); - } - return ret; -} - -static void ehea_allmulti(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - u64 hret; - - if (!port->allmulti) { - if (enable) { - /* Enable ALLMULTI */ - ehea_drop_multicast_list(dev); - hret = ehea_multicast_reg_helper(port, 0, H_REG_BCMC); - if (!hret) - port->allmulti = 1; - else - netdev_err(dev, - "failed enabling IFF_ALLMULTI\n"); - } - } else { - if (!enable) { - /* Disable ALLMULTI */ - hret = ehea_multicast_reg_helper(port, 0, H_DEREG_BCMC); - if (!hret) - port->allmulti = 0; - else - netdev_err(dev, - "failed disabling IFF_ALLMULTI\n"); - } - } -} - -static void ehea_add_multicast_entry(struct ehea_port *port, u8 *mc_mac_addr) -{ - struct ehea_mc_list *ehea_mcl_entry; - u64 hret; - - ehea_mcl_entry = kzalloc_obj(*ehea_mcl_entry, GFP_ATOMIC); - if (!ehea_mcl_entry) - return; - - INIT_LIST_HEAD(&ehea_mcl_entry->list); - - memcpy(&ehea_mcl_entry->macaddr, mc_mac_addr, ETH_ALEN); - - hret = ehea_multicast_reg_helper(port, ehea_mcl_entry->macaddr, - H_REG_BCMC); - if (!hret) - list_add(&ehea_mcl_entry->list, &port->mc_list->list); - else { - pr_err("failed registering mcast MAC\n"); - kfree(ehea_mcl_entry); - } -} - -static void ehea_set_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct netdev_hw_addr *ha; - int ret; - - ehea_promiscuous(dev, !!(dev->flags & IFF_PROMISC)); - - if (dev->flags & IFF_ALLMULTI) { - ehea_allmulti(dev, 1); - goto out; - } - ehea_allmulti(dev, 0); - - if (!netdev_mc_empty(dev)) { - ret = ehea_drop_multicast_list(dev); - if (ret) { - /* Dropping the current multicast list failed. - * Enabling ALL_MULTI is the best we can do. - */ - ehea_allmulti(dev, 1); - } - - if (netdev_mc_count(dev) > port->adapter->max_mc_mac) { - pr_info("Mcast registration limit reached (0x%llx). Use ALLMULTI!\n", - port->adapter->max_mc_mac); - goto out; - } - - netdev_for_each_mc_addr(ha, dev) - ehea_add_multicast_entry(port, ha->addr); - - } -out: - ehea_update_bcmc_registrations(); -} - -static void xmit_common(struct sk_buff *skb, struct ehea_swqe *swqe) -{ - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT | EHEA_SWQE_CRC; - - if (vlan_get_protocol(skb) != htons(ETH_P_IP)) - return; - - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_IP_CHECKSUM; - - swqe->ip_start = skb_network_offset(skb); - swqe->ip_end = swqe->ip_start + ip_hdrlen(skb) - 1; - - switch (ip_hdr(skb)->protocol) { - case IPPROTO_UDP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct udphdr, check); - break; - - case IPPROTO_TCP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct tcphdr, check); - break; - } -} - -static void ehea_xmit2(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - swqe->tx_control |= EHEA_SWQE_DESCRIPTORS_PRESENT; - - xmit_common(skb, swqe); - - write_swqe2_data(skb, dev, swqe, lkey); -} - -static void ehea_xmit3(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe) -{ - u8 *imm_data = &swqe->u.immdata_nodesc.immediate_data[0]; - - xmit_common(skb, swqe); - - if (!skb->data_len) - skb_copy_from_linear_data(skb, imm_data, skb->len); - else - skb_copy_bits(skb, 0, imm_data, skb->len); - - swqe->immediate_data_length = skb->len; - dev_consume_skb_any(skb); -} - -static netdev_tx_t ehea_start_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_swqe *swqe; - u32 lkey; - int swqe_index; - struct ehea_port_res *pr; - struct netdev_queue *txq; - - pr = &port->port_res[skb_get_queue_mapping(skb)]; - txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb)); - - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - if (skb_vlan_tag_present(skb)) { - swqe->tx_control |= EHEA_SWQE_VLAN_INSERT; - swqe->vlan_tag = skb_vlan_tag_get(skb); - } - - pr->tx_packets++; - pr->tx_bytes += skb->len; - - if (skb->len <= SWQE3_MAX_IMM) { - u32 sig_iv = port->sig_comp_iv; - u32 swqe_num = pr->swqe_id_counter; - ehea_xmit3(skb, dev, swqe); - swqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE3_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, swqe_num); - if (pr->swqe_ll_count >= (sig_iv - 1)) { - swqe->wr_id |= EHEA_BMASK_SET(EHEA_WR_ID_REFILL, - sig_iv); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - pr->swqe_ll_count = 0; - } else - pr->swqe_ll_count += 1; - } else { - swqe->wr_id = - EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE2_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, pr->swqe_id_counter) - | EHEA_BMASK_SET(EHEA_WR_ID_REFILL, 1) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, pr->sq_skba.index); - pr->sq_skba.arr[pr->sq_skba.index] = skb; - - pr->sq_skba.index++; - pr->sq_skba.index &= (pr->sq_skba.len - 1); - - lkey = pr->send_mr.lkey; - ehea_xmit2(skb, dev, swqe, lkey); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - } - pr->swqe_id_counter += 1; - - netif_info(port, tx_queued, dev, - "post swqe on QP %d\n", pr->qp->init_attr.qp_nr); - if (netif_msg_tx_queued(port)) - ehea_dump(swqe, 512, "swqe"); - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - netif_tx_stop_queue(txq); - swqe->tx_control |= EHEA_SWQE_PURGE; - } - - ehea_post_swqe(pr->qp, swqe); - - if (unlikely(atomic_read(&pr->swqe_avail) <= 1)) { - pr->p_stats.queue_stopped++; - netif_tx_stop_queue(txq); - } - - return NETDEV_TX_OK; -} - -static int ehea_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] |= ((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] &= ~((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_activate_qp(struct ehea_adapter *adapter, struct ehea_qp *qp) -{ - int ret = -EIO; - u64 hret; - u16 dummy16 = 0; - u64 dummy64 = 0; - struct hcp_modify_qp_cb0 *cb0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (2)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (3)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_RDY2SND; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (3)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (4)\n"); - goto out; - } - - ret = 0; -out: - free_page((unsigned long)cb0); - return ret; -} - -static int ehea_port_res_setup(struct ehea_port *port, int def_qps) -{ - int ret, i; - struct port_res_cfg pr_cfg, pr_cfg_small_rx; - enum ehea_eq_type eq_type = EHEA_EQ; - - port->qp_eq = ehea_create_eq(port->adapter, eq_type, - EHEA_MAX_ENTRIES_EQ, 1); - if (!port->qp_eq) { - ret = -EINVAL; - pr_err("ehea_create_eq failed (qp_eq)\n"); - goto out_kill_eq; - } - - pr_cfg.max_entries_rcq = rq1_entries + rq2_entries + rq3_entries; - pr_cfg.max_entries_scq = sq_entries * 2; - pr_cfg.max_entries_sq = sq_entries; - pr_cfg.max_entries_rq1 = rq1_entries; - pr_cfg.max_entries_rq2 = rq2_entries; - pr_cfg.max_entries_rq3 = rq3_entries; - - pr_cfg_small_rx.max_entries_rcq = 1; - pr_cfg_small_rx.max_entries_scq = sq_entries; - pr_cfg_small_rx.max_entries_sq = sq_entries; - pr_cfg_small_rx.max_entries_rq1 = 1; - pr_cfg_small_rx.max_entries_rq2 = 1; - pr_cfg_small_rx.max_entries_rq3 = 1; - - for (i = 0; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], &pr_cfg, i); - if (ret) - goto out_clean_pr; - } - for (i = def_qps; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], - &pr_cfg_small_rx, i); - if (ret) - goto out_clean_pr; - } - - return 0; - -out_clean_pr: - while (--i >= 0) - ehea_clean_portres(port, &port->port_res[i]); - -out_kill_eq: - ehea_destroy_eq(port->qp_eq); - return ret; -} - -static int ehea_clean_all_portres(struct ehea_port *port) -{ - int ret = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) - ret |= ehea_clean_portres(port, &port->port_res[i]); - - ret |= ehea_destroy_eq(port->qp_eq); - - return ret; -} - -static void ehea_remove_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return; - - ehea_rem_mr(&adapter->mr); -} - -static int ehea_add_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return 0; - - return ehea_reg_kernel_mr(adapter, &adapter->mr); -} - -static int ehea_up(struct net_device *dev) -{ - int ret, i; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_UP) - return 0; - - ret = ehea_port_res_setup(port, port->num_def_qps); - if (ret) { - netdev_err(dev, "port_res_failed\n"); - goto out; - } - - /* Set default QP for this port */ - ret = ehea_configure_port(port); - if (ret) { - netdev_err(dev, "ehea_configure_port failed. ret:%d\n", ret); - goto out_clean_pr; - } - - ret = ehea_reg_interrupts(dev); - if (ret) { - netdev_err(dev, "reg_interrupts failed. ret:%d\n", ret); - goto out_clean_pr; - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_activate_qp(port->adapter, port->port_res[i].qp); - if (ret) { - netdev_err(dev, "activate_qp failed\n"); - goto out_free_irqs; - } - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_fill_port_res(&port->port_res[i]); - if (ret) { - netdev_err(dev, "out_free_irqs\n"); - goto out_free_irqs; - } - } - - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) { - ret = -EIO; - goto out_free_irqs; - } - - port->state = EHEA_PORT_UP; - - ret = 0; - goto out; - -out_free_irqs: - ehea_free_interrupts(dev); - -out_clean_pr: - ehea_clean_all_portres(port); -out: - if (ret) - netdev_info(dev, "Failed starting. ret=%i\n", ret); - - ehea_update_bcmc_registrations(); - ehea_update_firmware_handles(); - - return ret; -} - -static void port_napi_disable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_disable(&port->port_res[i].napi); -} - -static void port_napi_enable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_enable(&port->port_res[i].napi); -} - -static int ehea_open(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - mutex_lock(&port->port_lock); - - netif_info(port, ifup, dev, "enabling port\n"); - - netif_carrier_off(dev); - - ret = ehea_up(dev); - if (!ret) { - port_napi_enable(port); - netif_tx_start_all_queues(dev); - } - - mutex_unlock(&port->port_lock); - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); - - return ret; -} - -static int ehea_down(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_DOWN) - return 0; - - ehea_drop_multicast_list(dev); - ehea_allmulti(dev, 0); - ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - - ehea_free_interrupts(dev); - - port->state = EHEA_PORT_DOWN; - - ehea_update_bcmc_registrations(); - - ret = ehea_clean_all_portres(port); - if (ret) - netdev_info(dev, "Failed freeing resources. ret=%i\n", ret); - - ehea_update_firmware_handles(); - - return ret; -} - -static int ehea_stop(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - netif_info(port, ifdown, dev, "disabling port\n"); - - set_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - mutex_lock(&port->port_lock); - netif_tx_stop_all_queues(dev); - port_napi_disable(port); - ret = ehea_down(dev); - mutex_unlock(&port->port_lock); - clear_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - return ret; -} - -static void ehea_purge_sq(struct ehea_qp *orig_qp) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_swqe *swqe; - int wqe_index; - int i; - - for (i = 0; i < init_attr->act_nr_send_wqes; i++) { - swqe = ehea_get_swqe(&qp, &wqe_index); - swqe->tx_control |= EHEA_SWQE_PURGE; - } -} - -static void ehea_flush_sq(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int swqe_max = pr->sq_skba_size - 2 - pr->swqe_ll_count; - int ret; - - ret = wait_event_timeout(port->swqe_avail_wq, - atomic_read(&pr->swqe_avail) >= swqe_max, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("WARNING: sq not flushed completely\n"); - break; - } - } -} - -static int ehea_stop_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_modify_qp_cb0 *cb0; - int ret = -EIO; - int dret; - int i; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - /* Purge send queue */ - ehea_purge_sq(qp); - - /* Disable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg &= ~H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - /* deregister shared memory regions */ - dret = ehea_rem_smrs(pr); - if (dret) { - pr_err("unreg shared memory region failed\n"); - goto out; - } - } - - ret = 0; -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_update_rqs(struct ehea_qp *orig_qp, struct ehea_port_res *pr) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_rwqe *rwqe; - struct sk_buff **skba_rq2 = pr->rq2_skba.arr; - struct sk_buff **skba_rq3 = pr->rq3_skba.arr; - struct sk_buff *skb; - u32 lkey = pr->recv_mr.lkey; - - - int i; - int index; - - for (i = 0; i < init_attr->act_nr_rwqes_rq2 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 2); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq2[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } - - for (i = 0; i < init_attr->act_nr_rwqes_rq3 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 3); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq3[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } -} - -static int ehea_restart_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - int ret = 0; - int i; - - struct hcp_modify_qp_cb0 *cb0; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - return -ENOMEM; - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - ret = ehea_gen_smrs(pr); - if (ret) { - netdev_err(dev, "creation of shared memory regions failed\n"); - goto out; - } - - ehea_update_rqs(qp, pr); - - /* Enable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg |= H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - netdev_err(dev, "modify_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (2)\n"); - ret = -EFAULT; - goto out; - } - - /* refill entire queue */ - ehea_refill_rq1(pr, pr->rq1_skba.index, 0); - ehea_refill_rq2(pr, 0); - ehea_refill_rq3(pr, 0); - } -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_reset_port(struct work_struct *work) -{ - int ret; - struct ehea_port *port = - container_of(work, struct ehea_port, reset_task); - struct net_device *dev = port->netdev; - - mutex_lock(&dlpar_mem_lock); - port->resets++; - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - - port_napi_disable(port); - - ehea_down(dev); - - ret = ehea_up(dev); - if (ret) - goto out; - - ehea_set_multicast_list(dev); - - netif_info(port, timer, dev, "reset successful\n"); - - port_napi_enable(port); - - netif_tx_wake_all_queues(dev); -out: - mutex_unlock(&port->port_lock); - mutex_unlock(&dlpar_mem_lock); -} - -static void ehea_rereg_mrs(void) -{ - int ret, i; - struct ehea_adapter *adapter; - - pr_info("LPAR memory changed - re-initializing driver\n"); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Shutdown all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - struct net_device *dev; - - if (!port) - continue; - - dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - ehea_flush_sq(port); - ret = ehea_stop_qps(dev); - if (ret) { - mutex_unlock(&port->port_lock); - goto out; - } - port_napi_disable(port); - mutex_unlock(&port->port_lock); - } - reset_sq_restart_flag(port); - } - - /* Unregister old memory region */ - ret = ehea_rem_mr(&adapter->mr); - if (ret) { - pr_err("unregister MR failed - driver inoperable!\n"); - goto out; - } - } - - clear_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Register new memory region */ - ret = ehea_reg_kernel_mr(adapter, &adapter->mr); - if (ret) { - pr_err("register MR failed - driver inoperable!\n"); - goto out; - } - - /* Restart all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - - if (port) { - struct net_device *dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - ret = ehea_restart_qps(dev); - if (!ret) { - check_sqs(port); - port_napi_enable(port); - netif_tx_wake_all_queues(dev); - } else { - netdev_err(dev, "Unable to restart QPS\n"); - } - mutex_unlock(&port->port_lock); - } - } - } - } - pr_info("re-initializing driver complete\n"); -out: - return; -} - -static void ehea_tx_watchdog(struct net_device *dev, unsigned int txqueue) -{ - struct ehea_port *port = netdev_priv(dev); - - if (netif_carrier_ok(dev) && - !test_bit(__EHEA_STOP_XFER, &ehea_driver_flags)) - ehea_schedule_port_reset(port); -} - -static int ehea_sense_adapter_attr(struct ehea_adapter *adapter) -{ - struct hcp_query_ehea *cb; - u64 hret; - int ret; - - cb = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea(adapter->handle, cb); - - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_herr; - } - - adapter->max_mc_mac = cb->max_mc_mac - 1; - ret = 0; - -out_herr: - free_page((unsigned long)cb); -out: - return ret; -} - -static int ehea_get_jumboframe_status(struct ehea_port *port, int *jumbo) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - *jumbo = 0; - - /* (Try to) enable *jumbo frames */ - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } else { - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, cb4); - if (hret == H_SUCCESS) { - if (cb4->jumbo_frame) - *jumbo = 1; - else { - cb4->jumbo_frame = 1; - hret = ehea_h_modify_ehea_port(port->adapter-> - handle, - port-> - logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, - cb4); - if (hret == H_SUCCESS) - *jumbo = 1; - } - } else - ret = -EINVAL; - - free_page((unsigned long)cb4); - } -out: - return ret; -} - -static ssize_t log_port_id_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - return sprintf(buf, "%d", port->logical_port_id); -} - -static DEVICE_ATTR_RO(log_port_id); - -static void logical_port_release(struct device *dev) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - of_node_put(port->ofdev.dev.of_node); -} - -static struct device *ehea_register_port(struct ehea_port *port, - struct device_node *dn) -{ - int ret; - - port->ofdev.dev.of_node = of_node_get(dn); - port->ofdev.dev.parent = &port->adapter->ofdev->dev; - port->ofdev.dev.bus = &ibmebus_bus_type; - - dev_set_name(&port->ofdev.dev, "port%d", port_name_cnt++); - port->ofdev.dev.release = logical_port_release; - - ret = of_device_register(&port->ofdev); - if (ret) { - pr_err("failed to register device. ret=%d\n", ret); - put_device(&port->ofdev.dev); - goto out; - } - - ret = device_create_file(&port->ofdev.dev, &dev_attr_log_port_id); - if (ret) { - pr_err("failed to register attributes, ret=%d\n", ret); - goto out_unreg_of_dev; - } - - return &port->ofdev.dev; - -out_unreg_of_dev: - of_device_unregister(&port->ofdev); -out: - return NULL; -} - -static void ehea_unregister_port(struct ehea_port *port) -{ - device_remove_file(&port->ofdev.dev, &dev_attr_log_port_id); - of_device_unregister(&port->ofdev); -} - -static const struct net_device_ops ehea_netdev_ops = { - .ndo_open = ehea_open, - .ndo_stop = ehea_stop, - .ndo_start_xmit = ehea_start_xmit, - .ndo_get_stats64 = ehea_get_stats64, - .ndo_set_mac_address = ehea_set_mac_addr, - .ndo_validate_addr = eth_validate_addr, - .ndo_set_rx_mode = ehea_set_multicast_list, - .ndo_vlan_rx_add_vid = ehea_vlan_rx_add_vid, - .ndo_vlan_rx_kill_vid = ehea_vlan_rx_kill_vid, - .ndo_tx_timeout = ehea_tx_watchdog, -}; - -static struct ehea_port *ehea_setup_single_port(struct ehea_adapter *adapter, - u32 logical_port_id, - struct device_node *dn) -{ - int ret; - struct net_device *dev; - struct ehea_port *port; - struct device *port_dev; - int jumbo; - - /* allocate memory for the port structures */ - dev = alloc_etherdev_mq(sizeof(struct ehea_port), EHEA_MAX_PORT_RES); - - if (!dev) { - ret = -ENOMEM; - goto out_err; - } - - port = netdev_priv(dev); - - mutex_init(&port->port_lock); - port->state = EHEA_PORT_DOWN; - port->sig_comp_iv = sq_entries / 10; - - port->adapter = adapter; - port->netdev = dev; - port->logical_port_id = logical_port_id; - - port->msg_enable = netif_msg_init(msg_level, EHEA_MSG_DEFAULT); - - port->mc_list = kzalloc_obj(struct ehea_mc_list); - if (!port->mc_list) { - ret = -ENOMEM; - goto out_free_ethdev; - } - - INIT_LIST_HEAD(&port->mc_list->list); - - ret = ehea_sense_port_attr(port); - if (ret) - goto out_free_mc_list; - - netif_set_real_num_rx_queues(dev, port->num_def_qps); - netif_set_real_num_tx_queues(dev, port->num_def_qps); - - port_dev = ehea_register_port(port, dn); - if (!port_dev) - goto out_free_mc_list; - - SET_NETDEV_DEV(dev, port_dev); - - /* initialize net_device structure */ - eth_hw_addr_set(dev, (u8 *)&port->mac_addr); - - dev->netdev_ops = &ehea_netdev_ops; - ehea_set_ethtool_ops(dev); - - dev->hw_features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_IP_CSUM | NETIF_F_HW_VLAN_CTAG_TX; - dev->features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | - NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX | - NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_RXCSUM; - dev->vlan_features = NETIF_F_SG | NETIF_F_TSO | NETIF_F_HIGHDMA | - NETIF_F_IP_CSUM; - dev->watchdog_timeo = EHEA_WATCH_DOG_TIMEOUT; - - /* MTU range: 68 - 9022 */ - dev->min_mtu = ETH_MIN_MTU; - dev->max_mtu = EHEA_MAX_PACKET_SIZE; - - INIT_WORK(&port->reset_task, ehea_reset_port); - INIT_DELAYED_WORK(&port->stats_work, ehea_update_stats); - - init_waitqueue_head(&port->swqe_avail_wq); - init_waitqueue_head(&port->restart_wq); - - ret = register_netdev(dev); - if (ret) { - pr_err("register_netdev failed. ret=%d\n", ret); - goto out_unreg_port; - } - - ret = ehea_get_jumboframe_status(port, &jumbo); - if (ret) - netdev_err(dev, "failed determining jumbo frame status\n"); - - netdev_info(dev, "Jumbo frames are %sabled\n", - jumbo == 1 ? "en" : "dis"); - - adapter->active_ports++; - - return port; - -out_unreg_port: - ehea_unregister_port(port); - -out_free_mc_list: - kfree(port->mc_list); - -out_free_ethdev: - free_netdev(dev); - -out_err: - pr_err("setting up logical port with id=%d failed, ret=%d\n", - logical_port_id, ret); - return NULL; -} - -static void ehea_shutdown_single_port(struct ehea_port *port) -{ - struct ehea_adapter *adapter = port->adapter; - - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - unregister_netdev(port->netdev); - ehea_unregister_port(port); - kfree(port->mc_list); - free_netdev(port->netdev); - adapter->active_ports--; -} - -static int ehea_setup_ports(struct ehea_adapter *adapter) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - - const u32 *dn_log_port_id; - int i = 0; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (!dn_log_port_id) { - pr_err("bad device node: eth_dn name=%pOF\n", eth_dn); - continue; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - adapter->port[i] = ehea_setup_single_port(adapter, - *dn_log_port_id, - eth_dn); - if (adapter->port[i]) - netdev_info(adapter->port[i]->netdev, - "logical port id #%d\n", *dn_log_port_id); - else - ehea_remove_adapter_mr(adapter); - - i++; - } - return 0; -} - -static struct device_node *ehea_get_eth_dn(struct ehea_adapter *adapter, - u32 logical_port_id) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - const u32 *dn_log_port_id; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (dn_log_port_id) - if (*dn_log_port_id == logical_port_id) - return eth_dn; - } - - return NULL; -} - -static ssize_t probe_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - struct device_node *eth_dn = NULL; - int i; - - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "adding port with logical port id=%d failed: port already configured\n", - logical_port_id); - return -EINVAL; - } - - eth_dn = ehea_get_eth_dn(adapter, logical_port_id); - - if (!eth_dn) { - pr_info("no logical port with id %d found\n", logical_port_id); - return -EINVAL; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - port = ehea_setup_single_port(adapter, logical_port_id, eth_dn); - - of_node_put(eth_dn); - - if (port) { - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (!adapter->port[i]) { - adapter->port[i] = port; - break; - } - - netdev_info(port->netdev, "added: (logical port id=%d)\n", - logical_port_id); - } else { - ehea_remove_adapter_mr(adapter); - return -EIO; - } - - return (ssize_t) count; -} - -static ssize_t remove_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - int i; - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "removed: (logical port id=%d)\n", - logical_port_id); - - ehea_shutdown_single_port(port); - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i] == port) { - adapter->port[i] = NULL; - break; - } - } else { - pr_err("removing port with logical port id=%d failed. port not configured.\n", - logical_port_id); - return -EINVAL; - } - - ehea_remove_adapter_mr(adapter); - - return (ssize_t) count; -} - -static DEVICE_ATTR_WO(probe_port); -static DEVICE_ATTR_WO(remove_port); - -static int ehea_create_device_sysfs(struct platform_device *dev) -{ - int ret = device_create_file(&dev->dev, &dev_attr_probe_port); - if (ret) - goto out; - - ret = device_create_file(&dev->dev, &dev_attr_remove_port); - if (ret) - device_remove_file(&dev->dev, &dev_attr_probe_port); -out: - return ret; -} - -static void ehea_remove_device_sysfs(struct platform_device *dev) -{ - device_remove_file(&dev->dev, &dev_attr_probe_port); - device_remove_file(&dev->dev, &dev_attr_remove_port); -} - -static int ehea_reboot_notifier(struct notifier_block *nb, - unsigned long action, void *unused) -{ - if (action == SYS_RESTART) { - pr_info("Reboot: freeing all eHEA resources\n"); - ibmebus_unregister_driver(&ehea_driver); - } - return NOTIFY_DONE; -} - -static struct notifier_block ehea_reboot_nb = { - .notifier_call = ehea_reboot_notifier, -}; - -static int ehea_mem_notifier(struct notifier_block *nb, - unsigned long action, void *data) -{ - int ret = NOTIFY_BAD; - struct memory_notify *arg = data; - - mutex_lock(&dlpar_mem_lock); - - switch (action) { - case MEM_CANCEL_OFFLINE: - pr_info("memory offlining canceled"); - fallthrough; /* re-add canceled memory block */ - - case MEM_ONLINE: - pr_info("memory is going online"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_add_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - case MEM_GOING_OFFLINE: - pr_info("memory is going offline"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_rem_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - default: - break; - } - - ehea_update_firmware_handles(); - ret = NOTIFY_OK; - -out_unlock: - mutex_unlock(&dlpar_mem_lock); - return ret; -} - -static struct notifier_block ehea_mem_nb = { - .notifier_call = ehea_mem_notifier, -}; - -static void ehea_crash_handler(void) -{ - int i; - - if (ehea_fw_handles.arr) - for (i = 0; i < ehea_fw_handles.num_entries; i++) - ehea_h_free_resource(ehea_fw_handles.arr[i].adh, - ehea_fw_handles.arr[i].fwh, - FORCE_FREE); - - if (ehea_bcmc_regs.arr) - for (i = 0; i < ehea_bcmc_regs.num_entries; i++) - ehea_h_reg_dereg_bcmc(ehea_bcmc_regs.arr[i].adh, - ehea_bcmc_regs.arr[i].port_id, - ehea_bcmc_regs.arr[i].reg_type, - ehea_bcmc_regs.arr[i].macaddr, - 0, H_DEREG_BCMC); -} - -static atomic_t ehea_memory_hooks_registered; - -/* Register memory hooks on probe of first adapter */ -static int ehea_register_memory_hooks(void) -{ - int ret = 0; - - if (atomic_inc_return(&ehea_memory_hooks_registered) > 1) - return 0; - - ret = ehea_create_busmap(); - if (ret) { - pr_info("ehea_create_busmap failed\n"); - goto out; - } - - ret = register_reboot_notifier(&ehea_reboot_nb); - if (ret) { - pr_info("register_reboot_notifier failed\n"); - goto out; - } - - ret = register_memory_notifier(&ehea_mem_nb); - if (ret) { - pr_info("register_memory_notifier failed\n"); - goto out2; - } - - ret = crash_shutdown_register(ehea_crash_handler); - if (ret) { - pr_info("crash_shutdown_register failed\n"); - goto out3; - } - - return 0; - -out3: - unregister_memory_notifier(&ehea_mem_nb); -out2: - unregister_reboot_notifier(&ehea_reboot_nb); -out: - atomic_dec(&ehea_memory_hooks_registered); - return ret; -} - -static void ehea_unregister_memory_hooks(void) -{ - /* Only remove the hooks if we've registered them */ - if (atomic_read(&ehea_memory_hooks_registered) == 0) - return; - - unregister_reboot_notifier(&ehea_reboot_nb); - if (crash_shutdown_unregister(ehea_crash_handler)) - pr_info("failed unregistering crash handler\n"); - unregister_memory_notifier(&ehea_mem_nb); -} - -static int ehea_probe_adapter(struct platform_device *dev) -{ - struct ehea_adapter *adapter; - const u64 *adapter_handle; - int ret; - int i; - - ret = ehea_register_memory_hooks(); - if (ret) - return ret; - - if (!dev || !dev->dev.of_node) { - pr_err("Invalid ibmebus device probed\n"); - return -EINVAL; - } - - adapter = devm_kzalloc(&dev->dev, sizeof(*adapter), GFP_KERNEL); - if (!adapter) { - ret = -ENOMEM; - dev_err(&dev->dev, "no mem for ehea_adapter\n"); - goto out; - } - - list_add(&adapter->list, &adapter_list); - - adapter->ofdev = dev; - - adapter_handle = of_get_property(dev->dev.of_node, "ibm,hea-handle", - NULL); - if (adapter_handle) - adapter->handle = *adapter_handle; - - if (!adapter->handle) { - dev_err(&dev->dev, "failed getting handle for adapter" - " '%pOF'\n", dev->dev.of_node); - ret = -ENODEV; - goto out_free_ad; - } - - adapter->pd = EHEA_PD_ID; - - platform_set_drvdata(dev, adapter); - - - /* initialize adapter and ports */ - /* get adapter properties */ - ret = ehea_sense_adapter_attr(adapter); - if (ret) { - dev_err(&dev->dev, "sense_adapter_attr failed: %d\n", ret); - goto out_free_ad; - } - - adapter->neq = ehea_create_eq(adapter, - EHEA_NEQ, EHEA_MAX_ENTRIES_EQ, 1); - if (!adapter->neq) { - ret = -EIO; - dev_err(&dev->dev, "NEQ creation failed\n"); - goto out_free_ad; - } - - tasklet_setup(&adapter->neq_tasklet, ehea_neq_tasklet); - - ret = ehea_create_device_sysfs(dev); - if (ret) - goto out_kill_eq; - - ret = ehea_setup_ports(adapter); - if (ret) { - dev_err(&dev->dev, "setup_ports failed\n"); - goto out_rem_dev_sysfs; - } - - ret = ibmebus_request_irq(adapter->neq->attr.ist1, - ehea_interrupt_neq, 0, - "ehea_neq", adapter); - if (ret) { - dev_err(&dev->dev, "requesting NEQ IRQ failed\n"); - goto out_shutdown_ports; - } - - /* Handle any events that might be pending. */ - tasklet_hi_schedule(&adapter->neq_tasklet); - - ret = 0; - goto out; - -out_shutdown_ports: - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - -out_rem_dev_sysfs: - ehea_remove_device_sysfs(dev); - -out_kill_eq: - ehea_destroy_eq(adapter->neq); - -out_free_ad: - list_del(&adapter->list); - -out: - ehea_update_firmware_handles(); - - return ret; -} - -static void ehea_remove(struct platform_device *dev) -{ - struct ehea_adapter *adapter = platform_get_drvdata(dev); - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - - ehea_remove_device_sysfs(dev); - - ibmebus_free_irq(adapter->neq->attr.ist1, adapter); - tasklet_kill(&adapter->neq_tasklet); - - ehea_destroy_eq(adapter->neq); - ehea_remove_adapter_mr(adapter); - list_del(&adapter->list); - - ehea_update_firmware_handles(); -} - -static int check_module_parm(void) -{ - int ret = 0; - - if ((rq1_entries < EHEA_MIN_ENTRIES_QP) || - (rq1_entries > EHEA_MAX_ENTRIES_RQ1)) { - pr_info("Bad parameter: rq1_entries\n"); - ret = -EINVAL; - } - if ((rq2_entries < EHEA_MIN_ENTRIES_QP) || - (rq2_entries > EHEA_MAX_ENTRIES_RQ2)) { - pr_info("Bad parameter: rq2_entries\n"); - ret = -EINVAL; - } - if ((rq3_entries < EHEA_MIN_ENTRIES_QP) || - (rq3_entries > EHEA_MAX_ENTRIES_RQ3)) { - pr_info("Bad parameter: rq3_entries\n"); - ret = -EINVAL; - } - if ((sq_entries < EHEA_MIN_ENTRIES_QP) || - (sq_entries > EHEA_MAX_ENTRIES_SQ)) { - pr_info("Bad parameter: sq_entries\n"); - ret = -EINVAL; - } - - return ret; -} - -static ssize_t capabilities_show(struct device_driver *drv, char *buf) -{ - return sprintf(buf, "%d", EHEA_CAPABILITIES); -} - -static DRIVER_ATTR_RO(capabilities); - -static int __init ehea_module_init(void) -{ - int ret; - - pr_info("IBM eHEA ethernet device driver (Release %s)\n", DRV_VERSION); - - memset(&ehea_fw_handles, 0, sizeof(ehea_fw_handles)); - memset(&ehea_bcmc_regs, 0, sizeof(ehea_bcmc_regs)); - - mutex_init(&ehea_fw_handles.lock); - spin_lock_init(&ehea_bcmc_regs.lock); - - ret = check_module_parm(); - if (ret) - goto out; - - ret = ibmebus_register_driver(&ehea_driver); - if (ret) { - pr_err("failed registering eHEA device driver on ebus\n"); - goto out; - } - - ret = driver_create_file(&ehea_driver.driver, - &driver_attr_capabilities); - if (ret) { - pr_err("failed to register capabilities attribute, ret=%d\n", - ret); - goto out2; - } - - return ret; - -out2: - ibmebus_unregister_driver(&ehea_driver); -out: - return ret; -} - -static void __exit ehea_module_exit(void) -{ - driver_remove_file(&ehea_driver.driver, &driver_attr_capabilities); - ibmebus_unregister_driver(&ehea_driver); - ehea_unregister_memory_hooks(); - kfree(ehea_fw_handles.arr); - kfree(ehea_bcmc_regs.arr); - ehea_destroy_busmap(); -} - -module_init(ehea_module_init); -module_exit(ehea_module_exit); diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c b/drivers/net/ethernet/ibm/ehea/ehea_phyp.c deleted file mode 100644 index e63716e139f5..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea_phyp.h" - - -static inline u16 get_order_of_qentries(u16 queue_entries) -{ - u8 ld = 1; /* logarithmus dualis */ - while (((1U << ld) - 1) < queue_entries) - ld++; - return ld - 1; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -static long ehea_plpar_hcall_norets(unsigned long opcode, - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7) -{ - long ret; - int i, sleep_msecs; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall_norets(opcode, arg1, arg2, arg3, arg4, - arg5, arg6, arg7); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - if (ret < H_SUCCESS) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - return ret; - } - - return H_BUSY; -} - -static long ehea_plpar_hcall9(unsigned long opcode, - unsigned long *outs, /* array of 9 outputs */ - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7, - unsigned long arg8, - unsigned long arg9) -{ - long ret; - int i, sleep_msecs; - u8 cb_cat; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall9(opcode, outs, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - cb_cat = EHEA_BMASK_GET(H_MEHEAPORT_CAT, arg2); - - if ((ret < H_SUCCESS) && !(((ret == H_AUTHORITY) - && (opcode == H_MODIFY_HEA_PORT)) - && (((cb_cat == H_PORT_CB4) && ((arg3 == H_PORT_CB4_JUMBO) - || (arg3 == H_PORT_CB4_SPEED))) || ((cb_cat == H_PORT_CB7) - && (arg3 == H_PORT_CB7_DUCQPN))))) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx arg8=%lx" - " arg9=%lx" - " out1=%lx out2=%lx out3=%lx out4=%lx" - " out5=%lx out6=%lx out7=%lx out8=%lx" - " out9=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9, - outs[0], outs[1], outs[2], outs[3], outs[4], - outs[5], outs[6], outs[7], outs[8]); - return ret; - } - - return H_BUSY; -} - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, void *cb_addr) -{ - return ehea_plpar_hcall_norets(H_QUERY_HEA_QP, - adapter_handle, /* R4 */ - qp_category, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0); -} - -/* input param R5 */ -#define H_ALL_RES_QP_EQPO EHEA_BMASK_IBM(9, 11) -#define H_ALL_RES_QP_QPP EHEA_BMASK_IBM(12, 12) -#define H_ALL_RES_QP_RQR EHEA_BMASK_IBM(13, 15) -#define H_ALL_RES_QP_EQEG EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_QP_LL_QP EHEA_BMASK_IBM(17, 17) -#define H_ALL_RES_QP_DMA128 EHEA_BMASK_IBM(19, 19) -#define H_ALL_RES_QP_HSM EHEA_BMASK_IBM(20, 21) -#define H_ALL_RES_QP_SIGT EHEA_BMASK_IBM(22, 23) -#define H_ALL_RES_QP_TENURE EHEA_BMASK_IBM(48, 55) -#define H_ALL_RES_QP_RES_TYP EHEA_BMASK_IBM(56, 63) - -/* input param R9 */ -#define H_ALL_RES_QP_TOKEN EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_PD EHEA_BMASK_IBM(32, 63) - -/* input param R10 */ -#define H_ALL_RES_QP_MAX_SWQE EHEA_BMASK_IBM(4, 7) -#define H_ALL_RES_QP_MAX_R1WQE EHEA_BMASK_IBM(12, 15) -#define H_ALL_RES_QP_MAX_R2WQE EHEA_BMASK_IBM(20, 23) -#define H_ALL_RES_QP_MAX_R3WQE EHEA_BMASK_IBM(28, 31) -/* Max Send Scatter Gather Elements */ -#define H_ALL_RES_QP_MAX_SSGE EHEA_BMASK_IBM(37, 39) -#define H_ALL_RES_QP_MAX_R1SGE EHEA_BMASK_IBM(45, 47) -/* Max Receive SG Elements RQ1 */ -#define H_ALL_RES_QP_MAX_R2SGE EHEA_BMASK_IBM(53, 55) -#define H_ALL_RES_QP_MAX_R3SGE EHEA_BMASK_IBM(61, 63) - -/* input param R11 */ -#define H_ALL_RES_QP_SWQE_IDL EHEA_BMASK_IBM(0, 7) -/* max swqe immediate data length */ -#define H_ALL_RES_QP_PORT_NUM EHEA_BMASK_IBM(48, 63) - -/* input param R12 */ -#define H_ALL_RES_QP_TH_RQ2 EHEA_BMASK_IBM(0, 15) -/* Threshold RQ2 */ -#define H_ALL_RES_QP_TH_RQ3 EHEA_BMASK_IBM(16, 31) -/* Threshold RQ3 */ - -/* output param R6 */ -#define H_ALL_RES_QP_ACT_SWQE EHEA_BMASK_IBM(0, 15) -#define H_ALL_RES_QP_ACT_R1WQE EHEA_BMASK_IBM(16, 31) -#define H_ALL_RES_QP_ACT_R2WQE EHEA_BMASK_IBM(32, 47) -#define H_ALL_RES_QP_ACT_R3WQE EHEA_BMASK_IBM(48, 63) - -/* output param, R7 */ -#define H_ALL_RES_QP_ACT_SSGE EHEA_BMASK_IBM(0, 7) -#define H_ALL_RES_QP_ACT_R1SGE EHEA_BMASK_IBM(8, 15) -#define H_ALL_RES_QP_ACT_R2SGE EHEA_BMASK_IBM(16, 23) -#define H_ALL_RES_QP_ACT_R3SGE EHEA_BMASK_IBM(24, 31) -#define H_ALL_RES_QP_ACT_SWQE_IDL EHEA_BMASK_IBM(32, 39) - -/* output param R8,R9 */ -#define H_ALL_RES_QP_SIZE_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_SIZE_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ3 EHEA_BMASK_IBM(32, 63) - -/* output param R11,R12 */ -#define H_ALL_RES_QP_LIOBN_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_LIOBN_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ3 EHEA_BMASK_IBM(32, 63) - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, const u32 pd, - u64 *qp_handle, struct h_epas *h_epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - u64 allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_QP_EQPO, init_attr->low_lat_rq1 ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_QPP, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_RQR, 6) /* rq1 & rq2 & rq3 */ - | EHEA_BMASK_SET(H_ALL_RES_QP_EQEG, 0) /* EQE gen. disabled */ - | EHEA_BMASK_SET(H_ALL_RES_QP_LL_QP, init_attr->low_lat_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_DMA128, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_HSM, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_SIGT, init_attr->signalingtype) - | EHEA_BMASK_SET(H_ALL_RES_QP_RES_TYP, H_ALL_RES_TYPE_QP); - - u64 r9_reg = EHEA_BMASK_SET(H_ALL_RES_QP_PD, pd) - | EHEA_BMASK_SET(H_ALL_RES_QP_TOKEN, init_attr->qp_token); - - u64 max_r10_reg = - EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SWQE, - get_order_of_qentries(init_attr->max_nr_send_wqes)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq1)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq2)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq3)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SSGE, init_attr->wqe_size_enc_sq) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1SGE, - init_attr->wqe_size_enc_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2SGE, - init_attr->wqe_size_enc_rq2) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3SGE, - init_attr->wqe_size_enc_rq3); - - u64 r11_in = - EHEA_BMASK_SET(H_ALL_RES_QP_SWQE_IDL, init_attr->swqe_imm_data_len) - | EHEA_BMASK_SET(H_ALL_RES_QP_PORT_NUM, init_attr->port_nr); - u64 threshold = - EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ2, init_attr->rq2_threshold) - | EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ3, init_attr->rq3_threshold); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - init_attr->send_cq_handle, /* R6 */ - init_attr->recv_cq_handle, /* R7 */ - init_attr->aff_eq_handle, /* R8 */ - r9_reg, /* R9 */ - max_r10_reg, /* R10 */ - r11_in, /* R11 */ - threshold); /* R12 */ - - *qp_handle = outs[0]; - init_attr->qp_nr = (u32)outs[1]; - - init_attr->act_nr_send_wqes = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_SWQE, outs[2]); - init_attr->act_nr_rwqes_rq1 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R1WQE, outs[2]); - init_attr->act_nr_rwqes_rq2 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R2WQE, outs[2]); - init_attr->act_nr_rwqes_rq3 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R3WQE, outs[2]); - - init_attr->act_wqe_size_enc_sq = init_attr->wqe_size_enc_sq; - init_attr->act_wqe_size_enc_rq1 = init_attr->wqe_size_enc_rq1; - init_attr->act_wqe_size_enc_rq2 = init_attr->wqe_size_enc_rq2; - init_attr->act_wqe_size_enc_rq3 = init_attr->wqe_size_enc_rq3; - - init_attr->nr_sq_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_SQ, outs[4]); - init_attr->nr_rq1_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ1, outs[4]); - init_attr->nr_rq2_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ2, outs[5]); - init_attr->nr_rq3_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ3, outs[5]); - - init_attr->liobn_sq = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_SQ, outs[7]); - init_attr->liobn_rq1 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ1, outs[7]); - init_attr->liobn_rq2 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ2, outs[8]); - init_attr->liobn_rq3 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ3, outs[8]); - - if (!hret) - hcp_epas_ctor(h_epas, outs[6], outs[6]); - - return hret; -} - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - H_ALL_RES_TYPE_CQ, /* R5 */ - cq_attr->eq_handle, /* R6 */ - cq_attr->cq_token, /* R7 */ - cq_attr->max_nr_of_cqes, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *cq_handle = outs[0]; - cq_attr->act_nr_of_cqes = outs[3]; - cq_attr->nr_pages = outs[4]; - - if (!hret) - hcp_epas_ctor(epas, outs[5], outs[6]); - - return hret; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -/* input param R5 */ -#define H_ALL_RES_EQ_NEQ EHEA_BMASK_IBM(0, 0) -#define H_ALL_RES_EQ_NON_NEQ_ISN EHEA_BMASK_IBM(6, 7) -#define H_ALL_RES_EQ_INH_EQE_GEN EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_EQ_RES_TYPE EHEA_BMASK_IBM(56, 63) -/* input param R6 */ -#define H_ALL_RES_EQ_MAX_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R6 */ -#define H_ALL_RES_EQ_LIOBN EHEA_BMASK_IBM(32, 63) - -/* output param R7 */ -#define H_ALL_RES_EQ_ACT_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R8 */ -#define H_ALL_RES_EQ_ACT_PS EHEA_BMASK_IBM(32, 63) - -/* output param R9 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_C EHEA_BMASK_IBM(30, 31) -#define H_ALL_RES_EQ_ACT_EQ_IST_1 EHEA_BMASK_IBM(40, 63) - -/* output param R10 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_2 EHEA_BMASK_IBM(40, 63) - -/* output param R11 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_3 EHEA_BMASK_IBM(40, 63) - -/* output param R12 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_4 EHEA_BMASK_IBM(40, 63) - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle) -{ - u64 hret, allocate_controls; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - /* resource type */ - allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_EQ_RES_TYPE, H_ALL_RES_TYPE_EQ) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NEQ, eq_attr->type ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_EQ_INH_EQE_GEN, !eq_attr->eqe_gen) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NON_NEQ_ISN, 1); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - eq_attr->max_nr_of_eqes, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R10 */ - - *eq_handle = outs[0]; - eq_attr->act_nr_of_eqes = outs[3]; - eq_attr->nr_pages = outs[4]; - eq_attr->ist1 = outs[5]; - eq_attr->ist2 = outs[6]; - eq_attr->ist3 = outs[7]; - eq_attr->ist4 = outs[8]; - - return hret; -} - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, const u8 cat, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr, u64 *inv_attr_id, u64 *proc_mask, - u16 *out_swr, u16 *out_rwr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_MODIFY_HEA_QP, - outs, - adapter_handle, /* R4 */ - (u64) cat, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *inv_attr_id = outs[0]; - *out_swr = outs[3]; - *out_rwr = outs[4]; - *proc_mask = outs[5]; - - return hret; -} - -u64 ehea_h_register_rpage(const u64 adapter_handle, const u8 pagesize, - const u8 queue_type, const u64 resource_handle, - const u64 log_pageaddr, u64 count) -{ - u64 reg_control; - - reg_control = EHEA_BMASK_SET(H_REG_RPAGE_PAGE_SIZE, pagesize) - | EHEA_BMASK_SET(H_REG_RPAGE_QT, queue_type); - - return ehea_plpar_hcall_norets(H_REGISTER_HEA_RPAGES, - adapter_handle, /* R4 */ - reg_control, /* R5 */ - resource_handle, /* R6 */ - log_pageaddr, /* R7 */ - count, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_REGISTER_SMR, - outs, - adapter_handle , /* R4 */ - orig_mr_handle, /* R5 */ - vaddr_in, /* R6 */ - (((u64)access_ctrl) << 32ULL), /* R7 */ - pd, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - mr->handle = outs[0]; - mr->lkey = (u32)outs[2]; - - return hret; -} - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - return ehea_plpar_hcall9(H_DISABLE_AND_GET_HEA, - outs, - adapter_handle, /* R4 */ - H_DISABLE_GET_EHEA_WQE_P, /* R5 */ - qp_handle, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit) -{ - return ehea_plpar_hcall_norets(H_FREE_RESOURCE, - adapter_handle, /* R4 */ - res_handle, /* R5 */ - force_bit, - 0, 0, 0, 0); /* R7-R10 */ -} - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - 5, /* R5 */ - vaddr, /* R6 */ - length, /* R7 */ - (((u64) access_ctrl) << 32ULL), /* R8 */ - pd, /* R9 */ - 0, 0, 0); /* R10-R12 */ - - *mr_handle = outs[0]; - *lkey = (u32)outs[2]; - return hret; -} - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count) -{ - if ((count > 1) && (log_pageaddr & ~PAGE_MASK)) { - pr_err("not on pageboundary\n"); - return H_PARAMETER; - } - - return ehea_h_register_rpage(adapter_handle, pagesize, - queue_type, mr_handle, - log_pageaddr, count); -} - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr) -{ - u64 hret, cb_logaddr; - - cb_logaddr = __pa(cb_addr); - - hret = ehea_plpar_hcall_norets(H_QUERY_HEA, - adapter_handle, /* R4 */ - cb_logaddr, /* R5 */ - 0, 0, 0, 0, 0); /* R6-R10 */ -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_query_ehea), "hcp_query_ehea"); -#endif - return hret; -} - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - u64 port_info; - u64 cb_logaddr = __pa(cb_addr); - u64 arr_index = 0; - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); - - return ehea_plpar_hcall_norets(H_QUERY_HEA_PORT, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - u64 port_info; - u64 arr_index = 0; - u64 cb_logaddr = __pa(cb_addr); - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_ehea_port_cb0), "Before HCALL"); -#endif - return ehea_plpar_hcall9(H_MODIFY_HEA_PORT, - outs, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id) -{ - u64 r5_port_num, r6_reg_type, r7_mc_mac_addr, r8_vlan_id; - u64 mac_addr = mc_mac_addr >> 16; - - r5_port_num = EHEA_BMASK_SET(H_REGBCMC_PN, port_num); - r6_reg_type = EHEA_BMASK_SET(H_REGBCMC_REGTYPE, reg_type); - r7_mc_mac_addr = EHEA_BMASK_SET(H_REGBCMC_MACADDR, mac_addr); - r8_vlan_id = EHEA_BMASK_SET(H_REGBCMC_VLANID, vlan_id); - - return ehea_plpar_hcall_norets(hcall_id, - adapter_handle, /* R4 */ - r5_port_num, /* R5 */ - r6_reg_type, /* R6 */ - r7_mc_mac_addr, /* R7 */ - r8_vlan_id, /* R8 */ - 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask) -{ - return ehea_plpar_hcall_norets(H_RESET_EVENTS, - adapter_handle, /* R4 */ - neq_handle, /* R5 */ - event_mask, /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock) -{ - return ehea_plpar_hcall_norets(H_ERROR_DATA, - adapter_handle, /* R4 */ - ressource_handle, /* R5 */ - __pa(rblock), /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h b/drivers/net/ethernet/ibm/ehea/ehea_phyp.h deleted file mode 100644 index e8b56c103410..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h +++ /dev/null @@ -1,433 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_PHYP_H__ -#define __EHEA_PHYP_H__ - -#include -#include -#include "ehea.h" -#include "ehea_hw.h" - -/* Some abbreviations used here: - * - * hcp_* - structures, variables and functions releated to Hypervisor Calls - */ - -/* Number of pages which can be registered at once by H_REGISTER_HEA_RPAGES */ -#define EHEA_MAX_RPAGE 512 - -/* Notification Event Queue (NEQ) Entry bit masks */ -#define NEQE_EVENT_CODE EHEA_BMASK_IBM(2, 7) -#define NEQE_PORTNUM EHEA_BMASK_IBM(32, 47) -#define NEQE_PORT_UP EHEA_BMASK_IBM(16, 16) -#define NEQE_EXTSWITCH_PORT_UP EHEA_BMASK_IBM(17, 17) -#define NEQE_EXTSWITCH_PRIMARY EHEA_BMASK_IBM(18, 18) -#define NEQE_PLID EHEA_BMASK_IBM(16, 47) - -/* Notification Event Codes */ -#define EHEA_EC_PORTSTATE_CHG 0x30 -#define EHEA_EC_ADAPTER_MALFUNC 0x32 -#define EHEA_EC_PORT_MALFUNC 0x33 - -/* Notification Event Log Register (NELR) bit masks */ -#define NELR_PORT_MALFUNC EHEA_BMASK_IBM(61, 61) -#define NELR_ADAPTER_MALFUNC EHEA_BMASK_IBM(62, 62) -#define NELR_PORTSTATE_CHG EHEA_BMASK_IBM(63, 63) - -static inline void hcp_epas_ctor(struct h_epas *epas, u64 paddr_kernel, - u64 paddr_user) -{ - /* To support 64k pages we must round to 64k page boundary */ - epas->kernel.addr = ioremap((paddr_kernel & PAGE_MASK), PAGE_SIZE) + - (paddr_kernel & ~PAGE_MASK); - epas->user.addr = paddr_user; -} - -static inline void hcp_epas_dtor(struct h_epas *epas) -{ - if (epas->kernel.addr) - iounmap((void __iomem *)((u64)epas->kernel.addr & PAGE_MASK)); - - epas->user.addr = 0; - epas->kernel.addr = 0; -} - -struct hcp_modify_qp_cb0 { - u64 qp_ctl_reg; /* 00 */ - u32 max_swqe; /* 02 */ - u32 max_rwqe; /* 03 */ - u32 port_nb; /* 04 */ - u32 reserved0; /* 05 */ - u64 qp_aer; /* 06 */ - u64 qp_tenure; /* 08 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 0 Selection Mask Bits */ -#define H_QPCB0_ALL EHEA_BMASK_IBM(0, 5) -#define H_QPCB0_QP_CTL_REG EHEA_BMASK_IBM(0, 0) -#define H_QPCB0_MAX_SWQE EHEA_BMASK_IBM(1, 1) -#define H_QPCB0_MAX_RWQE EHEA_BMASK_IBM(2, 2) -#define H_QPCB0_PORT_NB EHEA_BMASK_IBM(3, 3) -#define H_QPCB0_QP_AER EHEA_BMASK_IBM(4, 4) -#define H_QPCB0_QP_TENURE EHEA_BMASK_IBM(5, 5) - -/* Queue Pair Control Register Status Bits */ -#define H_QP_CR_ENABLED 0x8000000000000000ULL /* QP enabled */ - /* QP States: */ -#define H_QP_CR_STATE_RESET 0x0000010000000000ULL /* Reset */ -#define H_QP_CR_STATE_INITIALIZED 0x0000020000000000ULL /* Initialized */ -#define H_QP_CR_STATE_RDY2RCV 0x0000030000000000ULL /* Ready to recv */ -#define H_QP_CR_STATE_RDY2SND 0x0000050000000000ULL /* Ready to send */ -#define H_QP_CR_STATE_ERROR 0x0000800000000000ULL /* Error */ -#define H_QP_CR_RES_STATE 0x0000007F00000000ULL /* Resultant state */ - -struct hcp_modify_qp_cb1 { - u32 qpn; /* 00 */ - u32 qp_asyn_ev_eq_nb; /* 01 */ - u64 sq_cq_handle; /* 02 */ - u64 rq_cq_handle; /* 04 */ - /* sgel = scatter gather element */ - u32 sgel_nb_sq; /* 06 */ - u32 sgel_nb_rq1; /* 07 */ - u32 sgel_nb_rq2; /* 08 */ - u32 sgel_nb_rq3; /* 09 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 1 Selection Mask Bits */ -#define H_QPCB1_ALL EHEA_BMASK_IBM(0, 7) -#define H_QPCB1_QPN EHEA_BMASK_IBM(0, 0) -#define H_QPCB1_ASYN_EV_EQ_NB EHEA_BMASK_IBM(1, 1) -#define H_QPCB1_SQ_CQ_HANDLE EHEA_BMASK_IBM(2, 2) -#define H_QPCB1_RQ_CQ_HANDLE EHEA_BMASK_IBM(3, 3) -#define H_QPCB1_SGEL_NB_SQ EHEA_BMASK_IBM(4, 4) -#define H_QPCB1_SGEL_NB_RQ1 EHEA_BMASK_IBM(5, 5) -#define H_QPCB1_SGEL_NB_RQ2 EHEA_BMASK_IBM(6, 6) -#define H_QPCB1_SGEL_NB_RQ3 EHEA_BMASK_IBM(7, 7) - -struct hcp_query_ehea { - u32 cur_num_qps; /* 00 */ - u32 cur_num_cqs; /* 01 */ - u32 cur_num_eqs; /* 02 */ - u32 cur_num_mrs; /* 03 */ - u32 auth_level; /* 04 */ - u32 max_num_qps; /* 05 */ - u32 max_num_cqs; /* 06 */ - u32 max_num_eqs; /* 07 */ - u32 max_num_mrs; /* 08 */ - u32 reserved0; /* 09 */ - u32 int_clock_freq; /* 10 */ - u32 max_num_pds; /* 11 */ - u32 max_num_addr_handles; /* 12 */ - u32 max_num_cqes; /* 13 */ - u32 max_num_wqes; /* 14 */ - u32 max_num_sgel_rq1wqe; /* 15 */ - u32 max_num_sgel_rq2wqe; /* 16 */ - u32 max_num_sgel_rq3wqe; /* 17 */ - u32 mr_page_size; /* 18 */ - u32 reserved1; /* 19 */ - u64 max_mr_size; /* 20 */ - u64 reserved2; /* 22 */ - u32 num_ports; /* 24 */ - u32 reserved3; /* 25 */ - u32 reserved4; /* 26 */ - u32 reserved5; /* 27 */ - u64 max_mc_mac; /* 28 */ - u64 ehea_cap; /* 30 */ - u32 max_isn_per_eq; /* 32 */ - u32 max_num_neq; /* 33 */ - u64 max_num_vlan_ids; /* 34 */ - u32 max_num_port_group; /* 36 */ - u32 max_num_phys_port; /* 37 */ - -}; - -/* Hcall Query/Modify Port Control Block defines */ -#define H_PORT_CB0 0 -#define H_PORT_CB1 1 -#define H_PORT_CB2 2 -#define H_PORT_CB3 3 -#define H_PORT_CB4 4 -#define H_PORT_CB5 5 -#define H_PORT_CB6 6 -#define H_PORT_CB7 7 - -struct hcp_ehea_port_cb0 { - u64 port_mac_addr; - u64 port_rc; - u64 reserved0; - u32 port_op_state; - u32 port_speed; - u32 ext_swport_op_state; - u32 neg_tpf_prpf; - u32 num_default_qps; - u32 reserved1; - u64 default_qpn_arr[16]; -}; - -/* Hcall Query/Modify Port Control Block 0 Selection Mask Bits */ -#define H_PORT_CB0_ALL EHEA_BMASK_IBM(0, 7) /* Set all bits */ -#define H_PORT_CB0_MAC EHEA_BMASK_IBM(0, 0) /* MAC address */ -#define H_PORT_CB0_PRC EHEA_BMASK_IBM(1, 1) /* Port Recv Control */ -#define H_PORT_CB0_DEFQPNARRAY EHEA_BMASK_IBM(7, 7) /* Default QPN Array */ - -/* Hcall Query Port: Returned port speed values */ -#define H_SPEED_10M_H 1 /* 10 Mbps, Half Duplex */ -#define H_SPEED_10M_F 2 /* 10 Mbps, Full Duplex */ -#define H_SPEED_100M_H 3 /* 100 Mbps, Half Duplex */ -#define H_SPEED_100M_F 4 /* 100 Mbps, Full Duplex */ -#define H_SPEED_1G_F 6 /* 1 Gbps, Full Duplex */ -#define H_SPEED_10G_F 8 /* 10 Gbps, Full Duplex */ - -/* Port Receive Control Status Bits */ -#define PXLY_RC_VALID EHEA_BMASK_IBM(49, 49) -#define PXLY_RC_VLAN_XTRACT EHEA_BMASK_IBM(50, 50) -#define PXLY_RC_TCP_6_TUPLE EHEA_BMASK_IBM(51, 51) -#define PXLY_RC_UDP_6_TUPLE EHEA_BMASK_IBM(52, 52) -#define PXLY_RC_TCP_3_TUPLE EHEA_BMASK_IBM(53, 53) -#define PXLY_RC_TCP_2_TUPLE EHEA_BMASK_IBM(54, 54) -#define PXLY_RC_LLC_SNAP EHEA_BMASK_IBM(55, 55) -#define PXLY_RC_JUMBO_FRAME EHEA_BMASK_IBM(56, 56) -#define PXLY_RC_FRAG_IP_PKT EHEA_BMASK_IBM(57, 57) -#define PXLY_RC_TCP_UDP_CHKSUM EHEA_BMASK_IBM(58, 58) -#define PXLY_RC_IP_CHKSUM EHEA_BMASK_IBM(59, 59) -#define PXLY_RC_MAC_FILTER EHEA_BMASK_IBM(60, 60) -#define PXLY_RC_UNTAG_FILTER EHEA_BMASK_IBM(61, 61) -#define PXLY_RC_VLAN_TAG_FILTER EHEA_BMASK_IBM(62, 63) - -#define PXLY_RC_VLAN_FILTER 2 -#define PXLY_RC_VLAN_PERM 0 - - -#define H_PORT_CB1_ALL 0x8000000000000000ULL - -struct hcp_ehea_port_cb1 { - u64 vlan_filter[64]; -}; - -#define H_PORT_CB2_ALL 0xFFE0000000000000ULL - -struct hcp_ehea_port_cb2 { - u64 rxo; - u64 rxucp; - u64 rxufd; - u64 rxuerr; - u64 rxftl; - u64 rxmcp; - u64 rxbcp; - u64 txo; - u64 txucp; - u64 txmcp; - u64 txbcp; -}; - -struct hcp_ehea_port_cb3 { - u64 vlan_bc_filter[64]; - u64 vlan_mc_filter[64]; - u64 vlan_un_filter[64]; - u64 port_mac_hash_array[64]; -}; - -#define H_PORT_CB4_ALL 0xF000000000000000ULL -#define H_PORT_CB4_JUMBO 0x1000000000000000ULL -#define H_PORT_CB4_SPEED 0x8000000000000000ULL - -struct hcp_ehea_port_cb4 { - u32 port_speed; - u32 pause_frame; - u32 ens_port_op_state; - u32 jumbo_frame; - u32 ens_port_wrap; -}; - -/* Hcall Query/Modify Port Control Block 5 Selection Mask Bits */ -#define H_PORT_CB5_RCU 0x0001000000000000ULL -#define PXS_RCU EHEA_BMASK_IBM(61, 63) - -struct hcp_ehea_port_cb5 { - u64 prc; /* 00 */ - u64 uaa; /* 01 */ - u64 macvc; /* 02 */ - u64 xpcsc; /* 03 */ - u64 xpcsp; /* 04 */ - u64 pcsid; /* 05 */ - u64 xpcsst; /* 06 */ - u64 pthlb; /* 07 */ - u64 pthrb; /* 08 */ - u64 pqu; /* 09 */ - u64 pqd; /* 10 */ - u64 prt; /* 11 */ - u64 wsth; /* 12 */ - u64 rcb; /* 13 */ - u64 rcm; /* 14 */ - u64 rcu; /* 15 */ - u64 macc; /* 16 */ - u64 pc; /* 17 */ - u64 pst; /* 18 */ - u64 ducqpn; /* 19 */ - u64 mcqpn; /* 20 */ - u64 mma; /* 21 */ - u64 pmc0h; /* 22 */ - u64 pmc0l; /* 23 */ - u64 lbc; /* 24 */ -}; - -#define H_PORT_CB6_ALL 0xFFFFFE7FFFFF8000ULL - -struct hcp_ehea_port_cb6 { - u64 rxo; /* 00 */ - u64 rx64; /* 01 */ - u64 rx65; /* 02 */ - u64 rx128; /* 03 */ - u64 rx256; /* 04 */ - u64 rx512; /* 05 */ - u64 rx1024; /* 06 */ - u64 rxbfcs; /* 07 */ - u64 rxime; /* 08 */ - u64 rxrle; /* 09 */ - u64 rxorle; /* 10 */ - u64 rxftl; /* 11 */ - u64 rxjab; /* 12 */ - u64 rxse; /* 13 */ - u64 rxce; /* 14 */ - u64 rxrf; /* 15 */ - u64 rxfrag; /* 16 */ - u64 rxuoc; /* 17 */ - u64 rxcpf; /* 18 */ - u64 rxsb; /* 19 */ - u64 rxfd; /* 20 */ - u64 rxoerr; /* 21 */ - u64 rxaln; /* 22 */ - u64 ducqpn; /* 23 */ - u64 reserved0; /* 24 */ - u64 rxmcp; /* 25 */ - u64 rxbcp; /* 26 */ - u64 txmcp; /* 27 */ - u64 txbcp; /* 28 */ - u64 txo; /* 29 */ - u64 tx64; /* 30 */ - u64 tx65; /* 31 */ - u64 tx128; /* 32 */ - u64 tx256; /* 33 */ - u64 tx512; /* 34 */ - u64 tx1024; /* 35 */ - u64 txbfcs; /* 36 */ - u64 txcpf; /* 37 */ - u64 txlf; /* 38 */ - u64 txrf; /* 39 */ - u64 txime; /* 40 */ - u64 txsc; /* 41 */ - u64 txmc; /* 42 */ - u64 txsqe; /* 43 */ - u64 txdef; /* 44 */ - u64 txlcol; /* 45 */ - u64 txexcol; /* 46 */ - u64 txcse; /* 47 */ - u64 txbor; /* 48 */ -}; - -#define H_PORT_CB7_DUCQPN 0x8000000000000000ULL - -struct hcp_ehea_port_cb7 { - u64 def_uc_qpn; -}; - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, - const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, - const u8 cat, - const u64 qp_handle, - const u64 sel_mask, - void *cb_addr, - u64 *inv_attr_id, - u64 *proc_mask, u16 *out_swr, u16 *out_rwr); - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle); - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas); - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, - const u32 pd, - u64 *qp_handle, struct h_epas *h_epas); - -#define H_REG_RPAGE_PAGE_SIZE EHEA_BMASK_IBM(48, 55) -#define H_REG_RPAGE_QT EHEA_BMASK_IBM(62, 63) - -u64 ehea_h_register_rpage(const u64 adapter_handle, - const u8 pagesize, - const u8 queue_type, - const u64 resource_handle, - const u64 log_pageaddr, u64 count); - -#define H_DISABLE_GET_EHEA_WQE_P 1 -#define H_DISABLE_GET_SQ_WQE_P 2 -#define H_DISABLE_GET_RQC 3 - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle); - -#define FORCE_FREE 1 -#define NORMAL_FREE 0 - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit); - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey); - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count); - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr); - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr); - -/* output param R5 */ -#define H_MEHEAPORT_CAT EHEA_BMASK_IBM(40, 47) -#define H_MEHEAPORT_PN EHEA_BMASK_IBM(48, 63) - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -#define H_REGBCMC_PN EHEA_BMASK_IBM(48, 63) -#define H_REGBCMC_REGTYPE EHEA_BMASK_IBM(60, 63) -#define H_REGBCMC_MACADDR EHEA_BMASK_IBM(16, 63) -#define H_REGBCMC_VLANID EHEA_BMASK_IBM(52, 63) - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id); - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask); - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock); - -#endif /* __EHEA_PHYP_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c b/drivers/net/ethernet/ibm/ehea/ehea_qmr.c deleted file mode 100644 index 60629a0032b2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c +++ /dev/null @@ -1,999 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include "ehea.h" -#include "ehea_phyp.h" -#include "ehea_qmr.h" - -static struct ehea_bmap *ehea_bmap; - -static void *hw_qpageit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - - queue->current_q_offset += queue->pagesize; - if (queue->current_q_offset > queue->queue_length) { - queue->current_q_offset -= queue->pagesize; - retvalue = NULL; - } else if (((u64) retvalue) & (EHEA_PAGESIZE-1)) { - pr_err("not on pageboundary\n"); - retvalue = NULL; - } - return retvalue; -} - -static int hw_queue_ctor(struct hw_queue *queue, const u32 nr_of_pages, - const u32 pagesize, const u32 qe_size) -{ - int pages_per_kpage = PAGE_SIZE / pagesize; - int i, k; - - if ((pagesize > PAGE_SIZE) || (!pages_per_kpage)) { - pr_err("pagesize conflict! kernel pagesize=%d, ehea pagesize=%d\n", - (int)PAGE_SIZE, (int)pagesize); - return -EINVAL; - } - - queue->queue_length = nr_of_pages * pagesize; - queue->queue_pages = kmalloc_array(nr_of_pages, sizeof(void *), - GFP_KERNEL); - if (!queue->queue_pages) - return -ENOMEM; - - /* - * allocate pages for queue: - * outer loop allocates whole kernel pages (page aligned) and - * inner loop divides a kernel page into smaller hea queue pages - */ - i = 0; - while (i < nr_of_pages) { - u8 *kpage = (u8 *)get_zeroed_page(GFP_KERNEL); - if (!kpage) - goto out_nomem; - for (k = 0; k < pages_per_kpage && i < nr_of_pages; k++) { - (queue->queue_pages)[i] = (struct ehea_page *)kpage; - kpage += pagesize; - i++; - } - } - - queue->current_q_offset = 0; - queue->qe_size = qe_size; - queue->pagesize = pagesize; - queue->toggle_state = 1; - - return 0; -out_nomem: - for (i = 0; i < nr_of_pages; i += pages_per_kpage) { - if (!(queue->queue_pages)[i]) - break; - free_page((unsigned long)(queue->queue_pages)[i]); - } - return -ENOMEM; -} - -static void hw_queue_dtor(struct hw_queue *queue) -{ - int pages_per_kpage; - int i, nr_pages; - - if (!queue || !queue->queue_pages) - return; - - pages_per_kpage = PAGE_SIZE / queue->pagesize; - - nr_pages = queue->queue_length / queue->pagesize; - - for (i = 0; i < nr_pages; i += pages_per_kpage) - free_page((unsigned long)(queue->queue_pages)[i]); - - kfree(queue->queue_pages); -} - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, - int nr_of_cqe, u64 eq_handle, u32 cq_token) -{ - struct ehea_cq *cq; - u64 hret, rpage; - u32 counter; - int ret; - void *vpage; - - cq = kzalloc_obj(*cq); - if (!cq) - goto out_nomem; - - cq->attr.max_nr_of_cqes = nr_of_cqe; - cq->attr.cq_token = cq_token; - cq->attr.eq_handle = eq_handle; - - cq->adapter = adapter; - - hret = ehea_h_alloc_resource_cq(adapter->handle, &cq->attr, - &cq->fw_handle, &cq->epas); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_cq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&cq->hw_queue, cq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_cqe)); - if (ret) - goto out_freeres; - - for (counter = 0; counter < cq->attr.nr_pages; counter++) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, EHEA_CQ_REGISTER_ORIG, - cq->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_cq failed ehea_cq=%p hret=%llx counter=%i act_pages=%i\n", - cq, hret, counter, cq->attr.nr_pages); - goto out_kill_hwq; - } - - if (counter == (cq->attr.nr_pages - 1)) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - - if ((hret != H_SUCCESS) || (vpage)) { - pr_err("registration of pages not complete hret=%llx\n", - hret); - goto out_kill_hwq; - } - } else { - if (hret != H_PAGE_REGISTERED) { - pr_err("CQ: registration of page failed hret=%llx\n", - hret); - goto out_kill_hwq; - } - } - } - - hw_qeit_reset(&cq->hw_queue); - ehea_reset_cq_ep(cq); - ehea_reset_cq_n1(cq); - - return cq; - -out_kill_hwq: - hw_queue_dtor(&cq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, cq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(cq); - -out_nomem: - return NULL; -} - -static u64 ehea_destroy_cq_res(struct ehea_cq *cq, u64 force) -{ - u64 hret; - u64 adapter_handle = cq->adapter->handle; - - /* deregister all previous registered pages */ - hret = ehea_h_free_resource(adapter_handle, cq->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&cq->hw_queue); - kfree(cq); - - return hret; -} - -int ehea_destroy_cq(struct ehea_cq *cq) -{ - u64 hret, aer, aerr; - if (!cq) - return 0; - - hcp_epas_dtor(&cq->epas); - hret = ehea_destroy_cq_res(cq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(cq->adapter, cq->fw_handle, &aer, &aerr); - hret = ehea_destroy_cq_res(cq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy CQ failed\n"); - return -EIO; - } - - return 0; -} - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - const enum ehea_eq_type type, - const u32 max_nr_of_eqes, const u8 eqe_gen) -{ - int ret, i; - u64 hret, rpage; - void *vpage; - struct ehea_eq *eq; - - eq = kzalloc_obj(*eq); - if (!eq) - return NULL; - - eq->adapter = adapter; - eq->attr.type = type; - eq->attr.max_nr_of_eqes = max_nr_of_eqes; - eq->attr.eqe_gen = eqe_gen; - spin_lock_init(&eq->spinlock); - - hret = ehea_h_alloc_resource_eq(adapter->handle, - &eq->attr, &eq->fw_handle); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_eq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&eq->hw_queue, eq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_eqe)); - if (ret) { - pr_err("can't allocate eq pages\n"); - goto out_freeres; - } - - for (i = 0; i < eq->attr.nr_pages; i++) { - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - hret = H_RESOURCE; - goto out_kill_hwq; - } - - rpage = __pa(vpage); - - hret = ehea_h_register_rpage(adapter->handle, 0, - EHEA_EQ_REGISTER_ORIG, - eq->fw_handle, rpage, 1); - - if (i == (eq->attr.nr_pages - 1)) { - /* last page */ - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if ((hret != H_SUCCESS) || (vpage)) - goto out_kill_hwq; - - } else { - if (hret != H_PAGE_REGISTERED) - goto out_kill_hwq; - - } - } - - hw_qeit_reset(&eq->hw_queue); - return eq; - -out_kill_hwq: - hw_queue_dtor(&eq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, eq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(eq); - return NULL; -} - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq) -{ - struct ehea_eqe *eqe; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - eqe = hw_eqit_eq_get_inc_valid(&eq->hw_queue); - spin_unlock_irqrestore(&eq->spinlock, flags); - - return eqe; -} - -static u64 ehea_destroy_eq_res(struct ehea_eq *eq, u64 force) -{ - u64 hret; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - - hret = ehea_h_free_resource(eq->adapter->handle, eq->fw_handle, force); - spin_unlock_irqrestore(&eq->spinlock, flags); - - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&eq->hw_queue); - kfree(eq); - - return hret; -} - -int ehea_destroy_eq(struct ehea_eq *eq) -{ - u64 hret, aer, aerr; - if (!eq) - return 0; - - hcp_epas_dtor(&eq->epas); - - hret = ehea_destroy_eq_res(eq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(eq->adapter, eq->fw_handle, &aer, &aerr); - hret = ehea_destroy_eq_res(eq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy EQ failed\n"); - return -EIO; - } - - return 0; -} - -/* allocates memory for a queue and registers pages in phyp */ -static int ehea_qp_alloc_register(struct ehea_qp *qp, struct hw_queue *hw_queue, - int nr_pages, int wqe_size, int act_nr_sges, - struct ehea_adapter *adapter, int h_call_q_selector) -{ - u64 hret, rpage; - int ret, cnt; - void *vpage; - - ret = hw_queue_ctor(hw_queue, nr_pages, EHEA_PAGESIZE, wqe_size); - if (ret) - return ret; - - for (cnt = 0; cnt < nr_pages; cnt++) { - vpage = hw_qpageit_get_inc(hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, h_call_q_selector, - qp->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_qp failed\n"); - goto out_kill_hwq; - } - } - hw_qeit_reset(hw_queue); - return 0; - -out_kill_hwq: - hw_queue_dtor(hw_queue); - return -EIO; -} - -static inline u32 map_wqe_size(u8 wqe_enc_size) -{ - return 128 << wqe_enc_size; -} - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, - u32 pd, struct ehea_qp_init_attr *init_attr) -{ - int ret; - u64 hret; - struct ehea_qp *qp; - u32 wqe_size_in_bytes_sq, wqe_size_in_bytes_rq1; - u32 wqe_size_in_bytes_rq2, wqe_size_in_bytes_rq3; - - - qp = kzalloc_obj(*qp); - if (!qp) - return NULL; - - qp->adapter = adapter; - - hret = ehea_h_alloc_resource_qp(adapter->handle, init_attr, pd, - &qp->fw_handle, &qp->epas); - if (hret != H_SUCCESS) { - pr_err("ehea_h_alloc_resource_qp failed\n"); - goto out_freemem; - } - - wqe_size_in_bytes_sq = map_wqe_size(init_attr->act_wqe_size_enc_sq); - wqe_size_in_bytes_rq1 = map_wqe_size(init_attr->act_wqe_size_enc_rq1); - wqe_size_in_bytes_rq2 = map_wqe_size(init_attr->act_wqe_size_enc_rq2); - wqe_size_in_bytes_rq3 = map_wqe_size(init_attr->act_wqe_size_enc_rq3); - - ret = ehea_qp_alloc_register(qp, &qp->hw_squeue, init_attr->nr_sq_pages, - wqe_size_in_bytes_sq, - init_attr->act_wqe_size_enc_sq, adapter, - 0); - if (ret) { - pr_err("can't register for sq ret=%x\n", ret); - goto out_freeres; - } - - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue1, - init_attr->nr_rq1_pages, - wqe_size_in_bytes_rq1, - init_attr->act_wqe_size_enc_rq1, - adapter, 1); - if (ret) { - pr_err("can't register for rq1 ret=%x\n", ret); - goto out_kill_hwsq; - } - - if (init_attr->rq_count > 1) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue2, - init_attr->nr_rq2_pages, - wqe_size_in_bytes_rq2, - init_attr->act_wqe_size_enc_rq2, - adapter, 2); - if (ret) { - pr_err("can't register for rq2 ret=%x\n", ret); - goto out_kill_hwr1q; - } - } - - if (init_attr->rq_count > 2) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue3, - init_attr->nr_rq3_pages, - wqe_size_in_bytes_rq3, - init_attr->act_wqe_size_enc_rq3, - adapter, 3); - if (ret) { - pr_err("can't register for rq3 ret=%x\n", ret); - goto out_kill_hwr2q; - } - } - - qp->init_attr = *init_attr; - - return qp; - -out_kill_hwr2q: - hw_queue_dtor(&qp->hw_rqueue2); - -out_kill_hwr1q: - hw_queue_dtor(&qp->hw_rqueue1); - -out_kill_hwsq: - hw_queue_dtor(&qp->hw_squeue); - -out_freeres: - ehea_h_disable_and_get_hea(adapter->handle, qp->fw_handle); - ehea_h_free_resource(adapter->handle, qp->fw_handle, FORCE_FREE); - -out_freemem: - kfree(qp); - return NULL; -} - -static u64 ehea_destroy_qp_res(struct ehea_qp *qp, u64 force) -{ - u64 hret; - struct ehea_qp_init_attr *qp_attr = &qp->init_attr; - - - ehea_h_disable_and_get_hea(qp->adapter->handle, qp->fw_handle); - hret = ehea_h_free_resource(qp->adapter->handle, qp->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&qp->hw_squeue); - hw_queue_dtor(&qp->hw_rqueue1); - - if (qp_attr->rq_count > 1) - hw_queue_dtor(&qp->hw_rqueue2); - if (qp_attr->rq_count > 2) - hw_queue_dtor(&qp->hw_rqueue3); - kfree(qp); - - return hret; -} - -int ehea_destroy_qp(struct ehea_qp *qp) -{ - u64 hret, aer, aerr; - if (!qp) - return 0; - - hcp_epas_dtor(&qp->epas); - - hret = ehea_destroy_qp_res(qp, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(qp->adapter, qp->fw_handle, &aer, &aerr); - hret = ehea_destroy_qp_res(qp, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy QP failed\n"); - return -EIO; - } - - return 0; -} - -static inline int ehea_calc_index(unsigned long i, unsigned long s) -{ - return (i >> s) & EHEA_INDEX_MASK; -} - -static inline int ehea_init_top_bmap(struct ehea_top_bmap *ehea_top_bmap, - int dir) -{ - if (!ehea_top_bmap->dir[dir]) { - ehea_top_bmap->dir[dir] = - kzalloc_obj(struct ehea_dir_bmap); - if (!ehea_top_bmap->dir[dir]) - return -ENOMEM; - } - return 0; -} - -static inline int ehea_init_bmap(struct ehea_bmap *ehea_bmap, int top, int dir) -{ - if (!ehea_bmap->top[top]) { - ehea_bmap->top[top] = - kzalloc_obj(struct ehea_top_bmap); - if (!ehea_bmap->top[top]) - return -ENOMEM; - } - return ehea_init_top_bmap(ehea_bmap->top[top], dir); -} - -static DEFINE_MUTEX(ehea_busmap_mutex); -static unsigned long ehea_mr_len; - -#define EHEA_BUSMAP_ADD_SECT 1 -#define EHEA_BUSMAP_REM_SECT 0 - -static void ehea_rebuild_busmap(void) -{ - u64 vaddr = EHEA_BUSMAP_START; - int top, dir, idx; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - struct ehea_top_bmap *ehea_top; - int valid_dir_entries = 0; - - if (!ehea_bmap->top[top]) - continue; - ehea_top = ehea_bmap->top[top]; - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - struct ehea_dir_bmap *ehea_dir; - int valid_entries = 0; - - if (!ehea_top->dir[dir]) - continue; - valid_dir_entries++; - ehea_dir = ehea_top->dir[dir]; - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_dir->ent[idx]) - continue; - valid_entries++; - ehea_dir->ent[idx] = vaddr; - vaddr += EHEA_SECTSIZE; - } - if (!valid_entries) { - ehea_top->dir[dir] = NULL; - kfree(ehea_dir); - } - } - if (!valid_dir_entries) { - ehea_bmap->top[top] = NULL; - kfree(ehea_top); - } - } -} - -static int ehea_update_busmap(unsigned long pfn, unsigned long nr_pages, int add) -{ - unsigned long i, start_section, end_section; - - if (!nr_pages) - return 0; - - if (!ehea_bmap) { - ehea_bmap = kzalloc_obj(struct ehea_bmap); - if (!ehea_bmap) - return -ENOMEM; - } - - start_section = (pfn * PAGE_SIZE) / EHEA_SECTSIZE; - end_section = start_section + ((nr_pages * PAGE_SIZE) / EHEA_SECTSIZE); - /* Mark entries as valid or invalid only; address is assigned later */ - for (i = start_section; i < end_section; i++) { - u64 flag; - int top = ehea_calc_index(i, EHEA_TOP_INDEX_SHIFT); - int dir = ehea_calc_index(i, EHEA_DIR_INDEX_SHIFT); - int idx = i & EHEA_INDEX_MASK; - - if (add) { - int ret = ehea_init_bmap(ehea_bmap, top, dir); - if (ret) - return ret; - flag = 1; /* valid */ - ehea_mr_len += EHEA_SECTSIZE; - } else { - if (!ehea_bmap->top[top]) - continue; - if (!ehea_bmap->top[top]->dir[dir]) - continue; - flag = 0; /* invalid */ - ehea_mr_len -= EHEA_SECTSIZE; - } - - ehea_bmap->top[top]->dir[dir]->ent[idx] = flag; - } - ehea_rebuild_busmap(); /* Assign contiguous addresses for mr */ - return 0; -} - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_REM_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -static int ehea_is_hugepage(unsigned long pfn) -{ - if (pfn & EHEA_HUGEPAGE_PFN_MASK) - return 0; - - if (page_shift(pfn_to_page(pfn)) != EHEA_HUGEPAGESHIFT) - return 0; - - return 1; -} - -static int ehea_create_busmap_callback(unsigned long initial_pfn, - unsigned long total_nr_pages, void *arg) -{ - int ret; - unsigned long pfn, start_pfn, end_pfn, nr_pages; - - if ((total_nr_pages * PAGE_SIZE) < EHEA_HUGEPAGE_SIZE) - return ehea_update_busmap(initial_pfn, total_nr_pages, - EHEA_BUSMAP_ADD_SECT); - - /* Given chunk is >= 16GB -> check for hugepages */ - start_pfn = initial_pfn; - end_pfn = initial_pfn + total_nr_pages; - pfn = start_pfn; - - while (pfn < end_pfn) { - if (ehea_is_hugepage(pfn)) { - /* Add mem found in front of the hugepage */ - nr_pages = pfn - start_pfn; - ret = ehea_update_busmap(start_pfn, nr_pages, - EHEA_BUSMAP_ADD_SECT); - if (ret) - return ret; - - /* Skip the hugepage */ - pfn += (EHEA_HUGEPAGE_SIZE / PAGE_SIZE); - start_pfn = pfn; - } else - pfn += (EHEA_SECTSIZE / PAGE_SIZE); - } - - /* Add mem found behind the hugepage(s) */ - nr_pages = pfn - start_pfn; - return ehea_update_busmap(start_pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); -} - -int ehea_create_busmap(void) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ehea_mr_len = 0; - ret = walk_system_ram_range(0, 1ULL << MAX_PHYSMEM_BITS, NULL, - ehea_create_busmap_callback); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -void ehea_destroy_busmap(void) -{ - int top, dir; - mutex_lock(&ehea_busmap_mutex); - if (!ehea_bmap) - goto out_destroy; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - kfree(ehea_bmap->top[top]->dir[dir]); - } - - kfree(ehea_bmap->top[top]); - } - - kfree(ehea_bmap); - ehea_bmap = NULL; -out_destroy: - mutex_unlock(&ehea_busmap_mutex); -} - -u64 ehea_map_vaddr(void *caddr) -{ - int top, dir, idx; - unsigned long index, offset; - - if (!ehea_bmap) - return EHEA_INVAL_ADDR; - - index = __pa(caddr) >> SECTION_SIZE_BITS; - top = (index >> EHEA_TOP_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]) - return EHEA_INVAL_ADDR; - - dir = (index >> EHEA_DIR_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]) - return EHEA_INVAL_ADDR; - - idx = index & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - return EHEA_INVAL_ADDR; - - offset = (unsigned long)caddr & (EHEA_SECTSIZE - 1); - return ehea_bmap->top[top]->dir[dir]->ent[idx] | offset; -} - -static inline void *ehea_calc_sectbase(int top, int dir, int idx) -{ - unsigned long ret = idx; - ret |= dir << EHEA_DIR_INDEX_SHIFT; - ret |= top << EHEA_TOP_INDEX_SHIFT; - return __va(ret << SECTION_SIZE_BITS); -} - -static u64 ehea_reg_mr_section(int top, int dir, int idx, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - void *pg; - u64 j, m, hret; - unsigned long k = 0; - u64 pt_abs = __pa(pt); - - void *sectbase = ehea_calc_sectbase(top, dir, idx); - - for (j = 0; j < (EHEA_PAGES_PER_SECTION / EHEA_MAX_RPAGE); j++) { - - for (m = 0; m < EHEA_MAX_RPAGE; m++) { - pg = sectbase + ((k++) * EHEA_PAGESIZE); - pt[m] = __pa(pg); - } - hret = ehea_h_register_rpage_mr(adapter->handle, mr->handle, 0, - 0, pt_abs, EHEA_MAX_RPAGE); - - if ((hret != H_SUCCESS) && - (hret != H_PAGE_REGISTERED)) { - ehea_h_free_resource(adapter->handle, mr->handle, - FORCE_FREE); - pr_err("register_rpage_mr failed\n"); - return hret; - } - } - return hret; -} - -static u64 ehea_reg_mr_sections(int top, int dir, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int idx; - - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - continue; - - hret = ehea_reg_mr_section(top, dir, idx, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -static u64 ehea_reg_mr_dir_sections(int top, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int dir; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - hret = ehea_reg_mr_sections(top, dir, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr) -{ - int ret; - u64 *pt; - u64 hret; - u32 acc_ctrl = EHEA_MR_ACC_CTRL; - - unsigned long top; - - pt = (void *)get_zeroed_page(GFP_KERNEL); - if (!pt) { - pr_err("no mem\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_alloc_resource_mr(adapter->handle, EHEA_BUSMAP_START, - ehea_mr_len, acc_ctrl, adapter->pd, - &mr->handle, &mr->lkey); - - if (hret != H_SUCCESS) { - pr_err("alloc_resource_mr failed\n"); - ret = -EIO; - goto out; - } - - if (!ehea_bmap) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("no busmap available\n"); - ret = -EIO; - goto out; - } - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - hret = ehea_reg_mr_dir_sections(top, pt, adapter, mr); - if((hret != H_PAGE_REGISTERED) && (hret != H_SUCCESS)) - break; - } - - if (hret != H_SUCCESS) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("registering mr failed\n"); - ret = -EIO; - goto out; - } - - mr->vaddr = EHEA_BUSMAP_START; - mr->adapter = adapter; - ret = 0; -out: - free_page((unsigned long)pt); - return ret; -} - -int ehea_rem_mr(struct ehea_mr *mr) -{ - u64 hret; - - if (!mr || !mr->adapter) - return -EINVAL; - - hret = ehea_h_free_resource(mr->adapter->handle, mr->handle, - FORCE_FREE); - if (hret != H_SUCCESS) { - pr_err("destroy MR failed\n"); - return -EIO; - } - - return 0; -} - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr) -{ - u64 hret; - - hret = ehea_h_register_smr(adapter->handle, old_mr->handle, - old_mr->vaddr, EHEA_MR_ACC_CTRL, - adapter->pd, shared_mr); - if (hret != H_SUCCESS) - return -EIO; - - shared_mr->adapter = adapter; - - return 0; -} - -static void print_error_data(u64 *data) -{ - int length; - u64 type = EHEA_BMASK_GET(ERROR_DATA_TYPE, data[2]); - u64 resource = data[1]; - - length = EHEA_BMASK_GET(ERROR_DATA_LENGTH, data[0]); - - if (length > EHEA_PAGESIZE) - length = EHEA_PAGESIZE; - - if (type == EHEA_AER_RESTYPE_QP) - pr_err("QP (resource=%llX) state: AER=0x%llX, AERR=0x%llX, port=%llX\n", - resource, data[6], data[12], data[22]); - else if (type == EHEA_AER_RESTYPE_CQ) - pr_err("CQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - else if (type == EHEA_AER_RESTYPE_EQ) - pr_err("EQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - - ehea_dump(data, length, "error data"); -} - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr) -{ - unsigned long ret; - u64 *rblock; - u64 type = 0; - - rblock = (void *)get_zeroed_page(GFP_KERNEL); - if (!rblock) { - pr_err("Cannot allocate rblock memory\n"); - goto out; - } - - ret = ehea_h_error_data(adapter->handle, res_handle, rblock); - - if (ret == H_SUCCESS) { - type = EHEA_BMASK_GET(ERROR_DATA_TYPE, rblock[2]); - *aer = rblock[6]; - *aerr = rblock[12]; - print_error_data(rblock); - } else if (ret == H_R_STATE) { - pr_err("No error data available: %llX\n", res_handle); - } else - pr_err("Error data could not be fetched: %llX\n", res_handle); - - free_page((unsigned long)rblock); -out: - return type; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h b/drivers/net/ethernet/ibm/ehea/ehea_qmr.h deleted file mode 100644 index 7c7cccd820f7..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h +++ /dev/null @@ -1,390 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_QMR_H__ -#define __EHEA_QMR_H__ - -#include -#include "ehea.h" -#include "ehea_hw.h" - -/* - * page size of ehea hardware queues - */ - -#define EHEA_PAGESHIFT 12 -#define EHEA_PAGESIZE (1UL << EHEA_PAGESHIFT) -#define EHEA_SECTSIZE (1UL << 24) -#define EHEA_PAGES_PER_SECTION (EHEA_SECTSIZE >> EHEA_PAGESHIFT) -#define EHEA_HUGEPAGESHIFT 34 -#define EHEA_HUGEPAGE_SIZE (1UL << EHEA_HUGEPAGESHIFT) -#define EHEA_HUGEPAGE_PFN_MASK ((EHEA_HUGEPAGE_SIZE - 1) >> PAGE_SHIFT) - -#if ((1UL << SECTION_SIZE_BITS) < EHEA_SECTSIZE) -#error eHEA module cannot work if kernel sectionsize < ehea sectionsize -#endif - -/* Some abbreviations used here: - * - * WQE - Work Queue Entry - * SWQE - Send Work Queue Entry - * RWQE - Receive Work Queue Entry - * CQE - Completion Queue Entry - * EQE - Event Queue Entry - * MR - Memory Region - */ - -/* Use of WR_ID field for EHEA */ -#define EHEA_WR_ID_COUNT EHEA_BMASK_IBM(0, 19) -#define EHEA_WR_ID_TYPE EHEA_BMASK_IBM(20, 23) -#define EHEA_SWQE2_TYPE 0x1 -#define EHEA_SWQE3_TYPE 0x2 -#define EHEA_RWQE2_TYPE 0x3 -#define EHEA_RWQE3_TYPE 0x4 -#define EHEA_WR_ID_INDEX EHEA_BMASK_IBM(24, 47) -#define EHEA_WR_ID_REFILL EHEA_BMASK_IBM(48, 63) - -struct ehea_vsgentry { - u64 vaddr; - u32 l_key; - u32 len; -}; - -/* maximum number of sg entries allowed in a WQE */ -#define EHEA_MAX_WQE_SG_ENTRIES 252 -#define SWQE2_MAX_IMM (0xD0 - 0x30) -#define SWQE3_MAX_IMM 224 - -/* tx control flags for swqe */ -#define EHEA_SWQE_CRC 0x8000 -#define EHEA_SWQE_IP_CHECKSUM 0x4000 -#define EHEA_SWQE_TCP_CHECKSUM 0x2000 -#define EHEA_SWQE_TSO 0x1000 -#define EHEA_SWQE_SIGNALLED_COMPLETION 0x0800 -#define EHEA_SWQE_VLAN_INSERT 0x0400 -#define EHEA_SWQE_IMM_DATA_PRESENT 0x0200 -#define EHEA_SWQE_DESCRIPTORS_PRESENT 0x0100 -#define EHEA_SWQE_WRAP_CTL_REC 0x0080 -#define EHEA_SWQE_WRAP_CTL_FORCE 0x0040 -#define EHEA_SWQE_BIND 0x0020 -#define EHEA_SWQE_PURGE 0x0010 - -/* sizeof(struct ehea_swqe) less the union */ -#define SWQE_HEADER_SIZE 32 - -struct ehea_swqe { - u64 wr_id; - u16 tx_control; - u16 vlan_tag; - u8 reserved1; - u8 ip_start; - u8 ip_end; - u8 immediate_data_length; - u8 tcp_offset; - u8 reserved2; - u16 reserved2b; - u8 wrap_tag; - u8 descriptors; /* number of valid descriptors in WQE */ - u16 reserved3; - u16 reserved4; - u16 mss; - u32 reserved5; - union { - /* Send WQE Format 1 */ - struct { - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; - } no_immediate_data; - - /* Send WQE Format 2 */ - struct { - struct ehea_vsgentry sg_entry; - /* 0x30 */ - u8 immediate_data[SWQE2_MAX_IMM]; - /* 0xd0 */ - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES-1]; - } immdata_desc __packed; - - /* Send WQE Format 3 */ - struct { - u8 immediate_data[SWQE3_MAX_IMM]; - } immdata_nodesc; - } u; -}; - -struct ehea_rwqe { - u64 wr_id; /* work request ID */ - u8 reserved1[5]; - u8 data_segments; - u16 reserved2; - u64 reserved3; - u64 reserved4; - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; -}; - -#define EHEA_CQE_VLAN_TAG_XTRACT 0x0400 - -#define EHEA_CQE_TYPE_RQ 0x60 -#define EHEA_CQE_STAT_ERR_MASK 0x700F -#define EHEA_CQE_STAT_FAT_ERR_MASK 0xF -#define EHEA_CQE_BLIND_CKSUM 0x8000 -#define EHEA_CQE_STAT_ERR_TCP 0x4000 -#define EHEA_CQE_STAT_ERR_IP 0x2000 -#define EHEA_CQE_STAT_ERR_CRC 0x1000 - -/* Defines which bad send cqe stati lead to a port reset */ -#define EHEA_CQE_STAT_RESET_MASK 0x0002 - -struct ehea_cqe { - u64 wr_id; /* work request ID from WQE */ - u8 type; - u8 valid; - u16 status; - u16 reserved1; - u16 num_bytes_transfered; - u16 vlan_tag; - u16 inet_checksum_value; - u8 reserved2; - u8 header_length; - u16 reserved3; - u16 page_offset; - u16 wqe_count; - u32 qp_token; - u32 timestamp; - u32 reserved4; - u64 reserved5[3]; -}; - -#define EHEA_EQE_VALID EHEA_BMASK_IBM(0, 0) -#define EHEA_EQE_IS_CQE EHEA_BMASK_IBM(1, 1) -#define EHEA_EQE_IDENTIFIER EHEA_BMASK_IBM(2, 7) -#define EHEA_EQE_QP_CQ_NUMBER EHEA_BMASK_IBM(8, 31) -#define EHEA_EQE_QP_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_CQ_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_KEY EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_PORT_NUMBER EHEA_BMASK_IBM(56, 63) -#define EHEA_EQE_EQ_NUMBER EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_ID EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_MECH_NUMBER EHEA_BMASK_IBM(48, 55) -#define EHEA_EQE_SM_PORT_NUMBER EHEA_BMASK_IBM(56, 63) - -#define EHEA_AER_RESTYPE_QP 0x8 -#define EHEA_AER_RESTYPE_CQ 0x4 -#define EHEA_AER_RESTYPE_EQ 0x3 - -/* Defines which affiliated errors lead to a port reset */ -#define EHEA_AER_RESET_MASK 0xFFFFFFFFFEFFFFFFULL -#define EHEA_AERR_RESET_MASK 0xFFFFFFFFFFFFFFFFULL - -struct ehea_eqe { - u64 entry; -}; - -#define ERROR_DATA_LENGTH EHEA_BMASK_IBM(52, 63) -#define ERROR_DATA_TYPE EHEA_BMASK_IBM(0, 7) - -static inline void *hw_qeit_calc(struct hw_queue *queue, u64 q_offset) -{ - struct ehea_page *current_page; - - if (q_offset >= queue->queue_length) - q_offset -= queue->queue_length; - current_page = (queue->queue_pages)[q_offset >> EHEA_PAGESHIFT]; - return ¤t_page->entries[q_offset & (EHEA_PAGESIZE - 1)]; -} - -static inline void *hw_qeit_get(struct hw_queue *queue) -{ - return hw_qeit_calc(queue, queue->current_q_offset); -} - -static inline void hw_qeit_inc(struct hw_queue *queue) -{ - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset >= queue->queue_length) { - queue->current_q_offset = 0; - /* toggle the valid flag */ - queue->toggle_state = (~queue->toggle_state) & 1; - } -} - -static inline void *hw_qeit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - hw_qeit_inc(queue); - return retvalue; -} - -static inline void *hw_qeit_get_inc_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - u8 valid = retvalue->valid; - void *pref; - - if ((valid >> 7) == (queue->toggle_state & 1)) { - /* this is a good one */ - hw_qeit_inc(queue); - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - } else - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_get_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - void *pref; - u8 valid; - - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - prefetch(pref + 256); - valid = retvalue->valid; - if (!((valid >> 7) == (queue->toggle_state & 1))) - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_reset(struct hw_queue *queue) -{ - queue->current_q_offset = 0; - return hw_qeit_get(queue); -} - -static inline void *hw_qeit_eq_get_inc(struct hw_queue *queue) -{ - u64 last_entry_in_q = queue->queue_length - queue->qe_size; - void *retvalue; - - retvalue = hw_qeit_get(queue); - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset > last_entry_in_q) { - queue->current_q_offset = 0; - queue->toggle_state = (~queue->toggle_state) & 1; - } - return retvalue; -} - -static inline void *hw_eqit_eq_get_inc_valid(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - u32 qe = *(u8 *)retvalue; - if ((qe >> 7) == (queue->toggle_state & 1)) - hw_qeit_eq_get_inc(queue); - else - retvalue = NULL; - return retvalue; -} - -static inline struct ehea_rwqe *ehea_get_next_rwqe(struct ehea_qp *qp, - int rq_nr) -{ - struct hw_queue *queue; - - if (rq_nr == 1) - queue = &qp->hw_rqueue1; - else if (rq_nr == 2) - queue = &qp->hw_rqueue2; - else - queue = &qp->hw_rqueue3; - - return hw_qeit_get_inc(queue); -} - -static inline struct ehea_swqe *ehea_get_swqe(struct ehea_qp *my_qp, - int *wqe_index) -{ - struct hw_queue *queue = &my_qp->hw_squeue; - struct ehea_swqe *wqe_p; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_SQ); - wqe_p = hw_qeit_get_inc(&my_qp->hw_squeue); - - return wqe_p; -} - -static inline void ehea_post_swqe(struct ehea_qp *my_qp, struct ehea_swqe *swqe) -{ - iosync(); - ehea_update_sqa(my_qp, 1); -} - -static inline struct ehea_cqe *ehea_poll_rq1(struct ehea_qp *qp, int *wqe_index) -{ - struct hw_queue *queue = &qp->hw_rqueue1; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_RQ1); - return hw_qeit_get_valid(queue); -} - -static inline void ehea_inc_cq(struct ehea_cq *cq) -{ - hw_qeit_inc(&cq->hw_queue); -} - -static inline void ehea_inc_rq1(struct ehea_qp *qp) -{ - hw_qeit_inc(&qp->hw_rqueue1); -} - -static inline struct ehea_cqe *ehea_poll_cq(struct ehea_cq *my_cq) -{ - return hw_qeit_get_valid(&my_cq->hw_queue); -} - -#define EHEA_CQ_REGISTER_ORIG 0 -#define EHEA_EQ_REGISTER_ORIG 0 - -enum ehea_eq_type { - EHEA_EQ = 0, /* event queue */ - EHEA_NEQ /* notification event queue */ -}; - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - enum ehea_eq_type type, - const u32 length, const u8 eqe_gen); - -int ehea_destroy_eq(struct ehea_eq *eq); - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq); - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, int cqe, - u64 eq_handle, u32 cq_token); - -int ehea_destroy_cq(struct ehea_cq *cq); - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, u32 pd, - struct ehea_qp_init_attr *init_attr); - -int ehea_destroy_qp(struct ehea_qp *qp); - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr); - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr); - -int ehea_rem_mr(struct ehea_mr *mr); - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr); - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_create_busmap(void); -void ehea_destroy_busmap(void); -u64 ehea_map_vaddr(void *caddr); - -#endif /* __EHEA_QMR_H__ */ diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c index 74526002d52b..42027665f2a9 100644 --- a/drivers/net/ethernet/ibm/emac/mal.c +++ b/drivers/net/ethernet/ibm/emac/mal.c @@ -35,6 +35,7 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "reg(%08x, %08x)" NL, @@ -44,18 +45,20 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) if ((mal->tx_chan_mask & commac->tx_chan_mask) || (mal->rx_chan_mask & commac->rx_chan_mask)) { spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); printk(KERN_WARNING "mal%d: COMMAC channels conflict!\n", mal->index); return -EBUSY; } if (list_empty(&mal->list)) - napi_enable(&mal->napi); + napi_enable_locked(&mal->napi); mal->tx_chan_mask |= commac->tx_chan_mask; mal->rx_chan_mask |= commac->rx_chan_mask; list_add(&commac->list, &mal->list); spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); return 0; } @@ -64,7 +67,9 @@ void mal_unregister_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + bool disable_napi; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "unreg(%08x, %08x)" NL, @@ -73,10 +78,12 @@ void mal_unregister_commac(struct mal_instance *mal, mal->tx_chan_mask &= ~commac->tx_chan_mask; mal->rx_chan_mask &= ~commac->rx_chan_mask; list_del_init(&commac->list); - if (list_empty(&mal->list)) - napi_disable(&mal->napi); + disable_napi = list_empty(&mal->list); spin_unlock_irqrestore(&mal->lock, flags); + if (disable_napi) + napi_disable_locked(&mal->napi); + netdev_unlock(mal->napi.dev); } int mal_set_rcbs(struct mal_instance *mal, int channel, unsigned long size) diff --git a/drivers/net/ethernet/intel/Kconfig b/drivers/net/ethernet/intel/Kconfig index 288fa8ce53af..780f113986ea 100644 --- a/drivers/net/ethernet/intel/Kconfig +++ b/drivers/net/ethernet/intel/Kconfig @@ -398,4 +398,6 @@ config IGC_LEDS source "drivers/net/ethernet/intel/idpf/Kconfig" +source "drivers/net/ethernet/intel/ixd/Kconfig" + endif # NET_VENDOR_INTEL diff --git a/drivers/net/ethernet/intel/Makefile b/drivers/net/ethernet/intel/Makefile index 9a37dc76aef0..08b29f3b6801 100644 --- a/drivers/net/ethernet/intel/Makefile +++ b/drivers/net/ethernet/intel/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_IAVF) += iavf/ obj-$(CONFIG_FM10K) += fm10k/ obj-$(CONFIG_ICE) += ice/ obj-$(CONFIG_IDPF) += idpf/ +obj-$(CONFIG_IXD) += ixd/ diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h index 83e780919ac9..1b6a8fbaa648 100644 --- a/drivers/net/ethernet/intel/i40e/i40e.h +++ b/drivers/net/ethernet/intel/i40e/i40e.h @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/drivers/net/ethernet/intel/i40e/i40e_common.c b/drivers/net/ethernet/intel/i40e/i40e_common.c index 59f5c1e810eb..8dadfef2c09f 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_common.c +++ b/drivers/net/ethernet/intel/i40e/i40e_common.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2013 - 2021 Intel Corporation. */ -#include #include #include #include +#include #include #include "i40e_adminq_cmd.h" #include "i40e_devids.h" diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c index a04683004a56..0cd0e5597c90 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_main.c +++ b/drivers/net/ethernet/intel/i40e/i40e_main.c @@ -4864,16 +4864,10 @@ static void i40e_control_rx_q(struct i40e_pf *pf, int pf_q, bool enable) **/ int i40e_control_wait_rx_q(struct i40e_pf *pf, int pf_q, bool enable) { - int ret = 0; - i40e_control_rx_q(pf, pf_q, enable); /* wait for the change to finish */ - ret = i40e_pf_rxq_wait(pf, pf_q, enable); - if (ret) - return ret; - - return ret; + return i40e_pf_rxq_wait(pf, pf_q, enable); } /** diff --git a/drivers/net/ethernet/intel/i40e/i40e_prototype.h b/drivers/net/ethernet/intel/i40e/i40e_prototype.h index 26bb7bffe361..e3d57550090e 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_prototype.h +++ b/drivers/net/ethernet/intel/i40e/i40e_prototype.h @@ -5,7 +5,7 @@ #define _I40E_PROTOTYPE_H_ #include -#include +#include #include "i40e_debug.h" #include "i40e_type.h" diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c index 894f2d06d39d..ef5e657816f0 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c +++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c @@ -3129,7 +3129,7 @@ static int i40e_tso(struct i40e_tx_buffer *first, u8 *hdr_len, SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_offset = l4.hdr - skb->data; diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h index f558b45725c8..4e119c0502f3 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h @@ -4,7 +4,7 @@ #ifndef _I40E_VIRTCHNL_PF_H_ #define _I40E_VIRTCHNL_PF_H_ -#include +#include #include #include "i40e_type.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index 050f8241ef5e..dc31202b2a94 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +38,6 @@ #include #include "iavf_type.h" -#include #include "iavf_txrx.h" #include "iavf_fdir.h" #include "iavf_adv_rss.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf_common.c b/drivers/net/ethernet/intel/iavf/iavf_common.c index 614a886bca99..277193a97d91 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_common.c +++ b/drivers/net/ethernet/intel/iavf/iavf_common.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2013 - 2018 Intel Corporation. */ -#include +#include #include #include "iavf_type.h" #include "iavf_adminq.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf_prototype.h b/drivers/net/ethernet/intel/iavf/iavf_prototype.h index 7f9f9dbf959a..1b1f6ede3920 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_prototype.h +++ b/drivers/net/ethernet/intel/iavf/iavf_prototype.h @@ -4,9 +4,10 @@ #ifndef _IAVF_PROTOTYPE_H_ #define _IAVF_PROTOTYPE_H_ +#include + #include "iavf_type.h" #include "iavf_alloc.h" -#include /* Prototypes for shared code functions that are not in * the standard function pointer structures. These are diff --git a/drivers/net/ethernet/intel/iavf/iavf_txrx.c b/drivers/net/ethernet/intel/iavf/iavf_txrx.c index 363c42bf3dcf..c30abf17cf5d 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_txrx.c +++ b/drivers/net/ethernet/intel/iavf/iavf_txrx.c @@ -1774,7 +1774,7 @@ static int iavf_tso(struct iavf_tx_buffer *first, u8 *hdr_len, SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_offset = l4.hdr - skb->data; diff --git a/drivers/net/ethernet/intel/iavf/iavf_types.h b/drivers/net/ethernet/intel/iavf/iavf_types.h index a095855122bf..35d6d8fcca04 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_types.h +++ b/drivers/net/ethernet/intel/iavf/iavf_types.h @@ -4,9 +4,7 @@ #ifndef _IAVF_TYPES_H_ #define _IAVF_TYPES_H_ -#include "iavf_types.h" - -#include +#include #include /* structure used to queue PTP commands for processing */ diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index 22b7d8e6bd9e..8c2b63eef82b 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -1890,27 +1890,18 @@ static int ice_devlink_nvm_snapshot(struct devlink *devlink, */ for (i = 0; i < num_blks; i++) { u32 read_sz = min_t(u32, ICE_DEVLINK_READ_BLK_SIZE, left); - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - vfree(nvm_data); - return -EIO; - } + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; status = ice_read_flat_nvm(hw, i * ICE_DEVLINK_READ_BLK_SIZE, - &read_sz, tmp, read_shadow_ram); + &read_sz, tmp, read_shadow_ram, + &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - read_sz, status, hw->adminq.sq_last_status); + read_sz, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); vfree(nvm_data); return -EIO; } - ice_release_nvm(hw); tmp += read_sz; left -= read_sz; @@ -1943,6 +1934,7 @@ static int ice_devlink_nvm_read(struct devlink *devlink, struct netlink_ext_ack *extack, u64 offset, u32 size, u8 *data) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = devlink_priv(devlink); struct device *dev = ice_pf_to_dev(pf); struct ice_hw *hw = &pf->hw; @@ -1966,24 +1958,14 @@ static int ice_devlink_nvm_read(struct devlink *devlink, return -ERANGE; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - return -EIO; - } - status = ice_read_flat_nvm(hw, (u32)offset, &size, data, - read_shadow_ram); + read_shadow_ram, &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - size, status, hw->adminq.sq_last_status); + size, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); return -EIO; } - ice_release_nvm(hw); return 0; } diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index fc91b6665f90..db3c7015c56c 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/net/ethernet/intel/ice/ice_common.h b/drivers/net/ethernet/intel/ice/ice_common.h index 9f5344212195..d1d674ca644f 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.h +++ b/drivers/net/ethernet/intel/ice/ice_common.h @@ -5,13 +5,13 @@ #define _ICE_COMMON_H_ #include +#include #include "ice.h" #include "ice_type.h" #include "ice_nvm.h" #include "ice_flex_pipe.h" #include "ice_parser.h" -#include #include "ice_switch.h" #include "ice_fdir.h" diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 7eb380be7ed2..bf9a821c543b 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -853,6 +853,7 @@ static int ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, u8 *bytes) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = ice_netdev_to_pf(netdev); struct ice_hw *hw = &pf->hw; struct device *dev; @@ -869,24 +870,15 @@ ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, if (!buf) return -ENOMEM; - ret = ice_acquire_nvm(hw, ICE_RES_READ); + ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, + false, &read_aq_err); if (ret) { - dev_err(dev, "ice_acquire_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); + dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", + ret, libie_aq_str(read_aq_err)); goto out; } - ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, - false); - if (ret) { - dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); - goto release; - } - memcpy(bytes, buf, eeprom->len); -release: - ice_release_nvm(hw); out: kfree(buf); return ret; diff --git a/drivers/net/ethernet/intel/ice/ice_gnss.c b/drivers/net/ethernet/intel/ice/ice_gnss.c index 8fd954f1ebd6..7d21c3417b0b 100644 --- a/drivers/net/ethernet/intel/ice/ice_gnss.c +++ b/drivers/net/ethernet/intel/ice/ice_gnss.c @@ -2,6 +2,7 @@ /* Copyright (C) 2021-2022, Intel Corporation. */ #include "ice.h" +#include #include "ice_lib.h" /** @@ -124,7 +125,7 @@ static void ice_gnss_read(struct kthread_work *work) data_len = min_t(typeof(data_len), data_len, PAGE_SIZE); - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto requeue; @@ -151,7 +152,7 @@ static void ice_gnss_read(struct kthread_work *work) count, i); delay = ICE_GNSS_TIMER_DELAY_TIME; free_buf: - free_page((unsigned long)buf); + kfree(buf); requeue: kthread_queue_delayed_work(gnss->kworker, &gnss->read_work, delay); if (err) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 8cdc4fda89e9..9e08db376d3d 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -2871,6 +2871,9 @@ int ice_vsi_release(struct ice_vsi *vsi) return -ENODEV; pf = vsi->back; + if (ice_is_vsi_dflt_vsi(vsi)) + ice_clear_dflt_vsi(vsi); + if (test_bit(ICE_FLAG_RSS_ENA, pf->flags)) ice_rss_clean(vsi); diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c b/drivers/net/ethernet/intel/ice/ice_nvm.c index 7e187a804dfa..21f3b615dbbf 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.c +++ b/drivers/net/ethernet/intel/ice/ice_nvm.c @@ -53,17 +53,27 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, * @length: (in) number of bytes to read; (out) number of bytes actually read * @data: buffer to return data in (sized to fit the specified length) * @read_shadow_ram: if true, read from shadow RAM instead of NVM + * @read_aq_err: if non-NULL, receives the AQ error status of the failing read * * Reads a portion of the NVM, as a flat memory space. This function correctly * breaks read requests across Shadow RAM sectors and ensures that no single * read request exceeds the maximum 4KB read for a single AdminQ command. * + * FW caps the read lock at a maximum of 3000ms, so a read spanning multiple + * 4KB sectors cannot be done under a single lock without FW reclaiming it + * mid-read. The NVM lock is therefore acquired and released around each AQ + * read, so this function must be called without the lock held. + * + * Since ice_release_nvm() issues an AQ command that overwrites + * hw->adminq.sq_last_status, callers that need the failing read's AQ error + * must use @read_aq_err rather than inspecting sq_last_status afterwards. + * * Returns a status code on failure. Note that the data pointer may be * partially updated if some reads succeed before a failure. */ int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram) + bool read_shadow_ram, enum libie_aq_err *read_aq_err) { u32 inlen = *length; u32 bytes_read = 0; @@ -92,12 +102,30 @@ ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, last_cmd = !(bytes_read + read_size < inlen); + status = ice_acquire_nvm(hw, ICE_RES_READ); + if (status) { + ice_debug(hw, ICE_DBG_NVM, "Failed to acquire NVM lock, err %d aq_err %s\n", + status, libie_aq_str(hw->adminq.sq_last_status)); + break; + } + status = ice_aq_read_nvm(hw, ICE_AQC_NVM_START_POINT, offset, read_size, data + bytes_read, last_cmd, read_shadow_ram, NULL); - if (status) + if (status) { + /* Capture the read's AQ error before ice_release_nvm() + * issues its own AQ command and overwrites + * sq_last_status. + */ + if (read_aq_err) + *read_aq_err = hw->adminq.sq_last_status; + + ice_release_nvm(hw); break; + } + + ice_release_nvm(hw); bytes_read += read_size; offset += read_size; @@ -177,14 +205,19 @@ int ice_aq_erase_nvm(struct ice_hw *hw, u16 module_typeid, struct ice_sq_cd *cd) } /** - * ice_read_sr_word_aq - Reads Shadow RAM via AQ + * ice_read_sr_word - Reads Shadow RAM word * @hw: pointer to the HW structure * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) * @data: word read from the Shadow RAM * * Reads one 16 bit word from the Shadow RAM using ice_read_flat_nvm. + * + * The NVM lock is acquired and released internally by ice_read_flat_nvm() + * around the FW read, so this function must be called without the lock held. + * + * Return: zero on success, or a negative error code on failure. */ -static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) +int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) { u32 bytes = sizeof(u16); __le16 data_local; @@ -194,7 +227,7 @@ static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) * Shadow RAM sector restrictions necessary when reading from the NVM. */ status = ice_read_flat_nvm(hw, offset * sizeof(u16), &bytes, - (__force u8 *)&data_local, true); + (__force u8 *)&data_local, true, NULL); if (status) return status; @@ -330,13 +363,8 @@ ice_read_flash_module(struct ice_hw *hw, enum ice_bank_select bank, u16 module, return -EINVAL; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; - - status = ice_read_flat_nvm(hw, start + offset, &length, data, false); - - ice_release_nvm(hw); + status = ice_read_flat_nvm(hw, start + offset, &length, data, false, + NULL); return status; } @@ -418,27 +446,6 @@ ice_read_netlist_module(struct ice_hw *hw, enum ice_bank_select bank, u32 offset return status; } -/** - * ice_read_sr_word - Reads Shadow RAM word and acquire NVM if necessary - * @hw: pointer to the HW structure - * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) - * @data: word read from the Shadow RAM - * - * Reads one 16 bit word from the Shadow RAM using the ice_read_sr_word_aq. - */ -int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) -{ - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (!status) { - status = ice_read_sr_word_aq(hw, offset, data); - ice_release_nvm(hw); - } - - return status; -} - /** * ice_get_pfa_module_tlv - Reads sub module TLV from NVM PFA * @hw: pointer to hardware structure @@ -856,20 +863,18 @@ int ice_get_inactive_netlist_ver(struct ice_hw *hw, struct ice_netlist_info *net static int ice_discover_flash_size(struct ice_hw *hw) { u32 min_size = 0, max_size = ICE_AQC_NVM_MAX_OFFSET + 1; - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; + int status = 0; while ((max_size - min_size) > 1) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; u32 offset = (max_size + min_size) / 2; u32 len = 1; u8 data; - status = ice_read_flat_nvm(hw, offset, &len, &data, false); + status = ice_read_flat_nvm(hw, offset, &len, &data, false, + &read_aq_err); if (status == -EIO && - hw->adminq.sq_last_status == LIBIE_AQ_RC_EINVAL) { + read_aq_err == LIBIE_AQ_RC_EINVAL) { ice_debug(hw, ICE_DBG_NVM, "%s: New upper bound of %u bytes\n", __func__, offset); status = 0; @@ -880,7 +885,7 @@ static int ice_discover_flash_size(struct ice_hw *hw) min_size = offset; } else { /* an unexpected error occurred */ - goto err_read_flat_nvm; + return status; } } @@ -888,9 +893,6 @@ static int ice_discover_flash_size(struct ice_hw *hw) hw->flash.flash_size = max_size; -err_read_flat_nvm: - ice_release_nvm(hw); - return status; } diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.h b/drivers/net/ethernet/intel/ice/ice_nvm.h index 63cdc6bdac58..e1d1a11f5ca4 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.h +++ b/drivers/net/ethernet/intel/ice/ice_nvm.h @@ -19,7 +19,7 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, bool read_shadow_ram, struct ice_sq_cd *cd); int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram); + bool read_shadow_ram, enum libie_aq_err *read_aq_err); int ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len, u16 module_type); diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 8e5f97835954..3a41c711e751 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -4808,15 +4808,12 @@ static int ice_ptp_prep_phy_adj_ll_e810(struct ice_hw *hw, s32 adj) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); + + return err; } /** @@ -4837,8 +4834,12 @@ static int ice_ptp_prep_phy_adj_e810(struct ice_hw *hw, s32 adj) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL adj timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; @@ -4901,15 +4902,12 @@ static int ice_ptp_prep_phy_incval_ll_e810(struct ice_hw *hw, u64 incval) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); + + return err; } /** @@ -4927,8 +4925,12 @@ static int ice_ptp_prep_phy_incval_e810(struct ice_hw *hw, u64 incval) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL incval timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; low = lower_32_bits(incval); diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index c04c5856dad6..31303ab5be17 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1891,7 +1891,7 @@ int ice_tso(struct ice_tx_buf *first, struct ice_tx_offload_params *off) SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_start = (u8)(l4.hdr - skb->data); diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.h b/drivers/net/ethernet/intel/ice/ice_vf_lib.h index 7a9c75d1d07c..fa436b3b1eac 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib.h +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.h @@ -8,9 +8,9 @@ #include #include #include +#include #include #include -#include #include "ice_type.h" #include "ice_flow.h" #include "virt/fdir.h" diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.h b/drivers/net/ethernet/intel/ice/virt/virtchnl.h index 71bb456e2d71..d11789b3ae1f 100644 --- a/drivers/net/ethernet/intel/ice/virt/virtchnl.h +++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "ice_vf_lib.h" /* Restrict number of MAC Addr and VLAN that non-trusted VF can programmed */ diff --git a/drivers/net/ethernet/intel/idpf/Kconfig b/drivers/net/ethernet/intel/idpf/Kconfig index adab2154125b..586df3a4afe9 100644 --- a/drivers/net/ethernet/intel/idpf/Kconfig +++ b/drivers/net/ethernet/intel/idpf/Kconfig @@ -6,6 +6,7 @@ config IDPF depends on PCI_MSI depends on PTP_1588_CLOCK_OPTIONAL select DIMLIB + select LIBIE_CP select LIBETH_XDP help This driver supports Intel(R) Infrastructure Data Path Function diff --git a/drivers/net/ethernet/intel/idpf/Makefile b/drivers/net/ethernet/intel/idpf/Makefile index 651ddee942bd..4aaafa175ec3 100644 --- a/drivers/net/ethernet/intel/idpf/Makefile +++ b/drivers/net/ethernet/intel/idpf/Makefile @@ -6,8 +6,6 @@ obj-$(CONFIG_IDPF) += idpf.o idpf-y := \ - idpf_controlq.o \ - idpf_controlq_setup.o \ idpf_dev.o \ idpf_ethtool.o \ idpf_idc.o \ diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index ec1b75f039bb..470bc23c844c 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -23,10 +23,10 @@ struct idpf_rss_data; #include #include +#include +#include -#include "virtchnl2.h" #include "idpf_txrx.h" -#include "idpf_controlq.h" #define GETMAXVAL(num_bits) GENMASK((num_bits) - 1, 0) @@ -36,11 +36,10 @@ struct idpf_rss_data; #define IDPF_NUM_FILTERS_PER_MSG 20 #define IDPF_NUM_DFLT_MBX_Q 2 /* includes both TX and RX */ #define IDPF_DFLT_MBX_Q_LEN 64 -#define IDPF_DFLT_MBX_ID -1 /* maximum number of times to try before resetting mailbox */ #define IDPF_MB_MAX_ERR 20 #define IDPF_NUM_CHUNKS_PER_MSG(struct_sz, chunk_sz) \ - ((IDPF_CTLQ_MAX_BUF_LEN - (struct_sz)) / (chunk_sz)) + ((LIBIE_CTLQ_MAX_BUF_LEN - (struct_sz)) / (chunk_sz)) #define IDPF_WAIT_FOR_MARKER_TIMEO 500 #define IDPF_MAX_WAIT 500 @@ -201,8 +200,8 @@ struct idpf_vport_max_q { * @ptp_reg_init: PTP register initialization */ struct idpf_reg_ops { - void (*ctlq_reg_init)(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq); + void (*ctlq_reg_init)(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cctlq_info); int (*intr_reg_init)(struct idpf_vport *vport, struct idpf_q_vec_rsrc *rsrc); void (*mb_intr_reg_init)(struct idpf_adapter *adapter); @@ -605,8 +604,6 @@ struct idpf_vport_config { DECLARE_BITMAP(flags, IDPF_VPORT_CONFIG_FLAGS_NBITS); }; -struct idpf_vc_xn_manager; - #define idpf_for_each_vport(adapter, iter) \ for (struct idpf_vport **__##iter = &(adapter)->vports[0], \ *iter = (adapter)->max_vports ? *__##iter : NULL; \ @@ -624,7 +621,10 @@ struct idpf_vc_xn_manager; * @state: Init state machine * @flags: See enum idpf_flags * @reset_reg: See struct idpf_reset_reg - * @hw: Device access data + * @ctlq_ctx: controlq context + * @asq: Send control queue info + * @arq: Receive control queue info + * @xnm: Xn transaction manager * @num_avail_msix: Available number of MSIX vectors * @num_msix_entries: Number of entries in MSIX table * @msix_entries: MSIX table @@ -638,7 +638,6 @@ struct idpf_vc_xn_manager; * @avail_queues: Device given queue limits * @vports: Array to store vports created by the driver * @netdevs: Associated Vport netdevs - * @vport_params_reqd: Vport params requested * @vport_params_recvd: Vport params received * @vport_ids: Array of device given vport identifiers * @singleq_pt_lkup: Lookup table for singleq RX ptypes @@ -658,7 +657,6 @@ struct idpf_vc_xn_manager; * @stats_task: Periodic statistics retrieval task * @stats_wq: Workqueue for statistics task * @caps: Negotiated capabilities with device - * @vcxn_mngr: Virtchnl transaction manager * @dev_ops: See idpf_dev_ops * @cdev_info: IDC core device info pointer * @num_vfs: Number of allocated VFs through sysfs. PF does not directly talk @@ -682,7 +680,10 @@ struct idpf_adapter { enum idpf_state state; DECLARE_BITMAP(flags, IDPF_FLAGS_NBITS); struct idpf_reset_reg reset_reg; - struct idpf_hw hw; + struct libie_ctlq_ctx ctlq_ctx; + struct libie_ctlq_info *asq; + struct libie_ctlq_info *arq; + struct libie_ctlq_xn_manager *xnm; u16 num_avail_msix; u16 num_msix_entries; struct msix_entry *msix_entries; @@ -697,7 +698,6 @@ struct idpf_adapter { struct idpf_avail_queue_info avail_queues; struct idpf_vport **vports; struct net_device **netdevs; - struct virtchnl2_create_vport **vport_params_reqd; struct virtchnl2_create_vport **vport_params_recvd; u32 *vport_ids; @@ -720,7 +720,6 @@ struct idpf_adapter { struct delayed_work stats_task; struct workqueue_struct *stats_wq; struct virtchnl2_get_capabilities caps; - struct idpf_vc_xn_manager *vcxn_mngr; struct idpf_dev_ops dev_ops; struct iidc_rdma_core_dev_info *cdev_info; @@ -872,70 +871,6 @@ static inline u8 idpf_get_min_tx_pkt_len(struct idpf_adapter *adapter) return pkt_len ? pkt_len : IDPF_TX_MIN_PKT_LEN; } -/** - * idpf_get_mbx_reg_addr - Get BAR0 mailbox register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Return: BAR0 mailbox register address based on register offset. - */ -static inline void __iomem *idpf_get_mbx_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - return adapter->hw.mbx.vaddr + reg_offset; -} - -/** - * idpf_get_rstat_reg_addr - Get BAR0 rstat register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Return: BAR0 rstat register address based on register offset. - */ -static inline void __iomem *idpf_get_rstat_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - reg_offset -= adapter->dev_ops.static_reg_info[1].start; - - return adapter->hw.rstat.vaddr + reg_offset; -} - -/** - * idpf_get_reg_addr - Get BAR0 register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Based on the register offset, return the actual BAR0 register address - */ -static inline void __iomem *idpf_get_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - struct idpf_hw *hw = &adapter->hw; - - for (int i = 0; i < hw->num_lan_regs; i++) { - struct idpf_mmio_reg *region = &hw->lan_regs[i]; - - if (reg_offset >= region->addr_start && - reg_offset < (region->addr_start + region->addr_len)) { - /* Convert the offset so that it is relative to the - * start of the region. Then add the base address of - * the region to get the final address. - */ - reg_offset -= region->addr_start; - - return region->vaddr + reg_offset; - } - } - - /* It's impossible to hit this case with offsets from the CP. But if we - * do for any other reason, the kernel will panic on that register - * access. Might as well do it here to make it clear what's happening. - */ - BUG(); - - return NULL; -} - /** * idpf_is_reset_detected - check if we were reset at some point * @adapter: driver specific private structure @@ -944,11 +879,12 @@ static inline void __iomem *idpf_get_reg_addr(struct idpf_adapter *adapter, */ static inline bool idpf_is_reset_detected(struct idpf_adapter *adapter) { - if (!adapter->hw.arq) + struct libie_ctlq_info *arq = adapter->arq; + + if (!arq) return true; - return !(readl(idpf_get_mbx_reg_addr(adapter, adapter->hw.arq->reg.len)) & - adapter->hw.arq->reg.len_mask); + return !(readl(arq->reg.len) & arq->reg.len_mask); } /** @@ -1048,6 +984,7 @@ void idpf_vc_event_task(struct work_struct *work); void idpf_dev_ops_init(struct idpf_adapter *adapter); void idpf_vf_dev_ops_init(struct idpf_adapter *adapter); int idpf_intr_req(struct idpf_adapter *adapter); +void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter); void idpf_intr_rel(struct idpf_adapter *adapter); u16 idpf_get_max_tx_hdr_size(struct idpf_adapter *adapter); int idpf_initiate_soft_reset(struct idpf_vport *vport, diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.c b/drivers/net/ethernet/intel/idpf/idpf_controlq.c deleted file mode 100644 index d2dde43269e9..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.c +++ /dev/null @@ -1,621 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2023 Intel Corporation */ - -#include "idpf_controlq.h" - -/** - * idpf_ctlq_setup_regs - initialize control queue registers - * @cq: pointer to the specific control queue - * @q_create_info: structs containing info for each queue to be initialized - */ -static void idpf_ctlq_setup_regs(struct idpf_ctlq_info *cq, - struct idpf_ctlq_create_info *q_create_info) -{ - /* set control queue registers in our local struct */ - cq->reg.head = q_create_info->reg.head; - cq->reg.tail = q_create_info->reg.tail; - cq->reg.len = q_create_info->reg.len; - cq->reg.bah = q_create_info->reg.bah; - cq->reg.bal = q_create_info->reg.bal; - cq->reg.len_mask = q_create_info->reg.len_mask; - cq->reg.len_ena_mask = q_create_info->reg.len_ena_mask; - cq->reg.head_mask = q_create_info->reg.head_mask; -} - -/** - * idpf_ctlq_init_regs - Initialize control queue registers - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * @is_rxq: true if receive control queue, false otherwise - * - * Initialize registers. The caller is expected to have already initialized the - * descriptor ring memory and buffer memory - */ -static void idpf_ctlq_init_regs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - bool is_rxq) -{ - /* Update tail to post pre-allocated buffers for rx queues */ - if (is_rxq) - idpf_mbx_wr32(hw, cq->reg.tail, (u32)(cq->ring_size - 1)); - - /* For non-Mailbox control queues only TAIL need to be set */ - if (cq->q_id != -1) - return; - - /* Clear Head for both send or receive */ - idpf_mbx_wr32(hw, cq->reg.head, 0); - - /* set starting point */ - idpf_mbx_wr32(hw, cq->reg.bal, lower_32_bits(cq->desc_ring.pa)); - idpf_mbx_wr32(hw, cq->reg.bah, upper_32_bits(cq->desc_ring.pa)); - idpf_mbx_wr32(hw, cq->reg.len, (cq->ring_size | cq->reg.len_ena_mask)); -} - -/** - * idpf_ctlq_init_rxq_bufs - populate receive queue descriptors with buf - * @cq: pointer to the specific Control queue - * - * Record the address of the receive queue DMA buffers in the descriptors. - * The buffers must have been previously allocated. - */ -static void idpf_ctlq_init_rxq_bufs(struct idpf_ctlq_info *cq) -{ - int i; - - for (i = 0; i < cq->ring_size; i++) { - struct idpf_ctlq_desc *desc = IDPF_CTLQ_DESC(cq, i); - struct idpf_dma_mem *bi = cq->bi.rx_buff[i]; - - /* No buffer to post to descriptor, continue */ - if (!bi) - continue; - - desc->flags = - cpu_to_le16(IDPF_CTLQ_FLAG_BUF | IDPF_CTLQ_FLAG_RD); - desc->opcode = 0; - desc->datalen = cpu_to_le16(bi->size); - desc->ret_val = 0; - desc->v_opcode_dtype = 0; - desc->v_retval = 0; - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(bi->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(bi->pa)); - desc->params.indirect.param0 = 0; - desc->params.indirect.sw_cookie = 0; - desc->params.indirect.v_flags = 0; - } -} - -/** - * idpf_ctlq_shutdown - shutdown the CQ - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * The main shutdown routine for any controq queue - */ -static void idpf_ctlq_shutdown(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - spin_lock(&cq->cq_lock); - - /* free ring buffers and the ring itself */ - idpf_ctlq_dealloc_ring_res(hw, cq); - - /* Set ring_size to 0 to indicate uninitialized queue */ - cq->ring_size = 0; - - spin_unlock(&cq->cq_lock); -} - -/** - * idpf_ctlq_add - add one control queue - * @hw: pointer to hardware struct - * @qinfo: info for queue to be created - * @cq_out: (output) double pointer to control queue to be created - * - * Allocate and initialize a control queue and add it to the control queue list. - * The cq parameter will be allocated/initialized and passed back to the caller - * if no errors occur. - * - * Note: idpf_ctlq_init must be called prior to any calls to idpf_ctlq_add - */ -int idpf_ctlq_add(struct idpf_hw *hw, - struct idpf_ctlq_create_info *qinfo, - struct idpf_ctlq_info **cq_out) -{ - struct idpf_ctlq_info *cq; - bool is_rxq = false; - int err; - - cq = kzalloc_obj(*cq); - if (!cq) - return -ENOMEM; - - cq->cq_type = qinfo->type; - cq->q_id = qinfo->id; - cq->buf_size = qinfo->buf_size; - cq->ring_size = qinfo->len; - - cq->next_to_use = 0; - cq->next_to_clean = 0; - cq->next_to_post = cq->ring_size - 1; - - switch (qinfo->type) { - case IDPF_CTLQ_TYPE_MAILBOX_RX: - is_rxq = true; - fallthrough; - case IDPF_CTLQ_TYPE_MAILBOX_TX: - err = idpf_ctlq_alloc_ring_res(hw, cq); - break; - default: - err = -EBADR; - break; - } - - if (err) - goto init_free_q; - - if (is_rxq) { - idpf_ctlq_init_rxq_bufs(cq); - } else { - /* Allocate the array of msg pointers for TX queues */ - cq->bi.tx_msg = kzalloc_objs(struct idpf_ctlq_msg *, qinfo->len); - if (!cq->bi.tx_msg) { - err = -ENOMEM; - goto init_dealloc_q_mem; - } - } - - idpf_ctlq_setup_regs(cq, qinfo); - - idpf_ctlq_init_regs(hw, cq, is_rxq); - - spin_lock_init(&cq->cq_lock); - - list_add(&cq->cq_list, &hw->cq_list_head); - - *cq_out = cq; - - return 0; - -init_dealloc_q_mem: - /* free ring buffers and the ring itself */ - idpf_ctlq_dealloc_ring_res(hw, cq); -init_free_q: - kfree(cq); - - return err; -} - -/** - * idpf_ctlq_remove - deallocate and remove specified control queue - * @hw: pointer to hardware struct - * @cq: pointer to control queue to be removed - */ -void idpf_ctlq_remove(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - list_del(&cq->cq_list); - idpf_ctlq_shutdown(hw, cq); - kfree(cq); -} - -/** - * idpf_ctlq_init - main initialization routine for all control queues - * @hw: pointer to hardware struct - * @num_q: number of queues to initialize - * @q_info: array of structs containing info for each queue to be initialized - * - * This initializes any number and any type of control queues. This is an all - * or nothing routine; if one fails, all previously allocated queues will be - * destroyed. This must be called prior to using the individual add/remove - * APIs. - */ -int idpf_ctlq_init(struct idpf_hw *hw, u8 num_q, - struct idpf_ctlq_create_info *q_info) -{ - struct idpf_ctlq_info *cq, *tmp; - int err; - int i; - - INIT_LIST_HEAD(&hw->cq_list_head); - - for (i = 0; i < num_q; i++) { - struct idpf_ctlq_create_info *qinfo = q_info + i; - - err = idpf_ctlq_add(hw, qinfo, &cq); - if (err) - goto init_destroy_qs; - } - - return 0; - -init_destroy_qs: - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - idpf_ctlq_remove(hw, cq); - - return err; -} - -/** - * idpf_ctlq_deinit - destroy all control queues - * @hw: pointer to hw struct - */ -void idpf_ctlq_deinit(struct idpf_hw *hw) -{ - struct idpf_ctlq_info *cq, *tmp; - - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - idpf_ctlq_remove(hw, cq); -} - -/** - * idpf_ctlq_send - send command to Control Queue (CTQ) - * @hw: pointer to hw struct - * @cq: handle to control queue struct to send on - * @num_q_msg: number of messages to send on control queue - * @q_msg: pointer to array of queue messages to be sent - * - * The caller is expected to allocate DMAable buffers and pass them to the - * send routine via the q_msg struct / control queue specific data struct. - * The control queue will hold a reference to each send message until - * the completion for that message has been cleaned. - */ -int idpf_ctlq_send(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - u16 num_q_msg, struct idpf_ctlq_msg q_msg[]) -{ - struct idpf_ctlq_desc *desc; - int num_desc_avail; - int err = 0; - int i; - - spin_lock(&cq->cq_lock); - - /* Ensure there are enough descriptors to send all messages */ - num_desc_avail = IDPF_CTLQ_DESC_UNUSED(cq); - if (num_desc_avail == 0 || num_desc_avail < num_q_msg) { - err = -ENOSPC; - goto err_unlock; - } - - for (i = 0; i < num_q_msg; i++) { - struct idpf_ctlq_msg *msg = &q_msg[i]; - - desc = IDPF_CTLQ_DESC(cq, cq->next_to_use); - - desc->opcode = cpu_to_le16(msg->opcode); - desc->pfid_vfid = cpu_to_le16(msg->func_id); - - desc->v_opcode_dtype = cpu_to_le32(msg->cookie.mbx.chnl_opcode); - desc->v_retval = cpu_to_le32(msg->cookie.mbx.chnl_retval); - - desc->flags = cpu_to_le16((msg->host_id & IDPF_HOST_ID_MASK) << - IDPF_CTLQ_FLAG_HOST_ID_S); - if (msg->data_len) { - struct idpf_dma_mem *buff = msg->ctx.indirect.payload; - - desc->datalen |= cpu_to_le16(msg->data_len); - desc->flags |= cpu_to_le16(IDPF_CTLQ_FLAG_BUF); - desc->flags |= cpu_to_le16(IDPF_CTLQ_FLAG_RD); - - /* Update the address values in the desc with the pa - * value for respective buffer - */ - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(buff->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(buff->pa)); - - memcpy(&desc->params, msg->ctx.indirect.context, - IDPF_INDIRECT_CTX_SIZE); - } else { - memcpy(&desc->params, msg->ctx.direct, - IDPF_DIRECT_CTX_SIZE); - } - - /* Store buffer info */ - cq->bi.tx_msg[cq->next_to_use] = msg; - - (cq->next_to_use)++; - if (cq->next_to_use == cq->ring_size) - cq->next_to_use = 0; - } - - /* Force memory write to complete before letting hardware - * know that there are new descriptors to fetch. - */ - dma_wmb(); - - idpf_mbx_wr32(hw, cq->reg.tail, cq->next_to_use); - -err_unlock: - spin_unlock(&cq->cq_lock); - - return err; -} - -/** - * idpf_ctlq_clean_sq - reclaim send descriptors on HW write back for the - * requested queue - * @cq: pointer to the specific Control queue - * @clean_count: (input|output) number of descriptors to clean as input, and - * number of descriptors actually cleaned as output - * @msg_status: (output) pointer to msg pointer array to be populated; needs - * to be allocated by caller - * - * Returns an array of message pointers associated with the cleaned - * descriptors. The pointers are to the original ctlq_msgs sent on the cleaned - * descriptors. The status will be returned for each; any messages that failed - * to send will have a non-zero status. The caller is expected to free original - * ctlq_msgs and free or reuse the DMA buffers. - */ -int idpf_ctlq_clean_sq(struct idpf_ctlq_info *cq, u16 *clean_count, - struct idpf_ctlq_msg *msg_status[]) -{ - struct idpf_ctlq_desc *desc; - u16 i, num_to_clean; - u16 ntc, desc_err; - - if (*clean_count == 0) - return 0; - if (*clean_count > cq->ring_size) - return -EBADR; - - spin_lock(&cq->cq_lock); - - ntc = cq->next_to_clean; - - num_to_clean = *clean_count; - - for (i = 0; i < num_to_clean; i++) { - /* Fetch next descriptor and check if marked as done */ - desc = IDPF_CTLQ_DESC(cq, ntc); - if (!(le16_to_cpu(desc->flags) & IDPF_CTLQ_FLAG_DD)) - break; - - /* Ensure no other fields are read until DD flag is checked */ - dma_rmb(); - - /* strip off FW internal code */ - desc_err = le16_to_cpu(desc->ret_val) & 0xff; - - msg_status[i] = cq->bi.tx_msg[ntc]; - msg_status[i]->status = desc_err; - - cq->bi.tx_msg[ntc] = NULL; - - /* Zero out any stale data */ - memset(desc, 0, sizeof(*desc)); - - ntc++; - if (ntc == cq->ring_size) - ntc = 0; - } - - cq->next_to_clean = ntc; - - spin_unlock(&cq->cq_lock); - - /* Return number of descriptors actually cleaned */ - *clean_count = i; - - return 0; -} - -/** - * idpf_ctlq_post_rx_buffs - post buffers to descriptor ring - * @hw: pointer to hw struct - * @cq: pointer to control queue handle - * @buff_count: (input|output) input is number of buffers caller is trying to - * return; output is number of buffers that were not posted - * @buffs: array of pointers to dma mem structs to be given to hardware - * - * Caller uses this function to return DMA buffers to the descriptor ring after - * consuming them; buff_count will be the number of buffers. - * - * Note: this function needs to be called after a receive call even - * if there are no DMA buffers to be returned, i.e. buff_count = 0, - * buffs = NULL to support direct commands - */ -int idpf_ctlq_post_rx_buffs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - u16 *buff_count, struct idpf_dma_mem **buffs) -{ - struct idpf_ctlq_desc *desc; - u16 ntp = cq->next_to_post; - bool buffs_avail = false; - u16 tbp = ntp + 1; - int i = 0; - - if (*buff_count > cq->ring_size) - return -EBADR; - - if (*buff_count > 0) - buffs_avail = true; - - spin_lock(&cq->cq_lock); - - if (tbp >= cq->ring_size) - tbp = 0; - - if (tbp == cq->next_to_clean) - /* Nothing to do */ - goto post_buffs_out; - - /* Post buffers for as many as provided or up until the last one used */ - while (ntp != cq->next_to_clean) { - desc = IDPF_CTLQ_DESC(cq, ntp); - - if (cq->bi.rx_buff[ntp]) - goto fill_desc; - if (!buffs_avail) { - /* If the caller hasn't given us any buffers or - * there are none left, search the ring itself - * for an available buffer to move to this - * entry starting at the next entry in the ring - */ - tbp = ntp + 1; - - /* Wrap ring if necessary */ - if (tbp >= cq->ring_size) - tbp = 0; - - while (tbp != cq->next_to_clean) { - if (cq->bi.rx_buff[tbp]) { - cq->bi.rx_buff[ntp] = - cq->bi.rx_buff[tbp]; - cq->bi.rx_buff[tbp] = NULL; - - /* Found a buffer, no need to - * search anymore - */ - break; - } - - /* Wrap ring if necessary */ - tbp++; - if (tbp >= cq->ring_size) - tbp = 0; - } - - if (tbp == cq->next_to_clean) - goto post_buffs_out; - } else { - /* Give back pointer to DMA buffer */ - cq->bi.rx_buff[ntp] = buffs[i]; - i++; - - if (i >= *buff_count) - buffs_avail = false; - } - -fill_desc: - desc->flags = - cpu_to_le16(IDPF_CTLQ_FLAG_BUF | IDPF_CTLQ_FLAG_RD); - - /* Post buffers to descriptor */ - desc->datalen = cpu_to_le16(cq->bi.rx_buff[ntp]->size); - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(cq->bi.rx_buff[ntp]->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(cq->bi.rx_buff[ntp]->pa)); - - ntp++; - if (ntp == cq->ring_size) - ntp = 0; - } - -post_buffs_out: - /* Only update tail if buffers were actually posted */ - if (cq->next_to_post != ntp) { - if (ntp) - /* Update next_to_post to ntp - 1 since current ntp - * will not have a buffer - */ - cq->next_to_post = ntp - 1; - else - /* Wrap to end of end ring since current ntp is 0 */ - cq->next_to_post = cq->ring_size - 1; - - dma_wmb(); - - idpf_mbx_wr32(hw, cq->reg.tail, cq->next_to_post); - } - - spin_unlock(&cq->cq_lock); - - /* return the number of buffers that were not posted */ - *buff_count = *buff_count - i; - - return 0; -} - -/** - * idpf_ctlq_recv - receive control queue message call back - * @cq: pointer to control queue handle to receive on - * @num_q_msg: (input|output) input number of messages that should be received; - * output number of messages actually received - * @q_msg: (output) array of received control queue messages on this q; - * needs to be pre-allocated by caller for as many messages as requested - * - * Called by interrupt handler or polling mechanism. Caller is expected - * to free buffers - */ -int idpf_ctlq_recv(struct idpf_ctlq_info *cq, u16 *num_q_msg, - struct idpf_ctlq_msg *q_msg) -{ - u16 num_to_clean, ntc, flags; - struct idpf_ctlq_desc *desc; - int err = 0; - u16 i; - - /* take the lock before we start messing with the ring */ - spin_lock(&cq->cq_lock); - - ntc = cq->next_to_clean; - - num_to_clean = *num_q_msg; - - for (i = 0; i < num_to_clean; i++) { - /* Fetch next descriptor and check if marked as done */ - desc = IDPF_CTLQ_DESC(cq, ntc); - flags = le16_to_cpu(desc->flags); - - if (!(flags & IDPF_CTLQ_FLAG_DD)) - break; - - /* Ensure no other fields are read until DD flag is checked */ - dma_rmb(); - - q_msg[i].vmvf_type = (flags & - (IDPF_CTLQ_FLAG_FTYPE_VM | - IDPF_CTLQ_FLAG_FTYPE_PF)) >> - IDPF_CTLQ_FLAG_FTYPE_S; - - if (flags & IDPF_CTLQ_FLAG_ERR) - err = -EBADMSG; - - q_msg[i].cookie.mbx.chnl_opcode = - le32_to_cpu(desc->v_opcode_dtype); - q_msg[i].cookie.mbx.chnl_retval = - le32_to_cpu(desc->v_retval); - - q_msg[i].opcode = le16_to_cpu(desc->opcode); - q_msg[i].data_len = le16_to_cpu(desc->datalen); - q_msg[i].status = le16_to_cpu(desc->ret_val); - - if (desc->datalen) { - memcpy(q_msg[i].ctx.indirect.context, - &desc->params.indirect, IDPF_INDIRECT_CTX_SIZE); - - /* Assign pointer to dma buffer to ctlq_msg array - * to be given to upper layer - */ - q_msg[i].ctx.indirect.payload = cq->bi.rx_buff[ntc]; - - /* Zero out pointer to DMA buffer info; - * will be repopulated by post buffers API - */ - cq->bi.rx_buff[ntc] = NULL; - } else { - memcpy(q_msg[i].ctx.direct, desc->params.raw, - IDPF_DIRECT_CTX_SIZE); - } - - /* Zero out stale data in descriptor */ - memset(desc, 0, sizeof(struct idpf_ctlq_desc)); - - ntc++; - if (ntc == cq->ring_size) - ntc = 0; - } - - cq->next_to_clean = ntc; - - spin_unlock(&cq->cq_lock); - - *num_q_msg = i; - if (*num_q_msg == 0) - err = -ENOMSG; - - return err; -} diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.h b/drivers/net/ethernet/intel/idpf/idpf_controlq.h deleted file mode 100644 index de4ece40c2ff..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_CONTROLQ_H_ -#define _IDPF_CONTROLQ_H_ - -#include - -#include "idpf_controlq_api.h" - -/* Maximum buffer length for all control queue types */ -#define IDPF_CTLQ_MAX_BUF_LEN 4096 - -#define IDPF_CTLQ_DESC(R, i) \ - (&(((struct idpf_ctlq_desc *)((R)->desc_ring.va))[i])) - -#define IDPF_CTLQ_DESC_UNUSED(R) \ - ((u16)((((R)->next_to_clean > (R)->next_to_use) ? 0 : (R)->ring_size) + \ - (R)->next_to_clean - (R)->next_to_use - 1)) - -/* Control Queue default settings */ -#define IDPF_CTRL_SQ_CMD_TIMEOUT 250 /* msecs */ - -struct idpf_ctlq_desc { - /* Control queue descriptor flags */ - __le16 flags; - /* Control queue message opcode */ - __le16 opcode; - __le16 datalen; /* 0 for direct commands */ - union { - __le16 ret_val; - __le16 pfid_vfid; -#define IDPF_CTLQ_DESC_VF_ID_S 0 -#define IDPF_CTLQ_DESC_VF_ID_M (0x7FF << IDPF_CTLQ_DESC_VF_ID_S) -#define IDPF_CTLQ_DESC_PF_ID_S 11 -#define IDPF_CTLQ_DESC_PF_ID_M (0x1F << IDPF_CTLQ_DESC_PF_ID_S) - }; - - /* Virtchnl message opcode and virtchnl descriptor type - * v_opcode=[27:0], v_dtype=[31:28] - */ - __le32 v_opcode_dtype; - /* Virtchnl return value */ - __le32 v_retval; - union { - struct { - __le32 param0; - __le32 param1; - __le32 param2; - __le32 param3; - } direct; - struct { - __le32 param0; - __le16 sw_cookie; - /* Virtchnl flags */ - __le16 v_flags; - __le32 addr_high; - __le32 addr_low; - } indirect; - u8 raw[16]; - } params; -}; - -/* Flags sub-structure - * |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 | - * |DD |CMP|ERR| * RSV * |FTYPE | *RSV* |RD |VFC|BUF| HOST_ID | - */ -/* command flags and offsets */ -#define IDPF_CTLQ_FLAG_DD_S 0 -#define IDPF_CTLQ_FLAG_CMP_S 1 -#define IDPF_CTLQ_FLAG_ERR_S 2 -#define IDPF_CTLQ_FLAG_FTYPE_S 6 -#define IDPF_CTLQ_FLAG_RD_S 10 -#define IDPF_CTLQ_FLAG_VFC_S 11 -#define IDPF_CTLQ_FLAG_BUF_S 12 -#define IDPF_CTLQ_FLAG_HOST_ID_S 13 - -#define IDPF_CTLQ_FLAG_DD BIT(IDPF_CTLQ_FLAG_DD_S) /* 0x1 */ -#define IDPF_CTLQ_FLAG_CMP BIT(IDPF_CTLQ_FLAG_CMP_S) /* 0x2 */ -#define IDPF_CTLQ_FLAG_ERR BIT(IDPF_CTLQ_FLAG_ERR_S) /* 0x4 */ -#define IDPF_CTLQ_FLAG_FTYPE_VM BIT(IDPF_CTLQ_FLAG_FTYPE_S) /* 0x40 */ -#define IDPF_CTLQ_FLAG_FTYPE_PF BIT(IDPF_CTLQ_FLAG_FTYPE_S + 1) /* 0x80 */ -#define IDPF_CTLQ_FLAG_RD BIT(IDPF_CTLQ_FLAG_RD_S) /* 0x400 */ -#define IDPF_CTLQ_FLAG_VFC BIT(IDPF_CTLQ_FLAG_VFC_S) /* 0x800 */ -#define IDPF_CTLQ_FLAG_BUF BIT(IDPF_CTLQ_FLAG_BUF_S) /* 0x1000 */ - -/* Host ID is a special field that has 3b and not a 1b flag */ -#define IDPF_CTLQ_FLAG_HOST_ID_M MAKE_MASK(0x7000UL, IDPF_CTLQ_FLAG_HOST_ID_S) - -struct idpf_mbxq_desc { - u8 pad[8]; /* CTLQ flags/opcode/len/retval fields */ - u32 chnl_opcode; /* avoid confusion with desc->opcode */ - u32 chnl_retval; /* ditto for desc->retval */ - u32 pf_vf_id; /* used by CP when sending to PF */ -}; - -/* Max number of MMIO regions not including the mailbox and rstat regions in - * the fallback case when the whole bar is mapped. - */ -#define IDPF_MMIO_MAP_FALLBACK_MAX_REMAINING 3 - -struct idpf_mmio_reg { - void __iomem *vaddr; - resource_size_t addr_start; - resource_size_t addr_len; -}; - -/* Define the driver hardware struct to replace other control structs as needed - * Align to ctlq_hw_info - */ -struct idpf_hw { - struct idpf_mmio_reg mbx; - struct idpf_mmio_reg rstat; - /* Array of remaining LAN BAR regions */ - int num_lan_regs; - struct idpf_mmio_reg *lan_regs; - - struct idpf_adapter *back; - - /* control queue - send and receive */ - struct idpf_ctlq_info *asq; - struct idpf_ctlq_info *arq; - - /* pci info */ - u16 device_id; - u16 vendor_id; - u16 subsystem_device_id; - u16 subsystem_vendor_id; - u8 revision_id; - bool adapter_stopped; - - struct list_head cq_list_head; -}; - -int idpf_ctlq_alloc_ring_res(struct idpf_hw *hw, - struct idpf_ctlq_info *cq); - -void idpf_ctlq_dealloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq); - -/* prototype for functions used for dynamic memory allocation */ -void *idpf_alloc_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem, - u64 size); -void idpf_free_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem); -#endif /* _IDPF_CONTROLQ_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h b/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h deleted file mode 100644 index 3414c5f9a831..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h +++ /dev/null @@ -1,177 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_CONTROLQ_API_H_ -#define _IDPF_CONTROLQ_API_H_ - -#include "idpf_mem.h" - -struct idpf_hw; - -/* Used for queue init, response and events */ -enum idpf_ctlq_type { - IDPF_CTLQ_TYPE_MAILBOX_TX = 0, - IDPF_CTLQ_TYPE_MAILBOX_RX = 1, - IDPF_CTLQ_TYPE_CONFIG_TX = 2, - IDPF_CTLQ_TYPE_CONFIG_RX = 3, - IDPF_CTLQ_TYPE_EVENT_RX = 4, - IDPF_CTLQ_TYPE_RDMA_TX = 5, - IDPF_CTLQ_TYPE_RDMA_RX = 6, - IDPF_CTLQ_TYPE_RDMA_COMPL = 7 -}; - -/* Generic Control Queue Structures */ -struct idpf_ctlq_reg { - /* used for queue tracking */ - u32 head; - u32 tail; - /* Below applies only to default mb (if present) */ - u32 len; - u32 bah; - u32 bal; - u32 len_mask; - u32 len_ena_mask; - u32 head_mask; -}; - -/* Generic queue msg structure */ -struct idpf_ctlq_msg { - u8 vmvf_type; /* represents the source of the message on recv */ -#define IDPF_VMVF_TYPE_VF 0 -#define IDPF_VMVF_TYPE_VM 1 -#define IDPF_VMVF_TYPE_PF 2 - u8 host_id; - /* 3b field used only when sending a message to CP - to be used in - * combination with target func_id to route the message - */ -#define IDPF_HOST_ID_MASK 0x7 - - u16 opcode; - u16 data_len; /* data_len = 0 when no payload is attached */ - union { - u16 func_id; /* when sending a message */ - u16 status; /* when receiving a message */ - }; - union { - struct { - u32 chnl_opcode; - u32 chnl_retval; - } mbx; - } cookie; - union { -#define IDPF_DIRECT_CTX_SIZE 16 -#define IDPF_INDIRECT_CTX_SIZE 8 - /* 16 bytes of context can be provided or 8 bytes of context - * plus the address of a DMA buffer - */ - u8 direct[IDPF_DIRECT_CTX_SIZE]; - struct { - u8 context[IDPF_INDIRECT_CTX_SIZE]; - struct idpf_dma_mem *payload; - } indirect; - struct { - u32 rsvd; - u16 data; - u16 flags; - } sw_cookie; - } ctx; -}; - -/* Generic queue info structures */ -/* MB, CONFIG and EVENT q do not have extended info */ -struct idpf_ctlq_create_info { - enum idpf_ctlq_type type; - int id; /* absolute queue offset passed as input - * -1 for default mailbox if present - */ - u16 len; /* Queue length passed as input */ - u16 buf_size; /* buffer size passed as input */ - u64 base_address; /* output, HPA of the Queue start */ - struct idpf_ctlq_reg reg; /* registers accessed by ctlqs */ - - int ext_info_size; - void *ext_info; /* Specific to q type */ -}; - -/* Control Queue information */ -struct idpf_ctlq_info { - struct list_head cq_list; - - enum idpf_ctlq_type cq_type; - int q_id; - spinlock_t cq_lock; /* control queue lock */ - /* used for interrupt processing */ - u16 next_to_use; - u16 next_to_clean; - u16 next_to_post; /* starting descriptor to post buffers - * to after recev - */ - - struct idpf_dma_mem desc_ring; /* descriptor ring memory - * idpf_dma_mem is defined in OSdep.h - */ - union { - struct idpf_dma_mem **rx_buff; - struct idpf_ctlq_msg **tx_msg; - } bi; - - u16 buf_size; /* queue buffer size */ - u16 ring_size; /* Number of descriptors */ - struct idpf_ctlq_reg reg; /* registers accessed by ctlqs */ -}; - -/** - * enum idpf_mbx_opc - PF/VF mailbox commands - * @idpf_mbq_opc_send_msg_to_cp: used by PF or VF to send a message to its CP - * @idpf_mbq_opc_send_msg_to_peer_drv: used by PF or VF to send a message to - * any peer driver - */ -enum idpf_mbx_opc { - idpf_mbq_opc_send_msg_to_cp = 0x0801, - idpf_mbq_opc_send_msg_to_peer_drv = 0x0804, -}; - -/* API supported for control queue management */ -/* Will init all required q including default mb. "q_info" is an array of - * create_info structs equal to the number of control queues to be created. - */ -int idpf_ctlq_init(struct idpf_hw *hw, u8 num_q, - struct idpf_ctlq_create_info *q_info); - -/* Allocate and initialize a single control queue, which will be added to the - * control queue list; returns a handle to the created control queue - */ -int idpf_ctlq_add(struct idpf_hw *hw, - struct idpf_ctlq_create_info *qinfo, - struct idpf_ctlq_info **cq); - -/* Deinitialize and deallocate a single control queue */ -void idpf_ctlq_remove(struct idpf_hw *hw, - struct idpf_ctlq_info *cq); - -/* Sends messages to HW and will also free the buffer*/ -int idpf_ctlq_send(struct idpf_hw *hw, - struct idpf_ctlq_info *cq, - u16 num_q_msg, - struct idpf_ctlq_msg q_msg[]); - -/* Receives messages and called by interrupt handler/polling - * initiated by app/process. Also caller is supposed to free the buffers - */ -int idpf_ctlq_recv(struct idpf_ctlq_info *cq, u16 *num_q_msg, - struct idpf_ctlq_msg *q_msg); - -/* Reclaims send descriptors on HW write back */ -int idpf_ctlq_clean_sq(struct idpf_ctlq_info *cq, u16 *clean_count, - struct idpf_ctlq_msg *msg_status[]); - -/* Indicate RX buffers are done being processed */ -int idpf_ctlq_post_rx_buffs(struct idpf_hw *hw, - struct idpf_ctlq_info *cq, - u16 *buff_count, - struct idpf_dma_mem **buffs); - -/* Will destroy all q including the default mb */ -void idpf_ctlq_deinit(struct idpf_hw *hw); - -#endif /* _IDPF_CONTROLQ_API_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c b/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c deleted file mode 100644 index d4d488c7cfd6..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2023 Intel Corporation */ - -#include "idpf_controlq.h" - -/** - * idpf_ctlq_alloc_desc_ring - Allocate Control Queue (CQ) rings - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - */ -static int idpf_ctlq_alloc_desc_ring(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - size_t size = cq->ring_size * sizeof(struct idpf_ctlq_desc); - - cq->desc_ring.va = idpf_alloc_dma_mem(hw, &cq->desc_ring, size); - if (!cq->desc_ring.va) - return -ENOMEM; - - return 0; -} - -/** - * idpf_ctlq_alloc_bufs - Allocate Control Queue (CQ) buffers - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Allocate the buffer head for all control queues, and if it's a receive - * queue, allocate DMA buffers - */ -static int idpf_ctlq_alloc_bufs(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - int i; - - /* Do not allocate DMA buffers for transmit queues */ - if (cq->cq_type == IDPF_CTLQ_TYPE_MAILBOX_TX) - return 0; - - /* We'll be allocating the buffer info memory first, then we can - * allocate the mapped buffers for the event processing - */ - cq->bi.rx_buff = kzalloc_objs(struct idpf_dma_mem *, cq->ring_size); - if (!cq->bi.rx_buff) - return -ENOMEM; - - /* allocate the mapped buffers (except for the last one) */ - for (i = 0; i < cq->ring_size - 1; i++) { - struct idpf_dma_mem *bi; - int num = 1; /* number of idpf_dma_mem to be allocated */ - - cq->bi.rx_buff[i] = kzalloc_objs(struct idpf_dma_mem, num); - if (!cq->bi.rx_buff[i]) - goto unwind_alloc_cq_bufs; - - bi = cq->bi.rx_buff[i]; - - bi->va = idpf_alloc_dma_mem(hw, bi, cq->buf_size); - if (!bi->va) { - /* unwind will not free the failed entry */ - kfree(cq->bi.rx_buff[i]); - goto unwind_alloc_cq_bufs; - } - } - - return 0; - -unwind_alloc_cq_bufs: - /* don't try to free the one that failed... */ - i--; - for (; i >= 0; i--) { - idpf_free_dma_mem(hw, cq->bi.rx_buff[i]); - kfree(cq->bi.rx_buff[i]); - } - kfree(cq->bi.rx_buff); - - return -ENOMEM; -} - -/** - * idpf_ctlq_free_desc_ring - Free Control Queue (CQ) rings - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * This assumes the posted send buffers have already been cleaned - * and de-allocated - */ -static void idpf_ctlq_free_desc_ring(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - idpf_free_dma_mem(hw, &cq->desc_ring); -} - -/** - * idpf_ctlq_free_bufs - Free CQ buffer info elements - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Free the DMA buffers for RX queues, and DMA buffer header for both RX and TX - * queues. The upper layers are expected to manage freeing of TX DMA buffers - */ -static void idpf_ctlq_free_bufs(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - void *bi; - - if (cq->cq_type == IDPF_CTLQ_TYPE_MAILBOX_RX) { - int i; - - /* free DMA buffers for rx queues*/ - for (i = 0; i < cq->ring_size; i++) { - if (cq->bi.rx_buff[i]) { - idpf_free_dma_mem(hw, cq->bi.rx_buff[i]); - kfree(cq->bi.rx_buff[i]); - } - } - - bi = (void *)cq->bi.rx_buff; - } else { - bi = (void *)cq->bi.tx_msg; - } - - /* free the buffer header */ - kfree(bi); -} - -/** - * idpf_ctlq_dealloc_ring_res - Free memory allocated for control queue - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Free the memory used by the ring, buffers and other related structures - */ -void idpf_ctlq_dealloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - /* free ring buffers and the ring itself */ - idpf_ctlq_free_bufs(hw, cq); - idpf_ctlq_free_desc_ring(hw, cq); -} - -/** - * idpf_ctlq_alloc_ring_res - allocate memory for descriptor ring and bufs - * @hw: pointer to hw struct - * @cq: pointer to control queue struct - * - * Do *NOT* hold cq_lock when calling this as the memory allocation routines - * called are not going to be atomic context safe - */ -int idpf_ctlq_alloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - int err; - - /* allocate the ring memory */ - err = idpf_ctlq_alloc_desc_ring(hw, cq); - if (err) - return err; - - /* allocate buffers in the rings */ - err = idpf_ctlq_alloc_bufs(hw, cq); - if (err) - goto idpf_init_cq_free_ring; - - /* success! */ - return 0; - -idpf_init_cq_free_ring: - idpf_free_dma_mem(hw, &cq->desc_ring); - - return err; -} diff --git a/drivers/net/ethernet/intel/idpf/idpf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_dev.c index 4079a787657f..083cf6319d26 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_dev.c @@ -10,45 +10,32 @@ /** * idpf_ctlq_reg_init - initialize default mailbox registers - * @adapter: adapter structure - * @cq: pointer to the array of create control queues + * @mmio: struct that contains MMIO region info + * @cci: struct where the register offset pointer to be copied to */ -static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq) +static void idpf_ctlq_reg_init(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cci) { - resource_size_t mbx_start = adapter->dev_ops.static_reg_info[0].start; - int i; + struct libie_ctlq_reg *tx_reg = &cci[LIBIE_CTLQ_TYPE_TX].reg; + struct libie_ctlq_reg *rx_reg = &cci[LIBIE_CTLQ_TYPE_RX].reg; - for (i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { - struct idpf_ctlq_create_info *ccq = cq + i; + tx_reg->head = libie_pci_get_mmio_addr(mmio, PF_FW_ATQH); + tx_reg->tail = libie_pci_get_mmio_addr(mmio, PF_FW_ATQT); + tx_reg->len = libie_pci_get_mmio_addr(mmio, PF_FW_ATQLEN); + tx_reg->addr_high = libie_pci_get_mmio_addr(mmio, PF_FW_ATQBAH); + tx_reg->addr_low = libie_pci_get_mmio_addr(mmio, PF_FW_ATQBAL); + tx_reg->len_mask = PF_FW_ATQLEN_ATQLEN_M; + tx_reg->len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M; + tx_reg->head_mask = PF_FW_ATQH_ATQH_M; - switch (ccq->type) { - case IDPF_CTLQ_TYPE_MAILBOX_TX: - /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ATQH - mbx_start; - ccq->reg.tail = PF_FW_ATQT - mbx_start; - ccq->reg.len = PF_FW_ATQLEN - mbx_start; - ccq->reg.bah = PF_FW_ATQBAH - mbx_start; - ccq->reg.bal = PF_FW_ATQBAL - mbx_start; - ccq->reg.len_mask = PF_FW_ATQLEN_ATQLEN_M; - ccq->reg.len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M; - ccq->reg.head_mask = PF_FW_ATQH_ATQH_M; - break; - case IDPF_CTLQ_TYPE_MAILBOX_RX: - /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ARQH - mbx_start; - ccq->reg.tail = PF_FW_ARQT - mbx_start; - ccq->reg.len = PF_FW_ARQLEN - mbx_start; - ccq->reg.bah = PF_FW_ARQBAH - mbx_start; - ccq->reg.bal = PF_FW_ARQBAL - mbx_start; - ccq->reg.len_mask = PF_FW_ARQLEN_ARQLEN_M; - ccq->reg.len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M; - ccq->reg.head_mask = PF_FW_ARQH_ARQH_M; - break; - default: - break; - } - } + rx_reg->head = libie_pci_get_mmio_addr(mmio, PF_FW_ARQH); + rx_reg->tail = libie_pci_get_mmio_addr(mmio, PF_FW_ARQT); + rx_reg->len = libie_pci_get_mmio_addr(mmio, PF_FW_ARQLEN); + rx_reg->addr_high = libie_pci_get_mmio_addr(mmio, PF_FW_ARQBAH); + rx_reg->addr_low = libie_pci_get_mmio_addr(mmio, PF_FW_ARQBAL); + rx_reg->len_mask = PF_FW_ARQLEN_ARQLEN_M; + rx_reg->len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M; + rx_reg->head_mask = PF_FW_ARQH_ARQH_M; } /** @@ -57,13 +44,14 @@ static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, */ static void idpf_mb_intr_reg_init(struct idpf_adapter *adapter) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct idpf_intr_reg *intr = &adapter->mb_vector.intr_reg; u32 dyn_ctl = le32_to_cpu(adapter->caps.mailbox_dyn_ctl); - intr->dyn_ctl = idpf_get_reg_addr(adapter, dyn_ctl); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, dyn_ctl); intr->dyn_ctl_intena_m = PF_GLINT_DYN_CTL_INTENA_M; intr->dyn_ctl_itridx_m = PF_GLINT_DYN_CTL_ITR_INDX_M; - intr->icr_ena = idpf_get_reg_addr(adapter, PF_INT_DIR_OICR_ENA); + intr->icr_ena = libie_pci_get_mmio_addr(mmio, PF_INT_DIR_OICR_ENA); intr->icr_ena_ctlq_m = PF_INT_DIR_OICR_ENA_M; } @@ -78,6 +66,7 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, struct idpf_adapter *adapter = vport->adapter; u16 num_vecs = rsrc->num_q_vectors; struct idpf_vec_regs *reg_vals; + struct libie_mmio_info *mmio; int num_regs, i, err = 0; u32 rx_itr, tx_itr, val; u16 total_vecs; @@ -93,14 +82,17 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, goto free_reg_vals; } + mmio = &adapter->ctlq_ctx.mmio_info; + for (i = 0; i < num_vecs; i++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[i]; u16 vec_id = rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC; struct idpf_intr_reg *intr = &q_vector->intr_reg; + struct idpf_vec_regs *reg = ®_vals[vec_id]; u32 spacing; - intr->dyn_ctl = idpf_get_reg_addr(adapter, - reg_vals[vec_id].dyn_ctl_reg); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, + reg->dyn_ctl_reg); intr->dyn_ctl_intena_m = PF_GLINT_DYN_CTL_INTENA_M; intr->dyn_ctl_intena_msk_m = PF_GLINT_DYN_CTL_INTENA_MSK_M; intr->dyn_ctl_itridx_s = PF_GLINT_DYN_CTL_ITR_INDX_S; @@ -110,22 +102,21 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, intr->dyn_ctl_sw_itridx_ena_m = PF_GLINT_DYN_CTL_SW_ITR_INDX_ENA_M; - spacing = IDPF_ITR_IDX_SPACING(reg_vals[vec_id].itrn_index_spacing, + spacing = IDPF_ITR_IDX_SPACING(reg->itrn_index_spacing, IDPF_PF_ITR_IDX_SPACING); rx_itr = PF_GLINT_ITR_ADDR(VIRTCHNL2_ITR_IDX_0, - reg_vals[vec_id].itrn_reg, - spacing); + reg->itrn_reg, spacing); tx_itr = PF_GLINT_ITR_ADDR(VIRTCHNL2_ITR_IDX_1, - reg_vals[vec_id].itrn_reg, - spacing); - intr->rx_itr = idpf_get_reg_addr(adapter, rx_itr); - intr->tx_itr = idpf_get_reg_addr(adapter, tx_itr); + reg->itrn_reg, spacing); + intr->rx_itr = libie_pci_get_mmio_addr(mmio, rx_itr); + intr->tx_itr = libie_pci_get_mmio_addr(mmio, tx_itr); } /* Data vector for NOIRQ queues */ val = reg_vals[rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC].dyn_ctl_reg; - rsrc->noirq_dyn_ctl = idpf_get_reg_addr(adapter, val); + rsrc->noirq_dyn_ctl = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, val); val = PF_GLINT_DYN_CTL_WB_ON_ITR_M | PF_GLINT_DYN_CTL_INTENA_MSK_M | FIELD_PREP(PF_GLINT_DYN_CTL_ITR_INDX_M, IDPF_NO_ITR_UPDATE_IDX); @@ -143,7 +134,9 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, */ static void idpf_reset_reg_init(struct idpf_adapter *adapter) { - adapter->reset_reg.rstat = idpf_get_rstat_reg_addr(adapter, PFGEN_RSTAT); + adapter->reset_reg.rstat = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + PFGEN_RSTAT); adapter->reset_reg.rstat_m = PFGEN_RSTAT_PFR_STATE_M; } @@ -155,11 +148,11 @@ static void idpf_reset_reg_init(struct idpf_adapter *adapter) static void idpf_trigger_reset(struct idpf_adapter *adapter, enum idpf_flags __always_unused trig_cause) { - u32 reset_reg; + void __iomem *addr; - reset_reg = readl(idpf_get_rstat_reg_addr(adapter, PFGEN_CTRL)); - writel(reset_reg | PFGEN_CTRL_PFSWR, - idpf_get_rstat_reg_addr(adapter, PFGEN_CTRL)); + addr = libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + PFGEN_CTRL); + writel(readl(addr) | PFGEN_CTRL_PFSWR, addr); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index bb99d9e7c65d..95c45f12b0f9 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -225,7 +225,7 @@ static int idpf_add_flow_steer(struct net_device *netdev, spin_unlock_bh(&vport_config->flow_steer_list_lock); if (err) - goto out; + goto out_free_fltr; rule->vport_id = cpu_to_le32(vport->vport_id); rule->count = cpu_to_le32(1); @@ -252,17 +252,15 @@ static int idpf_add_flow_steer(struct net_device *netdev, break; default: err = -EINVAL; - goto out; + goto out_free_fltr; } err = idpf_add_del_fsteer_filters(vport->adapter, rule, VIRTCHNL2_OP_ADD_FLOW_RULE); - if (err) - goto out; - - if (info->status != cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { - err = -EIO; - goto out; + if (err) { + /* virtchnl2 rule is already consumed */ + kfree(fltr); + return err; } /* Save a copy of the user's flow spec so ethtool can later retrieve it */ @@ -274,9 +272,10 @@ static int idpf_add_flow_steer(struct net_device *netdev, user_config->num_fsteer_fltrs++; spin_unlock_bh(&vport_config->flow_steer_list_lock); - goto out_free_rule; -out: + return 0; + +out_free_fltr: kfree(fltr); out_free_rule: kfree(rule); @@ -319,12 +318,7 @@ static int idpf_del_flow_steer(struct net_device *netdev, err = idpf_add_del_fsteer_filters(vport->adapter, rule, VIRTCHNL2_OP_DEL_FLOW_RULE); if (err) - goto out; - - if (info->status != cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { - err = -EIO; - goto out; - } + return err; spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry_safe(f, iter, @@ -340,8 +334,6 @@ static int idpf_del_flow_steer(struct net_device *netdev, out_unlock: spin_unlock_bh(&vport_config->flow_steer_list_lock); -out: - kfree(rule); return err; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_idc.c b/drivers/net/ethernet/intel/idpf/idpf_idc.c index b7d6b08fc89e..b6cd1c25ae5d 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_idc.c +++ b/drivers/net/ethernet/intel/idpf/idpf_idc.c @@ -416,9 +416,12 @@ idpf_idc_init_msix_data(struct idpf_adapter *adapter) int idpf_idc_init_aux_core_dev(struct idpf_adapter *adapter, enum iidc_function_type ftype) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct iidc_rdma_core_dev_info *cdev_info; struct iidc_rdma_priv_dev_info *privd; - int err, i; + struct libie_pci_mmio_region *mr; + size_t num_mem_regions; + int err, i = 0; adapter->cdev_info = kzalloc_obj(*cdev_info); if (!adapter->cdev_info) @@ -436,22 +439,31 @@ int idpf_idc_init_aux_core_dev(struct idpf_adapter *adapter, cdev_info->rdma_protocol = IIDC_RDMA_PROTOCOL_ROCEV2; privd->ftype = ftype; + num_mem_regions = list_count_nodes(&mmio->mmio_list); + if (num_mem_regions <= IDPF_MMIO_REG_NUM_STATIC) { + err = -EINVAL; + goto err_plug_aux_dev; + } + + num_mem_regions -= IDPF_MMIO_REG_NUM_STATIC; privd->mapped_mem_regions = kzalloc_objs(struct iidc_rdma_lan_mapped_mem_region, - adapter->hw.num_lan_regs); + num_mem_regions); if (!privd->mapped_mem_regions) { err = -ENOMEM; goto err_plug_aux_dev; } - privd->num_memory_regions = cpu_to_le16(adapter->hw.num_lan_regs); - for (i = 0; i < adapter->hw.num_lan_regs; i++) { - privd->mapped_mem_regions[i].region_addr = - adapter->hw.lan_regs[i].vaddr; - privd->mapped_mem_regions[i].size = - cpu_to_le64(adapter->hw.lan_regs[i].addr_len); - privd->mapped_mem_regions[i].start_offset = - cpu_to_le64(adapter->hw.lan_regs[i].addr_start); + privd->num_memory_regions = cpu_to_le16(num_mem_regions); + list_for_each_entry(mr, &mmio->mmio_list, list) { + if (!idpf_mmio_region_non_static(&adapter->ctlq_ctx.mmio_info, + mr)) + continue; + + privd->mapped_mem_regions[i].region_addr = mr->addr; + privd->mapped_mem_regions[i].size = cpu_to_le64(mr->size); + privd->mapped_mem_regions[i++].start_offset = + cpu_to_le64(mr->offset); } idpf_idc_init_msix_data(adapter); diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index bb81e620c5c8..827c795afcb6 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -68,9 +68,11 @@ static void idpf_deinit_vector_stack(struct idpf_adapter *adapter) * This will also disable interrupt mode and queue up mailbox task. Mailbox * task will reschedule itself if not in interrupt mode. */ -static void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter) +void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter) { - clear_bit(IDPF_MB_INTR_MODE, adapter->flags); + if (!test_and_clear_bit(IDPF_MB_INTR_MODE, adapter->flags)) + return; + kfree(free_irq(adapter->msix_entries[0].vector, adapter)); queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); } @@ -1109,8 +1111,6 @@ static void idpf_vport_rel(struct idpf_vport *vport) kfree(adapter->vport_params_recvd[idx]); adapter->vport_params_recvd[idx] = NULL; - kfree(adapter->vport_params_reqd[idx]); - adapter->vport_params_reqd[idx] = NULL; kfree(vport); adapter->num_alloc_vports--; @@ -1373,6 +1373,7 @@ void idpf_statistics_task(struct work_struct *work) */ void idpf_mbx_task(struct work_struct *work) { + struct libie_ctlq_xn_recv_params xn_params; struct idpf_adapter *adapter; adapter = container_of(work, struct idpf_adapter, mbx_task.work); @@ -1383,7 +1384,14 @@ void idpf_mbx_task(struct work_struct *work) queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, usecs_to_jiffies(300)); - idpf_recv_mb_msg(adapter, adapter->hw.arq); + xn_params = (struct libie_ctlq_xn_recv_params) { + .xnm = adapter->xnm, + .ctlq = adapter->arq, + .ctlq_msg_handler = idpf_recv_event_msg, + .budget = LIBIE_CTLQ_MAX_XN_ENTRIES, + }; + + libie_ctlq_xn_recv(&xn_params); } /** @@ -1849,15 +1857,14 @@ void idpf_deinit_task(struct idpf_adapter *adapter) /** * idpf_check_reset_complete - check that reset is complete - * @hw: pointer to hw struct + * @adapter: adapter to check * @reset_reg: struct with reset registers * * Returns 0 if device is ready to use, or -EBUSY if it's in reset. **/ -static int idpf_check_reset_complete(struct idpf_hw *hw, +static int idpf_check_reset_complete(struct idpf_adapter *adapter, struct idpf_reset_reg *reset_reg) { - struct idpf_adapter *adapter = hw->back; int i; for (i = 0; i < 2000; i++) { @@ -1920,7 +1927,7 @@ static void idpf_init_hard_reset(struct idpf_adapter *adapter) } /* Wait for reset to complete */ - err = idpf_check_reset_complete(&adapter->hw, &adapter->reset_reg); + err = idpf_check_reset_complete(adapter, &adapter->reset_reg); if (err) { dev_err(dev, "The driver was unable to contact the device's firmware. Check that the FW is running. Driver state= 0x%x\n", adapter->state); @@ -1934,14 +1941,11 @@ static void idpf_init_hard_reset(struct idpf_adapter *adapter) goto unlock_mutex; } - queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); - /* Initialize the state machine, also allocate memory and request * resources */ err = idpf_vc_core_init(adapter); if (err) { - cancel_delayed_work_sync(&adapter->mbx_task); idpf_deinit_dflt_mbx(adapter); goto unlock_mutex; } @@ -1987,7 +1991,8 @@ void idpf_vc_event_task(struct work_struct *work) return; func_reset: - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); drv_load: set_bit(IDPF_HR_RESET_IN_PROG, adapter->flags); idpf_init_hard_reset(adapter); @@ -2570,44 +2575,6 @@ static int idpf_set_mac(struct net_device *netdev, void *p) return err; } -/** - * idpf_alloc_dma_mem - Allocate dma memory - * @hw: pointer to hw struct - * @mem: pointer to dma_mem struct - * @size: size of the memory to allocate - */ -void *idpf_alloc_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem, u64 size) -{ - struct idpf_adapter *adapter = hw->back; - size_t sz = ALIGN(size, 4096); - - /* The control queue resources are freed under a spinlock, contiguous - * pages will avoid IOMMU remapping and the use vmap (and vunmap in - * dma_free_*() path. - */ - mem->va = dma_alloc_attrs(&adapter->pdev->dev, sz, &mem->pa, - GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); - mem->size = sz; - - return mem->va; -} - -/** - * idpf_free_dma_mem - Free the allocated dma memory - * @hw: pointer to hw struct - * @mem: pointer to dma_mem struct - */ -void idpf_free_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem) -{ - struct idpf_adapter *adapter = hw->back; - - dma_free_attrs(&adapter->pdev->dev, mem->size, - mem->va, mem->pa, DMA_ATTR_FORCE_CONTIGUOUS); - mem->size = 0; - mem->va = NULL; - mem->pa = 0; -} - static int idpf_hwtstamp_set(struct net_device *netdev, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack) diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index ab3c409e587b..9840580fbe51 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -15,6 +15,8 @@ MODULE_DESCRIPTION(DRV_SUMMARY); MODULE_IMPORT_NS("LIBETH"); +MODULE_IMPORT_NS("LIBIE_CP"); +MODULE_IMPORT_NS("LIBIE_PCI"); MODULE_IMPORT_NS("LIBETH_XDP"); MODULE_LICENSE("GPL"); @@ -56,8 +58,16 @@ static int idpf_get_device_type(struct pci_dev *pdev) static int idpf_dev_init(struct idpf_adapter *adapter, const struct pci_device_id *ent) { + struct libie_mmio_info *mmio_info = &adapter->ctlq_ctx.mmio_info; int ret; + ret = libie_pci_init_dev(adapter->pdev); + if (ret) + return ret; + + mmio_info->pdev = adapter->pdev; + INIT_LIST_HEAD(&mmio_info->mmio_list); + if (ent->class == IDPF_CLASS_NETWORK_ETHERNET_PROGIF) { ret = idpf_get_device_type(adapter->pdev); switch (ret) { @@ -90,6 +100,15 @@ static int idpf_dev_init(struct idpf_adapter *adapter, return 0; } +/** + * idpf_decfg_device - deconfigure device and device specific resources + * @adapter: driver specific private structure + */ +static void idpf_decfg_device(struct idpf_adapter *adapter) +{ + libie_pci_unmap_all_mmio_regions(&adapter->ctlq_ctx.mmio_info); +} + /** * idpf_remove - Device removal routine * @pdev: PCI device information struct @@ -151,14 +170,13 @@ static void idpf_remove(struct pci_dev *pdev) adapter->vport_config = NULL; kfree(adapter->netdevs); adapter->netdevs = NULL; - kfree(adapter->vcxn_mngr); - adapter->vcxn_mngr = NULL; mutex_destroy(&adapter->vport_ctrl_lock); mutex_destroy(&adapter->vector_lock); mutex_destroy(&adapter->queue_lock); mutex_destroy(&adapter->vc_buf_lock); + idpf_decfg_device(adapter); pci_set_drvdata(pdev, NULL); kfree(adapter); } @@ -181,46 +199,44 @@ static void idpf_shutdown(struct pci_dev *pdev) } /** - * idpf_cfg_hw - Initialize HW struct - * @adapter: adapter to setup hw struct for + * idpf_cfg_device - configure device and device specific resources + * @adapter: driver specific private structure * - * Returns 0 on success, negative on failure + * Return: %0 on success, -%errno on failure. */ -static int idpf_cfg_hw(struct idpf_adapter *adapter) +static int idpf_cfg_device(struct idpf_adapter *adapter) { - resource_size_t res_start, mbx_start, rstat_start; + struct libie_mmio_info *mmio_info = &adapter->ctlq_ctx.mmio_info; struct pci_dev *pdev = adapter->pdev; - struct idpf_hw *hw = &adapter->hw; - struct device *dev = &pdev->dev; - long len; - - res_start = pci_resource_start(pdev, 0); + struct resource *region; + bool mapped; + int err; /* Map mailbox space for virtchnl communication */ - mbx_start = res_start + adapter->dev_ops.static_reg_info[0].start; - len = resource_size(&adapter->dev_ops.static_reg_info[0]); - hw->mbx.vaddr = devm_ioremap(dev, mbx_start, len); - if (!hw->mbx.vaddr) { - pci_err(pdev, "failed to allocate BAR0 mbx region\n"); - + region = &adapter->dev_ops.static_reg_info[0]; + mapped = libie_pci_map_mmio_region(mmio_info, region->start, + resource_size(region)); + if (!mapped) { + pci_err(pdev, "failed to map BAR0 mbx region\n"); return -ENOMEM; } - hw->mbx.addr_start = adapter->dev_ops.static_reg_info[0].start; - hw->mbx.addr_len = len; /* Map rstat space for resets */ - rstat_start = res_start + adapter->dev_ops.static_reg_info[1].start; - len = resource_size(&adapter->dev_ops.static_reg_info[1]); - hw->rstat.vaddr = devm_ioremap(dev, rstat_start, len); - if (!hw->rstat.vaddr) { - pci_err(pdev, "failed to allocate BAR0 rstat region\n"); + region = &adapter->dev_ops.static_reg_info[1]; + mapped = libie_pci_map_mmio_region(mmio_info, region->start, + resource_size(region)); + if (!mapped) { + pci_err(pdev, "failed to map BAR0 rstat region\n"); + libie_pci_unmap_all_mmio_regions(mmio_info); return -ENOMEM; } - hw->rstat.addr_start = adapter->dev_ops.static_reg_info[1].start; - hw->rstat.addr_len = len; - hw->back = adapter; + err = pci_enable_ptm(pdev); + if (err) + pci_dbg(pdev, "PCIe PTM is not supported by PCIe bus/controller\n"); + + pci_set_drvdata(pdev, adapter); return 0; } @@ -246,32 +262,21 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) adapter->req_rx_splitq = true; adapter->pdev = pdev; - err = pcim_enable_device(pdev); - if (err) - goto err_free; - err = pcim_request_region(pdev, 0, pci_name(pdev)); + err = idpf_dev_init(adapter, ent); if (err) { - pci_err(pdev, "pcim_request_region failed %pe\n", ERR_PTR(err)); - + dev_err(&pdev->dev, "Failed to initialize device (ID 0x%x): %d\n", + ent->device, err); goto err_free; } - err = pci_enable_ptm(pdev); - if (err) - pci_dbg(pdev, "PCIe PTM is not supported by PCIe bus/controller\n"); - - /* set up for high or low dma */ - err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + err = idpf_cfg_device(adapter); if (err) { - pci_err(pdev, "DMA configuration failed: %pe\n", ERR_PTR(err)); - + pci_err(pdev, "Failed to configure device specific resources: %pe\n", + ERR_PTR(err)); goto err_free; } - pci_set_master(pdev); - pci_set_drvdata(pdev, adapter); - adapter->init_wq = alloc_workqueue("%s-%s-init", WQ_UNBOUND | WQ_MEM_RECLAIM, 0, dev_driver_string(dev), @@ -279,7 +284,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (!adapter->init_wq) { dev_err(dev, "Failed to allocate init workqueue\n"); err = -ENOMEM; - goto err_free; + goto err_init_wq; } adapter->serv_wq = alloc_workqueue("%s-%s-service", @@ -324,20 +329,6 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* setup msglvl */ adapter->msg_enable = netif_msg_init(-1, IDPF_AVAIL_NETIF_M); - err = idpf_dev_init(adapter, ent); - if (err) { - dev_err(&pdev->dev, "Unexpected dev ID 0x%x in idpf probe\n", - ent->device); - goto destroy_vc_event_wq; - } - - err = idpf_cfg_hw(adapter); - if (err) { - dev_err(dev, "Failed to configure HW structure for adapter: %d\n", - err); - goto destroy_vc_event_wq; - } - mutex_init(&adapter->vport_ctrl_lock); mutex_init(&adapter->vector_lock); mutex_init(&adapter->queue_lock); @@ -356,8 +347,6 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -destroy_vc_event_wq: - destroy_workqueue(adapter->vc_event_wq); err_vc_event_wq_alloc: destroy_workqueue(adapter->stats_wq); err_stats_wq_alloc: @@ -366,6 +355,8 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) destroy_workqueue(adapter->serv_wq); err_serv_wq_alloc: destroy_workqueue(adapter->init_wq); +err_init_wq: + idpf_decfg_device(adapter); err_free: kfree(adapter); return err; diff --git a/drivers/net/ethernet/intel/idpf/idpf_mem.h b/drivers/net/ethernet/intel/idpf/idpf_mem.h deleted file mode 100644 index 2aaabdc02dd2..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_mem.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_MEM_H_ -#define _IDPF_MEM_H_ - -#include - -struct idpf_dma_mem { - void *va; - dma_addr_t pa; - size_t size; -}; - -#define idpf_mbx_wr32(a, reg, value) writel((value), ((a)->mbx.vaddr + (reg))) -#define idpf_mbx_rd32(a, reg) readl((a)->mbx.vaddr + (reg)) -#define idpf_mbx_wr64(a, reg, value) writeq((value), ((a)->mbx.vaddr + (reg))) -#define idpf_mbx_rd64(a, reg) readq((a)->mbx.vaddr + (reg)) - -#endif /* _IDPF_MEM_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index c724d429a7aa..24b91be25676 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -2408,7 +2408,7 @@ void idpf_tx_splitq_build_flow_desc(union idpf_tx_flex_desc *desc, struct idpf_tx_splitq_params *params, u16 td_cmd, u16 size) { - *(u32 *)&desc->flow.qw1.cmd_dtype = (u8)(params->dtype | td_cmd); + *(__le32 *)&desc->flow.qw1.cmd_dtype = cpu_to_le32((u8)(params->dtype | td_cmd)); desc->flow.qw1.rxr_bufsize = cpu_to_le16((u16)size); desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag); } @@ -2871,7 +2871,7 @@ int idpf_tso(struct sk_buff *skb, struct idpf_tx_offload_params *off) (__force __wsum)htonl(paylen)); /* compute length of segmentation header */ off->tso_hdr_len = sizeof(struct udphdr) + l4_start; - l4.udp->len = htons(shinfo->gso_size + sizeof(struct udphdr)); + udp_set_len_short(l4.udp, shinfo->gso_size + sizeof(struct udphdr)); break; default: return -EINVAL; @@ -4676,11 +4676,11 @@ int idpf_config_rss(struct idpf_vport *vport, struct idpf_rss_data *rss_data) u32 vport_id = vport->vport_id; int err; - err = idpf_send_get_set_rss_key_msg(adapter, rss_data, vport_id, false); + err = idpf_send_set_rss_key_msg(adapter, rss_data, vport_id); if (err) return err; - return idpf_send_get_set_rss_lut_msg(adapter, rss_data, vport_id, false); + return idpf_send_set_rss_lut_msg(adapter, rss_data, vport_id); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 908dfa28674e..93547597efd2 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -5,6 +5,7 @@ #define _IDPF_TXRX_H_ #include +#include #include #include @@ -13,7 +14,6 @@ #include #include "idpf_lan_txrx.h" -#include "virtchnl2_lan_desc.h" #define IDPF_LARGE_MAX_Q 256 #define IDPF_MAX_Q 16 @@ -236,7 +236,7 @@ enum idpf_tx_ctx_desc_eipt_offload { (sizeof(u16) * IDPF_RX_MAX_PTYPE_PROTO_IDS)) #define IDPF_RX_PTYPE_HDR_SZ sizeof(struct virtchnl2_get_ptype_info) #define IDPF_RX_MAX_PTYPES_PER_BUF \ - DIV_ROUND_DOWN_ULL((IDPF_CTLQ_MAX_BUF_LEN - IDPF_RX_PTYPE_HDR_SZ), \ + DIV_ROUND_DOWN_ULL(LIBIE_CTLQ_MAX_BUF_LEN - IDPF_RX_PTYPE_HDR_SZ, \ IDPF_RX_MAX_PTYPE_SZ) #define IDPF_GET_PTYPE_SIZE(p) struct_size((p), proto_id, (p)->proto_id_count) diff --git a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c index 6726084f6cfa..b537de3592f4 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c @@ -9,45 +9,32 @@ /** * idpf_vf_ctlq_reg_init - initialize default mailbox registers - * @adapter: adapter structure - * @cq: pointer to the array of create control queues + * @mmio: struct that contains MMIO region info + * @cci: struct where the register offset pointer to be copied to */ -static void idpf_vf_ctlq_reg_init(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq) +static void idpf_vf_ctlq_reg_init(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cci) { - resource_size_t mbx_start = adapter->dev_ops.static_reg_info[0].start; - int i; + struct libie_ctlq_reg *tx_reg = &cci[LIBIE_CTLQ_TYPE_TX].reg; + struct libie_ctlq_reg *rx_reg = &cci[LIBIE_CTLQ_TYPE_RX].reg; - for (i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { - struct idpf_ctlq_create_info *ccq = cq + i; + tx_reg->head = libie_pci_get_mmio_addr(mmio, VF_ATQH); + tx_reg->tail = libie_pci_get_mmio_addr(mmio, VF_ATQT); + tx_reg->len = libie_pci_get_mmio_addr(mmio, VF_ATQLEN); + tx_reg->addr_high = libie_pci_get_mmio_addr(mmio, VF_ATQBAH); + tx_reg->addr_low = libie_pci_get_mmio_addr(mmio, VF_ATQBAL); + tx_reg->len_mask = VF_ATQLEN_ATQLEN_M; + tx_reg->len_ena_mask = VF_ATQLEN_ATQENABLE_M; + tx_reg->head_mask = VF_ATQH_ATQH_M; - switch (ccq->type) { - case IDPF_CTLQ_TYPE_MAILBOX_TX: - /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ATQH - mbx_start; - ccq->reg.tail = VF_ATQT - mbx_start; - ccq->reg.len = VF_ATQLEN - mbx_start; - ccq->reg.bah = VF_ATQBAH - mbx_start; - ccq->reg.bal = VF_ATQBAL - mbx_start; - ccq->reg.len_mask = VF_ATQLEN_ATQLEN_M; - ccq->reg.len_ena_mask = VF_ATQLEN_ATQENABLE_M; - ccq->reg.head_mask = VF_ATQH_ATQH_M; - break; - case IDPF_CTLQ_TYPE_MAILBOX_RX: - /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ARQH - mbx_start; - ccq->reg.tail = VF_ARQT - mbx_start; - ccq->reg.len = VF_ARQLEN - mbx_start; - ccq->reg.bah = VF_ARQBAH - mbx_start; - ccq->reg.bal = VF_ARQBAL - mbx_start; - ccq->reg.len_mask = VF_ARQLEN_ARQLEN_M; - ccq->reg.len_ena_mask = VF_ARQLEN_ARQENABLE_M; - ccq->reg.head_mask = VF_ARQH_ARQH_M; - break; - default: - break; - } - } + rx_reg->head = libie_pci_get_mmio_addr(mmio, VF_ARQH); + rx_reg->tail = libie_pci_get_mmio_addr(mmio, VF_ARQT); + rx_reg->len = libie_pci_get_mmio_addr(mmio, VF_ARQLEN); + rx_reg->addr_high = libie_pci_get_mmio_addr(mmio, VF_ARQBAH); + rx_reg->addr_low = libie_pci_get_mmio_addr(mmio, VF_ARQBAL); + rx_reg->len_mask = VF_ARQLEN_ARQLEN_M; + rx_reg->len_ena_mask = VF_ARQLEN_ARQENABLE_M; + rx_reg->head_mask = VF_ARQH_ARQH_M; } /** @@ -56,13 +43,14 @@ static void idpf_vf_ctlq_reg_init(struct idpf_adapter *adapter, */ static void idpf_vf_mb_intr_reg_init(struct idpf_adapter *adapter) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct idpf_intr_reg *intr = &adapter->mb_vector.intr_reg; u32 dyn_ctl = le32_to_cpu(adapter->caps.mailbox_dyn_ctl); - intr->dyn_ctl = idpf_get_reg_addr(adapter, dyn_ctl); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, dyn_ctl); intr->dyn_ctl_intena_m = VF_INT_DYN_CTL0_INTENA_M; intr->dyn_ctl_itridx_m = VF_INT_DYN_CTL0_ITR_INDX_M; - intr->icr_ena = idpf_get_reg_addr(adapter, VF_INT_ICR0_ENA1); + intr->icr_ena = libie_pci_get_mmio_addr(mmio, VF_INT_ICR0_ENA1); intr->icr_ena_ctlq_m = VF_INT_ICR0_ENA1_ADMINQ_M; } @@ -77,6 +65,7 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, struct idpf_adapter *adapter = vport->adapter; u16 num_vecs = rsrc->num_q_vectors; struct idpf_vec_regs *reg_vals; + struct libie_mmio_info *mmio; int num_regs, i, err = 0; u32 rx_itr, tx_itr, val; u16 total_vecs; @@ -92,14 +81,17 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, goto free_reg_vals; } + mmio = &adapter->ctlq_ctx.mmio_info; + for (i = 0; i < num_vecs; i++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[i]; u16 vec_id = rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC; struct idpf_intr_reg *intr = &q_vector->intr_reg; + struct idpf_vec_regs *reg = ®_vals[vec_id]; u32 spacing; - intr->dyn_ctl = idpf_get_reg_addr(adapter, - reg_vals[vec_id].dyn_ctl_reg); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, + reg->dyn_ctl_reg); intr->dyn_ctl_intena_m = VF_INT_DYN_CTLN_INTENA_M; intr->dyn_ctl_intena_msk_m = VF_INT_DYN_CTLN_INTENA_MSK_M; intr->dyn_ctl_itridx_s = VF_INT_DYN_CTLN_ITR_INDX_S; @@ -109,22 +101,21 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, intr->dyn_ctl_sw_itridx_ena_m = VF_INT_DYN_CTLN_SW_ITR_INDX_ENA_M; - spacing = IDPF_ITR_IDX_SPACING(reg_vals[vec_id].itrn_index_spacing, + spacing = IDPF_ITR_IDX_SPACING(reg->itrn_index_spacing, IDPF_VF_ITR_IDX_SPACING); rx_itr = VF_INT_ITRN_ADDR(VIRTCHNL2_ITR_IDX_0, - reg_vals[vec_id].itrn_reg, - spacing); + reg->itrn_reg, spacing); tx_itr = VF_INT_ITRN_ADDR(VIRTCHNL2_ITR_IDX_1, - reg_vals[vec_id].itrn_reg, - spacing); - intr->rx_itr = idpf_get_reg_addr(adapter, rx_itr); - intr->tx_itr = idpf_get_reg_addr(adapter, tx_itr); + reg->itrn_reg, spacing); + intr->rx_itr = libie_pci_get_mmio_addr(mmio, rx_itr); + intr->tx_itr = libie_pci_get_mmio_addr(mmio, tx_itr); } /* Data vector for NOIRQ queues */ val = reg_vals[rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC].dyn_ctl_reg; - rsrc->noirq_dyn_ctl = idpf_get_reg_addr(adapter, val); + rsrc->noirq_dyn_ctl = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, val); val = VF_INT_DYN_CTLN_WB_ON_ITR_M | VF_INT_DYN_CTLN_INTENA_MSK_M | FIELD_PREP(VF_INT_DYN_CTLN_ITR_INDX_M, IDPF_NO_ITR_UPDATE_IDX); @@ -142,7 +133,9 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, */ static void idpf_vf_reset_reg_init(struct idpf_adapter *adapter) { - adapter->reset_reg.rstat = idpf_get_rstat_reg_addr(adapter, VFGEN_RSTAT); + adapter->reset_reg.rstat = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + VFGEN_RSTAT); adapter->reset_reg.rstat_m = VFGEN_RSTAT_VFR_STATE_M; } @@ -157,8 +150,7 @@ static void idpf_vf_trigger_reset(struct idpf_adapter *adapter, /* Do not send VIRTCHNL2_OP_RESET_VF message on driver unload */ if (trig_cause == IDPF_HR_FUNC_RESET && !test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) - idpf_send_mb_msg(adapter, adapter->hw.asq, - VIRTCHNL2_OP_RESET_VF, 0, NULL, 0); + idpf_send_vf_reset_msg(adapter); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 8bd6cca64c9b..1caf52706973 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -2,26 +2,13 @@ /* Copyright (C) 2023 Intel Corporation */ #include +#include #include #include "idpf.h" #include "idpf_virtchnl.h" #include "idpf_ptp.h" -/** - * struct idpf_vc_xn_manager - Manager for tracking transactions - * @ring: backing and lookup for transactions - * @free_xn_bm: bitmap for free transactions - * @xn_bm_lock: make bitmap access synchronous where necessary - * @salt: used to make cookie unique every message - */ -struct idpf_vc_xn_manager { - struct idpf_vc_xn ring[IDPF_VC_XN_RING_LEN]; - DECLARE_BITMAP(free_xn_bm, IDPF_VC_XN_RING_LEN); - spinlock_t xn_bm_lock; - u8 salt; -}; - /** * idpf_vid_to_vport - Translate vport id to vport pointer * @adapter: private data struct @@ -82,79 +69,62 @@ static void idpf_handle_event_link(struct idpf_adapter *adapter, /** * idpf_recv_event_msg - Receive virtchnl event message - * @adapter: Driver specific private structure + * @ctx: control queue context * @ctlq_msg: message to copy from * * Receive virtchnl event message */ -static void idpf_recv_event_msg(struct idpf_adapter *adapter, - struct idpf_ctlq_msg *ctlq_msg) +void idpf_recv_event_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *ctlq_msg) { - int payload_size = ctlq_msg->ctx.indirect.payload->size; + struct kvec *buff = &ctlq_msg->recv_mem; + int payload_size = buff->iov_len; + struct idpf_adapter *adapter; struct virtchnl2_event *v2e; u32 event; - if (payload_size < sizeof(*v2e)) { - dev_err_ratelimited(&adapter->pdev->dev, "Failed to receive valid payload for event msg (op %d len %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, - payload_size); - return; + adapter = container_of(ctx, struct idpf_adapter, ctlq_ctx); + if (ctlq_msg->chnl_opcode != VIRTCHNL2_OP_EVENT) { + dev_dbg(&adapter->pdev->dev, + "Unhandled message with opcode %u from CP\n", + ctlq_msg->chnl_opcode); + goto free_rx_buf; } - v2e = (struct virtchnl2_event *)ctlq_msg->ctx.indirect.payload->va; + if (payload_size < sizeof(*v2e)) { + dev_err_ratelimited(&adapter->pdev->dev, "Failed to receive valid payload for event msg (op %d len %d)\n", + ctlq_msg->chnl_opcode, + payload_size); + goto free_rx_buf; + } + + v2e = (struct virtchnl2_event *)buff->iov_base; event = le32_to_cpu(v2e->event); switch (event) { case VIRTCHNL2_EVENT_LINK_CHANGE: idpf_handle_event_link(adapter, v2e); - return; + break; default: dev_err(&adapter->pdev->dev, "Unknown event %d from PF\n", event); break; } + +free_rx_buf: + libie_ctlq_release_rx_buf(buff); } /** * idpf_mb_clean - Reclaim the send mailbox queue entries - * @adapter: driver specific private structure * @asq: send control queue info + * @deinit: release all buffers before destroying the queue * - * Reclaim the send mailbox queue entries to be used to send further messages - * - * Return: 0 on success, negative on failure + * This is a helper function to clean the send mailbox queue entries. */ -static int idpf_mb_clean(struct idpf_adapter *adapter, - struct idpf_ctlq_info *asq) +static void idpf_mb_clean(struct libie_ctlq_info *asq, bool deinit) { - u16 i, num_q_msg = IDPF_DFLT_MBX_Q_LEN; - struct idpf_ctlq_msg **q_msg; - struct idpf_dma_mem *dma_mem; - int err; - - q_msg = kzalloc_objs(struct idpf_ctlq_msg *, num_q_msg, GFP_ATOMIC); - if (!q_msg) - return -ENOMEM; - - err = idpf_ctlq_clean_sq(asq, &num_q_msg, q_msg); - if (err) - goto err_kfree; - - for (i = 0; i < num_q_msg; i++) { - if (!q_msg[i]) - continue; - dma_mem = q_msg[i]->ctx.indirect.payload; - if (dma_mem) - dma_free_coherent(&adapter->pdev->dev, dma_mem->size, - dma_mem->va, dma_mem->pa); - kfree(q_msg[i]); - kfree(dma_mem); - } - -err_kfree: - kfree(q_msg); - - return err; + libie_ctlq_xn_send_clean(asq, kfree, deinit); } #if IS_ENABLED(CONFIG_PTP_1588_CLOCK) @@ -188,7 +158,7 @@ static bool idpf_ptp_is_mb_msg(u32 op) * @ctlq_msg: Corresponding control queue message */ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, - struct idpf_ctlq_msg *ctlq_msg) + struct libie_ctlq_msg *ctlq_msg) { /* If the message is PTP-related and the secondary mailbox is available, * send the message through the secondary mailbox. @@ -196,532 +166,108 @@ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, if (!idpf_ptp_is_mb_msg(op) || !adapter->ptp->secondary_mbx.valid) return; - ctlq_msg->opcode = idpf_mbq_opc_send_msg_to_peer_drv; + ctlq_msg->opcode = LIBIE_CTLQ_SEND_MSG_TO_PEER; ctlq_msg->func_id = adapter->ptp->secondary_mbx.peer_mbx_q_id; - ctlq_msg->host_id = adapter->ptp->secondary_mbx.peer_id; + ctlq_msg->flags = FIELD_PREP(LIBIE_CTLQ_DESC_FLAG_HOST_ID, + adapter->ptp->secondary_mbx.peer_id); } #else /* !CONFIG_PTP_1588_CLOCK */ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, - struct idpf_ctlq_msg *ctlq_msg) + struct libie_ctlq_msg *ctlq_msg) { } #endif /* CONFIG_PTP_1588_CLOCK */ /** - * idpf_send_mb_msg - Send message over mailbox + * idpf_send_mb_msg - send mailbox message to the device control plane * @adapter: driver specific private structure - * @asq: control queue to send message to - * @op: virtchnl opcode - * @msg_size: size of the payload - * @msg: pointer to buffer holding the payload - * @cookie: unique SW generated cookie per message + * @xn_params: Xn send parameters to fill + * @send_buf: buffer to send + * @send_buf_size: size of the send buffer * - * Will prepare the control queue message and initiates the send api + * Fill the Xn parameters with the required info to send a virtchnl message. + * The send buffer is DMA mapped in the libie to avoid memcpy. * - * Return: 0 on success, negative on failure - */ -int idpf_send_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *asq, - u32 op, u16 msg_size, u8 *msg, u16 cookie) -{ - struct idpf_ctlq_msg *ctlq_msg; - struct idpf_dma_mem *dma_mem; - int err; - - /* If we are here and a reset is detected nothing much can be - * done. This thread should silently abort and expected to - * be corrected with a new run either by user or driver - * flows after reset - */ - if (idpf_is_reset_detected(adapter)) - return 0; - - err = idpf_mb_clean(adapter, asq); - if (err) - return err; - - ctlq_msg = kzalloc_obj(*ctlq_msg, GFP_ATOMIC); - if (!ctlq_msg) - return -ENOMEM; - - dma_mem = kzalloc_obj(*dma_mem, GFP_ATOMIC); - if (!dma_mem) { - err = -ENOMEM; - goto dma_mem_error; - } - - ctlq_msg->opcode = idpf_mbq_opc_send_msg_to_cp; - ctlq_msg->func_id = 0; - - idpf_prepare_ptp_mb_msg(adapter, op, ctlq_msg); - - ctlq_msg->data_len = msg_size; - ctlq_msg->cookie.mbx.chnl_opcode = op; - ctlq_msg->cookie.mbx.chnl_retval = 0; - dma_mem->size = IDPF_CTLQ_MAX_BUF_LEN; - dma_mem->va = dma_alloc_coherent(&adapter->pdev->dev, dma_mem->size, - &dma_mem->pa, GFP_ATOMIC); - if (!dma_mem->va) { - err = -ENOMEM; - goto dma_alloc_error; - } - - /* It's possible we're just sending an opcode but no buffer */ - if (msg && msg_size) - memcpy(dma_mem->va, msg, msg_size); - ctlq_msg->ctx.indirect.payload = dma_mem; - ctlq_msg->ctx.sw_cookie.data = cookie; - - err = idpf_ctlq_send(&adapter->hw, asq, 1, ctlq_msg); - if (err) - goto send_error; - - return 0; - -send_error: - dma_free_coherent(&adapter->pdev->dev, dma_mem->size, dma_mem->va, - dma_mem->pa); -dma_alloc_error: - kfree(dma_mem); -dma_mem_error: - kfree(ctlq_msg); - - return err; -} - -/* API for virtchnl "transaction" support ("xn" for short). */ - -/** - * idpf_vc_xn_lock - Request exclusive access to vc transaction - * @xn: struct idpf_vc_xn* to access - */ -#define idpf_vc_xn_lock(xn) \ - spin_lock(&(xn)->lock) - -/** - * idpf_vc_xn_unlock - Release exclusive access to vc transaction - * @xn: struct idpf_vc_xn* to access - */ -#define idpf_vc_xn_unlock(xn) \ - spin_unlock(&(xn)->lock) - -/** - * idpf_vc_xn_release_bufs - Release reference to reply buffer(s) and - * reset the transaction state. - * @xn: struct idpf_vc_xn to update - */ -static void idpf_vc_xn_release_bufs(struct idpf_vc_xn *xn) -{ - xn->reply.iov_base = NULL; - xn->reply.iov_len = 0; - - if (xn->state != IDPF_VC_XN_SHUTDOWN) - xn->state = IDPF_VC_XN_IDLE; -} - -/** - * idpf_vc_xn_init - Initialize virtchnl transaction object - * @vcxn_mngr: pointer to vc transaction manager struct - */ -static void idpf_vc_xn_init(struct idpf_vc_xn_manager *vcxn_mngr) -{ - int i; - - spin_lock_init(&vcxn_mngr->xn_bm_lock); - - for (i = 0; i < ARRAY_SIZE(vcxn_mngr->ring); i++) { - struct idpf_vc_xn *xn = &vcxn_mngr->ring[i]; - - xn->state = IDPF_VC_XN_IDLE; - xn->idx = i; - idpf_vc_xn_release_bufs(xn); - spin_lock_init(&xn->lock); - init_completion(&xn->completed); - } - - bitmap_fill(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); -} - -/** - * idpf_vc_xn_shutdown - Uninitialize virtchnl transaction object - * @vcxn_mngr: pointer to vc transaction manager struct + * Cleanup the mailbox queue entries of the previously sent message to + * unmap and release the buffer. * - * All waiting threads will be woken-up and their transaction aborted. Further - * operations on that object will fail. + * Return: 0 if the request was successful, -%EBUSY if reset is detected + * or Tx control queue is full, other negative error code on failure. */ -void idpf_vc_xn_shutdown(struct idpf_vc_xn_manager *vcxn_mngr) +int idpf_send_mb_msg(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size) { - int i; + struct libie_ctlq_msg ctlq_msg = {}; - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - bitmap_zero(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); + if (idpf_is_reset_detected(adapter)) { + if (!libie_cp_can_send_onstack(send_buf_size)) + kfree(send_buf); - for (i = 0; i < ARRAY_SIZE(vcxn_mngr->ring); i++) { - struct idpf_vc_xn *xn = &vcxn_mngr->ring[i]; - - idpf_vc_xn_lock(xn); - xn->state = IDPF_VC_XN_SHUTDOWN; - idpf_vc_xn_release_bufs(xn); - idpf_vc_xn_unlock(xn); - complete_all(&xn->completed); + return -EBUSY; } + + idpf_prepare_ptp_mb_msg(adapter, xn_params->chnl_opcode, &ctlq_msg); + xn_params->ctlq_msg = ctlq_msg.opcode ? &ctlq_msg : NULL; + + xn_params->send_buf.iov_base = send_buf; + xn_params->send_buf.iov_len = send_buf_size; + xn_params->xnm = adapter->xnm; + xn_params->ctlq = xn_params->ctlq ? xn_params->ctlq : adapter->asq; + xn_params->rel_tx_buf = kfree; + + idpf_mb_clean(xn_params->ctlq, false); + + return libie_ctlq_xn_send(xn_params); } /** - * idpf_vc_xn_pop_free - Pop a free transaction from free list - * @vcxn_mngr: transaction manager to pop from - * - * Returns NULL if no free transactions - */ -static -struct idpf_vc_xn *idpf_vc_xn_pop_free(struct idpf_vc_xn_manager *vcxn_mngr) -{ - struct idpf_vc_xn *xn = NULL; - unsigned long free_idx; - - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - free_idx = find_first_bit(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); - if (free_idx == IDPF_VC_XN_RING_LEN) - goto do_unlock; - - clear_bit(free_idx, vcxn_mngr->free_xn_bm); - xn = &vcxn_mngr->ring[free_idx]; - xn->salt = vcxn_mngr->salt++; - -do_unlock: - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); - - return xn; -} - -/** - * idpf_vc_xn_push_free - Push a free transaction to free list - * @vcxn_mngr: transaction manager to push to - * @xn: transaction to push - */ -static void idpf_vc_xn_push_free(struct idpf_vc_xn_manager *vcxn_mngr, - struct idpf_vc_xn *xn) -{ - idpf_vc_xn_release_bufs(xn); - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - set_bit(xn->idx, vcxn_mngr->free_xn_bm); - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); -} - -/** - * idpf_vc_xn_exec - Perform a send/recv virtchnl transaction - * @adapter: driver specific private structure with vcxn_mngr - * @params: parameters for this particular transaction including - * -vc_op: virtchannel operation to send - * -send_buf: kvec iov for send buf and len - * -recv_buf: kvec iov for recv buf and len (ignored if NULL) - * -timeout_ms: timeout waiting for a reply (milliseconds) - * -async: don't wait for message reply, will lose caller context - * -async_handler: callback to handle async replies - * - * @returns >= 0 for success, the size of the initial reply (may or may not be - * >= @recv_buf.iov_len, but we never overflow @@recv_buf_iov_base). < 0 for - * error. - */ -ssize_t idpf_vc_xn_exec(struct idpf_adapter *adapter, - const struct idpf_vc_xn_params *params) -{ - const struct kvec *send_buf = ¶ms->send_buf; - struct idpf_vc_xn *xn; - ssize_t retval; - u16 cookie; - - xn = idpf_vc_xn_pop_free(adapter->vcxn_mngr); - /* no free transactions available */ - if (!xn) - return -ENOSPC; - - idpf_vc_xn_lock(xn); - if (xn->state == IDPF_VC_XN_SHUTDOWN) { - retval = -ENXIO; - goto only_unlock; - } else if (xn->state != IDPF_VC_XN_IDLE) { - /* We're just going to clobber this transaction even though - * it's not IDLE. If we don't reuse it we could theoretically - * eventually leak all the free transactions and not be able to - * send any messages. At least this way we make an attempt to - * remain functional even though something really bad is - * happening that's corrupting what was supposed to be free - * transactions. - */ - WARN_ONCE(1, "There should only be idle transactions in free list (idx %d op %d)\n", - xn->idx, xn->vc_op); - } - - xn->reply = params->recv_buf; - xn->reply_sz = 0; - xn->state = params->async ? IDPF_VC_XN_ASYNC : IDPF_VC_XN_WAITING; - xn->vc_op = params->vc_op; - xn->async_handler = params->async_handler; - idpf_vc_xn_unlock(xn); - - if (!params->async) - reinit_completion(&xn->completed); - cookie = FIELD_PREP(IDPF_VC_XN_SALT_M, xn->salt) | - FIELD_PREP(IDPF_VC_XN_IDX_M, xn->idx); - - retval = idpf_send_mb_msg(adapter, adapter->hw.asq, params->vc_op, - send_buf->iov_len, send_buf->iov_base, - cookie); - if (retval) { - idpf_vc_xn_lock(xn); - goto release_and_unlock; - } - - if (params->async) - return 0; - - wait_for_completion_timeout(&xn->completed, - msecs_to_jiffies(params->timeout_ms)); - - /* No need to check the return value; we check the final state of the - * transaction below. It's possible the transaction actually gets more - * timeout than specified if we get preempted here but after - * wait_for_completion_timeout returns. This should be non-issue - * however. - */ - idpf_vc_xn_lock(xn); - switch (xn->state) { - case IDPF_VC_XN_SHUTDOWN: - retval = -ENXIO; - goto only_unlock; - case IDPF_VC_XN_WAITING: - dev_notice_ratelimited(&adapter->pdev->dev, - "Transaction timed-out (op:%d cookie:%04x vc_op:%d salt:%02x timeout:%dms)\n", - params->vc_op, cookie, xn->vc_op, - xn->salt, params->timeout_ms); - retval = -ETIME; - break; - case IDPF_VC_XN_COMPLETED_SUCCESS: - retval = xn->reply_sz; - break; - case IDPF_VC_XN_COMPLETED_FAILED: - dev_notice_ratelimited(&adapter->pdev->dev, "Transaction failed (op %d)\n", - params->vc_op); - retval = -EIO; - break; - default: - /* Invalid state. */ - WARN_ON_ONCE(1); - retval = -EIO; - break; - } - -release_and_unlock: - idpf_vc_xn_push_free(adapter->vcxn_mngr, xn); - /* If we receive a VC reply after here, it will be dropped. */ -only_unlock: - idpf_vc_xn_unlock(xn); - - return retval; -} - -/** - * idpf_vc_xn_forward_async - Handle async reply receives - * @adapter: private data struct - * @xn: transaction to handle - * @ctlq_msg: corresponding ctlq_msg - * - * For async sends we're going to lose the caller's context so, if an - * async_handler was provided, it can deal with the reply, otherwise we'll just - * check and report if there is an error. - */ -static int -idpf_vc_xn_forward_async(struct idpf_adapter *adapter, struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) -{ - int err = 0; - - if (ctlq_msg->cookie.mbx.chnl_opcode != xn->vc_op) { - dev_err_ratelimited(&adapter->pdev->dev, "Async message opcode does not match transaction opcode (msg: %d) (xn: %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, xn->vc_op); - xn->reply_sz = 0; - err = -EINVAL; - goto release_bufs; - } - - if (xn->async_handler) { - err = xn->async_handler(adapter, xn, ctlq_msg); - goto release_bufs; - } - - if (ctlq_msg->cookie.mbx.chnl_retval) { - xn->reply_sz = 0; - dev_err_ratelimited(&adapter->pdev->dev, "Async message failure (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EINVAL; - } - -release_bufs: - idpf_vc_xn_push_free(adapter->vcxn_mngr, xn); - - return err; -} - -/** - * idpf_vc_xn_forward_reply - copy a reply back to receiving thread - * @adapter: driver specific private structure with vcxn_mngr - * @ctlq_msg: controlq message to send back to receiving thread - */ -static int -idpf_vc_xn_forward_reply(struct idpf_adapter *adapter, - const struct idpf_ctlq_msg *ctlq_msg) -{ - const void *payload = NULL; - size_t payload_size = 0; - struct idpf_vc_xn *xn; - u16 msg_info; - int err = 0; - u16 xn_idx; - u16 salt; - - msg_info = ctlq_msg->ctx.sw_cookie.data; - xn_idx = FIELD_GET(IDPF_VC_XN_IDX_M, msg_info); - if (xn_idx >= ARRAY_SIZE(adapter->vcxn_mngr->ring)) { - dev_err_ratelimited(&adapter->pdev->dev, "Out of bounds cookie received: %02x\n", - xn_idx); - return -EINVAL; - } - xn = &adapter->vcxn_mngr->ring[xn_idx]; - idpf_vc_xn_lock(xn); - salt = FIELD_GET(IDPF_VC_XN_SALT_M, msg_info); - if (xn->salt != salt) { - dev_err_ratelimited(&adapter->pdev->dev, "Transaction salt does not match (exp:%d@%02x(%d) != got:%d@%02x)\n", - xn->vc_op, xn->salt, xn->state, - ctlq_msg->cookie.mbx.chnl_opcode, salt); - idpf_vc_xn_unlock(xn); - return -EINVAL; - } - - switch (xn->state) { - case IDPF_VC_XN_WAITING: - /* success */ - break; - case IDPF_VC_XN_IDLE: - dev_err_ratelimited(&adapter->pdev->dev, "Unexpected or belated VC reply (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EINVAL; - goto out_unlock; - case IDPF_VC_XN_SHUTDOWN: - /* ENXIO is a bit special here as the recv msg loop uses that - * know if it should stop trying to clean the ring if we lost - * the virtchnl. We need to stop playing with registers and - * yield. - */ - err = -ENXIO; - goto out_unlock; - case IDPF_VC_XN_ASYNC: - /* Set reply_sz from the actual payload so that async_handler - * can evaluate the response. - */ - xn->reply_sz = ctlq_msg->data_len; - err = idpf_vc_xn_forward_async(adapter, xn, ctlq_msg); - idpf_vc_xn_unlock(xn); - return err; - default: - dev_err_ratelimited(&adapter->pdev->dev, "Overwriting VC reply (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EBUSY; - goto out_unlock; - } - - if (ctlq_msg->cookie.mbx.chnl_opcode != xn->vc_op) { - dev_err_ratelimited(&adapter->pdev->dev, "Message opcode does not match transaction opcode (msg: %d) (xn: %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, xn->vc_op); - xn->reply_sz = 0; - xn->state = IDPF_VC_XN_COMPLETED_FAILED; - err = -EINVAL; - goto out_unlock; - } - - if (ctlq_msg->cookie.mbx.chnl_retval) { - xn->reply_sz = 0; - xn->state = IDPF_VC_XN_COMPLETED_FAILED; - err = -EINVAL; - goto out_unlock; - } - - if (ctlq_msg->data_len) { - payload = ctlq_msg->ctx.indirect.payload->va; - payload_size = ctlq_msg->data_len; - } - - xn->reply_sz = payload_size; - xn->state = IDPF_VC_XN_COMPLETED_SUCCESS; - - if (xn->reply.iov_base && xn->reply.iov_len && payload_size) - memcpy(xn->reply.iov_base, payload, - min_t(size_t, xn->reply.iov_len, payload_size)); - -out_unlock: - idpf_vc_xn_unlock(xn); - /* we _cannot_ hold lock while calling complete */ - complete(&xn->completed); - - return err; -} - -/** - * idpf_recv_mb_msg - Receive message over mailbox + * idpf_send_mb_msg_kfree - send mailbox message and free the send buffer * @adapter: driver specific private structure - * @arq: control queue to receive message from + * @xn_params: Xn send parameters to fill + * @send_buf: buffer to send, can be released with kfree() + * @send_buf_size: size of the send buffer * - * Will receive control queue message and posts the receive buffer. + * libie_cp functions consume only buffers above certain size, + * smaller buffers are assumed to be on the stack. However, for some + * commands with variable message size it makes sense to always use kzalloc(), + * which means we have to free smaller buffers ourselves. * - * Return: 0 on success and negative on failure. + * Return: 0 if no unexpected errors were encountered, + * negative error code otherwise. */ -int idpf_recv_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *arq) +int idpf_send_mb_msg_kfree(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size) { - struct idpf_ctlq_msg ctlq_msg; - struct idpf_dma_mem *dma_mem; - int post_err, err; - u16 num_recv; + int err = idpf_send_mb_msg(adapter, xn_params, send_buf, send_buf_size); - while (1) { - /* This will get <= num_recv messages and output how many - * actually received on num_recv. - */ - num_recv = 1; - err = idpf_ctlq_recv(arq, &num_recv, &ctlq_msg); - if (err || !num_recv) - break; - - if (ctlq_msg.data_len) { - dma_mem = ctlq_msg.ctx.indirect.payload; - } else { - dma_mem = NULL; - num_recv = 0; - } - - if (ctlq_msg.cookie.mbx.chnl_opcode == VIRTCHNL2_OP_EVENT) - idpf_recv_event_msg(adapter, &ctlq_msg); - else - err = idpf_vc_xn_forward_reply(adapter, &ctlq_msg); - - post_err = idpf_ctlq_post_rx_buffs(&adapter->hw, arq, - &num_recv, &dma_mem); - - /* If post failed clear the only buffer we supplied */ - if (post_err) { - if (dma_mem) - dma_free_coherent(&adapter->pdev->dev, - dma_mem->size, dma_mem->va, - dma_mem->pa); - break; - } - - /* virtchnl trying to shutdown, stop cleaning */ - if (err == -ENXIO) - break; - } + if (libie_cp_can_send_onstack(send_buf_size)) + kfree(send_buf); return err; } +/** + * idpf_send_vf_reset_msg - send one way VF reset message + * @adapter: driver specific private structure + */ +void idpf_send_vf_reset_msg(struct idpf_adapter *adapter) +{ + struct libie_ctlq_info *ctlq = adapter->asq; + + /* Forcefully claim send queue slot */ + idpf_mb_clean(ctlq, true); + + scoped_guard(spinlock, &ctlq->lock) { + *ctlq->tx_msg[ctlq->next_to_use] = (struct libie_ctlq_msg) { + .opcode = LIBIE_CTLQ_SEND_MSG_TO_CP, + .chnl_opcode = VIRTCHNL2_OP_RESET_VF, + }; + + libie_ctlq_send(adapter->asq, 1); + } +} + struct idpf_chunked_msg_params { u32 (*prepare_msg)(u32 vport_id, void *buf, const void *pos, u32 num); @@ -767,45 +313,43 @@ struct idpf_queue_set *idpf_alloc_queue_set(struct idpf_adapter *adapter, static int idpf_send_chunked_msg(struct idpf_adapter *adapter, const struct idpf_chunked_msg_params *params) { - struct idpf_vc_xn_params xn_params = { - .vc_op = params->vc_op, + struct libie_ctlq_xn_send_params xn_params = { .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = params->vc_op, }; const void *pos = params->chunks; - u32 num_chunks, num_msgs, buf_sz; - void *buf __free(kfree) = NULL; u32 totqs = params->num_chunks; u32 vid = params->vport_id; + u32 num_chunks, num_msgs; - num_chunks = min(IDPF_NUM_CHUNKS_PER_MSG(params->config_sz, - params->chunk_sz), totqs); + num_chunks = IDPF_NUM_CHUNKS_PER_MSG(params->config_sz, + params->chunk_sz); num_msgs = DIV_ROUND_UP(totqs, num_chunks); - buf_sz = params->config_sz + num_chunks * params->chunk_sz; - buf = kzalloc(buf_sz, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - xn_params.send_buf.iov_base = buf; - for (u32 i = 0; i < num_msgs; i++) { - ssize_t reply_sz; - - memset(buf, 0, buf_sz); - xn_params.send_buf.iov_len = buf_sz; - - if (params->prepare_msg(vid, buf, pos, num_chunks) != buf_sz) - return -EINVAL; - - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - - pos += num_chunks * params->chunk_sz; - totqs -= num_chunks; + u32 buf_sz; + void *buf; + int err; num_chunks = min(num_chunks, totqs); buf_sz = params->config_sz + num_chunks * params->chunk_sz; + buf = kzalloc(buf_sz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (params->prepare_msg(vid, buf, pos, num_chunks) != buf_sz) { + kfree(buf); + return -EINVAL; + } + + err = idpf_send_mb_msg_kfree(adapter, &xn_params, buf, buf_sz); + if (err) + return err; + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + xn_params.recv_mem = (struct kvec) {}; + pos += num_chunks * params->chunk_sz; + totqs -= num_chunks; } return 0; @@ -880,11 +424,14 @@ static int idpf_wait_for_marker_event(struct idpf_vport *vport) */ static int idpf_send_ver_msg(struct idpf_adapter *adapter) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_VERSION, + }; + struct virtchnl2_version_info *vvi_recv; struct virtchnl2_version_info vvi; - ssize_t reply_sz; u32 major, minor; - int err = 0; + int err; if (adapter->virt_ver_maj) { vvi.major = cpu_to_le32(adapter->virt_ver_maj); @@ -894,24 +441,23 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) vvi.minor = cpu_to_le32(IDPF_VIRTCHNL_VERSION_MINOR); } - xn_params.vc_op = VIRTCHNL2_OP_VERSION; - xn_params.send_buf.iov_base = &vvi; - xn_params.send_buf.iov_len = sizeof(vvi); - xn_params.recv_buf = xn_params.send_buf; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &vvi); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(vvi)) - return -EIO; + if (xn_params.recv_mem.iov_len < sizeof(*vvi_recv)) { + err = -EIO; + goto free_rx_buf; + } - major = le32_to_cpu(vvi.major); - minor = le32_to_cpu(vvi.minor); + vvi_recv = xn_params.recv_mem.iov_base; + major = le32_to_cpu(vvi_recv->major); + minor = le32_to_cpu(vvi_recv->minor); if (major > IDPF_VIRTCHNL_VERSION_MAJOR) { dev_warn(&adapter->pdev->dev, "Virtchnl major version greater than supported\n"); - return -EINVAL; + err = -EINVAL; + goto free_rx_buf; } if (major == IDPF_VIRTCHNL_VERSION_MAJOR && @@ -929,6 +475,9 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) adapter->virt_ver_maj = major; adapter->virt_ver_min = minor; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } @@ -941,9 +490,12 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) */ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_CAPS, + }; struct virtchnl2_get_capabilities caps = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; caps.csum_caps = cpu_to_le32(VIRTCHNL2_CAP_TX_CSUM_L3_IPV4 | @@ -1003,145 +555,156 @@ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter) VIRTCHNL2_CAP_LOOPBACK | VIRTCHNL2_CAP_PTP); - xn_params.vc_op = VIRTCHNL2_OP_GET_CAPS; - xn_params.send_buf.iov_base = ∩︀ - xn_params.send_buf.iov_len = sizeof(caps); - xn_params.recv_buf.iov_base = &adapter->caps; - xn_params.recv_buf.iov_len = sizeof(adapter->caps); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &caps); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(adapter->caps)) - return -EIO; - - return 0; -} - -/** - * idpf_send_get_lan_memory_regions - Send virtchnl get LAN memory regions msg - * @adapter: Driver specific private struct - * - * Return: 0 on success or error code on failure. - */ -static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) -{ - struct virtchnl2_get_lan_memory_regions *rcvd_regions __free(kfree); - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_GET_LAN_MEMORY_REGIONS, - .recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN, - .send_buf.iov_len = - sizeof(struct virtchnl2_get_lan_memory_regions) + - sizeof(struct virtchnl2_mem_region), - .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, - }; - int num_regions, size; - struct idpf_hw *hw; - ssize_t reply_sz; - int err = 0; - - rcvd_regions = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcvd_regions) - return -ENOMEM; - - xn_params.recv_buf.iov_base = rcvd_regions; - rcvd_regions->num_memory_regions = cpu_to_le16(1); - xn_params.send_buf.iov_base = rcvd_regions; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - - num_regions = le16_to_cpu(rcvd_regions->num_memory_regions); - size = struct_size(rcvd_regions, mem_reg, num_regions); - if (reply_sz < size) - return -EIO; - - if (size > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; - - hw = &adapter->hw; - hw->lan_regs = kzalloc_objs(*hw->lan_regs, num_regions); - if (!hw->lan_regs) - return -ENOMEM; - - for (int i = 0; i < num_regions; i++) { - hw->lan_regs[i].addr_len = - le64_to_cpu(rcvd_regions->mem_reg[i].size); - hw->lan_regs[i].addr_start = - le64_to_cpu(rcvd_regions->mem_reg[i].start_offset); + if (xn_params.recv_mem.iov_len < sizeof(adapter->caps)) { + err = -EIO; + goto free_rx_buf; } - hw->num_lan_regs = num_regions; + + memcpy(&adapter->caps, xn_params.recv_mem.iov_base, + sizeof(adapter->caps)); + +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return err; } /** - * idpf_calc_remaining_mmio_regs - calculate MMIO regions outside mbx and rstat - * @adapter: Driver specific private structure + * idpf_mmio_region_non_static - Check if region is not static + * @mmio_info: PCI resources info + * @reg: region to check * - * Called when idpf_send_get_lan_memory_regions is not supported. This will - * calculate the offsets and sizes for the regions before, in between, and - * after the mailbox and rstat MMIO mappings. - * - * Return: 0 on success or error code on failure. + * Return: %true if region can be received though virtchnl command, + * %false if region is related to mailbox or resetting */ -static int idpf_calc_remaining_mmio_regs(struct idpf_adapter *adapter) +bool idpf_mmio_region_non_static(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg) { - struct resource *rstat_reg = &adapter->dev_ops.static_reg_info[1]; - struct resource *mbx_reg = &adapter->dev_ops.static_reg_info[0]; - struct idpf_hw *hw = &adapter->hw; + struct idpf_adapter *adapter = + container_of(mmio_info, struct idpf_adapter, + ctlq_ctx.mmio_info); - hw->num_lan_regs = IDPF_MMIO_MAP_FALLBACK_MAX_REMAINING; - hw->lan_regs = kzalloc_objs(*hw->lan_regs, hw->num_lan_regs); - if (!hw->lan_regs) - return -ENOMEM; + for (uint i = 0; i < IDPF_MMIO_REG_NUM_STATIC; i++) { + if (reg->bar_idx == 0 && + reg->offset == adapter->dev_ops.static_reg_info[i].start) + return false; + } - /* Region preceding mailbox */ - hw->lan_regs[0].addr_start = 0; - hw->lan_regs[0].addr_len = mbx_reg->start; - /* Region between mailbox and rstat */ - hw->lan_regs[1].addr_start = mbx_reg->end + 1; - hw->lan_regs[1].addr_len = rstat_reg->start - - hw->lan_regs[1].addr_start; - /* Region after rstat */ - hw->lan_regs[2].addr_start = rstat_reg->end + 1; - hw->lan_regs[2].addr_len = pci_resource_len(adapter->pdev, 0) - - hw->lan_regs[2].addr_start; - - return 0; + return true; } /** - * idpf_map_lan_mmio_regs - map remaining LAN BAR regions + * idpf_decfg_lan_memory_regions - Unmap non-static memory regions * @adapter: Driver specific private structure + */ +static void idpf_decfg_lan_memory_regions(struct idpf_adapter *adapter) +{ + libie_pci_unmap_fltr_regs(&adapter->ctlq_ctx.mmio_info, + idpf_mmio_region_non_static); +} + +/** + * idpf_cfg_lan_memory_regions - Get (via virtchnl) and map LAN memory regions + * @adapter: Driver specific private struct * * Return: 0 on success or error code on failure. */ -static int idpf_map_lan_mmio_regs(struct idpf_adapter *adapter) +static int idpf_cfg_lan_memory_regions(struct idpf_adapter *adapter) { - struct pci_dev *pdev = adapter->pdev; - struct idpf_hw *hw = &adapter->hw; - resource_size_t res_start; + struct virtchnl2_get_lan_memory_regions *send_regions, *rcvd_regions; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_GET_LAN_MEMORY_REGIONS, + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + }; + size_t send_sz, reply_sz, size; + int num_regions; + int err = 0; - res_start = pci_resource_start(pdev, 0); + send_sz = sizeof(struct virtchnl2_get_lan_memory_regions) + + sizeof(struct virtchnl2_mem_region); + send_regions = kzalloc(send_sz, GFP_KERNEL); + if (!send_regions) + return -ENOMEM; - for (int i = 0; i < hw->num_lan_regs; i++) { - resource_size_t start; - long len; + send_regions->num_memory_regions = cpu_to_le16(1); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, send_regions, + send_sz); + if (err) + return err; - len = hw->lan_regs[i].addr_len; - if (!len) - continue; - start = hw->lan_regs[i].addr_start + res_start; + rcvd_regions = xn_params.recv_mem.iov_base; + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz < sizeof(*rcvd_regions)) { + err = -EIO; + goto rel_rx_buf; + } + num_regions = le16_to_cpu(rcvd_regions->num_memory_regions); + size = struct_size(rcvd_regions, mem_reg, num_regions); + if (reply_sz < size) { + err = -EIO; + goto rel_rx_buf; + } - hw->lan_regs[i].vaddr = devm_ioremap(&pdev->dev, start, len); - if (!hw->lan_regs[i].vaddr) { - pci_err(pdev, "failed to allocate BAR0 region\n"); - return -ENOMEM; + for (int i = 0; i < num_regions; i++) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; + resource_size_t offset, len; + + offset = le64_to_cpu(rcvd_regions->mem_reg[i].start_offset); + len = le64_to_cpu(rcvd_regions->mem_reg[i].size); + if (len && !libie_pci_map_mmio_region(mmio, offset, len)) { + idpf_decfg_lan_memory_regions(adapter); + err = -EIO; + goto rel_rx_buf; } } +rel_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; +} + +/** + * idpf_map_remaining_mmio_regs - map MMIO regions outside mbx and rstat + * @adapter: Driver specific private structure + * + * Called when idpf_cfg_lan_memory_regions is not supported. This will + * calculate the offsets and sizes for the regions before, in between, and + * after the mailbox and rstat MMIO mappings, and map those ranges. + * + * Return: 0 on success or error code on failure. + */ +static int idpf_map_remaining_mmio_regs(struct idpf_adapter *adapter) +{ + struct resource *rstat_reg = &adapter->dev_ops.static_reg_info[1]; + struct resource *mbx_reg = &adapter->dev_ops.static_reg_info[0]; + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; + resource_size_t reg_start, size; + bool ok = true; + + /* Region preceding mailbox */ + size = mbx_reg->start; + ok &= !size || libie_pci_map_mmio_region(mmio, 0, size); + + /* Region between mailbox and rstat */ + reg_start = mbx_reg->end + 1; + size = rstat_reg->start - reg_start; + ok &= !size || libie_pci_map_mmio_region(mmio, reg_start, size); + + /* Region after rstat */ + reg_start = rstat_reg->end + 1; + size = pci_resource_len(adapter->pdev, 0) - reg_start; + ok &= !size || libie_pci_map_mmio_region(mmio, reg_start, size); + + if (!ok) { + idpf_decfg_lan_memory_regions(adapter); + return -ENOMEM; + } + return 0; } @@ -1160,24 +723,43 @@ int idpf_add_del_fsteer_filters(struct idpf_adapter *adapter, struct virtchnl2_flow_rule_add_del *rule, enum virtchnl2_op opcode) { + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = opcode, + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + }; + struct virtchnl2_flow_rule_add_del *rx_rule; int rule_count = le32_to_cpu(rule->count); - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + size_t send_sz; + int err; if (opcode != VIRTCHNL2_OP_ADD_FLOW_RULE && - opcode != VIRTCHNL2_OP_DEL_FLOW_RULE) + opcode != VIRTCHNL2_OP_DEL_FLOW_RULE) { + kfree(rule); return -EINVAL; + } - xn_params.vc_op = opcode; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.async = false; - xn_params.send_buf.iov_base = rule; - xn_params.send_buf.iov_len = struct_size(rule, rule_info, rule_count); - xn_params.recv_buf.iov_base = rule; - xn_params.recv_buf.iov_len = struct_size(rule, rule_info, rule_count); + send_sz = struct_size(rule, rule_info, rule_count); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rule, send_sz); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - return reply_sz < 0 ? reply_sz : 0; + if (xn_params.recv_mem.iov_len < send_sz) { + err = -EIO; + goto rel_rx; + } + + rx_rule = xn_params.recv_mem.iov_base; + for (int i = 0; i < rule_count; i++) { + if (rx_rule->rule_info[i].status != + cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { + err = -EIO; + goto rel_rx; + } + } + +rel_rx: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -1414,7 +996,7 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_q_vec_rsrc *rsrc, u32 *reg_vals, int num_regs, u32 q_type) { - struct idpf_adapter *adapter = vport->adapter; + struct libie_mmio_info *mmio = &vport->adapter->ctlq_ctx.mmio_info; int i, j, k = 0; switch (q_type) { @@ -1424,7 +1006,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, for (j = 0; j < tx_qgrp->num_txq && k < num_regs; j++, k++) tx_qgrp->txqs[j]->tail = - idpf_get_reg_addr(adapter, reg_vals[k]); + libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } break; case VIRTCHNL2_QUEUE_TYPE_RX: @@ -1436,8 +1019,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_rx_queue *q; q = rx_qgrp->singleq.rxqs[j]; - q->tail = idpf_get_reg_addr(adapter, - reg_vals[k]); + q->tail = libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } } break; @@ -1450,8 +1033,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_buf_queue *q; q = &rx_qgrp->splitq.bufq_sets[j].bufq; - q->tail = idpf_get_reg_addr(adapter, - reg_vals[k]); + q->tail = libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } } break; @@ -1551,21 +1134,19 @@ int idpf_queue_reg_init(struct idpf_vport *vport, int idpf_send_create_vport_msg(struct idpf_adapter *adapter, struct idpf_vport_max_q *max_q) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_CREATE_VPORT, + }; struct virtchnl2_create_vport *vport_msg; - struct idpf_vc_xn_params xn_params = {}; u16 idx = adapter->next_vport; int err, buf_size; - ssize_t reply_sz; buf_size = sizeof(struct virtchnl2_create_vport); - if (!adapter->vport_params_reqd[idx]) { - adapter->vport_params_reqd[idx] = kzalloc(buf_size, - GFP_KERNEL); - if (!adapter->vport_params_reqd[idx]) - return -ENOMEM; - } + vport_msg = kzalloc(buf_size, GFP_KERNEL); + if (!vport_msg) + return -ENOMEM; - vport_msg = adapter->vport_params_reqd[idx]; vport_msg->vport_type = cpu_to_le16(VIRTCHNL2_VPORT_TYPE_DEFAULT); vport_msg->vport_index = cpu_to_le16(idx); @@ -1582,38 +1163,35 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, err = idpf_vport_calc_total_qs(adapter, idx, vport_msg, max_q); if (err) { dev_err(&adapter->pdev->dev, "Enough queues are not available"); - - return err; + goto rel_buf; } if (!adapter->vport_params_recvd[idx]) { - adapter->vport_params_recvd[idx] = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, - GFP_KERNEL); + adapter->vport_params_recvd[idx] = + kzalloc(LIBIE_CTLQ_MAX_BUF_LEN, GFP_KERNEL); if (!adapter->vport_params_recvd[idx]) { err = -ENOMEM; - goto free_vport_params; + goto rel_buf; } } - xn_params.vc_op = VIRTCHNL2_OP_CREATE_VPORT; - xn_params.send_buf.iov_base = vport_msg; - xn_params.send_buf.iov_len = buf_size; - xn_params.recv_buf.iov_base = adapter->vport_params_recvd[idx]; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) { - err = reply_sz; - goto free_vport_params; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, vport_msg, + sizeof(*vport_msg)); + if (err) { + kfree(adapter->vport_params_recvd[idx]); + adapter->vport_params_recvd[idx] = NULL; + return err; } + memcpy(adapter->vport_params_recvd[idx], xn_params.recv_mem.iov_base, + xn_params.recv_mem.iov_len); + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return 0; -free_vport_params: - kfree(adapter->vport_params_recvd[idx]); - adapter->vport_params_recvd[idx] = NULL; - kfree(adapter->vport_params_reqd[idx]); - adapter->vport_params_reqd[idx] = NULL; +rel_buf: + kfree(vport_msg); return err; } @@ -1673,19 +1251,22 @@ int idpf_check_supported_desc_ids(struct idpf_vport *vport) */ int idpf_send_destroy_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DESTROY_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_DESTROY_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -1697,19 +1278,22 @@ int idpf_send_destroy_vport_msg(struct idpf_adapter *adapter, u32 vport_id) */ int idpf_send_enable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ENABLE_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_ENABLE_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -1721,19 +1305,22 @@ int idpf_send_enable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) */ int idpf_send_disable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DISABLE_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_DISABLE_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2572,11 +2159,14 @@ int idpf_send_delete_queues_msg(struct idpf_adapter *adapter, struct idpf_queue_id_reg_info *chunks, u32 vport_id) { - struct virtchnl2_del_ena_dis_queues *eq __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DEL_QUEUES, + }; + struct virtchnl2_del_ena_dis_queues *eq; + ssize_t buf_size; u16 num_chunks; - int buf_size; + int err; num_chunks = chunks->num_chunks; buf_size = struct_size(eq, chunks.chunks, num_chunks); @@ -2591,13 +2181,13 @@ int idpf_send_delete_queues_msg(struct idpf_adapter *adapter, idpf_convert_reg_to_queue_chunks(eq->chunks.chunks, chunks->queue_chunks, num_chunks); - xn_params.vc_op = VIRTCHNL2_OP_DEL_QUEUES; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = eq; - xn_params.send_buf.iov_len = buf_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, eq, buf_size); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2635,15 +2225,14 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, struct idpf_q_vec_rsrc *rsrc, u32 vport_id) { - struct virtchnl2_add_queues *vc_msg __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ADD_QUEUES, + }; + struct virtchnl2_add_queues *vc_msg; struct virtchnl2_add_queues aq = {}; - ssize_t reply_sz; - int size; - - vc_msg = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!vc_msg) - return -ENOMEM; + size_t size; + int err; aq.vport_id = cpu_to_le32(vport_id); aq.num_tx_q = cpu_to_le16(rsrc->num_txq); @@ -2651,29 +2240,38 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, aq.num_rx_q = cpu_to_le16(rsrc->num_rxq); aq.num_rx_bufq = cpu_to_le16(rsrc->num_bufq); - xn_params.vc_op = VIRTCHNL2_OP_ADD_QUEUES; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &aq; - xn_params.send_buf.iov_len = sizeof(aq); - xn_params.recv_buf.iov_base = vc_msg; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &aq); + if (err) + return err; + + vc_msg = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*vc_msg)) { + err = -EIO; + goto free_rx_buf; + } /* compare vc_msg num queues with vport num queues */ if (le16_to_cpu(vc_msg->num_tx_q) != rsrc->num_txq || le16_to_cpu(vc_msg->num_rx_q) != rsrc->num_rxq || le16_to_cpu(vc_msg->num_tx_complq) != rsrc->num_complq || - le16_to_cpu(vc_msg->num_rx_bufq) != rsrc->num_bufq) - return -EINVAL; + le16_to_cpu(vc_msg->num_rx_bufq) != rsrc->num_bufq) { + err = -EINVAL; + goto free_rx_buf; + } size = struct_size(vc_msg, chunks.chunks, le16_to_cpu(vc_msg->chunks.num_chunks)); - if (reply_sz < size) - return -EIO; + if (xn_params.recv_mem.iov_len < size) { + err = -EIO; + goto free_rx_buf; + } - return idpf_vport_init_queue_reg_chunks(vport_config, &vc_msg->chunks); + err = idpf_vport_init_queue_reg_chunks(vport_config, &vc_msg->chunks); + +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2685,49 +2283,51 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, */ int idpf_send_alloc_vectors_msg(struct idpf_adapter *adapter, u16 num_vectors) { - struct virtchnl2_alloc_vectors *rcvd_vec __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ALLOC_VECTORS, + }; + struct virtchnl2_alloc_vectors *rcvd_vec; struct virtchnl2_alloc_vectors ac = {}; - ssize_t reply_sz; u16 num_vchunks; - int size; + int size, err; ac.num_vectors = cpu_to_le16(num_vectors); - rcvd_vec = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcvd_vec) - return -ENOMEM; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &ac); + if (err) + return err; - xn_params.vc_op = VIRTCHNL2_OP_ALLOC_VECTORS; - xn_params.send_buf.iov_base = ∾ - xn_params.send_buf.iov_len = sizeof(ac); - xn_params.recv_buf.iov_base = rcvd_vec; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + rcvd_vec = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*rcvd_vec)) { + err = -EIO; + goto free_rx_buf; + } num_vchunks = le16_to_cpu(rcvd_vec->vchunks.num_vchunks); size = struct_size(rcvd_vec, vchunks.vchunks, num_vchunks); - if (reply_sz < size) - return -EIO; - - if (size > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; + if (xn_params.recv_mem.iov_len < size) { + err = -EIO; + goto free_rx_buf; + } kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = kmemdup(rcvd_vec, size, GFP_KERNEL); - if (!adapter->req_vec_chunks) - return -ENOMEM; + if (!adapter->req_vec_chunks) { + err = -ENOMEM; + goto free_rx_buf; + } if (le16_to_cpu(adapter->req_vec_chunks->num_vectors) < num_vectors) { kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = NULL; - return -EINVAL; + err = -EINVAL; } - return 0; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2739,24 +2339,28 @@ int idpf_send_alloc_vectors_msg(struct idpf_adapter *adapter, u16 num_vectors) int idpf_send_dealloc_vectors_msg(struct idpf_adapter *adapter) { struct virtchnl2_alloc_vectors *ac = adapter->req_vec_chunks; - struct virtchnl2_vector_chunks *vcs = &ac->vchunks; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; - int buf_size; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DEALLOC_VECTORS, + }; + struct virtchnl2_vector_chunks *vcs; + int buf_size, err; - buf_size = struct_size(vcs, vchunks, le16_to_cpu(vcs->num_vchunks)); + buf_size = struct_size(&ac->vchunks, vchunks, + le16_to_cpu(ac->vchunks.num_vchunks)); + vcs = kmemdup(&ac->vchunks, buf_size, GFP_KERNEL); + if (!vcs) + return -ENOMEM; - xn_params.vc_op = VIRTCHNL2_OP_DEALLOC_VECTORS; - xn_params.send_buf.iov_base = vcs; - xn_params.send_buf.iov_len = buf_size; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, vcs, buf_size); + if (err) + return err; kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = NULL; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return 0; } @@ -2780,18 +2384,22 @@ static int idpf_get_max_vfs(struct idpf_adapter *adapter) */ int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_SRIOV_VFS, + }; struct virtchnl2_sriov_vfs_info svi = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; svi.num_vfs = cpu_to_le16(num_vfs); - xn_params.vc_op = VIRTCHNL2_OP_SET_SRIOV_VFS; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &svi; - xn_params.send_buf.iov_len = sizeof(svi); - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - return reply_sz < 0 ? reply_sz : 0; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &svi); + if (err) + return err; + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2804,10 +2412,14 @@ int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs) int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, struct idpf_port_stats *port_stats) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_STATS, + }; struct rtnl_link_stats64 *netstats = &np->netstats; + struct virtchnl2_vport_stats *stats_recv; struct virtchnl2_vport_stats stats_msg = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; /* Don't send get_stats message if the link is down */ @@ -2816,65 +2428,65 @@ int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, stats_msg.vport_id = cpu_to_le32(np->vport_id); - xn_params.vc_op = VIRTCHNL2_OP_GET_STATS; - xn_params.send_buf.iov_base = &stats_msg; - xn_params.send_buf.iov_len = sizeof(stats_msg); - xn_params.recv_buf = xn_params.send_buf; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(np->adapter, &xn_params, &stats_msg); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(np->adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(stats_msg)) - return -EIO; + if (xn_params.recv_mem.iov_len < sizeof(*stats_recv)) { + err = -EIO; + goto free_rx_buf; + } + + stats_recv = xn_params.recv_mem.iov_base; spin_lock_bh(&np->stats_lock); - netstats->rx_packets = le64_to_cpu(stats_msg.rx_unicast) + - le64_to_cpu(stats_msg.rx_multicast) + - le64_to_cpu(stats_msg.rx_broadcast); - netstats->tx_packets = le64_to_cpu(stats_msg.tx_unicast) + - le64_to_cpu(stats_msg.tx_multicast) + - le64_to_cpu(stats_msg.tx_broadcast); - netstats->rx_bytes = le64_to_cpu(stats_msg.rx_bytes); - netstats->tx_bytes = le64_to_cpu(stats_msg.tx_bytes); - netstats->rx_errors = le64_to_cpu(stats_msg.rx_errors); - netstats->tx_errors = le64_to_cpu(stats_msg.tx_errors); - netstats->rx_dropped = le64_to_cpu(stats_msg.rx_discards); - netstats->tx_dropped = le64_to_cpu(stats_msg.tx_discards); + netstats->rx_packets = le64_to_cpu(stats_recv->rx_unicast) + + le64_to_cpu(stats_recv->rx_multicast) + + le64_to_cpu(stats_recv->rx_broadcast); + netstats->tx_packets = le64_to_cpu(stats_recv->tx_unicast) + + le64_to_cpu(stats_recv->tx_multicast) + + le64_to_cpu(stats_recv->tx_broadcast); + netstats->rx_bytes = le64_to_cpu(stats_recv->rx_bytes); + netstats->tx_bytes = le64_to_cpu(stats_recv->tx_bytes); + netstats->rx_errors = le64_to_cpu(stats_recv->rx_errors); + netstats->tx_errors = le64_to_cpu(stats_recv->tx_errors); + netstats->rx_dropped = le64_to_cpu(stats_recv->rx_discards); + netstats->tx_dropped = le64_to_cpu(stats_recv->tx_discards); - port_stats->vport_stats = stats_msg; + port_stats->vport_stats = *stats_recv; spin_unlock_bh(&np->stats_lock); - return 0; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** - * idpf_send_get_set_rss_lut_msg - Send virtchnl get or set RSS lut message + * idpf_send_set_rss_lut_msg - Send virtchnl set RSS lut message * @adapter: adapter pointer used to send virtchnl message * @rss_data: pointer to RSS key and lut info * @vport_id: vport identifier used while preparing the virtchnl message - * @get: flag to set or get RSS look up table * - * When rxhash is disabled, RSS LUT will be configured with zeros. If rxhash + * When rxhash is disabled, RSS LUT will be configured with zeros. If rxhash * is enabled, the LUT values stored in driver's soft copy will be used to setup * the HW. * * Return: 0 on success, negative on failure. */ -int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get) +int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_lut *recv_rl __free(kfree) = NULL; - struct virtchnl2_rss_lut *rl __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; - int buf_size, lut_buf_size; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_RSS_LUT, + }; + struct virtchnl2_rss_lut *rl; struct idpf_vport *vport; - ssize_t reply_sz; + int buf_size, i, err; bool rxhash_ena; - int i; vport = idpf_vid_to_vport(adapter, vport_id); if (!vport) @@ -2888,76 +2500,36 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, return -ENOMEM; rl->vport_id = cpu_to_le32(vport_id); + rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); + for (i = 0; i < rss_data->rss_lut_size; i++) + rl->lut[i] = rxhash_ena ? cpu_to_le32(rss_data->rss_lut[i]) : 0; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = rl; - xn_params.send_buf.iov_len = buf_size; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rl, buf_size); + if (err) + return err; - if (get) { - recv_rl = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!recv_rl) - return -ENOMEM; - xn_params.vc_op = VIRTCHNL2_OP_GET_RSS_LUT; - xn_params.recv_buf.iov_base = recv_rl; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - } else { - rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); - for (i = 0; i < rss_data->rss_lut_size; i++) - rl->lut[i] = rxhash_ena ? - cpu_to_le32(rss_data->rss_lut[i]) : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_LUT; - } - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (!get) - return 0; - if (reply_sz < sizeof(struct virtchnl2_rss_lut)) - return -EIO; - - lut_buf_size = le16_to_cpu(recv_rl->lut_entries) * sizeof(u32); - if (reply_sz < lut_buf_size) - return -EIO; - - /* size didn't change, we can reuse existing lut buf */ - if (rss_data->rss_lut_size == le16_to_cpu(recv_rl->lut_entries)) - goto do_memcpy; - - rss_data->rss_lut_size = le16_to_cpu(recv_rl->lut_entries); - kfree(rss_data->rss_lut); - - rss_data->rss_lut = kzalloc(lut_buf_size, GFP_KERNEL); - if (!rss_data->rss_lut) { - rss_data->rss_lut_size = 0; - return -ENOMEM; - } - -do_memcpy: - memcpy(rss_data->rss_lut, recv_rl->lut, rss_data->rss_lut_size); - - return 0; + return err; } /** - * idpf_send_get_set_rss_key_msg - Send virtchnl get or set RSS key message + * idpf_send_set_rss_key_msg - Send virtchnl set RSS key message * @adapter: adapter pointer used to send virtchnl message * @rss_data: pointer to RSS key and lut info * @vport_id: vport identifier used while preparing the virtchnl message - * @get: flag to set or get RSS look up table * * Return: 0 on success, negative on failure */ -int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get) +int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_key *recv_rk __free(kfree) = NULL; - struct virtchnl2_rss_key *rk __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; - int i, buf_size; - u16 key_size; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_RSS_KEY, + }; + struct virtchnl2_rss_key *rk; + int i, buf_size, err; buf_size = struct_size(rk, key_flex, rss_data->rss_key_size); rk = kzalloc(buf_size, GFP_KERNEL); @@ -2965,54 +2537,17 @@ int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, return -ENOMEM; rk->vport_id = cpu_to_le32(vport_id); - xn_params.send_buf.iov_base = rk; - xn_params.send_buf.iov_len = buf_size; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - if (get) { - recv_rk = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!recv_rk) - return -ENOMEM; + rk->key_len = cpu_to_le16(rss_data->rss_key_size); + for (i = 0; i < rss_data->rss_key_size; i++) + rk->key_flex[i] = rss_data->rss_key[i]; - xn_params.vc_op = VIRTCHNL2_OP_GET_RSS_KEY; - xn_params.recv_buf.iov_base = recv_rk; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - } else { - rk->key_len = cpu_to_le16(rss_data->rss_key_size); - for (i = 0; i < rss_data->rss_key_size; i++) - rk->key_flex[i] = rss_data->rss_key[i]; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rk, buf_size); + if (err) + return err; - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_KEY; - } + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (!get) - return 0; - if (reply_sz < sizeof(struct virtchnl2_rss_key)) - return -EIO; - - key_size = min_t(u16, NETDEV_RSS_KEY_LEN, - le16_to_cpu(recv_rk->key_len)); - if (reply_sz < key_size) - return -EIO; - - /* key len didn't change, reuse existing buf */ - if (rss_data->rss_key_size == key_size) - goto do_memcpy; - - rss_data->rss_key_size = key_size; - kfree(rss_data->rss_key); - rss_data->rss_key = kzalloc(key_size, GFP_KERNEL); - if (!rss_data->rss_key) { - rss_data->rss_key_size = 0; - return -ENOMEM; - } - -do_memcpy: - memcpy(rss_data->rss_key, recv_rk->key_flex, rss_data->rss_key_size); - - return 0; + return err; } /** @@ -3189,60 +2724,63 @@ static void idpf_parse_protocol_ids(struct virtchnl2_ptype *ptype, */ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) { - struct virtchnl2_get_ptype_info *get_ptype_info __free(kfree) = NULL; - struct virtchnl2_get_ptype_info *ptype_info __free(kfree) = NULL; - struct libeth_rx_pt *singleq_pt_lkup __free(kfree) = NULL; - struct libeth_rx_pt *splitq_pt_lkup __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_PTYPE_INFO, + }; + struct virtchnl2_get_ptype_info *get_ptype_info; + struct virtchnl2_get_ptype_info *ptype_info; + int err = 0, max_ptype = IDPF_RX_MAX_PTYPE; + int buf_size = sizeof(*get_ptype_info); + struct libeth_rx_pt *singleq_pt_lkup; + struct libeth_rx_pt *splitq_pt_lkup; int ptypes_recvd = 0, ptype_offset; - u32 max_ptype = IDPF_RX_MAX_PTYPE; u16 next_ptype_id = 0; - ssize_t reply_sz; singleq_pt_lkup = kzalloc_objs(*singleq_pt_lkup, IDPF_RX_MAX_BASE_PTYPE); if (!singleq_pt_lkup) return -ENOMEM; splitq_pt_lkup = kzalloc_objs(*splitq_pt_lkup, max_ptype); - if (!splitq_pt_lkup) - return -ENOMEM; - - get_ptype_info = kzalloc_obj(*get_ptype_info); - if (!get_ptype_info) - return -ENOMEM; - - ptype_info = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!ptype_info) - return -ENOMEM; - - xn_params.vc_op = VIRTCHNL2_OP_GET_PTYPE_INFO; - xn_params.send_buf.iov_base = get_ptype_info; - xn_params.send_buf.iov_len = sizeof(*get_ptype_info); - xn_params.recv_buf.iov_base = ptype_info; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + if (!splitq_pt_lkup) { + err = -ENOMEM; + goto free_singleq; + } while (next_ptype_id < max_ptype) { + u16 num_ptypes; + + get_ptype_info = kzalloc(buf_size, GFP_KERNEL); + if (!get_ptype_info) { + err = -ENOMEM; + goto free_splitq; + } + get_ptype_info->start_ptype_id = cpu_to_le16(next_ptype_id); if ((next_ptype_id + IDPF_RX_MAX_PTYPES_PER_BUF) > max_ptype) - get_ptype_info->num_ptypes = - cpu_to_le16(max_ptype - next_ptype_id); + num_ptypes = max_ptype - next_ptype_id; else - get_ptype_info->num_ptypes = - cpu_to_le16(IDPF_RX_MAX_PTYPES_PER_BUF); + num_ptypes = IDPF_RX_MAX_PTYPES_PER_BUF; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + get_ptype_info->num_ptypes = cpu_to_le16(num_ptypes); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, + get_ptype_info, buf_size); + if (err) + goto free_splitq; + ptype_info = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*ptype_info)) { + err = -EIO; + goto free_rx_buf; + } ptypes_recvd += le16_to_cpu(ptype_info->num_ptypes); - if (ptypes_recvd > max_ptype) - return -EINVAL; - - next_ptype_id = le16_to_cpu(get_ptype_info->start_ptype_id) + - le16_to_cpu(get_ptype_info->num_ptypes); + if (ptypes_recvd > max_ptype) { + err = -EINVAL; + goto free_rx_buf; + } + next_ptype_id = next_ptype_id + num_ptypes; ptype_offset = IDPF_RX_PTYPE_HDR_SZ; for (u16 i = 0; i < le16_to_cpu(ptype_info->num_ptypes); i++) { @@ -3252,19 +2790,28 @@ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) ptype = (struct virtchnl2_ptype *) ((u8 *)ptype_info + ptype_offset); + if (xn_params.recv_mem.iov_len < + ptype_offset + sizeof(struct virtchnl2_ptype)) { + err = -EINVAL; + goto free_rx_buf; + } pt_10 = le16_to_cpu(ptype->ptype_id_10); pt_8 = ptype->ptype_id_8; ptype_offset += IDPF_GET_PTYPE_SIZE(ptype); - if (ptype_offset > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; + if (xn_params.recv_mem.iov_len < ptype_offset) { + err = -EINVAL; + goto free_rx_buf; + } /* 0xFFFF indicates end of ptypes */ if (pt_10 == IDPF_INVALID_PTYPE_ID) goto out; - if (pt_10 >= max_ptype) - return -EINVAL; + if (pt_10 >= max_ptype) { + err = -EINVAL; + goto free_rx_buf; + } idpf_parse_protocol_ids(ptype, &rx_pt); idpf_finalize_ptype_lookup(&rx_pt); @@ -3278,13 +2825,24 @@ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) if (!singleq_pt_lkup[pt_8].outer_ip) singleq_pt_lkup[pt_8] = rx_pt; } + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + xn_params.recv_mem = (struct kvec) {}; } out: - adapter->splitq_pt_lkup = no_free_ptr(splitq_pt_lkup); - adapter->singleq_pt_lkup = no_free_ptr(singleq_pt_lkup); + adapter->splitq_pt_lkup = splitq_pt_lkup; + adapter->singleq_pt_lkup = singleq_pt_lkup; + splitq_pt_lkup = NULL; + singleq_pt_lkup = NULL; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); +free_splitq: + kfree(splitq_pt_lkup); +free_singleq: + kfree(singleq_pt_lkup); - return 0; + return err; } /** @@ -3312,40 +2870,23 @@ static void idpf_rel_rx_pt_lkup(struct idpf_adapter *adapter) int idpf_send_ena_dis_loopback_msg(struct idpf_adapter *adapter, u32 vport_id, bool loopback_ena) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_LOOPBACK, + }; struct virtchnl2_loopback loopback; - ssize_t reply_sz; + int err; loopback.vport_id = cpu_to_le32(vport_id); loopback.enable = loopback_ena; - xn_params.vc_op = VIRTCHNL2_OP_LOOPBACK; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &loopback; - xn_params.send_buf.iov_len = sizeof(loopback); - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &loopback); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; -} + libie_ctlq_release_rx_buf(&xn_params.recv_mem); -/** - * idpf_find_ctlq - Given a type and id, find ctlq info - * @hw: hardware struct - * @type: type of ctrlq to find - * @id: ctlq id to find - * - * Returns pointer to found ctlq info struct, NULL otherwise. - */ -static struct idpf_ctlq_info *idpf_find_ctlq(struct idpf_hw *hw, - enum idpf_ctlq_type type, int id) -{ - struct idpf_ctlq_info *cq, *tmp; - - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - if (cq->q_id == id && cq->cq_type == type) - return cq; - - return NULL; + return 0; } /** @@ -3356,42 +2897,49 @@ static struct idpf_ctlq_info *idpf_find_ctlq(struct idpf_hw *hw, */ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) { - struct idpf_ctlq_create_info ctlq_info[] = { + struct libie_ctlq_ctx *ctx = &adapter->ctlq_ctx; + struct libie_ctlq_create_info ctlq_info[] = { { - .type = IDPF_CTLQ_TYPE_MAILBOX_TX, - .id = IDPF_DFLT_MBX_ID, + .type = LIBIE_CTLQ_TYPE_TX, + .id = LIBIE_CTLQ_MBX_ID, .len = IDPF_DFLT_MBX_Q_LEN, - .buf_size = IDPF_CTLQ_MAX_BUF_LEN }, { - .type = IDPF_CTLQ_TYPE_MAILBOX_RX, - .id = IDPF_DFLT_MBX_ID, + .type = LIBIE_CTLQ_TYPE_RX, + .id = LIBIE_CTLQ_MBX_ID, .len = IDPF_DFLT_MBX_Q_LEN, - .buf_size = IDPF_CTLQ_MAX_BUF_LEN } }; - struct idpf_hw *hw = &adapter->hw; + struct libie_ctlq_xn_init_params params = { + .num_qs = IDPF_NUM_DFLT_MBX_Q, + .cctlq_info = ctlq_info, + .ctx = ctx, + }; int err; - adapter->dev_ops.reg_ops.ctlq_reg_init(adapter, ctlq_info); + adapter->dev_ops.reg_ops.ctlq_reg_init(&ctx->mmio_info, + params.cctlq_info); - err = idpf_ctlq_init(hw, IDPF_NUM_DFLT_MBX_Q, ctlq_info); + err = libie_ctlq_xn_init(¶ms); if (err) return err; - hw->asq = idpf_find_ctlq(hw, IDPF_CTLQ_TYPE_MAILBOX_TX, - IDPF_DFLT_MBX_ID); - hw->arq = idpf_find_ctlq(hw, IDPF_CTLQ_TYPE_MAILBOX_RX, - IDPF_DFLT_MBX_ID); - - if (!hw->asq || !hw->arq) { - idpf_ctlq_deinit(hw); - + adapter->asq = libie_find_ctlq(ctx, LIBIE_CTLQ_TYPE_TX, + LIBIE_CTLQ_MBX_ID); + adapter->arq = libie_find_ctlq(ctx, LIBIE_CTLQ_TYPE_RX, + LIBIE_CTLQ_MBX_ID); + if (!adapter->asq || !adapter->arq) { + adapter->asq = NULL; + adapter->arq = NULL; + libie_ctlq_xn_deinit(params.xnm, ctx); return -ENOENT; } + adapter->xnm = params.xnm; adapter->state = __IDPF_VER_CHECK; + queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); + return 0; } @@ -3401,12 +2949,18 @@ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) */ void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter) { - if (adapter->hw.arq && adapter->hw.asq) { - idpf_mb_clean(adapter, adapter->hw.asq); - idpf_ctlq_deinit(&adapter->hw); + idpf_mb_intr_rel_irq(adapter); + cancel_delayed_work_sync(&adapter->mbx_task); + + if (adapter->xnm) { + libie_ctlq_xn_shutdown(adapter->xnm); + idpf_mb_clean(adapter->asq, true); + libie_ctlq_xn_deinit(adapter->xnm, &adapter->ctlq_ctx); } - adapter->hw.arq = NULL; - adapter->hw.asq = NULL; + + adapter->arq = NULL; + adapter->asq = NULL; + adapter->xnm = NULL; } /** @@ -3419,8 +2973,6 @@ static void idpf_vport_params_buf_rel(struct idpf_adapter *adapter) { kfree(adapter->vport_params_recvd); adapter->vport_params_recvd = NULL; - kfree(adapter->vport_params_reqd); - adapter->vport_params_reqd = NULL; kfree(adapter->vport_ids); adapter->vport_ids = NULL; } @@ -3435,15 +2987,10 @@ static int idpf_vport_params_buf_alloc(struct idpf_adapter *adapter) { u16 num_max_vports = idpf_get_max_vports(adapter); - adapter->vport_params_reqd = kzalloc_objs(*adapter->vport_params_reqd, - num_max_vports); - if (!adapter->vport_params_reqd) - return -ENOMEM; - adapter->vport_params_recvd = kzalloc_objs(*adapter->vport_params_recvd, num_max_vports); if (!adapter->vport_params_recvd) - goto err_mem; + return -ENOMEM; adapter->vport_ids = kcalloc(num_max_vports, sizeof(u32), GFP_KERNEL); if (!adapter->vport_ids) @@ -3484,15 +3031,6 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) u16 num_max_vports; int err = 0; - if (!adapter->vcxn_mngr) { - adapter->vcxn_mngr = kzalloc_obj(*adapter->vcxn_mngr); - if (!adapter->vcxn_mngr) { - err = -ENOMEM; - goto init_failed; - } - } - idpf_vc_xn_init(adapter->vcxn_mngr); - while (adapter->state != __IDPF_INIT_SW) { switch (adapter->state) { case __IDPF_VER_CHECK: @@ -3531,34 +3069,29 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) } if (idpf_is_cap_ena(adapter, IDPF_OTHER_CAPS, VIRTCHNL2_CAP_LAN_MEMORY_REGIONS)) { - err = idpf_send_get_lan_memory_regions(adapter); + err = idpf_cfg_lan_memory_regions(adapter); if (err) { - dev_err(&adapter->pdev->dev, "Failed to get LAN memory regions: %d\n", + dev_err(&adapter->pdev->dev, "Failed to configure LAN memory regions: %d\n", err); return -EINVAL; } } else { /* Fallback to mapping the remaining regions of the entire BAR */ - err = idpf_calc_remaining_mmio_regs(adapter); + err = idpf_map_remaining_mmio_regs(adapter); if (err) { - dev_err(&adapter->pdev->dev, "Failed to allocate BAR0 region(s): %d\n", + dev_err(&adapter->pdev->dev, "Failed to configure BAR0 region(s): %d\n", err); - return -ENOMEM; + return err; } } - err = idpf_map_lan_mmio_regs(adapter); - if (err) { - dev_err(&adapter->pdev->dev, "Failed to map BAR0 region(s): %d\n", - err); - return -ENOMEM; - } - pci_sriov_set_totalvfs(adapter->pdev, idpf_get_max_vfs(adapter)); num_max_vports = idpf_get_max_vports(adapter); adapter->vports = kzalloc_objs(*adapter->vports, num_max_vports); - if (!adapter->vports) - return -ENOMEM; + if (!adapter->vports) { + err = -ENOMEM; + goto decfg_regions; + } if (!adapter->netdevs) { adapter->netdevs = kzalloc_objs(struct net_device *, @@ -3630,6 +3163,8 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) err_netdev_alloc: kfree(adapter->vports); adapter->vports = NULL; +decfg_regions: + idpf_decfg_lan_memory_regions(adapter); return err; init_failed: @@ -3647,8 +3182,7 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) * the mailbox again */ adapter->state = __IDPF_VER_CHECK; - if (adapter->vcxn_mngr) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); set_bit(IDPF_HR_DRV_LOAD, adapter->flags); queue_delayed_work(adapter->vc_event_wq, &adapter->vc_event_task, msecs_to_jiffies(task_delay)); @@ -3663,7 +3197,6 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) */ void idpf_vc_core_deinit(struct idpf_adapter *adapter) { - struct idpf_hw *hw = &adapter->hw; bool remove_in_prog; if (!test_bit(IDPF_VC_CORE_INIT, adapter->flags)) @@ -3672,7 +3205,7 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) /* Avoid transaction timeouts when called during reset */ remove_in_prog = test_bit(IDPF_REMOVE_IN_PROG, adapter->flags); if (!remove_in_prog) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); idpf_ptp_release(adapter); idpf_deinit_task(adapter); @@ -3681,19 +3214,17 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) idpf_intr_rel(adapter); if (remove_in_prog) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); cancel_delayed_work_sync(&adapter->serv_task); cancel_delayed_work_sync(&adapter->mbx_task); idpf_vport_params_buf_rel(adapter); - kfree(hw->lan_regs); - hw->lan_regs = NULL; - kfree(adapter->vports); adapter->vports = NULL; + idpf_decfg_lan_memory_regions(adapter); clear_bit(IDPF_VC_CORE_INIT, adapter->flags); } @@ -4220,9 +3751,9 @@ static void idpf_set_mac_type(const u8 *default_mac_addr, /** * idpf_mac_filter_async_handler - Async callback for mac filters - * @adapter: private data struct - * @xn: transaction for message - * @ctlq_msg: received message + * @ctx: controlq context structure + * @buff: response buffer pointer and size + * @status: async call return value * * In some scenarios driver can't sleep and wait for a reply (e.g.: stack is * holding rtnl_lock) when adding a new mac filter. It puts us in a difficult @@ -4230,13 +3761,14 @@ static void idpf_set_mac_type(const u8 *default_mac_addr, * ultimately do is remove it from our list of mac filters and report the * error. */ -static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, - struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) +static void idpf_mac_filter_async_handler(void *ctx, + struct kvec *buff, + int status) { struct virtchnl2_mac_addr_list *ma_list; struct idpf_vport_config *vport_config; struct virtchnl2_mac_addr *mac_addr; + struct idpf_adapter *adapter = ctx; struct idpf_mac_filter *f, *tmp; struct list_head *ma_list_head; struct idpf_vport *vport; @@ -4244,18 +3776,18 @@ static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, int i; /* if success we're done, we're only here if something bad happened */ - if (!ctlq_msg->cookie.mbx.chnl_retval) - return 0; + if (!status || status == -ETIMEDOUT) + return; + ma_list = buff->iov_base; /* make sure at least struct is there */ - if (xn->reply_sz < sizeof(*ma_list)) + if (buff->iov_len < sizeof(*ma_list)) goto invalid_payload; - ma_list = ctlq_msg->ctx.indirect.payload->va; mac_addr = ma_list->mac_addr_list; num_entries = le16_to_cpu(ma_list->num_mac_addr); /* we should have received a buffer at least this big */ - if (xn->reply_sz < struct_size(ma_list, mac_addr_list, num_entries)) + if (buff->iov_len < struct_size(ma_list, mac_addr_list, num_entries)) goto invalid_payload; vport = idpf_vid_to_vport(adapter, le32_to_cpu(ma_list->vport_id)); @@ -4275,16 +3807,13 @@ static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, if (ether_addr_equal(mac_addr[i].addr, f->macaddr)) list_del(&f->list); spin_unlock_bh(&vport_config->mac_filter_list_lock); - dev_err_ratelimited(&adapter->pdev->dev, "Received error sending MAC filter request (op %d)\n", - xn->vc_op); - - return 0; + dev_err_ratelimited(&adapter->pdev->dev, "Received error %d on sending MAC filter request\n", + status); + return; invalid_payload: - dev_err_ratelimited(&adapter->pdev->dev, "Received invalid MAC filter payload (op %d) (len %zd)\n", - xn->vc_op, xn->reply_sz); - - return -EINVAL; + dev_err_ratelimited(&adapter->pdev->dev, "Received invalid MAC filter payload (len %zd)\n", + buff->iov_len); } /** @@ -4303,19 +3832,21 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, const u8 *default_mac_addr, u32 vport_id, bool add, bool async) { - struct virtchnl2_mac_addr_list *ma_list __free(kfree) = NULL; struct virtchnl2_mac_addr *mac_addr __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = add ? VIRTCHNL2_OP_ADD_MAC_ADDR : + VIRTCHNL2_OP_DEL_MAC_ADDR, + }; + struct virtchnl2_mac_addr_list *ma_list; u32 num_msgs, total_filters = 0; struct idpf_mac_filter *f; - ssize_t reply_sz; - int i = 0, k; + int i = 0; - xn_params.vc_op = add ? VIRTCHNL2_OP_ADD_MAC_ADDR : - VIRTCHNL2_OP_DEL_MAC_ADDR; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.async = async; - xn_params.async_handler = idpf_mac_filter_async_handler; + if (async) { + xn_params.resp_cb = idpf_mac_filter_async_handler; + xn_params.send_ctx = adapter; + } spin_lock_bh(&vport_config->mac_filter_list_lock); @@ -4370,32 +3901,31 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, */ num_msgs = DIV_ROUND_UP(total_filters, IDPF_NUM_FILTERS_PER_MSG); - for (i = 0, k = 0; i < num_msgs; i++) { - u32 entries_size, buf_size, num_entries; + for (u32 i = 0, k = 0; i < num_msgs; i++) { + u32 entries_size, num_entries; + size_t buf_size; + int err; num_entries = min_t(u32, total_filters, IDPF_NUM_FILTERS_PER_MSG); entries_size = sizeof(struct virtchnl2_mac_addr) * num_entries; buf_size = struct_size(ma_list, mac_addr_list, num_entries); - if (!ma_list || num_entries != IDPF_NUM_FILTERS_PER_MSG) { - kfree(ma_list); - ma_list = kzalloc(buf_size, GFP_ATOMIC); - if (!ma_list) - return -ENOMEM; - } else { - memset(ma_list, 0, buf_size); - } + ma_list = kzalloc(buf_size, GFP_ATOMIC); + if (!ma_list) + return -ENOMEM; ma_list->vport_id = cpu_to_le32(vport_id); ma_list->num_mac_addr = cpu_to_le16(num_entries); memcpy(ma_list->mac_addr_list, &mac_addr[k], entries_size); - xn_params.send_buf.iov_base = ma_list; - xn_params.send_buf.iov_len = buf_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, ma_list, + buf_size); + if (err) + return err; + + if (!async) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); k += num_entries; total_filters -= num_entries; @@ -4404,6 +3934,26 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, return 0; } +/** + * idpf_promiscuous_async_handler - async callback for promiscuous mode + * @ctx: controlq context structure + * @buff: response buffer pointer and size + * @status: async call return value + * + * Nobody is waiting for the promiscuous virtchnl message response. Print + * an error message if something went wrong and return. + */ +static void idpf_promiscuous_async_handler(void *ctx, + struct kvec *buff, + int status) +{ + struct idpf_adapter *adapter = ctx; + + if (status) + dev_err_ratelimited(&adapter->pdev->dev, "Failed to set promiscuous mode: %d\n", + status); +} + /** * idpf_set_promiscuous - set promiscuous and send message to mailbox * @adapter: Driver specific private structure @@ -4418,9 +3968,13 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, struct idpf_vport_user_config_data *config_data, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_CONFIG_PROMISCUOUS_MODE, + .resp_cb = idpf_promiscuous_async_handler, + .send_ctx = adapter, + }; struct virtchnl2_promisc_info vpi; - ssize_t reply_sz; u16 flags = 0; if (test_bit(__IDPF_PROMISC_UC, config_data->user_flags)) @@ -4431,15 +3985,7 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, vpi.vport_id = cpu_to_le32(vport_id); vpi.flags = cpu_to_le16(flags); - xn_params.vc_op = VIRTCHNL2_OP_CONFIG_PROMISCUOUS_MODE; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &vpi; - xn_params.send_buf.iov_len = sizeof(vpi); - /* setting promiscuous is only ever done asynchronously */ - xn_params.async = true; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - - return reply_sz < 0 ? reply_sz : 0; + return idpf_send_mb_msg_stack(adapter, &xn_params, &vpi); } /** @@ -4448,7 +3994,7 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, * @send_msg: message to send * @msg_size: size of message to send * @recv_msg: message to populate on reception of response - * @recv_len: length of message copied into recv_msg or 0 on error + * @recv_len: on input, maximum response size; on success, actual response size * * Return: 0 on success or error code on failure. */ @@ -4457,26 +4003,39 @@ int idpf_idc_rdma_vc_send_sync(struct iidc_rdma_core_dev_info *cdev_info, u8 *recv_msg, u16 *recv_len) { struct idpf_adapter *adapter = pci_get_drvdata(cdev_info->pdev); - struct idpf_vc_xn_params xn_params = { }; - ssize_t reply_sz; - u16 recv_size; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_RDMA, + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + }; + u8 on_stack_buf[LIBIE_CP_TX_COPYBREAK]; + void *send_buf; + int err; - if (!recv_msg || !recv_len || msg_size > IDPF_CTLQ_MAX_BUF_LEN) + if (!recv_msg || !recv_len || msg_size > LIBIE_CTLQ_MAX_BUF_LEN) return -EINVAL; - recv_size = min_t(u16, *recv_len, IDPF_CTLQ_MAX_BUF_LEN); - *recv_len = 0; - xn_params.vc_op = VIRTCHNL2_OP_RDMA; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = send_msg; - xn_params.send_buf.iov_len = msg_size; - xn_params.recv_buf.iov_base = recv_msg; - xn_params.recv_buf.iov_len = recv_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - *recv_len = reply_sz; + if (!libie_cp_can_send_onstack(msg_size)) { + send_buf = kzalloc(msg_size, GFP_KERNEL); + if (!send_buf) + return -ENOMEM; + } else { + send_buf = on_stack_buf; + } - return 0; + memcpy(send_buf, send_msg, msg_size); + err = idpf_send_mb_msg(adapter, &xn_params, send_buf, msg_size); + if (err) + return err; + + if (xn_params.recv_mem.iov_len > *recv_len) { + err = -EINVAL; + goto rel_buf; + } + + *recv_len = xn_params.recv_mem.iov_len; + memcpy(recv_msg, xn_params.recv_mem.iov_base, *recv_len); +rel_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } EXPORT_SYMBOL_GPL(idpf_idc_rdma_vc_send_sync); diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index 9b1c9c86f6ea..5d27805ff40f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -4,89 +4,9 @@ #ifndef _IDPF_VIRTCHNL_H_ #define _IDPF_VIRTCHNL_H_ -#include "virtchnl2.h" +#include #define IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC (60 * 1000) -#define IDPF_VC_XN_IDX_M GENMASK(7, 0) -#define IDPF_VC_XN_SALT_M GENMASK(15, 8) -#define IDPF_VC_XN_RING_LEN U8_MAX - -/** - * enum idpf_vc_xn_state - Virtchnl transaction status - * @IDPF_VC_XN_IDLE: not expecting a reply, ready to be used - * @IDPF_VC_XN_WAITING: expecting a reply, not yet received - * @IDPF_VC_XN_COMPLETED_SUCCESS: a reply was expected and received, buffer - * updated - * @IDPF_VC_XN_COMPLETED_FAILED: a reply was expected and received, but there - * was an error, buffer not updated - * @IDPF_VC_XN_SHUTDOWN: transaction object cannot be used, VC torn down - * @IDPF_VC_XN_ASYNC: transaction sent asynchronously and doesn't have the - * return context; a callback may be provided to handle - * return - */ -enum idpf_vc_xn_state { - IDPF_VC_XN_IDLE = 1, - IDPF_VC_XN_WAITING, - IDPF_VC_XN_COMPLETED_SUCCESS, - IDPF_VC_XN_COMPLETED_FAILED, - IDPF_VC_XN_SHUTDOWN, - IDPF_VC_XN_ASYNC, -}; - -struct idpf_vc_xn; -/* Callback for asynchronous messages */ -typedef int (*async_vc_cb) (struct idpf_adapter *, struct idpf_vc_xn *, - const struct idpf_ctlq_msg *); - -/** - * struct idpf_vc_xn - Data structure representing virtchnl transactions - * @completed: virtchnl event loop uses that to signal when a reply is - * available, uses kernel completion API - * @lock: protects the transaction state fields below - * @state: virtchnl event loop stores the data below, protected by @lock - * @reply_sz: Original size of reply, may be > reply_buf.iov_len; it will be - * truncated on its way to the receiver thread according to - * reply_buf.iov_len. - * @reply: Reference to the buffer(s) where the reply data should be written - * to. May be 0-length (then NULL address permitted) if the reply data - * should be ignored. - * @async_handler: if sent asynchronously, a callback can be provided to handle - * the reply when it's received - * @vc_op: corresponding opcode sent with this transaction - * @idx: index used as retrieval on reply receive, used for cookie - * @salt: changed every message to make unique, used for cookie - */ -struct idpf_vc_xn { - struct completion completed; - spinlock_t lock; - enum idpf_vc_xn_state state; - size_t reply_sz; - struct kvec reply; - async_vc_cb async_handler; - u32 vc_op; - u8 idx; - u8 salt; -}; - -/** - * struct idpf_vc_xn_params - Parameters for executing transaction - * @send_buf: kvec for send buffer - * @recv_buf: kvec for recv buffer, may be NULL, must then have zero length - * @timeout_ms: timeout to wait for reply - * @async: send message asynchronously, will not wait on completion - * @async_handler: If sent asynchronously, optional callback handler. The user - * must be careful when using async handlers as the memory for - * the recv_buf _cannot_ be on stack if this is async. - * @vc_op: virtchnl op to send - */ -struct idpf_vc_xn_params { - struct kvec send_buf; - struct kvec recv_buf; - int timeout_ms; - bool async; - async_vc_cb async_handler; - u32 vc_op; -}; struct idpf_adapter; struct idpf_netdev_priv; @@ -96,8 +16,6 @@ struct idpf_vport_max_q; struct idpf_vport_config; struct idpf_vport_user_config_data; -ssize_t idpf_vc_xn_exec(struct idpf_adapter *adapter, - const struct idpf_vc_xn_params *params); int idpf_init_dflt_mbx(struct idpf_adapter *adapter); void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter); int idpf_vc_core_init(struct idpf_adapter *adapter); @@ -124,9 +42,35 @@ bool idpf_sideband_action_ena(struct idpf_vport *vport, struct ethtool_rx_flow_spec *fsp); unsigned int idpf_fsteer_max_rules(struct idpf_vport *vport); -int idpf_recv_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *arq); -int idpf_send_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *asq, - u32 op, u16 msg_size, u8 *msg, u16 cookie); +void idpf_recv_event_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *ctlq_msg); +int idpf_send_mb_msg(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size); +int idpf_send_mb_msg_kfree(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size); +void idpf_send_vf_reset_msg(struct idpf_adapter *adapter); +bool idpf_mmio_region_non_static(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg); + +/** + * idpf_send_mb_msg_stack - send a mailbox message from an on-stack buffer + * @adapter: driver specific private structure + * @xn_params: Xn send parameters to fill + * @ptr: pointer to the on-stack message object to send + * + * Send size is deduced based on the pointer type. + * + * Return: %0 on success, -%errno on failure. + */ +#define idpf_send_mb_msg_stack(adapter, xn_params, ptr) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + \ + static_assert(sizeof(*__ptr) <= LIBIE_CP_TX_COPYBREAK); \ + idpf_send_mb_msg(adapter, xn_params, __ptr, sizeof(*__ptr)); \ +}) struct idpf_queue_ptr { enum virtchnl2_queue_type type; @@ -208,13 +152,10 @@ int idpf_send_ena_dis_loopback_msg(struct idpf_adapter *adapter, u32 vport_id, int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, struct idpf_port_stats *port_stats); int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs); -int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get); -int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get); -void idpf_vc_xn_shutdown(struct idpf_vc_xn_manager *vcxn_mngr); +int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id); +int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id); int idpf_idc_rdma_vc_send_sync(struct iidc_rdma_core_dev_info *cdev_info, u8 *send_msg, u16 msg_size, u8 *recv_msg, u16 *recv_len); diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c index d9bcc3f61c65..14dba40a993f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c @@ -15,7 +15,6 @@ */ int idpf_ptp_get_caps(struct idpf_adapter *adapter) { - struct virtchnl2_ptp_get_caps *recv_ptp_caps_msg __free(kfree) = NULL; struct virtchnl2_ptp_get_caps send_ptp_caps_msg = { .caps = cpu_to_le32(VIRTCHNL2_CAP_PTP_GET_DEVICE_CLK_TIME | VIRTCHNL2_CAP_PTP_GET_DEVICE_CLK_TIME_MB | @@ -24,33 +23,33 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) VIRTCHNL2_CAP_PTP_ADJ_DEVICE_CLK_MB | VIRTCHNL2_CAP_PTP_TX_TSTAMPS_MB) }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_CAPS, - .send_buf.iov_base = &send_ptp_caps_msg, - .send_buf.iov_len = sizeof(send_ptp_caps_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_CAPS, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; struct virtchnl2_ptp_cross_time_reg_offsets cross_tstamp_offsets; + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct virtchnl2_ptp_clk_adj_reg_offsets clk_adj_offsets; struct virtchnl2_ptp_clk_reg_offsets clock_offsets; + struct virtchnl2_ptp_get_caps *recv_ptp_caps_msg; struct idpf_ptp_secondary_mbx *scnd_mbx; struct idpf_ptp *ptp = adapter->ptp; enum idpf_ptp_access access_type; u32 temp_offset; - int reply_sz; + size_t reply_sz; + int err; - recv_ptp_caps_msg = kzalloc_obj(struct virtchnl2_ptp_get_caps); - if (!recv_ptp_caps_msg) - return -ENOMEM; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &send_ptp_caps_msg); + if (err) + return err; - xn_params.recv_buf.iov_base = recv_ptp_caps_msg; - xn_params.recv_buf.iov_len = sizeof(*recv_ptp_caps_msg); + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz != sizeof(*recv_ptp_caps_msg)) { + err = -EIO; + goto free_resp; + } - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - else if (reply_sz != sizeof(*recv_ptp_caps_msg)) - return -EIO; + recv_ptp_caps_msg = xn_params.recv_mem.iov_base; ptp->caps = le32_to_cpu(recv_ptp_caps_msg->caps); ptp->base_incval = le64_to_cpu(recv_ptp_caps_msg->base_incval); @@ -76,19 +75,20 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) clock_offsets = recv_ptp_caps_msg->clk_offsets; temp_offset = le32_to_cpu(clock_offsets.dev_clk_ns_l); - ptp->dev_clk_regs.dev_clk_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.dev_clk_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.dev_clk_ns_h); - ptp->dev_clk_regs.dev_clk_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.dev_clk_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.phy_clk_ns_l); - ptp->dev_clk_regs.phy_clk_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_clk_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.phy_clk_ns_h); - ptp->dev_clk_regs.phy_clk_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_clk_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.cmd_sync_trigger); - ptp->dev_clk_regs.cmd_sync = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd_sync = + libie_pci_get_mmio_addr(mmio, temp_offset); cross_tstamp: access_type = ptp->get_cross_tstamp_access; @@ -98,48 +98,54 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) cross_tstamp_offsets = recv_ptp_caps_msg->cross_time_offsets; temp_offset = le32_to_cpu(cross_tstamp_offsets.sys_time_ns_l); - ptp->dev_clk_regs.sys_time_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.sys_time_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(cross_tstamp_offsets.sys_time_ns_h); - ptp->dev_clk_regs.sys_time_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.sys_time_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(cross_tstamp_offsets.cmd_sync_trigger); - ptp->dev_clk_regs.cmd_sync = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd_sync = + libie_pci_get_mmio_addr(mmio, temp_offset); discipline_clock: access_type = ptp->adj_dev_clk_time_access; if (access_type != IDPF_PTP_DIRECT) - return 0; + goto free_resp; clk_adj_offsets = recv_ptp_caps_msg->clk_adj_offsets; /* Device clock offsets */ temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_cmd_type); - ptp->dev_clk_regs.cmd = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_incval_l); - ptp->dev_clk_regs.incval_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.incval_l = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_incval_h); - ptp->dev_clk_regs.incval_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.incval_h = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_shadj_l); - ptp->dev_clk_regs.shadj_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.shadj_l = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_shadj_h); - ptp->dev_clk_regs.shadj_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.shadj_h = libie_pci_get_mmio_addr(mmio, temp_offset); /* PHY clock offsets */ temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_cmd_type); - ptp->dev_clk_regs.phy_cmd = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_cmd = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_incval_l); - ptp->dev_clk_regs.phy_incval_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_incval_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_incval_h); - ptp->dev_clk_regs.phy_incval_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_incval_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_shadj_l); - ptp->dev_clk_regs.phy_shadj_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_shadj_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_shadj_h); - ptp->dev_clk_regs.phy_shadj_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_shadj_h = + libie_pci_get_mmio_addr(mmio, temp_offset); - return 0; +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -154,28 +160,34 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) int idpf_ptp_get_dev_clk_time(struct idpf_adapter *adapter, struct idpf_ptp_dev_timers *dev_clk_time) { + struct virtchnl2_ptp_get_dev_clk_time *get_dev_clk_time_resp; struct virtchnl2_ptp_get_dev_clk_time get_dev_clk_time_msg; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_DEV_CLK_TIME, - .send_buf.iov_base = &get_dev_clk_time_msg, - .send_buf.iov_len = sizeof(get_dev_clk_time_msg), - .recv_buf.iov_base = &get_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(get_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + size_t reply_sz; u64 dev_time; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(get_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &get_dev_clk_time_msg); + if (err) + return err; - dev_time = le64_to_cpu(get_dev_clk_time_msg.dev_time_ns); + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz != sizeof(*get_dev_clk_time_resp)) { + err = -EIO; + goto free_resp; + } + + get_dev_clk_time_resp = xn_params.recv_mem.iov_base; + dev_time = le64_to_cpu(get_dev_clk_time_resp->dev_time_ns); dev_clk_time->dev_clk_time_ns = dev_time; - return 0; +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -191,27 +203,29 @@ int idpf_ptp_get_dev_clk_time(struct idpf_adapter *adapter, int idpf_ptp_get_cross_time(struct idpf_adapter *adapter, struct idpf_ptp_dev_timers *cross_time) { - struct virtchnl2_ptp_get_cross_time cross_time_msg; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_CROSS_TIME, - .send_buf.iov_base = &cross_time_msg, - .send_buf.iov_len = sizeof(cross_time_msg), - .recv_buf.iov_base = &cross_time_msg, - .recv_buf.iov_len = sizeof(cross_time_msg), + struct virtchnl2_ptp_get_cross_time cross_time_send, *cross_time_recv; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_CROSS_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err = 0; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(cross_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &cross_time_send); + if (err) + return err; - cross_time->dev_clk_time_ns = le64_to_cpu(cross_time_msg.dev_time_ns); - cross_time->sys_time_ns = le64_to_cpu(cross_time_msg.sys_time_ns); + if (xn_params.recv_mem.iov_len != sizeof(*cross_time_recv)) { + err = -EIO; + goto free_resp; + } - return 0; + cross_time_recv = xn_params.recv_mem.iov_base; + cross_time->dev_clk_time_ns = le64_to_cpu(cross_time_recv->dev_time_ns); + cross_time->sys_time_ns = le64_to_cpu(cross_time_recv->sys_time_ns); + +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -228,23 +242,18 @@ int idpf_ptp_set_dev_clk_time(struct idpf_adapter *adapter, u64 time) struct virtchnl2_ptp_set_dev_clk_time set_dev_clk_time_msg = { .dev_time_ns = cpu_to_le64(time), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_SET_DEV_CLK_TIME, - .send_buf.iov_base = &set_dev_clk_time_msg, - .send_buf.iov_len = sizeof(set_dev_clk_time_msg), - .recv_buf.iov_base = &set_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(set_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_SET_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(set_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &set_dev_clk_time_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -261,23 +270,18 @@ int idpf_ptp_adj_dev_clk_time(struct idpf_adapter *adapter, s64 delta) struct virtchnl2_ptp_adj_dev_clk_time adj_dev_clk_time_msg = { .delta = cpu_to_le64(delta), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_TIME, - .send_buf.iov_base = &adj_dev_clk_time_msg, - .send_buf.iov_len = sizeof(adj_dev_clk_time_msg), - .recv_buf.iov_base = &adj_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(adj_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(adj_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &adj_dev_clk_time_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -295,23 +299,18 @@ int idpf_ptp_adj_dev_clk_fine(struct idpf_adapter *adapter, u64 incval) struct virtchnl2_ptp_adj_dev_clk_fine adj_dev_clk_fine_msg = { .incval = cpu_to_le64(incval), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_FINE, - .send_buf.iov_base = &adj_dev_clk_fine_msg, - .send_buf.iov_len = sizeof(adj_dev_clk_fine_msg), - .recv_buf.iov_base = &adj_dev_clk_fine_msg, - .recv_buf.iov_len = sizeof(adj_dev_clk_fine_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_FINE, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(adj_dev_clk_fine_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &adj_dev_clk_fine_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -330,18 +329,16 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) struct virtchnl2_ptp_tx_tstamp_latch_caps tx_tstamp_latch_caps; struct idpf_ptp_vport_tx_tstamp_caps *tstamp_caps; struct idpf_ptp_tx_tstamp *ptp_tx_tstamp, *tmp; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP_CAPS, - .send_buf.iov_base = &send_tx_tstamp_caps, - .send_buf.iov_len = sizeof(send_tx_tstamp_caps), - .recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN, + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP_CAPS, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; enum idpf_ptp_access tstamp_access, get_dev_clk_access; struct idpf_ptp *ptp = vport->adapter->ptp; struct list_head *head; - int err = 0, reply_sz; + size_t reply_sz; u16 num_latches; + int err = 0; u32 size; if (!ptp) @@ -353,19 +350,19 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) get_dev_clk_access == IDPF_PTP_NONE) return -EOPNOTSUPP; - rcv_tx_tstamp_caps = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcv_tx_tstamp_caps) - return -ENOMEM; - send_tx_tstamp_caps.vport_id = cpu_to_le32(vport->vport_id); - xn_params.recv_buf.iov_base = rcv_tx_tstamp_caps; - reply_sz = idpf_vc_xn_exec(vport->adapter, &xn_params); - if (reply_sz < 0) { - err = reply_sz; + err = idpf_send_mb_msg_stack(vport->adapter, &xn_params, + &send_tx_tstamp_caps); + if (err) + return err; + + rcv_tx_tstamp_caps = xn_params.recv_mem.iov_base; + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz < sizeof(*rcv_tx_tstamp_caps)) { + err = -EIO; goto get_tstamp_caps_out; } - num_latches = le16_to_cpu(rcv_tx_tstamp_caps->num_latches); size = struct_size(rcv_tx_tstamp_caps, tstamp_latches, num_latches); if (reply_sz != size) { @@ -420,7 +417,7 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) } vport->tx_tstamp_caps = tstamp_caps; - kfree(rcv_tx_tstamp_caps); + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return 0; @@ -433,7 +430,7 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) kfree(tstamp_caps); get_tstamp_caps_out: - kfree(rcv_tx_tstamp_caps); + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return err; } @@ -530,9 +527,9 @@ idpf_ptp_get_tstamp_value(struct idpf_vport *vport, /** * idpf_ptp_get_tx_tstamp_async_handler - Async callback for getting Tx tstamps - * @adapter: Driver specific private structure - * @xn: transaction for message - * @ctlq_msg: received message + * @ctx: adapter pointer + * @mem: address and size of the response + * @status: return value of the request * * Read the tstamps Tx tstamp values from a received message and put them * directly to the skb. The number of timestamps to read is specified by @@ -540,22 +537,26 @@ idpf_ptp_get_tstamp_value(struct idpf_vport *vport, * * Return: 0 on success, -errno otherwise. */ -static int -idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, - struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) +static void +idpf_ptp_get_tx_tstamp_async_handler(void *ctx, struct kvec *mem, int status) { struct virtchnl2_ptp_get_vport_tx_tstamp_latches *recv_tx_tstamp_msg; struct idpf_ptp_vport_tx_tstamp_caps *tx_tstamp_caps; struct virtchnl2_ptp_tx_tstamp_latch tstamp_latch; struct idpf_ptp_tx_tstamp *tx_tstamp, *tmp; struct idpf_vport *tstamp_vport = NULL; + struct idpf_adapter *adapter = ctx; struct list_head *head; u16 num_latches; u32 vport_id; - int err = 0; - recv_tx_tstamp_msg = ctlq_msg->ctx.indirect.payload->va; + if (status) + return; + + recv_tx_tstamp_msg = mem->iov_base; + if (mem->iov_len < sizeof(*recv_tx_tstamp_msg)) + return; + vport_id = le32_to_cpu(recv_tx_tstamp_msg->vport_id); idpf_for_each_vport(adapter, vport) { @@ -569,10 +570,13 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, } if (!tstamp_vport || !tstamp_vport->tx_tstamp_caps) - return -EINVAL; + return; tx_tstamp_caps = tstamp_vport->tx_tstamp_caps; num_latches = le16_to_cpu(recv_tx_tstamp_msg->num_latches); + if (mem->iov_len < struct_size(recv_tx_tstamp_msg, tstamp_latches, + num_latches)) + return; spin_lock_bh(&tx_tstamp_caps->latches_lock); head = &tx_tstamp_caps->latches_in_use; @@ -583,13 +587,13 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, if (!tstamp_latch.valid) continue; - if (list_empty(head)) { - err = -ENOBUFS; + if (list_empty(head)) goto unlock; - } list_for_each_entry_safe(tx_tstamp, tmp, head, list_member) { if (tstamp_latch.index == tx_tstamp->idx) { + int err; + list_del(&tx_tstamp->list_member); err = idpf_ptp_get_tstamp_value(tstamp_vport, &tstamp_latch, @@ -604,8 +608,6 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, unlock: spin_unlock_bh(&tx_tstamp_caps->latches_lock); - - return err; } /** @@ -621,15 +623,15 @@ int idpf_ptp_get_tx_tstamp(struct idpf_vport *vport) { struct virtchnl2_ptp_get_vport_tx_tstamp_latches *send_tx_tstamp_msg; struct idpf_ptp_vport_tx_tstamp_caps *tx_tstamp_caps; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP, + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, - .async = true, - .async_handler = idpf_ptp_get_tx_tstamp_async_handler, + .resp_cb = idpf_ptp_get_tx_tstamp_async_handler, + .send_ctx = vport->adapter, }; struct idpf_ptp_tx_tstamp *ptp_tx_tstamp; - int reply_sz, size, msg_size; struct list_head *head; + int size, msg_size; bool state_upd; u16 id = 0; @@ -662,11 +664,7 @@ int idpf_ptp_get_tx_tstamp(struct idpf_vport *vport) msg_size = struct_size(send_tx_tstamp_msg, tstamp_latches, id); send_tx_tstamp_msg->vport_id = cpu_to_le32(vport->vport_id); send_tx_tstamp_msg->num_latches = cpu_to_le16(id); - xn_params.send_buf.iov_base = send_tx_tstamp_msg; - xn_params.send_buf.iov_len = msg_size; - reply_sz = idpf_vc_xn_exec(vport->adapter, &xn_params); - kfree(send_tx_tstamp_msg); - - return min(reply_sz, 0); + return idpf_send_mb_msg_kfree(vport->adapter, &xn_params, + send_tx_tstamp_msg, msg_size); } diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.h b/drivers/net/ethernet/intel/igb/e1000_82575.h index 63ec253ac788..9e696d55e512 100644 --- a/drivers/net/ethernet/intel/igb/e1000_82575.h +++ b/drivers/net/ethernet/intel/igb/e1000_82575.h @@ -87,6 +87,27 @@ union e1000_adv_rx_desc { } wb; /* writeback */ }; +#define E1000_RSS_TYPE_NO_HASH 0 +#define E1000_RSS_TYPE_HASH_TCP_IPV4 1 +#define E1000_RSS_TYPE_HASH_IPV4 2 +#define E1000_RSS_TYPE_HASH_TCP_IPV6 3 +#define E1000_RSS_TYPE_HASH_IPV6_EX 4 +#define E1000_RSS_TYPE_HASH_IPV6 5 +#define E1000_RSS_TYPE_HASH_TCP_IPV6_EX 6 +#define E1000_RSS_TYPE_HASH_UDP_IPV4 7 +#define E1000_RSS_TYPE_HASH_UDP_IPV6 8 +#define E1000_RSS_TYPE_HASH_UDP_IPV6_EX 9 + +#define E1000_RSS_TYPE_MASK GENMASK(3, 0) + +#define E1000_RSS_L4_TYPES_MASK \ + (BIT(E1000_RSS_TYPE_HASH_TCP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6_EX) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6_EX)) + #define E1000_RXDADV_HDRBUFLEN_MASK 0x7FE0 #define E1000_RXDADV_HDRBUFLEN_SHIFT 5 #define E1000_RXDADV_STAT_TS 0x10000 /* Pkt was time stamped */ diff --git a/drivers/net/ethernet/intel/igb/e1000_i210.c b/drivers/net/ethernet/intel/igb/e1000_i210.c index 9db29b231d6a..784f9a7bcbed 100644 --- a/drivers/net/ethernet/intel/igb/e1000_i210.c +++ b/drivers/net/ethernet/intel/igb/e1000_i210.c @@ -756,11 +756,7 @@ static s32 __igb_access_xmdio_reg(struct e1000_hw *hw, u16 address, return ret_val; /* Recalibrate the device back to 0 */ - ret_val = hw->phy.ops.write_reg(hw, E1000_MMDAC, 0); - if (ret_val) - return ret_val; - - return ret_val; + return hw->phy.ops.write_reg(hw, E1000_MMDAC, 0); } /** diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h index 0fff1df81b7b..8c9b02058cec 100644 --- a/drivers/net/ethernet/intel/igb/igb.h +++ b/drivers/net/ethernet/intel/igb/igb.h @@ -495,6 +495,7 @@ struct hwmon_buff { #define IGB_N_PEROUT 2 #define IGB_N_SDP 4 #define IGB_RETA_SIZE 128 +#define IGB_RSS_KEY_SIZE 40 enum igb_filter_match_flags { IGB_FILTER_FLAG_ETHER_TYPE = 0x1, @@ -655,6 +656,7 @@ struct igb_adapter { struct i2c_client *i2c_client; u32 rss_indir_tbl_init; u8 rss_indir_tbl[IGB_RETA_SIZE]; + u8 rss_key[IGB_RSS_KEY_SIZE]; unsigned long link_check_timeout; int copper_tries; @@ -735,6 +737,7 @@ void igb_down(struct igb_adapter *); void igb_reinit_locked(struct igb_adapter *); void igb_reset(struct igb_adapter *); int igb_reinit_queues(struct igb_adapter *); +void igb_write_rss_key(struct igb_adapter *adapter); void igb_write_rss_indir_tbl(struct igb_adapter *); int igb_set_spd_dplx(struct igb_adapter *, u32, u8); int igb_setup_tx_resources(struct igb_ring *); diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c index f7938c1da835..65014a54a6d1 100644 --- a/drivers/net/ethernet/intel/igb/igb_ethtool.c +++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c @@ -3019,6 +3019,27 @@ static int igb_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd) return ret; } +/** + * igb_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the E1000 hardware registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igb_write_rss_key(struct igb_adapter *adapter) +{ + struct e1000_hw *hw = &adapter->hw; + + ASSERT_RTNL(); + + for (int i = 0; i < IGB_RSS_KEY_SIZE / 4; i++) { + u32 val = get_unaligned_le32(&adapter->rss_key[i * 4]); + + wr32(E1000_RSSRK(i), val); + } +} + static int igb_get_eee(struct net_device *netdev, struct ethtool_keee *edata) { struct igb_adapter *adapter = netdev_priv(netdev); @@ -3276,10 +3297,12 @@ static int igb_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGB_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + if (rxfh->indir) + for (i = 0; i < IGB_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -3319,6 +3342,11 @@ void igb_write_rss_indir_tbl(struct igb_adapter *adapter) } } +static u32 igb_get_rxfh_key_size(struct net_device *netdev) +{ + return IGB_RSS_KEY_SIZE; +} + static int igb_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh, struct netlink_ext_ack *extack) @@ -3329,35 +3357,39 @@ static int igb_set_rxfh(struct net_device *netdev, u32 num_queues; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - switch (hw->mac.type) { - case e1000_82576: - /* 82576 supports 2 RSS queues for SR-IOV */ - if (adapter->vfs_allocated_count) - num_queues = 2; - break; - default: - break; + switch (hw->mac.type) { + case e1000_82576: + /* 82576 supports 2 RSS queues for SR-IOV */ + if (adapter->vfs_allocated_count) + num_queues = 2; + break; + default: + break; + } + + /* Verify user input. */ + for (i = 0; i < IGB_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; + + + for (i = 0; i < IGB_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; + + igb_write_rss_indir_tbl(adapter); } - /* Verify user input. */ - for (i = 0; i < IGB_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; - - - for (i = 0; i < IGB_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; - - igb_write_rss_indir_tbl(adapter); + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igb_write_rss_key(adapter); + } return 0; } @@ -3483,6 +3515,7 @@ static const struct ethtool_ops igb_ethtool_ops = { .get_module_eeprom = igb_get_module_eeprom, .get_rxfh_indir_size = igb_get_rxfh_indir_size, .get_rxfh = igb_get_rxfh, + .get_rxfh_key_size = igb_get_rxfh_key_size, .set_rxfh = igb_set_rxfh, .get_rxfh_fields = igb_get_rxfh_fields, .set_rxfh_fields = igb_set_rxfh_fields, diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index a1e89a375744..d4a897a8c82c 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -4048,6 +4048,9 @@ static int igb_sw_init(struct igb_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGB_DEFAULT_TXD; adapter->rx_ring_count = IGB_DEFAULT_RXD; @@ -4522,11 +4525,8 @@ static void igb_setup_mrqc(struct igb_adapter *adapter) struct e1000_hw *hw = &adapter->hw; u32 mrqc, rxcsum; u32 j, num_rx_queues; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(E1000_RSSRK(j), rss_key[j]); + igb_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; @@ -8820,10 +8820,19 @@ static inline void igb_rx_hash(struct igb_ring *ring, union e1000_adv_rx_desc *rx_desc, struct sk_buff *skb) { - if (ring->netdev->features & NETIF_F_RXHASH) - skb_set_hash(skb, - le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), - PKT_HASH_TYPE_L3); + u16 rss_type; + + if (!(ring->netdev->features & NETIF_F_RXHASH)) + return; + + rss_type = le16_to_cpu(rx_desc->wb.lower.lo_dword.pkt_info) & + E1000_RSS_TYPE_MASK; + if (!rss_type) + return; + + skb_set_hash(skb, le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), + (E1000_RSS_L4_TYPES_MASK & BIT(rss_type)) ? + PKT_HASH_TYPE_L4 : PKT_HASH_TYPE_L3); } /** diff --git a/drivers/net/ethernet/intel/igc/igc.h b/drivers/net/ethernet/intel/igc/igc.h index 46d625b15f44..17f213cc93e4 100644 --- a/drivers/net/ethernet/intel/igc/igc.h +++ b/drivers/net/ethernet/intel/igc/igc.h @@ -30,6 +30,7 @@ void igc_ethtool_set_ops(struct net_device *); #define MAX_ETYPE_FILTER 8 #define IGC_RETA_SIZE 128 +#define IGC_RSS_KEY_SIZE 40 /* SDP support */ #define IGC_N_EXTTS 2 @@ -302,6 +303,7 @@ struct igc_adapter { unsigned int nfc_rule_count; u8 rss_indir_tbl[IGC_RETA_SIZE]; + u8 rss_key[IGC_RSS_KEY_SIZE]; unsigned long link_check_timeout; struct igc_info ei; @@ -361,6 +363,7 @@ unsigned int igc_get_max_rss_queues(struct igc_adapter *adapter); void igc_set_flag_queue_pairs(struct igc_adapter *adapter, const u32 max_rss_queues); int igc_reinit_queues(struct igc_adapter *adapter); +void igc_write_rss_key(struct igc_adapter *adapter); void igc_write_rss_indir_tbl(struct igc_adapter *adapter); bool igc_has_link(struct igc_adapter *adapter); void igc_reset(struct igc_adapter *adapter); diff --git a/drivers/net/ethernet/intel/igc/igc_base.c b/drivers/net/ethernet/intel/igc/igc_base.c index 1613b562d17c..ab9120a3127f 100644 --- a/drivers/net/ethernet/intel/igc/igc_base.c +++ b/drivers/net/ethernet/intel/igc/igc_base.c @@ -114,11 +114,35 @@ static s32 igc_setup_copper_link_base(struct igc_hw *hw) u32 ctrl; ctrl = rd32(IGC_CTRL); - ctrl |= IGC_CTRL_SLU; - ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX); - wr32(IGC_CTRL, ctrl); + ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX | + IGC_CTRL_SPEED_MASK | IGC_CTRL_FD); - ret_val = igc_setup_copper_link(hw); + if (hw->mac.autoneg_enabled) { + ctrl |= IGC_CTRL_SLU; + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } else { + ctrl |= IGC_CTRL_SLU | IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX; + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + ctrl |= IGC_CTRL_SPEED_10; + break; + case IGC_FORCED_10F: + ctrl |= IGC_CTRL_SPEED_10 | IGC_CTRL_FD; + break; + case IGC_FORCED_100H: + ctrl |= IGC_CTRL_SPEED_100; + break; + case IGC_FORCED_100F: + ctrl |= IGC_CTRL_SPEED_100 | IGC_CTRL_FD; + break; + default: + return -IGC_ERR_CONFIG; + } + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } return ret_val; } @@ -443,6 +467,7 @@ static const struct igc_phy_operations igc_phy_ops_base = { .reset = igc_phy_hw_reset, .read_reg = igc_read_phy_reg_gpy, .write_reg = igc_write_phy_reg_gpy, + .force_speed_duplex = igc_force_speed_duplex, }; const struct igc_info igc_base_info = { diff --git a/drivers/net/ethernet/intel/igc/igc_defines.h b/drivers/net/ethernet/intel/igc/igc_defines.h index 9482ab11f050..3f504751c2d9 100644 --- a/drivers/net/ethernet/intel/igc/igc_defines.h +++ b/drivers/net/ethernet/intel/igc/igc_defines.h @@ -129,10 +129,13 @@ #define IGC_ERR_SWFW_SYNC 13 /* Device Control */ +#define IGC_CTRL_FD BIT(0) /* Full Duplex */ #define IGC_CTRL_RST 0x04000000 /* Global reset */ - #define IGC_CTRL_PHY_RST 0x80000000 /* PHY Reset */ #define IGC_CTRL_SLU 0x00000040 /* Set link up (Force Link) */ +#define IGC_CTRL_SPEED_MASK GENMASK(10, 8) +#define IGC_CTRL_SPEED_10 FIELD_PREP(IGC_CTRL_SPEED_MASK, 0) +#define IGC_CTRL_SPEED_100 FIELD_PREP(IGC_CTRL_SPEED_MASK, 1) #define IGC_CTRL_FRCSPD 0x00000800 /* Force Speed */ #define IGC_CTRL_FRCDPX 0x00001000 /* Force Duplex */ #define IGC_CTRL_VME 0x40000000 /* IEEE VLAN mode enable */ @@ -673,6 +676,10 @@ #define IGC_GEN_POLL_TIMEOUT 1920 /* PHY Control Register */ +#define MII_CR_SPEED_MASK (BIT(6) | BIT(13)) +#define MII_CR_SPEED_10 0x0000 /* SSM=0, SSL=0: 10 Mb/s */ +#define MII_CR_SPEED_100 BIT(13) /* SSM=0, SSL=1: 100 Mb/s */ +#define MII_CR_DUPLEX_EN BIT(8) /* 0 = Half Duplex, 1 = Full Duplex */ #define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */ #define MII_CR_POWER_DOWN 0x0800 /* Power down */ #define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */ diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index 0122009bedd0..89fe2788a565 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1460,6 +1460,26 @@ static int igc_ethtool_set_rxnfc(struct net_device *dev, } } +/** + * igc_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the IGC_RSSRK registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igc_write_rss_key(struct igc_adapter *adapter) +{ + struct igc_hw *hw = &adapter->hw; + u32 val; + int i; + + for (i = 0; i < IGC_RSS_KEY_SIZE / 4; i++) { + val = get_unaligned_le32(&adapter->rss_key[i * 4]); + wr32(IGC_RSSRK(i), val); + } +} + void igc_write_rss_indir_tbl(struct igc_adapter *adapter) { struct igc_hw *hw = &adapter->hw; @@ -1482,6 +1502,11 @@ void igc_write_rss_indir_tbl(struct igc_adapter *adapter) } } +static u32 igc_ethtool_get_rxfh_key_size(struct net_device *netdev) +{ + return IGC_RSS_KEY_SIZE; +} + static u32 igc_ethtool_get_rxfh_indir_size(struct net_device *netdev) { return IGC_RETA_SIZE; @@ -1494,10 +1519,13 @@ static int igc_ethtool_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGC_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->indir) + for (i = 0; i < IGC_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -1511,24 +1539,28 @@ static int igc_ethtool_set_rxfh(struct net_device *netdev, int i; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - /* Verify user input. */ - for (i = 0; i < IGC_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; + /* Verify user input. */ + for (i = 0; i < IGC_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; - for (i = 0; i < IGC_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; + for (i = 0; i < IGC_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; - igc_write_rss_indir_tbl(adapter); + igc_write_rss_indir_tbl(adapter); + } + + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igc_write_rss_key(adapter); + } return 0; } @@ -1914,44 +1946,58 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, ethtool_link_ksettings_add_link_mode(cmd, supported, TP); ethtool_link_ksettings_add_link_mode(cmd, advertising, TP); - /* advertising link modes */ - if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 1000baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 2500baseT_Full); - /* set autoneg settings */ ethtool_link_ksettings_add_link_mode(cmd, supported, Autoneg); - ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + if (hw->mac.autoneg_enabled) { + ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + cmd->base.autoneg = AUTONEG_ENABLE; - /* Set pause flow control settings */ - ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); + /* advertising link modes only apply when autoneg is on */ + if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 1000baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 2500baseT_Full); - switch (hw->fc.requested_mode) { - case igc_fc_full: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - break; - case igc_fc_rx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - case igc_fc_tx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - default: - break; + /* Set pause flow control advertising */ + switch (hw->fc.requested_mode) { + case igc_fc_full: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + break; + case igc_fc_rx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + case igc_fc_tx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + default: + break; + } + } else { + cmd->base.autoneg = AUTONEG_DISABLE; } + /* Pause is always supported */ + ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); + status = pm_runtime_suspended(&adapter->pdev->dev) ? 0 : rd32(IGC_STATUS); @@ -1983,7 +2029,6 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, cmd->base.duplex = DUPLEX_UNKNOWN; } cmd->base.speed = speed; - cmd->base.autoneg = AUTONEG_ENABLE; /* MDI-X => 2; MDI =>1; Invalid =>0 */ if (hw->phy.media_type == igc_media_type_copper) @@ -2000,38 +2045,51 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, return 0; } -static int -igc_ethtool_set_link_ksettings(struct net_device *netdev, - const struct ethtool_link_ksettings *cmd) +/** + * igc_handle_autoneg_disabled - Configure forced speed/duplex settings + * @adapter: private driver structure + * @speed: requested speed (must be SPEED_10 or SPEED_100) + * @duplex: requested duplex + * + * Records forced speed/duplex when autoneg is disabled. + * Caller must validate speed before calling this function. + */ +static void igc_handle_autoneg_disabled(struct igc_adapter *adapter, u32 speed, + u8 duplex) +{ + struct igc_mac_info *mac = &adapter->hw.mac; + + switch (speed) { + case SPEED_10: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_10F : IGC_FORCED_10H; + break; + case SPEED_100: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_100F : IGC_FORCED_100H; + break; + default: + WARN_ONCE(1, "Unsupported speed %u\n", speed); + return; + } + + mac->autoneg_enabled = false; +} + +/** + * igc_handle_autoneg_enabled - Configure autonegotiation advertisement + * @adapter: private driver structure + * @cmd: ethtool link ksettings from user + * + * Records advertised speeds and flow control settings when autoneg + * is enabled. + */ +static void igc_handle_autoneg_enabled(struct igc_adapter *adapter, + const struct ethtool_link_ksettings *cmd) { - struct igc_adapter *adapter = netdev_priv(netdev); - struct net_device *dev = adapter->netdev; struct igc_hw *hw = &adapter->hw; u16 advertised = 0; - /* When adapter in resetting mode, autoneg/speed/duplex - * cannot be changed - */ - if (igc_check_reset_block(hw)) { - netdev_err(dev, "Cannot change link characteristics when reset is active\n"); - return -EINVAL; - } - - /* MDI setting is only allowed when autoneg enabled because - * some hardware doesn't allow MDI setting when speed or - * duplex is forced. - */ - if (cmd->base.eth_tp_mdix_ctrl) { - if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && - cmd->base.autoneg != AUTONEG_ENABLE) { - netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); - return -EINVAL; - } - } - - while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) - usleep_range(1000, 2000); - if (ethtool_link_ksettings_test_link_mode(cmd, advertising, 2500baseT_Full)) advertised |= ADVERTISE_2500_FULL; @@ -2056,14 +2114,66 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev, 10baseT_Half)) advertised |= ADVERTISE_10_HALF; - if (cmd->base.autoneg == AUTONEG_ENABLE) { - hw->phy.autoneg_advertised = advertised; - if (adapter->fc_autoneg) - hw->fc.requested_mode = igc_fc_default; - } else { - netdev_info(dev, "Force mode currently not supported\n"); + hw->mac.autoneg_enabled = true; + hw->phy.autoneg_advertised = advertised; + if (adapter->fc_autoneg) + hw->fc.requested_mode = igc_fc_default; +} + +static int +igc_ethtool_set_link_ksettings(struct net_device *netdev, + const struct ethtool_link_ksettings *cmd) +{ + struct igc_adapter *adapter = netdev_priv(netdev); + struct net_device *dev = adapter->netdev; + struct igc_hw *hw = &adapter->hw; + + /* When adapter in resetting mode, autoneg/speed/duplex + * cannot be changed + */ + if (igc_check_reset_block(hw)) { + netdev_err(dev, "Cannot change link characteristics when reset is active\n"); + return -EINVAL; } + if (cmd->base.autoneg != AUTONEG_ENABLE && + cmd->base.autoneg != AUTONEG_DISABLE) { + netdev_info(dev, "Unsupported autoneg setting\n"); + return -EINVAL; + } + + /* MDI setting is only allowed when autoneg enabled because + * some hardware doesn't allow MDI setting when speed or + * duplex is forced. + */ + if (cmd->base.eth_tp_mdix_ctrl) { + if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && + cmd->base.autoneg != AUTONEG_ENABLE) { + netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); + return -EINVAL; + } + } + + if (cmd->base.autoneg == AUTONEG_DISABLE) { + if (cmd->base.speed != SPEED_10 && cmd->base.speed != SPEED_100) { + netdev_info(dev, "Unsupported speed for forced link\n"); + return -EINVAL; + } + if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) { + netdev_info(dev, "Duplex must be half or full for forced link\n"); + return -EINVAL; + } + } + + while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) + usleep_range(1000, 2000); + + if (cmd->base.autoneg == AUTONEG_ENABLE) + igc_handle_autoneg_enabled(adapter, cmd); + else + igc_handle_autoneg_disabled(adapter, cmd->base.speed, + cmd->base.duplex); + /* MDI-X => 2; MDI => 1; Auto => 3 */ if (cmd->base.eth_tp_mdix_ctrl) { /* fix up the value for auto (3 => 0) as zero is mapped @@ -2175,6 +2285,7 @@ static const struct ethtool_ops igc_ethtool_ops = { .get_rxnfc = igc_ethtool_get_rxnfc, .set_rxnfc = igc_ethtool_set_rxnfc, .get_rx_ring_count = igc_ethtool_get_rx_ring_count, + .get_rxfh_key_size = igc_ethtool_get_rxfh_key_size, .get_rxfh_indir_size = igc_ethtool_get_rxfh_indir_size, .get_rxfh = igc_ethtool_get_rxfh, .set_rxfh = igc_ethtool_set_rxfh, diff --git a/drivers/net/ethernet/intel/igc/igc_hw.h b/drivers/net/ethernet/intel/igc/igc_hw.h index be8a49a86d09..62aaee55668a 100644 --- a/drivers/net/ethernet/intel/igc/igc_hw.h +++ b/drivers/net/ethernet/intel/igc/igc_hw.h @@ -73,6 +73,13 @@ struct igc_info { extern const struct igc_info igc_base_info; +enum igc_forced_speed_duplex { + IGC_FORCED_10H, + IGC_FORCED_10F, + IGC_FORCED_100H, + IGC_FORCED_100F, +}; + struct igc_mac_info { struct igc_mac_operations ops; @@ -92,8 +99,9 @@ struct igc_mac_info { bool asf_firmware_present; bool arc_subsystem_valid; - bool autoneg_failed; bool get_link_status; + bool autoneg_enabled; + enum igc_forced_speed_duplex forced_speed_duplex; }; struct igc_nvm_operations { diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ethernet/intel/igc/igc_mac.c index 7ac6637f8db7..d6f3f6618469 100644 --- a/drivers/net/ethernet/intel/igc/igc_mac.c +++ b/drivers/net/ethernet/intel/igc/igc_mac.c @@ -438,26 +438,23 @@ void igc_config_collision_dist(struct igc_hw *hw) * Checks the status of auto-negotiation after link up to ensure that the * speed and duplex were not forced. If the link needed to be forced, then * flow control needs to be forced also. If auto-negotiation is enabled - * and did not fail, then we configure flow control based on our link - * partner. + * then we configure flow control based on our link partner. */ s32 igc_config_fc_after_link_up(struct igc_hw *hw) { u16 mii_status_reg, mii_nway_adv_reg, mii_nway_lp_ability_reg; - struct igc_mac_info *mac = &hw->mac; u16 speed, duplex; s32 ret_val = 0; - /* Check for the case where we have fiber media and auto-neg failed - * so we had to force link. In this case, we need to force the - * configuration of the MAC to match the "fc" parameter. + /* Without autoneg, flow control capability is not exchanged with the + * link partner. IEEE 802.3 prohibits flow control in half-duplex mode. */ - if (mac->autoneg_failed) - ret_val = igc_force_mac_fc(hw); + if (!hw->mac.autoneg_enabled) { + if (hw->mac.forced_speed_duplex == IGC_FORCED_10H || + hw->mac.forced_speed_duplex == IGC_FORCED_100H) + hw->fc.current_mode = igc_fc_none; - if (ret_val) { - hw_dbg("Error forcing flow control settings\n"); - goto out; + goto force_fc; } /* In auto-neg, we need to check and see if Auto-Neg has completed, @@ -472,15 +469,15 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; if (!(mii_status_reg & MII_SR_AUTONEG_COMPLETE)) { hw_dbg("Copper PHY and Auto Neg has not completed.\n"); - goto out; + return ret_val; } /* The AutoNeg process has completed, so we now need to @@ -492,11 +489,11 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_AUTONEG_ADV, &mii_nway_adv_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_LP_ABILITY, &mii_nway_lp_ability_reg); if (ret_val) - goto out; + return ret_val; /* Two bits in the Auto Negotiation Advertisement Register * (Address 4) and two bits in the Auto Negotiation Base * Page Ability Register (Address 5) determine flow control @@ -612,7 +609,7 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->mac.ops.get_speed_and_duplex(hw, &speed, &duplex); if (ret_val) { hw_dbg("Error getting link speed and duplex\n"); - goto out; + return ret_val; } if (duplex == HALF_DUPLEX) @@ -621,13 +618,13 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) /* Now we call a subroutine to actually force the MAC * controller to use the correct flow control settings. */ +force_fc: ret_val = igc_force_mac_fc(hw); if (ret_val) { hw_dbg("Error forcing flow control settings\n"); - goto out; + return ret_val; } -out: return ret_val; } diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 39043d8ca1bf..8b9ce7cbfff9 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -785,11 +785,8 @@ static void igc_setup_mrqc(struct igc_adapter *adapter) struct igc_hw *hw = &adapter->hw; u32 j, num_rx_queues; u32 mrqc, rxcsum; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(IGC_RSSRK(j), rss_key[j]); + igc_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; @@ -5048,6 +5045,9 @@ static int igc_sw_init(struct igc_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGC_DEFAULT_TXD; adapter->rx_ring_count = IGC_DEFAULT_RXD; @@ -7297,7 +7297,7 @@ static int igc_probe(struct pci_dev *pdev, /* Initialize link properties that are user-changeable */ adapter->fc_autoneg = true; hw->phy.autoneg_advertised = 0xaf; - + hw->mac.autoneg_enabled = true; hw->fc.requested_mode = igc_fc_default; hw->fc.current_mode = igc_fc_default; diff --git a/drivers/net/ethernet/intel/igc/igc_phy.c b/drivers/net/ethernet/intel/igc/igc_phy.c index 6c4d204aecfa..b758a7e0f013 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.c +++ b/drivers/net/ethernet/intel/igc/igc_phy.c @@ -494,12 +494,20 @@ s32 igc_setup_copper_link(struct igc_hw *hw) s32 ret_val = 0; bool link; - /* Setup autoneg and flow control advertisement and perform - * autonegotiation. - */ - ret_val = igc_copper_link_autoneg(hw); - if (ret_val) - goto out; + if (hw->mac.autoneg_enabled) { + /* Setup autoneg and flow control advertisement and perform + * autonegotiation. + */ + ret_val = igc_copper_link_autoneg(hw); + if (ret_val) + goto out; + } else { + ret_val = hw->phy.ops.force_speed_duplex(hw); + if (ret_val) { + hw_dbg("Error Forcing Speed/Duplex\n"); + goto out; + } + } /* Check link status. Wait up to 100 microseconds for link to become * valid. @@ -667,11 +675,7 @@ static s32 __igc_access_xmdio_reg(struct igc_hw *hw, u16 address, return ret_val; /* Recalibrate the device back to 0 */ - ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAC, 0); - if (ret_val) - return ret_val; - - return ret_val; + return hw->phy.ops.write_reg(hw, IGC_MMDAC, 0); } /** @@ -778,3 +782,48 @@ u16 igc_read_phy_fw_version(struct igc_hw *hw) return gphy_version; } + +/** + * igc_force_speed_duplex - Force PHY speed and duplex settings + * @hw: pointer to the HW structure + * + * Programs the GPY PHY control register to disable autonegotiation + * and force the speed/duplex indicated by hw->mac.forced_speed_duplex. + */ +s32 igc_force_speed_duplex(struct igc_hw *hw) +{ + struct igc_phy_info *phy = &hw->phy; + u16 phy_ctrl; + s32 ret_val; + + ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl); + if (ret_val) + return ret_val; + + phy_ctrl &= ~(MII_CR_SPEED_MASK | MII_CR_DUPLEX_EN | + MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + phy_ctrl |= MII_CR_SPEED_10; + break; + case IGC_FORCED_10F: + phy_ctrl |= MII_CR_SPEED_10 | MII_CR_DUPLEX_EN; + break; + case IGC_FORCED_100H: + phy_ctrl |= MII_CR_SPEED_100; + break; + case IGC_FORCED_100F: + phy_ctrl |= MII_CR_SPEED_100 | MII_CR_DUPLEX_EN; + break; + default: + return -IGC_ERR_CONFIG; + } + + ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl); + if (ret_val) + return ret_val; + + hw->mac.get_link_status = true; + return 0; +} diff --git a/drivers/net/ethernet/intel/igc/igc_phy.h b/drivers/net/ethernet/intel/igc/igc_phy.h index 832a7e359f18..d37a89174826 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.h +++ b/drivers/net/ethernet/intel/igc/igc_phy.h @@ -18,5 +18,6 @@ void igc_power_down_phy_copper(struct igc_hw *hw); s32 igc_write_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 data); s32 igc_read_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 *data); u16 igc_read_phy_fw_version(struct igc_hw *hw); +s32 igc_force_speed_duplex(struct igc_hw *hw); #endif diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 52de2bcbadbe..d23a45a34fa3 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -182,14 +182,16 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter, struct net_device *dev = adapter->netdev; u32 i, queue = 0; - for (i = 0; i < dev->num_tc; i++) { + for (i = 0; i < netdev_get_num_tc(dev); i++) { + struct netdev_tc_txq res; u32 offset, count; if (!(preemptible_tcs & BIT(i))) continue; - offset = dev->tc_to_txq[i].offset; - count = dev->tc_to_txq[i].count; + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); + offset = res.offset; + count = res.count; queue |= GENMASK(offset + count - 1, offset); } diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig new file mode 100644 index 000000000000..0a48b3bb7bc2 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Intel Corporation + +config IXD + tristate "Intel(R) Control Plane Function Support" + depends on PCI_MSI + select LIBIE_CP + select LIBIE_PCI + select NET_DEVLINK + help + This driver supports Intel(R) Control Plane PCI Function + of Intel E2100 and later IPUs and FNICs. + It facilitates a centralized control over multiple IDPF PFs/VFs/SFs + exposed by the same card. diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile new file mode 100644 index 000000000000..03760a2580b9 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Intel Corporation + +# Intel(R) Control Plane Function Linux Driver + +obj-$(CONFIG_IXD) += ixd.o + +ixd-y := ixd_main.o +ixd-y += ixd_ctlq.o +ixd-y += ixd_dev.o +ixd-y += ixd_devlink.o +ixd-y += ixd_lib.o +ixd-y += ixd_virtchnl.o diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h new file mode 100644 index 000000000000..2a09ccba13d5 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_H_ +#define _IXD_H_ + +#include + +#define IXD_INIT_TASK_DELAY_JIFFIES msecs_to_jiffies(500) + +/** + * struct ixd_adapter - Data structure representing a CPF + * @cp_ctx: Control plane communication context + * @init_task: Delayed initialization after reset + * @init_task.init_work: Delayed initialization work + * @init_task.reset_retries: How many times to check, whether reset is completed + * @init_task.vc_retries: Number of retries to establish mailbox communication + * @init_task.success: init_work completion status + * @mbx_task: Control queue Rx handling + * @xnm: virtchnl transaction manager + * @asq: Send control queue info + * @arq: Receive control queue info + * @vc_ver: Negotiated virtchnl version + * @vc_ver.major: Negotiated major virtchnl version + * @vc_ver.minor: Negotiated minor virtchnl version + * @caps: Negotiated virtchnl capabilities + */ +struct ixd_adapter { + struct libie_ctlq_ctx cp_ctx; + struct { + struct delayed_work init_work; + u8 reset_retries; + u8 vc_retries; + bool success; + } init_task; + struct delayed_work mbx_task; + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *asq; + struct libie_ctlq_info *arq; + struct { + u32 major; + u32 minor; + } vc_ver; + struct virtchnl2_get_capabilities caps; +}; + +/** + * ixd_to_dev - Get the corresponding device struct from an adapter + * @adapter: PCI device driver-specific private data + * + * Return: struct device corresponding to the given adapter + */ +static inline struct device *ixd_to_dev(struct ixd_adapter *adapter) +{ + return &adapter->cp_ctx.mmio_info.pdev->dev; +} + +void ixd_ctlq_reg_init(struct ixd_adapter *adapter, + struct libie_ctlq_reg *ctlq_reg_tx, + struct libie_ctlq_reg *ctlq_reg_rx); +void ixd_trigger_reset(struct ixd_adapter *adapter); +bool ixd_check_reset_complete(struct ixd_adapter *adapter); +void ixd_init_task(struct work_struct *work); +int ixd_init_dflt_mbx(struct ixd_adapter *adapter); +void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter); + +#endif /* _IXD_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.c b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c new file mode 100644 index 000000000000..8712e10c8c50 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_virtchnl.h" + +#define IXD_CTLQ_RX_TASK_DELAY_JIFFIES msecs_to_jiffies(300) + +/** + * ixd_ctlq_clean_sq - Clean the send control queue after sending the message + * @adapter: The adapter that sent the messages + * @force: Clean regardless of send status + * + * Free the libie send resources after sending the message and handling + * the response. + */ +void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force) +{ + libie_ctlq_xn_send_clean(adapter->asq, kfree, force); +} + +/** + * ixd_ctlq_init_sparams - Initialize control queue send parameters + * @adapter: The adapter with initialized mailbox + * @sparams: Parameters to initialize + * @msg_buf: DMA-mappable pointer to the message being sent + * @msg_size: Message size + */ +static void ixd_ctlq_init_sparams(struct ixd_adapter *adapter, + struct libie_ctlq_xn_send_params *sparams, + void *msg_buf, size_t msg_size) +{ + *sparams = (struct libie_ctlq_xn_send_params) { + .rel_tx_buf = kfree, + .xnm = adapter->xnm, + .ctlq = adapter->asq, + .timeout_ms = IXD_CTLQ_TIMEOUT, + .send_buf = (struct kvec) { + .iov_base = msg_buf, + .iov_len = msg_size, + }, + }; +} + +/** + * ixd_ctlq_do_req - Perform a standard virtchnl request + * @adapter: The adapter with initialized mailbox + * @req: virtchnl request description + * + * Return: %0 if a message was sent and received a response + * that was successfully handled by the custom callback, + * negative error otherwise. + */ +int ixd_ctlq_do_req(struct ixd_adapter *adapter, const struct ixd_ctlq_req *req) +{ + u8 onstack_send_buff[LIBIE_CP_TX_COPYBREAK] __aligned_largest = {}; + struct libie_ctlq_xn_send_params send_params = {}; + struct kvec *recv_mem; + void *send_buff; + int err; + + send_buff = libie_cp_can_send_onstack(req->send_size) ? + &onstack_send_buff : kzalloc(req->send_size, GFP_KERNEL); + if (!send_buff) + return -ENOMEM; + + ixd_ctlq_init_sparams(adapter, &send_params, send_buff, + req->send_size); + + send_params.chnl_opcode = req->opcode; + + if (req->send_buff_init) + req->send_buff_init(adapter, send_buff, req->ctx); + + ixd_ctlq_clean_sq(adapter, false); + err = libie_ctlq_xn_send(&send_params); + if (err) + return err; + + recv_mem = &send_params.recv_mem; + if (req->recv_process) + err = req->recv_process(adapter, recv_mem->iov_base, + recv_mem->iov_len, req->ctx); + + libie_ctlq_release_rx_buf(recv_mem); + + return err; +} + +/** + * ixd_ctlq_handle_msg - Default control queue message handler + * @ctx: Control plane communication context + * @msg: Message received + */ +static void ixd_ctlq_handle_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *msg) +{ + struct ixd_adapter *adapter = pci_get_drvdata(ctx->mmio_info.pdev); + + if (ixd_vc_can_handle_msg(msg)) + ixd_vc_recv_event_msg(adapter, msg); + else + dev_dbg_ratelimited(ixd_to_dev(adapter), + "Received an unsupported opcode 0x%x from the CP\n", + msg->chnl_opcode); + + libie_ctlq_release_rx_buf(&msg->recv_mem); +} + +/** + * ixd_ctlq_recv_mb_msg - Receive a potential message over mailbox periodically + * @adapter: The adapter with initialized mailbox + */ +static void ixd_ctlq_recv_mb_msg(struct ixd_adapter *adapter) +{ + struct libie_ctlq_xn_recv_params xn_params = { + .xnm = adapter->xnm, + .ctlq = adapter->arq, + .ctlq_msg_handler = ixd_ctlq_handle_msg, + .budget = LIBIE_CTLQ_MAX_XN_ENTRIES, + }; + + libie_ctlq_xn_recv(&xn_params); +} + +/** + * ixd_ctlq_rx_task - Periodically check for mailbox responses and events + * @work: work handle + */ +void ixd_ctlq_rx_task(struct work_struct *work) +{ + struct ixd_adapter *adapter; + + adapter = container_of(work, struct ixd_adapter, mbx_task.work); + + queue_delayed_work(system_dfl_wq, &adapter->mbx_task, + IXD_CTLQ_RX_TASK_DELAY_JIFFIES); + + ixd_ctlq_recv_mb_msg(adapter); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.h b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h new file mode 100644 index 000000000000..8839f9f8f6d5 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_CTLQ_H_ +#define _IXD_CTLQ_H_ + +#include + +#define IXD_CTLQ_TIMEOUT 2000 + +/** + * struct ixd_ctlq_req - Standard virtchnl request description + * @opcode: protocol opcode, only virtchnl2 is needed for now + * @send_size: required length of the send buffer + * @send_buff_init: function to initialize the allocated send buffer + * @recv_process: function to handle the CP response + * @ctx: additional context for callbacks + */ +struct ixd_ctlq_req { + enum virtchnl2_op opcode; + size_t send_size; + void (*send_buff_init)(struct ixd_adapter *adapter, void *send_buff, + void *ctx); + int (*recv_process)(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx); + void *ctx; +}; + +void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force); +int ixd_ctlq_do_req(struct ixd_adapter *adapter, + const struct ixd_ctlq_req *req); +void ixd_ctlq_rx_task(struct work_struct *work); + +#endif /* _IXD_CTLQ_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_dev.c b/drivers/net/ethernet/intel/ixd/ixd_dev.c new file mode 100644 index 000000000000..cdd5477cc1f4 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_dev.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_lan_regs.h" + +/** + * ixd_ctlq_reg_init - Initialize default mailbox registers + * @adapter: PCI device driver-specific private data + * @ctlq_reg_tx: Transmit queue registers info to be filled + * @ctlq_reg_rx: Receive queue registers info to be filled + */ +void ixd_ctlq_reg_init(struct ixd_adapter *adapter, + struct libie_ctlq_reg *ctlq_reg_tx, + struct libie_ctlq_reg *ctlq_reg_rx) +{ + struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info; + *ctlq_reg_tx = (struct libie_ctlq_reg) { + .head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQH), + .tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQT), + .len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQLEN), + .addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAH), + .addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAL), + .len_mask = PF_FW_ATQLEN_ATQLEN_M, + .len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M, + .head_mask = PF_FW_ATQH_ATQH_M, + }; + + *ctlq_reg_rx = (struct libie_ctlq_reg) { + .head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQH), + .tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQT), + .len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQLEN), + .addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAH), + .addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAL), + .len_mask = PF_FW_ARQLEN_ARQLEN_M, + .len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M, + .head_mask = PF_FW_ARQH_ARQH_M, + }; +} + +static const struct ixd_reset_reg ixd_reset_reg = { + .rstat = PFGEN_RSTAT, + .rstat_m = PFGEN_RSTAT_PFR_STATE_M, + .rstat_ok_v = 0b01, + .rtrigger = PFGEN_CTRL, + .rtrigger_m = PFGEN_CTRL_PFSWR, +}; + +/** + * ixd_trigger_reset - Trigger PFR reset + * @adapter: the device with mapped reset register + */ +void ixd_trigger_reset(struct ixd_adapter *adapter) +{ + void __iomem *addr; + u32 reg_val; + + addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info, + ixd_reset_reg.rtrigger); + reg_val = readl(addr); + writel(reg_val | ixd_reset_reg.rtrigger_m, addr); +} + +/** + * ixd_check_reset_complete - Check if the PFR reset is completed + * @adapter: CPF being reset + * + * Return: %true if the register read indicates reset has been finished, + * %false otherwise + */ +bool ixd_check_reset_complete(struct ixd_adapter *adapter) +{ + u32 reg_val, reset_status; + void __iomem *addr; + + addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info, + ixd_reset_reg.rstat); + reg_val = readl(addr); + reset_status = reg_val & ixd_reset_reg.rstat_m; + + /* 0xFFFFFFFF might be read if the other side hasn't cleared + * the register for us yet. + */ + if (reg_val != GENMASK(31, 0) && + reset_status == ixd_reset_reg.rstat_ok_v) + return true; + + return false; +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.c b/drivers/net/ethernet/intel/ixd/ixd_devlink.c new file mode 100644 index 000000000000..828132db8323 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025, Intel Corporation. */ + +#include "ixd.h" +#include "ixd_devlink.h" + +#define IXD_DEVLINK_INFO_LEN 128 + +/** + * ixd_fill_dsn - Get the serial number for the ixd device + * @adapter: adapter to query + * @buf: storage buffer for the info request + */ +static void ixd_fill_dsn(struct ixd_adapter *adapter, char *buf) +{ + u8 dsn[8]; + + /* Copy the DSN into an array in Big Endian format */ + put_unaligned_be64(pci_get_dsn(adapter->cp_ctx.mmio_info.pdev), dsn); + + snprintf(buf, IXD_DEVLINK_INFO_LEN, "%8phD", dsn); +} + +/** + * ixd_fill_device_name - Get the name of the underlying hardware + * @adapter: adapter to query + * @buf: storage buffer for the info request + * @buf_size: size of the storage buffer + */ +static void ixd_fill_device_name(struct ixd_adapter *adapter, char *buf, + size_t buf_size) +{ + if (adapter->caps.device_type == cpu_to_le32(VIRTCHNL2_MEV_DEVICE)) + snprintf(buf, buf_size, "%s", "MEV"); + else + snprintf(buf, buf_size, "%s", "UNKNOWN"); +} + +/** + * ixd_devlink_info_get - .info_get devlink handler + * @devlink: devlink instance structure + * @req: the devlink info request + * @extack: extended netdev ack structure + * + * Callback for the devlink .info_get operation. Reports information about the + * device. + * + * Return: zero on success or an error code on failure. + */ +static int ixd_devlink_info_get(struct devlink *devlink, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct ixd_adapter *adapter = devlink_priv(devlink); + char buf[IXD_DEVLINK_INFO_LEN]; + int err; + + ixd_fill_dsn(adapter, buf); + err = devlink_info_serial_number_put(req, buf); + if (err) + return err; + + ixd_fill_device_name(adapter, buf, IXD_DEVLINK_INFO_LEN); + err = devlink_info_version_fixed_put(req, "device.type", buf); + if (err) + return err; + + snprintf(buf, sizeof(buf), "%u.%u", + adapter->vc_ver.major, adapter->vc_ver.minor); + + return devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW_MGMT_API, + buf); +} + +static const struct devlink_ops ixd_devlink_ops = { + .info_get = ixd_devlink_info_get, +}; + +/** + * ixd_adapter_alloc - Allocate devlink and return adapter pointer + * @dev: the device to allocate for + * + * Allocate a devlink instance for this device and return the private area as + * the adapter structure. + * + * Return: adapter structure on success, NULL on failure + */ +struct ixd_adapter *ixd_adapter_alloc(struct device *dev) +{ + struct devlink *devlink; + + devlink = devlink_alloc(&ixd_devlink_ops, sizeof(struct ixd_adapter), + dev); + if (!devlink) + return NULL; + + return devlink_priv(devlink); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.h b/drivers/net/ethernet/intel/ixd/ixd_devlink.h new file mode 100644 index 000000000000..b23a1b37aebc --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2025, Intel Corporation. */ + +#ifndef _IXD_DEVLINK_H_ +#define _IXD_DEVLINK_H_ + +#include + +#include "ixd.h" + +struct ixd_adapter *ixd_adapter_alloc(struct device *dev); + +/** + * ixd_devlink_free - teardown the devlink + * @adapter: the adapter structure to free + * + */ +static inline void ixd_devlink_free(struct ixd_adapter *adapter) +{ + struct devlink *devlink = priv_to_devlink(adapter); + + devlink_free(devlink); +} + +/** + * ixd_devlink_unregister - Unregister devlink for this adapter. + * @adapter: the adapter structure to cleanup + * + * Init task must be completed or cancelled beforehand. + */ +static inline void ixd_devlink_unregister(struct ixd_adapter *adapter) +{ + if (!adapter->init_task.success) + return; + + devlink_unregister(priv_to_devlink(adapter)); +} + +/** + * ixd_devlink_register - Register devlink interface for this adapter + * @adapter: pointer to ixd adapter structure to be associated with devlink + * + * Register the devlink instance associated with this adapter + */ +static inline void ixd_devlink_register(struct ixd_adapter *adapter) +{ + devlink_register(priv_to_devlink(adapter)); +} + +#endif /* _IXD_DEVLINK_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h new file mode 100644 index 000000000000..58e58c75981b --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_LAN_REGS_H_ +#define _IXD_LAN_REGS_H_ + +/* Control Plane Function PCI ID */ +#define IXD_DEV_ID_CPF 0x1efe + +/* Control Queue (Mailbox) */ +#define PF_FW_MBX_REG_LEN 4096 +#define PF_FW_MBX 0x08400000 + +#define PF_FW_ARQBAL (PF_FW_MBX) +#define PF_FW_ARQBAH (PF_FW_MBX + 0x4) +#define PF_FW_ARQLEN (PF_FW_MBX + 0x8) +#define PF_FW_ARQLEN_ARQLEN_M GENMASK(12, 0) +#define PF_FW_ARQLEN_ARQENABLE_S 31 +#define PF_FW_ARQLEN_ARQENABLE_M BIT(PF_FW_ARQLEN_ARQENABLE_S) +#define PF_FW_ARQH_ARQH_M GENMASK(12, 0) +#define PF_FW_ARQH (PF_FW_MBX + 0xC) +#define PF_FW_ARQT (PF_FW_MBX + 0x10) + +#define PF_FW_ATQBAL (PF_FW_MBX + 0x14) +#define PF_FW_ATQBAH (PF_FW_MBX + 0x18) +#define PF_FW_ATQLEN (PF_FW_MBX + 0x1C) +#define PF_FW_ATQLEN_ATQLEN_M GENMASK(9, 0) +#define PF_FW_ATQLEN_ATQENABLE_S 31 +#define PF_FW_ATQLEN_ATQENABLE_M BIT(PF_FW_ATQLEN_ATQENABLE_S) +#define PF_FW_ATQH_ATQH_M GENMASK(9, 0) +#define PF_FW_ATQH (PF_FW_MBX + 0x20) +#define PF_FW_ATQT (PF_FW_MBX + 0x24) + +/* Reset registers */ +#define PFGEN_RTRIG_REG_LEN 2048 +#define PFGEN_RTRIG 0x08407000 /* Device resets */ +#define PFGEN_RSTAT 0x08407008 /* PFR status */ +#define PFGEN_RSTAT_PFR_STATE_M GENMASK(1, 0) +#define PFGEN_CTRL 0x0840700C /* PFR trigger */ +#define PFGEN_CTRL_PFSWR BIT(0) + +/** + * struct ixd_bar_region - BAR region description + * @offset: BAR region offset + * @size: BAR region size + */ +struct ixd_bar_region { + resource_size_t offset; + resource_size_t size; +}; + +/** + * struct ixd_reset_reg - structure for reset registers + * @rstat: offset of status in register + * @rstat_m: status mask + * @rstat_ok_v: value that indicates PFR completed status + * @rtrigger: offset of reset trigger in register + * @rtrigger_m: reset trigger mask + */ +struct ixd_reset_reg { + u32 rstat; + u32 rstat_m; + u32 rstat_ok_v; + u32 rtrigger; + u32 rtrigger_m; +}; + +#endif /* _IXD_LAN_REGS_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c new file mode 100644 index 000000000000..8311f7590666 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_devlink.h" +#include "ixd_virtchnl.h" + +#define IXD_DFLT_MBX_Q_LEN 64 + +/** + * ixd_init_ctlq_create_info - Initialize control queue info for creation + * @info: destination + * @type: type of the queue to create + * @ctlq_reg: register assigned to the control queue + */ +static void ixd_init_ctlq_create_info(struct libie_ctlq_create_info *info, + enum libie_ctlq_type type, + const struct libie_ctlq_reg *ctlq_reg) +{ + *info = (struct libie_ctlq_create_info) { + .type = type, + .id = -1, + .reg = *ctlq_reg, + .len = IXD_DFLT_MBX_Q_LEN, + }; +} + +/** + * ixd_init_libie_xn_params - Initialize xn transaction manager creation info + * @params: destination + * @adapter: adapter info struct + * @ctlqs: list of the managed queues to create + * @num_queues: length of the queue list + */ +static void ixd_init_libie_xn_params(struct libie_ctlq_xn_init_params *params, + struct ixd_adapter *adapter, + struct libie_ctlq_create_info *ctlqs, + uint num_queues) +{ + *params = (struct libie_ctlq_xn_init_params){ + .cctlq_info = ctlqs, + .ctx = &adapter->cp_ctx, + .num_qs = num_queues, + }; +} + +/** + * ixd_adapter_fill_dflt_ctlqs - Find default control queues and store them + * @adapter: adapter info struct + */ +static void ixd_adapter_fill_dflt_ctlqs(struct ixd_adapter *adapter) +{ + adapter->arq = libie_find_ctlq(&adapter->cp_ctx, LIBIE_CTLQ_TYPE_RX, + LIBIE_CTLQ_MBX_ID); + adapter->asq = libie_find_ctlq(&adapter->cp_ctx, LIBIE_CTLQ_TYPE_TX, + LIBIE_CTLQ_MBX_ID); +} + +/** + * ixd_deinit_dflt_mbx - Deinitialize default mailbox + * @adapter: adapter info struct + */ +void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter) +{ + cancel_delayed_work_sync(&adapter->mbx_task); + + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); + + if (adapter->asq) + ixd_ctlq_clean_sq(adapter, true); + + if (adapter->xnm) + libie_ctlq_xn_deinit(adapter->xnm, &adapter->cp_ctx); + + adapter->arq = NULL; + adapter->asq = NULL; + adapter->xnm = NULL; +} + +/** + * ixd_init_dflt_mbx - Setup default mailbox parameters and make request + * @adapter: adapter info struct + * + * Return: %0 on success, negative errno code on failure + */ +int ixd_init_dflt_mbx(struct ixd_adapter *adapter) +{ + struct libie_ctlq_create_info ctlqs_info[2]; + struct libie_ctlq_xn_init_params xn_params; + struct libie_ctlq_reg ctlq_reg_tx; + struct libie_ctlq_reg ctlq_reg_rx; + int err; + + ixd_ctlq_reg_init(adapter, &ctlq_reg_tx, &ctlq_reg_rx); + ixd_init_ctlq_create_info(&ctlqs_info[0], LIBIE_CTLQ_TYPE_TX, + &ctlq_reg_tx); + ixd_init_ctlq_create_info(&ctlqs_info[1], LIBIE_CTLQ_TYPE_RX, + &ctlq_reg_rx); + ixd_init_libie_xn_params(&xn_params, adapter, ctlqs_info, + ARRAY_SIZE(ctlqs_info)); + err = libie_ctlq_xn_init(&xn_params); + if (err) + return err; + adapter->xnm = xn_params.xnm; + + ixd_adapter_fill_dflt_ctlqs(adapter); + + if (!adapter->asq || !adapter->arq) { + ixd_deinit_dflt_mbx(adapter); + return -ENOENT; + } + + queue_delayed_work(system_dfl_wq, &adapter->mbx_task, 0); + + return 0; +} + +/** + * ixd_init_task - Initialize after reset + * @work: init work struct + */ +void ixd_init_task(struct work_struct *work) +{ + struct ixd_adapter *adapter; + int err; + + adapter = container_of(work, struct ixd_adapter, + init_task.init_work.work); + + if (!ixd_check_reset_complete(adapter)) { + if (++adapter->init_task.reset_retries < 10) + queue_delayed_work(system_dfl_wq, + &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); + else + dev_err(ixd_to_dev(adapter), + "Device reset failed. The driver was unable to contact the device's firmware. Check that the FW is running.\n"); + return; + } + + adapter->init_task.reset_retries = 0; + err = ixd_init_dflt_mbx(adapter); + if (err) { + dev_err(ixd_to_dev(adapter), + "Failed to initialize the default mailbox: %pe\n", + ERR_PTR(err)); + return; + } + + err = ixd_vc_dev_init(adapter); + if (!err) { + adapter->init_task.vc_retries = 0; + adapter->init_task.success = true; + ixd_devlink_register(adapter); + return; + } + + libie_ctlq_xn_shutdown(adapter->xnm); + ixd_trigger_reset(adapter); + ixd_deinit_dflt_mbx(adapter); + if (++adapter->init_task.vc_retries > 5 || + (err != -ETIMEDOUT && err != -EAGAIN && err != -EBUSY)) { + dev_err(ixd_to_dev(adapter), + "Failed to establish mailbox communication with the hardware: %pe\n", + ERR_PTR(err)); + return; + } + + queue_delayed_work(system_dfl_wq, &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c new file mode 100644 index 000000000000..5db992d4f9fb --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_main.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_lan_regs.h" +#include "ixd_devlink.h" + +MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver"); +MODULE_IMPORT_NS("LIBIE_CP"); +MODULE_IMPORT_NS("LIBIE_PCI"); +MODULE_LICENSE("GPL"); + +/** + * ixd_remove - remove a CPF PCI device + * @pdev: PCI device being removed + */ +static void ixd_remove(struct pci_dev *pdev) +{ + struct ixd_adapter *adapter = pci_get_drvdata(pdev); + + /* Do not mix removal with (re)initialization */ + cancel_delayed_work_sync(&adapter->init_task.init_work); + + ixd_devlink_unregister(adapter); + + /* Leave the device clean on exit */ + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); + ixd_trigger_reset(adapter); + ixd_deinit_dflt_mbx(adapter); + + libie_pci_unmap_all_mmio_regions(&adapter->cp_ctx.mmio_info); + ixd_devlink_free(adapter); +} + +/** + * ixd_shutdown - shut down a CPF PCI device + * @pdev: PCI device being shut down + */ +static void ixd_shutdown(struct pci_dev *pdev) +{ + ixd_remove(pdev); + + if (system_state == SYSTEM_POWER_OFF) + pci_set_power_state(pdev, PCI_D3hot); +} + +/** + * ixd_iomap_regions - iomap PCI BARs + * @adapter: adapter to map memory regions for + * + * Returns: %0 on success, negative on failure + */ +static int ixd_iomap_regions(struct ixd_adapter *adapter) +{ + const struct ixd_bar_region regions[] = { + { + .offset = PFGEN_RTRIG, + .size = PFGEN_RTRIG_REG_LEN, + }, + { + .offset = PF_FW_MBX, + .size = PF_FW_MBX_REG_LEN, + }, + }; + + for (int i = 0; i < ARRAY_SIZE(regions); i++) { + struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info; + bool map_ok; + + map_ok = libie_pci_map_mmio_region(mmio_info, + regions[i].offset, + regions[i].size); + if (!map_ok) { + dev_err(ixd_to_dev(adapter), + "Failed to map PCI device MMIO region\n"); + + libie_pci_unmap_all_mmio_regions(mmio_info); + return -EIO; + } + } + + return 0; +} + +/** + * ixd_probe - probe a CPF PCI device + * @pdev: corresponding PCI device + * @ent: entry in ixd_pci_tbl + * + * Returns: %0 on success, negative errno code on failure + */ +static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct ixd_adapter *adapter; + int err; + + adapter = ixd_adapter_alloc(&pdev->dev); + if (!adapter) + return -ENOMEM; + + adapter->cp_ctx.mmio_info.pdev = pdev; + INIT_LIST_HEAD(&adapter->cp_ctx.mmio_info.mmio_list); + + err = libie_pci_init_dev(pdev); + if (err) + goto free_adapter; + + pci_set_drvdata(pdev, adapter); + + err = ixd_iomap_regions(adapter); + if (err) + goto free_adapter; + + INIT_DELAYED_WORK(&adapter->init_task.init_work, + ixd_init_task); + INIT_DELAYED_WORK(&adapter->mbx_task, ixd_ctlq_rx_task); + + ixd_trigger_reset(adapter); + queue_delayed_work(system_dfl_wq, &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); + + return 0; + +free_adapter: + ixd_devlink_free(adapter); + return err; +} + +static const struct pci_device_id ixd_pci_tbl[] = { + { PCI_VDEVICE(INTEL, IXD_DEV_ID_CPF) }, + { } +}; +MODULE_DEVICE_TABLE(pci, ixd_pci_tbl); + +static struct pci_driver ixd_driver = { + .name = KBUILD_MODNAME, + .id_table = ixd_pci_tbl, + .probe = ixd_probe, + .remove = ixd_remove, + .shutdown = ixd_shutdown, +}; +module_pci_driver(ixd_driver); diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c new file mode 100644 index 000000000000..fc3b6d2e28c5 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_virtchnl.h" + +/** + * ixd_vc_recv_event_msg - Handle virtchnl event message + * @adapter: The adapter handling the message + * @ctlq_msg: Message received + */ +void ixd_vc_recv_event_msg(struct ixd_adapter *adapter, + struct libie_ctlq_msg *ctlq_msg) +{ + int payload_size = ctlq_msg->data_len; + struct virtchnl2_event *v2e; + + if (payload_size < sizeof(*v2e)) { + dev_warn_ratelimited(ixd_to_dev(adapter), + "Failed to receive valid payload for event msg (op 0x%X len %u)\n", + ctlq_msg->chnl_opcode, + payload_size); + return; + } + + v2e = (struct virtchnl2_event *)ctlq_msg->recv_mem.iov_base; + + dev_dbg(ixd_to_dev(adapter), "Got event 0x%X from the CP\n", + le32_to_cpu(v2e->event)); +} + +/** + * ixd_vc_can_handle_msg - Decide if an event has to be handled by virtchnl code + * @ctlq_msg: Message received + * + * Return: %true if virtchnl code can handle the event, %false otherwise + */ +bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg) +{ + return ctlq_msg->chnl_opcode == VIRTCHNL2_OP_EVENT; +} + +/** + * ixd_handle_caps - Handle VIRTCHNL2_OP_GET_CAPS response + * @adapter: The adapter for which the capabilities are being updated + * @recv_buff: Buffer containing the response + * @recv_size: Response buffer size + * @ctx: unused + * + * Return: %0 if the response format is correct and was handled as expected, + * negative error otherwise. + */ +static int ixd_handle_caps(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx) +{ + if (recv_size < sizeof(adapter->caps)) + return -EBADMSG; + + adapter->caps = *(typeof(adapter->caps) *)recv_buff; + + return 0; +} + +/** + * ixd_req_vc_caps - Request and save device capability + * @adapter: The adapter to get the capabilities for + * + * Return: success or error if sending the get capability message fails + */ +static int ixd_req_vc_caps(struct ixd_adapter *adapter) +{ + const struct ixd_ctlq_req req = { + .opcode = VIRTCHNL2_OP_GET_CAPS, + .send_size = sizeof(struct virtchnl2_get_capabilities), + .ctx = NULL, + .send_buff_init = NULL, + .recv_process = ixd_handle_caps, + }; + + return ixd_ctlq_do_req(adapter, &req); +} + +/** + * ixd_get_vc_ver - Get version info from adapter + * + * Return: filled in virtchannel2 version info, ready for sending + */ +static struct virtchnl2_version_info ixd_get_vc_ver(void) +{ + return (struct virtchnl2_version_info) { + .major = cpu_to_le32(VIRTCHNL2_VERSION_MAJOR_2), + .minor = cpu_to_le32(VIRTCHNL2_VERSION_MINOR_0), + }; +} + +static void ixd_fill_vc_ver(struct ixd_adapter *adapter, void *send_buff, + void *ctx) +{ + *(struct virtchnl2_version_info *)send_buff = ixd_get_vc_ver(); +} + +/** + * ixd_handle_vc_ver - Handle VIRTCHNL2_OP_VERSION response + * @adapter: The adapter for which the version is being updated + * @recv_buff: Buffer containing the response + * @recv_size: Response buffer size + * @ctx: Unused + * + * Return: %0 if the response format is correct and was handled as expected, + * negative error otherwise. + */ +static int ixd_handle_vc_ver(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx) +{ + struct virtchnl2_version_info need_ver = ixd_get_vc_ver(); + struct virtchnl2_version_info *recv_ver; + + if (recv_size < sizeof(need_ver)) + return -EBADMSG; + + recv_ver = recv_buff; + if (le32_to_cpu(need_ver.major) != le32_to_cpu(recv_ver->major) || + le32_to_cpu(need_ver.minor) != le32_to_cpu(recv_ver->minor)) + dev_warn(ixd_to_dev(adapter), + "Virtchnl version does not match (expected %u.%u, received %u.%u)\n", + le32_to_cpu(need_ver.major), + le32_to_cpu(need_ver.minor), + le32_to_cpu(recv_ver->major), + le32_to_cpu(recv_ver->minor)); + + if (le32_to_cpu(need_ver.major) != le32_to_cpu(recv_ver->major)) { + dev_err(ixd_to_dev(adapter), + "Device initialization failed due to virtchnl major version mismatch\n"); + return -EOPNOTSUPP; + } + + adapter->vc_ver.major = le32_to_cpu(recv_ver->major); + adapter->vc_ver.minor = le32_to_cpu(recv_ver->minor); + + return 0; +} + +/** + * ixd_req_vc_version - Request and save Virtchannel2 version + * @adapter: The adapter to get the version for + * + * Return: success or error if sending fails or the response was not as expected + */ +static int ixd_req_vc_version(struct ixd_adapter *adapter) +{ + const struct ixd_ctlq_req req = { + .opcode = VIRTCHNL2_OP_VERSION, + .send_size = sizeof(struct virtchnl2_version_info), + .ctx = NULL, + .send_buff_init = ixd_fill_vc_ver, + .recv_process = ixd_handle_vc_ver, + }; + + return ixd_ctlq_do_req(adapter, &req); +} + +/** + * ixd_vc_dev_init - virtchnl device core initialization + * @adapter: device information + * + * Return: %0 on success or error if any step of the initialization fails + */ +int ixd_vc_dev_init(struct ixd_adapter *adapter) +{ + int err; + + err = ixd_req_vc_version(adapter); + if (err) { + dev_warn(ixd_to_dev(adapter), + "Getting virtchnl version failed, error=%pe\n", + ERR_PTR(err)); + return err; + } + + err = ixd_req_vc_caps(adapter); + if (err) { + dev_warn(ixd_to_dev(adapter), + "Getting virtchnl capabilities failed, error=%pe\n", + ERR_PTR(err)); + return err; + } + + return err; +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h new file mode 100644 index 000000000000..1a53da8b545c --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_VIRTCHNL_H_ +#define _IXD_VIRTCHNL_H_ + +int ixd_vc_dev_init(struct ixd_adapter *adapter); +bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg); +void ixd_vc_recv_event_msg(struct ixd_adapter *adapter, + struct libie_ctlq_msg *ctlq_msg); + +#endif /* _IXD_VIRTCHNL_H_ */ diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 8873a8cc4a18..f91856498eb2 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -9273,10 +9273,11 @@ static u16 ixgbe_select_queue(struct net_device *dev, struct sk_buff *skb, if (sb_dev) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); struct net_device *vdev = sb_dev; + struct netdev_tc_txq res; - txq = vdev->tc_to_txq[tc].offset; - txq += reciprocal_scale(skb_get_hash(skb), - vdev->tc_to_txq[tc].count); + res.combined = READ_ONCE(vdev->tc_to_txq[tc].combined); + txq = res.offset; + txq += reciprocal_scale(skb_get_hash(skb), res.count); return txq; } diff --git a/drivers/net/ethernet/intel/libeth/rx.c b/drivers/net/ethernet/intel/libeth/rx.c index 62521a1f4ec9..0c1a565a1b3a 100644 --- a/drivers/net/ethernet/intel/libeth/rx.c +++ b/drivers/net/ethernet/intel/libeth/rx.c @@ -145,25 +145,29 @@ static bool libeth_rx_page_pool_params_zc(struct libeth_fq *fq, /** * libeth_rx_fq_create - create a PP with the default libeth settings * @fq: buffer queue struct to fill - * @napi: &napi_struct covering this PP (no usage outside its poll loops) + * @napi_dev: &napi_struct for NAPI (data) queues, &device for others * * Return: %0 on success, -%errno on failure. */ -int libeth_rx_fq_create(struct libeth_fq *fq, struct napi_struct *napi) +int libeth_rx_fq_create(struct libeth_fq *fq, void *napi_dev) { + struct napi_struct *napi = fq->no_napi ? NULL : napi_dev; struct page_pool_params pp = { .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, .order = LIBETH_RX_PAGE_ORDER, .pool_size = fq->count, .nid = fq->nid, - .dev = napi->dev->dev.parent, - .netdev = napi->dev, + .dev = napi ? napi->dev->dev.parent : napi_dev, + .netdev = napi ? napi->dev : NULL, .napi = napi, }; struct libeth_fqe *fqes; struct page_pool *pool; int ret; + if (!pp.netdev && fq->type == LIBETH_FQE_MTU) + return -EINVAL; + pp.dma_dir = fq->xdp ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE; if (!fq->hsplit) diff --git a/drivers/net/ethernet/intel/libie/Kconfig b/drivers/net/ethernet/intel/libie/Kconfig index 70831c7e336e..9c5fdebb6766 100644 --- a/drivers/net/ethernet/intel/libie/Kconfig +++ b/drivers/net/ethernet/intel/libie/Kconfig @@ -15,6 +15,14 @@ config LIBIE_ADMINQ Helper functions used by Intel Ethernet drivers for administration queue command interface (aka adminq). +config LIBIE_CP + tristate + select LIBETH + select LIBIE_PCI + help + Common helper routines to communicate with the device Control Plane + using virtchnl2 or related mailbox protocols. + config LIBIE_FWLOG tristate select LIBIE_ADMINQ @@ -23,3 +31,9 @@ config LIBIE_FWLOG for it. Firmware logging is using admin queue interface to communicate with the device. Debugfs is a user interface used to config logging and dump all collected logs. + +config LIBIE_PCI + tristate + help + Helper functions for management of PCI resources belonging + to networking devices. diff --git a/drivers/net/ethernet/intel/libie/Makefile b/drivers/net/ethernet/intel/libie/Makefile index db57fc6780ea..3065aa057798 100644 --- a/drivers/net/ethernet/intel/libie/Makefile +++ b/drivers/net/ethernet/intel/libie/Makefile @@ -9,6 +9,14 @@ obj-$(CONFIG_LIBIE_ADMINQ) += libie_adminq.o libie_adminq-y := adminq.o +obj-$(CONFIG_LIBIE_CP) += libie_cp.o + +libie_cp-y := controlq.o + obj-$(CONFIG_LIBIE_FWLOG) += libie_fwlog.o libie_fwlog-y := fwlog.o + +obj-$(CONFIG_LIBIE_PCI) += libie_pci.o + +libie_pci-y := pci.o diff --git a/drivers/net/ethernet/intel/libie/controlq.c b/drivers/net/ethernet/intel/libie/controlq.c new file mode 100644 index 000000000000..45a49eba6a82 --- /dev/null +++ b/drivers/net/ethernet/intel/libie/controlq.c @@ -0,0 +1,1295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include +#include + +#include + +#define LIBIE_CTLQ_DESC_QWORD0(sz) \ + (LIBIE_CTLQ_DESC_FLAG_BUF | \ + LIBIE_CTLQ_DESC_FLAG_RD | \ + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_LEN, sz)) + +/** + * libie_ctlq_free_fq - free fill queue resources, including buffers + * @ctlq: Rx control queue whose resources need to be freed + */ +static void libie_ctlq_free_fq(struct libie_ctlq_info *ctlq) +{ + struct libeth_fq fq = { + .fqes = ctlq->rx_fqes, + .pp = ctlq->pp, + }; + + for (u32 ntc = ctlq->next_to_clean; ntc != ctlq->next_to_post; ) { + page_pool_put_full_netmem(fq.pp, fq.fqes[ntc].netmem, false); + + if (++ntc >= ctlq->ring_len) + ntc = 0; + } + + libeth_rx_fq_destroy(&fq); +} + +/** + * libie_ctlq_init_fq - initialize fill queue for an Rx controlq + * @ctlq: control queue that needs Rx buffer allocation + * + * Return: %0 on success, -%errno on failure + */ +static int libie_ctlq_init_fq(struct libie_ctlq_info *ctlq) +{ + struct libeth_fq fq = { + .count = ctlq->ring_len, + .truesize = LIBIE_CTLQ_MAX_BUF_LEN, + .nid = NUMA_NO_NODE, + .type = LIBETH_FQE_SHORT, + .hsplit = true, + .no_napi = true, + }; + int err; + + err = libeth_rx_fq_create(&fq, ctlq->dev); + if (err) + return err; + + ctlq->pp = fq.pp; + ctlq->rx_fqes = fq.fqes; + ctlq->truesize = fq.truesize; + + return 0; +} + +/** + * libie_ctlq_prep_rx_desc - prepare the descriptor with a new address + * @desc: descriptor to (re)initialize + * @addr: physical address to put into descriptor + * @mem_truesize: size of the accessible memory + */ +static void libie_ctlq_prep_rx_desc(struct libie_ctlq_desc *desc, + dma_addr_t addr, u32 mem_truesize) +{ + u64 qword; + + qword = LIBIE_CTLQ_DESC_QWORD0(mem_truesize); + desc->qword0 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, + upper_32_bits(addr)) | + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, + lower_32_bits(addr)); + desc->qword3 = cpu_to_le64(qword); +} + +/** + * libie_ctlq_post_rx_buffs - post buffers to descriptor ring + * @ctlq: control queue that requires Rx descriptor ring to be initialized with + * new Rx buffers + * + * The caller must make sure that calls to libie_ctlq_post_rx_buffs() + * and libie_ctlq_recv() for each queue are either serialized + * or used under ctlq->lock. + * + * Return: %0 on success, -%ENOMEM if any buffer could not be allocated + */ +int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq) +{ + u32 ntp = ctlq->next_to_post, ntc = ctlq->next_to_clean, num_to_post; + const struct libeth_fq_fp fq = { + .pp = ctlq->pp, + .fqes = ctlq->rx_fqes, + .truesize = ctlq->truesize, + .count = ctlq->ring_len, + }; + int ret = 0; + + num_to_post = (ntc > ntp ? 0 : ctlq->ring_len) + ntc - ntp - 1; + + while (num_to_post--) { + dma_addr_t addr; + + ctlq->descs[ntp] = (struct libie_ctlq_desc) {}; + + addr = libeth_rx_alloc(&fq, ntp); + if (unlikely(addr == DMA_MAPPING_ERROR)) { + ret = -ENOMEM; + goto post_bufs; + } + + libie_ctlq_prep_rx_desc(&ctlq->descs[ntp], addr, fq.truesize); + + if (unlikely(++ntp == ctlq->ring_len)) + ntp = 0; + } + +post_bufs: + if (likely(ctlq->next_to_post != ntp)) { + ctlq->next_to_post = ntp; + + dma_wmb(); + writel(ntp, ctlq->reg.tail); + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_post_rx_buffs, "LIBIE_CP"); + +/** + * libie_ctlq_free_tx_msgs - Free Tx control queue messages + * @ctlq: Tx control queue being destroyed + * @num_msgs: number of messages allocated so far + */ +static void libie_ctlq_free_tx_msgs(struct libie_ctlq_info *ctlq, + u32 num_msgs) +{ + for (u32 i = 0; i < num_msgs; i++) + kfree(ctlq->tx_msg[i]); + + kvfree(ctlq->tx_msg); +} + +/** + * libie_ctlq_alloc_tx_msgs - Allocate Tx control queue messages + * @ctlq: Tx control queue being created + * + * Return: %0 on success, -%ENOMEM on allocation error + */ +static int libie_ctlq_alloc_tx_msgs(struct libie_ctlq_info *ctlq) +{ + ctlq->tx_msg = kvzalloc_objs(*ctlq->tx_msg, ctlq->ring_len, + GFP_KERNEL); + if (!ctlq->tx_msg) + return -ENOMEM; + + for (u32 i = 0; i < ctlq->ring_len; i++) { + ctlq->tx_msg[i] = kzalloc_obj(*ctlq->tx_msg[i]); + if (!ctlq->tx_msg[i]) { + libie_ctlq_free_tx_msgs(ctlq, i); + return -ENOMEM; + } + } + + return 0; +} + +/** + * libie_cp_free_desc_mem - free the previously allocated descriptor DMA memory + * @dev: device information + * @mem: DMA memory information + */ +static void libie_cp_free_desc_mem(struct device *dev, + struct libie_cp_dma_mem *mem) +{ + dma_free_coherent(dev, mem->size, mem->va, mem->pa); + mem->va = NULL; +} + +/** + * libie_ctlq_dealloc_ring_res - free memory allocated for control queue + * @ctlq: control queue that requires its ring memory to be freed + * + * Free the memory used by the ring, buffers and other related structures. + */ +static void libie_ctlq_dealloc_ring_res(struct libie_ctlq_info *ctlq) +{ + struct libie_cp_dma_mem *dma = &ctlq->ring_mem; + + if (ctlq->type == LIBIE_CTLQ_TYPE_TX) + libie_ctlq_free_tx_msgs(ctlq, ctlq->ring_len); + else + libie_ctlq_free_fq(ctlq); + + libie_cp_free_desc_mem(ctlq->dev, dma); +} + +/** + * libie_cp_alloc_desc_mem - allocate DMA memory for descriptor ring + * @dev: device information + * @mem: memory for DMA information to be stored + * @size: size of the memory to allocate + * + * Return: virtual address of DMA memory or NULL. + */ +static void *libie_cp_alloc_desc_mem(struct device *dev, + struct libie_cp_dma_mem *mem, u32 size) +{ + size = LARGEST_ALIGN(size); + + mem->va = dma_alloc_coherent(dev, size, &mem->pa, GFP_KERNEL); + mem->size = size; + mem->direction = DMA_BIDIRECTIONAL; + + return mem->va; +} + +/** + * libie_ctlq_alloc_queue_res - allocate memory for descriptor ring and bufs + * @ctlq: control queue that requires its ring resources to be allocated + * + * Return: %0 on success, -%errno on failure + */ +static int libie_ctlq_alloc_queue_res(struct libie_ctlq_info *ctlq) +{ + size_t size = array_size(ctlq->ring_len, sizeof(*ctlq->descs)); + struct libie_cp_dma_mem *dma = &ctlq->ring_mem; + int err = -ENOMEM; + + if (!libie_cp_alloc_desc_mem(ctlq->dev, dma, size)) + return -ENOMEM; + + ctlq->descs = dma->va; + + if (ctlq->type == LIBIE_CTLQ_TYPE_TX) { + if (libie_ctlq_alloc_tx_msgs(ctlq)) + goto free_dma_mem; + } else { + err = libie_ctlq_init_fq(ctlq); + if (err) + goto free_dma_mem; + + err = libie_ctlq_post_rx_buffs(ctlq); + if (err) { + libie_ctlq_free_fq(ctlq); + goto free_dma_mem; + } + } + + return 0; + +free_dma_mem: + libie_cp_free_desc_mem(ctlq->dev, dma); + + return err; +} + +/** + * libie_ctlq_init_regs - Initialize control queue registers + * @ctlq: control queue that needs to be initialized + * + * Initialize registers. The caller is expected to have already initialized the + * descriptor ring memory and buffer memory. + */ +static void libie_ctlq_init_regs(struct libie_ctlq_info *ctlq) +{ + u32 dword; + + if (ctlq->type == LIBIE_CTLQ_TYPE_RX) + writel(ctlq->ring_len - 1, ctlq->reg.tail); + else + writel(0, ctlq->reg.tail); + + writel(0, ctlq->reg.head); + writel(lower_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_low); + writel(upper_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_high); + + dword = FIELD_PREP(LIBIE_CTLQ_MBX_ATQ_LEN, ctlq->ring_len) | + ctlq->reg.len_ena_mask; + writel(dword, ctlq->reg.len); +} + +/** + * libie_find_ctlq - find the controlq for the given id and type + * @ctx: libie CP context information + * @type: type of controlq to find + * @id: controlq id to find + * + * Return: control queue info pointer on success, NULL on failure + */ +struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx, + enum libie_ctlq_type type, + int id) +{ + struct libie_ctlq_info *cq; + + guard(spinlock)(&ctx->ctlqs_lock); + + list_for_each_entry(cq, &ctx->ctlqs, list) + if (cq->qid == id && cq->type == type) + return cq; + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(libie_find_ctlq, "LIBIE_CP"); + +/** + * libie_ctlq_add - add one control queue + * @ctx: libie CP context information + * @qinfo: information required for queue creation + * + * Allocate and initialize a control queue and add it to the control queue list. + * libie_ctlq_init() must be called prior to any calls to libie_ctlq_add. + * + * Return: added control queue info pointer on success, error pointer on failure + */ +static struct libie_ctlq_info * +libie_ctlq_add(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo) +{ + struct libie_ctlq_info *ctlq; + int err; + + if (qinfo->id != LIBIE_CTLQ_MBX_ID) + return ERR_PTR(-EOPNOTSUPP); + + if (qinfo->len > FIELD_MAX(LIBIE_CTLQ_MBX_ATQ_LEN) || !qinfo->len) + return ERR_PTR(-EINVAL); + + ctlq = kvzalloc_obj(*ctlq); + if (!ctlq) + return ERR_PTR(-ENOMEM); + + ctlq->type = qinfo->type; + ctlq->qid = qinfo->id; + ctlq->ring_len = qinfo->len; + ctlq->dev = &ctx->mmio_info.pdev->dev; + ctlq->reg = qinfo->reg; + + err = libie_ctlq_alloc_queue_res(ctlq); + if (err) { + kvfree(ctlq); + return ERR_PTR(err); + } + + libie_ctlq_init_regs(ctlq); + + spin_lock_init(&ctlq->lock); + + scoped_guard(spinlock, &ctx->ctlqs_lock) + list_add(&ctlq->list, &ctx->ctlqs); + + return ctlq; +} + +/** + * libie_ctlq_remove - deallocate and remove specified control queue + * @ctx: libie CP context information + * @ctlq: specific control queue that needs to be removed + */ +static void libie_ctlq_remove(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_info *ctlq) +{ + scoped_guard(spinlock, &ctx->ctlqs_lock) + list_del(&ctlq->list); + + libie_ctlq_dealloc_ring_res(ctlq); + kvfree(ctlq); +} + +/** + * libie_ctlq_init - main initialization routine for all control queues + * @ctx: libie CP context information + * @qinfo: array of structs containing info for each queue to be initialized + * @numq: number of queues to initialize + * + * This initializes queue list and adds any number and any type of control + * queues. This is an all or nothing routine; if one fails, all previously + * allocated queues will be destroyed. + * + * Please note that any control queue send/receive functions are not + * softirq/NAPI safe, and therefore API can be used in process context only. + * + * Return: %0 on success, -%errno on failure + */ +int libie_ctlq_init(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo, + u32 numq) +{ + INIT_LIST_HEAD(&ctx->ctlqs); + spin_lock_init(&ctx->ctlqs_lock); + + for (u32 i = 0; i < numq; i++) { + struct libie_ctlq_info *ctlq; + + ctlq = libie_ctlq_add(ctx, &qinfo[i]); + if (IS_ERR(ctlq)) { + libie_ctlq_deinit(ctx); + return PTR_ERR(ctlq); + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_init, "LIBIE_CP"); + +/** + * libie_ctlq_deinit - destroy all control queues + * @ctx: libie CP context information + */ +void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx) +{ + struct libie_ctlq_info *ctlq, *tmp; + + list_for_each_entry_safe(ctlq, tmp, &ctx->ctlqs, list) + libie_ctlq_remove(ctx, ctlq); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_deinit, "LIBIE_CP"); + +/** + * libie_ctlq_tx_desc_from_msg - initialize a Tx descriptor from a message + * @desc: descriptor to be initialized + * @msg: filled control queue message + */ +static void libie_ctlq_tx_desc_from_msg(struct libie_ctlq_desc *desc, + const struct libie_ctlq_msg *msg) +{ + const struct libie_cp_dma_mem *dma = &msg->send_mem; + u64 qword; + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_FLAGS, msg->flags) | + FIELD_PREP(LIBIE_CTLQ_DESC_INFRA_OPCODE, msg->opcode) | + FIELD_PREP(LIBIE_CTLQ_DESC_PFID_VFID, msg->func_id); + desc->qword0 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, + msg->chnl_opcode) | + FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, + msg->chnl_retval); + desc->qword1 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_MSG_PARAM0, msg->param0) | + FIELD_PREP(LIBIE_CTLQ_DESC_SW_COOKIE, + msg->sw_cookie) | + FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, + msg->virt_flags); + desc->qword2 = cpu_to_le64(qword); + + if (likely(msg->data_len)) { + desc->qword0 |= + cpu_to_le64(LIBIE_CTLQ_DESC_QWORD0(msg->data_len)); + qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, + upper_32_bits(dma->pa)) | + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, + lower_32_bits(dma->pa)); + } else { + qword = msg->addr_param; + } + + desc->qword3 = cpu_to_le64(qword); +} + +/** + * libie_ctlq_send_desc_avail - get number of free descriptors on a Tx ctlq + * @ctlq: specific control queue which is going be used for sending messages + * + * The caller must hold ctlq->lock. Any dependent sending must be done + * in the same critical section. + * + * Return: number of available descriptors/messages on a given control queue. + */ +u32 libie_ctlq_send_desc_avail(const struct libie_ctlq_info *ctlq) +{ + u32 ntu = ctlq->next_to_use, ntc = ctlq->next_to_clean; + + lockdep_assert_held(&ctlq->lock); + + return (ntc > ntu ? 0 : ctlq->ring_len) + ntc - ntu - 1; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_desc_avail, "LIBIE_CP"); + +/** + * libie_ctlq_send - send a message to Control Plane or Peer + * @ctlq: specific control queue which is used for sending a message + * @num_q_msg: number of messages present to send on @ctlq, + * positive and no greater than the number of available descriptors + * + * The caller must fill in @num_q_msg Tx messages starting at ntu beforehand. + * + * The caller must hold ctlq->lock. The intended pattern is to first check + * the number of descriptors available, then fill in the messages and perform + * send within a single critical section. + */ +void libie_ctlq_send(struct libie_ctlq_info *ctlq, u32 num_q_msg) +{ + u32 ntu = ctlq->next_to_use; + + lockdep_assert_held(&ctlq->lock); + + for (int i = 0; i < num_q_msg; i++) { + struct libie_ctlq_msg *msg = ctlq->tx_msg[ntu]; + struct libie_ctlq_desc *desc; + + desc = &ctlq->descs[ntu]; + libie_ctlq_tx_desc_from_msg(desc, msg); + + if (unlikely(++ntu == ctlq->ring_len)) + ntu = 0; + } + dma_wmb(); + writel(ntu, ctlq->reg.tail); + ctlq->next_to_use = ntu; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send, "LIBIE_CP"); + +/** + * libie_ctlq_send_clean - cleanup the send control queue message buffers + * @params: information for handling of Tx completions + * + * Cleanup the send buffers for the given control queue, if force is set, then + * clear all the outstanding send messages irrespective of their send status, + * until a zero-length message is encountered, which is either a message that + * is already cleared, or a VF reset message, which is always last. + * Force should be used during deinit or reset. + * + * Return: number of send buffers cleaned. + */ +u32 libie_ctlq_send_clean(const struct libie_ctlq_clean_params *params) +{ + struct libie_ctlq_info *ctlq = params->ctlq; + u32 ntc, i; + + spin_lock(&ctlq->lock); + ntc = ctlq->next_to_clean; + + for (i = 0; i < params->num_msgs; i++) { + struct libie_ctlq_msg *msg = ctlq->tx_msg[ntc]; + struct libie_ctlq_desc *desc; + u64 qword; + + desc = &ctlq->descs[ntc]; + qword = le64_to_cpu(desc->qword0); + + if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword) && + !(unlikely(params->force) && msg->data_len)) + break; + + /* This cannot be reordered and lock is taken, so no barriers */ + desc->qword0 = 0; + + params->rel_dma_mem(params->rel_ctx, &msg->send_mem); + memset(msg, 0, sizeof(*msg)); + + if (unlikely(++ntc == ctlq->ring_len)) + ntc = 0; + } + + ctlq->next_to_clean = ntc; + spin_unlock(&ctlq->lock); + + return i; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_clean, "LIBIE_CP"); + +/** + * libie_ctlq_fill_rx_msg - fill in a message from Rx descriptor and buffer + * @msg: message to be filled in + * @desc: received descriptor + * @rx_buf: fill queue buffer associated with the descriptor + */ +static void libie_ctlq_fill_rx_msg(struct libie_ctlq_msg *msg, + const struct libie_ctlq_desc *desc, + struct libeth_fqe *rx_buf) +{ + u64 qword = le64_to_cpu(desc->qword0); + + msg->flags = FIELD_GET(LIBIE_CTLQ_DESC_FLAGS, qword); + msg->opcode = FIELD_GET(LIBIE_CTLQ_DESC_INFRA_OPCODE, qword); + msg->data_len = FIELD_GET(LIBIE_CTLQ_DESC_DATA_LEN, qword); + msg->hw_retval = FIELD_GET(LIBIE_CTLQ_DESC_HW_RETVAL, qword); + + qword = le64_to_cpu(desc->qword1); + msg->chnl_opcode = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, qword); + msg->chnl_retval = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, qword); + + qword = le64_to_cpu(desc->qword2); + msg->param0 = + FIELD_GET(LIBIE_CTLQ_DESC_MSG_PARAM0, qword); + msg->sw_cookie = + FIELD_GET(LIBIE_CTLQ_DESC_SW_COOKIE, qword); + msg->virt_flags = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, qword); + + if (likely(msg->data_len)) { + if (unlikely(msg->data_len > LIBIE_CTLQ_MAX_BUF_LEN)) { + msg->data_len = LIBIE_CTLQ_MAX_BUF_LEN; + msg->chnl_retval = U32_MAX; + } + msg->recv_mem = (struct kvec) { + .iov_base = netmem_address(rx_buf->netmem) + + rx_buf->offset, + .iov_len = msg->data_len, + }; + libeth_rx_sync_for_cpu(rx_buf, msg->data_len); + } else { + msg->recv_mem = (struct kvec) {}; + msg->addr_param = le64_to_cpu(desc->qword3); + page_pool_put_full_netmem(netmem_get_pp(rx_buf->netmem), + rx_buf->netmem, false); + } +} + +/** + * libie_ctlq_recv - receive control queue messages + * @ctlq: control queue that needs to processed for receive + * @msg: array of received control queue messages on this q; + * needs to be pre-allocated by caller for as many messages as requested + * @num_q_msg: number of messages that can be stored in msg buffer, + * no greater than number of posted buffers + * + * Caller is expected to return buffers via libie_ctlq_release_rx_buf(). + * + * The caller must make sure that calls to libie_ctlq_post_rx_buffs() + * and libie_ctlq_recv() for each queue are either serialized + * or used under ctlq->lock. + * + * Return: number of messages received + */ +u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, + u32 num_q_msg) +{ + u32 ntc, i; + + ntc = ctlq->next_to_clean; + + for (i = 0; i < num_q_msg; i++) { + struct libie_ctlq_desc *desc = &ctlq->descs[ntc]; + struct libeth_fqe *rx_buf = &ctlq->rx_fqes[ntc]; + u64 qword; + + qword = le64_to_cpu(desc->qword0); + if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword)) + break; + + dma_rmb(); + + libie_ctlq_fill_rx_msg(&msg[i], desc, rx_buf); + desc->qword0 = 0; + + if (unlikely(++ntc == ctlq->ring_len)) + ntc = 0; + } + + ctlq->next_to_clean = ntc; + + return i; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_recv, "LIBIE_CP"); + +/** + * libie_ctlq_xn_pop_free - get a free Xn entry from the free list + * @xnm: Xn transaction manager + * + * Retrieve a free Xn entry from the free list. + * + * Return: valid Xn entry pointer or NULL if there are no free Xn entries. + */ +static struct libie_ctlq_xn * +libie_ctlq_xn_pop_free(struct libie_ctlq_xn_manager *xnm) +{ + struct libie_ctlq_xn *xn; + u32 free_idx; + + guard(spinlock)(&xnm->free_xns_bm_lock); + + if (unlikely(xnm->shutdown)) + return NULL; + + for_each_set_bit(free_idx, xnm->free_xns_bm, + LIBIE_CTLQ_MAX_XN_ENTRIES) { + xn = &xnm->ring[free_idx]; + + /* Torn read of the physical address is possible, the worst case + * scenario is a transient spurious skip. If the physical + * address is dirty in any way, reuse is already safe. + */ + if (xn->tx_msg && + data_race(xn->tx_msg->send_mem.pa) == xn->small_dma_mem.pa) + continue; + + clear_bit(free_idx, xnm->free_xns_bm); + + return xn; + } + + return NULL; +} + +/** + * __libie_ctlq_xn_push_free - unsafely push an xn entry into the free list + * @xnm: Xn transaction manager + * @xn: xn entry to be added into the free list + * + * Return: whether xnm destruction can be triggered by the caller + */ +static bool __libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_xn *xn) +{ + xn->cookie++; + set_bit(xn->index, xnm->free_xns_bm); + + if (unlikely(xnm->shutdown) && + bitmap_full(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES)) + return true; + + return false; +} + +/** + * libie_ctlq_xn_push_free - push a Xn entry into the free list + * @xnm: Xn transaction manager + * @xn: xn entry to be added into the free list, not locked + * + * Safely add a used Xn entry back to the free list. + */ +static void libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_xn *xn) +{ + bool can_destroy; + + scoped_guard(spinlock, &xnm->free_xns_bm_lock) + can_destroy = __libie_ctlq_xn_push_free(xnm, xn); + + if (can_destroy) + complete(&xnm->can_destroy); +} + +/** + * libie_ctlq_xn_deinit_dma - free the DMA memory allocated for send messages + * @xnm: pointer to the transaction manager + * @num_entries: number of Xn entries to free the DMA for + */ +static void libie_ctlq_xn_deinit_dma(struct libie_ctlq_xn_manager *xnm, + u32 num_entries) +{ + for (u32 i = 0; i < num_entries; i++) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + dma_pool_free(xnm->small_buff_pool, xn->small_dma_mem.va, + xn->small_dma_mem.pa); + } + + dma_pool_destroy(xnm->small_buff_pool); +} + +/** + * libie_ctlq_xn_init_dma - pre-allocate DMA memory for send messages that use + * stack variables + * @dev: device pointer + * @xnm: pointer to transaction manager + * + * Return: %0 on success or error if memory allocation fails + */ +static int libie_ctlq_xn_init_dma(struct device *dev, + struct libie_ctlq_xn_manager *xnm) +{ + u32 i; + + xnm->small_buff_pool = + dma_pool_create("libie_ctlq_xn_tx", dev, LIBIE_CP_TX_COPYBREAK, + LIBIE_CP_TX_COPYBREAK, 0); + if (!xnm->small_buff_pool) + return -ENOMEM; + + for (i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { + struct libie_cp_dma_mem *mem = &xnm->ring[i].small_dma_mem; + + mem->va = dma_pool_zalloc(xnm->small_buff_pool, GFP_KERNEL, + &mem->pa); + if (!mem->va) + goto dealloc_dma; + + mem->direction = DMA_BIDIRECTIONAL; + mem->size = LIBIE_CP_TX_COPYBREAK; + } + + return 0; + +dealloc_dma: + libie_ctlq_xn_deinit_dma(xnm, i); + + return -ENOMEM; +} + +/** + * libie_ctlq_xn_process_recv - process Xn data in receive message + * @params: Xn receive param information to handle a receive message + * @ctlq_msg: received control queue message + * + * Process a control queue receive message and send a complete event + * notification. + * + * Return: true if a message has been processed, false otherwise. + */ +static bool +libie_ctlq_xn_process_recv(struct libie_ctlq_xn_recv_params *params, + struct libie_ctlq_msg *ctlq_msg) +{ + struct libie_ctlq_xn_manager *xnm = params->xnm; + struct libie_ctlq_xn *xn; + u16 msg_cookie, xn_index; + struct kvec *response; + int status; + u16 data; + + data = ctlq_msg->sw_cookie; + xn_index = FIELD_GET(LIBIE_CTLQ_XN_INDEX_M, data); + msg_cookie = FIELD_GET(LIBIE_CTLQ_XN_COOKIE_M, data); + status = ctlq_msg->chnl_retval ? -EFAULT : 0; + + xn = &xnm->ring[xn_index]; + spin_lock(&xn->xn_lock); + if (ctlq_msg->chnl_opcode != xn->virtchnl_opcode || + msg_cookie != xn->cookie) { + spin_unlock(&xn->xn_lock); + return false; + } + + if (xn->state != LIBIE_CTLQ_XN_ASYNC && + xn->state != LIBIE_CTLQ_XN_WAITING) { + spin_unlock(&xn->xn_lock); + return false; + } + + response = &ctlq_msg->recv_mem; + if (xn->state == LIBIE_CTLQ_XN_ASYNC) { + xn->resp_cb(xn->send_ctx, response, status); + libie_ctlq_release_rx_buf(response); + xn->state = LIBIE_CTLQ_XN_IDLE; + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(xnm, xn); + + return true; + } + + xn->recv_mem = *response; + xn->state = status ? LIBIE_CTLQ_XN_COMPLETED_FAILED : + LIBIE_CTLQ_XN_COMPLETED_SUCCESS; + + complete(&xn->cmd_completion_event); + spin_unlock(&xn->xn_lock); + + return true; +} + +/** + * libie_xn_check_async_timeout - Check for asynchronous message timeouts + * @xnm: Xn transaction manager + * + * Call the corresponding callback to notify the caller about the timeout. + * Iterates free_xns_bm locklessly, potential races are caught under + * xn->xn_lock. + */ +static void libie_xn_check_async_timeout(struct libie_ctlq_xn_manager *xnm) +{ + u32 idx; + + for_each_clear_bit(idx, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { + struct libie_ctlq_xn *xn = &xnm->ring[idx]; + u64 timeout_ms; + + spin_lock(&xn->xn_lock); + + timeout_ms = ktime_ms_delta(ktime_get(), xn->timestamp); + if (xn->state != LIBIE_CTLQ_XN_ASYNC || + timeout_ms < xn->timeout_ms) { + spin_unlock(&xn->xn_lock); + continue; + } + + xn->resp_cb(xn->send_ctx, NULL, -ETIMEDOUT); + xn->state = LIBIE_CTLQ_XN_IDLE; + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(xnm, xn); + } +} + +/** + * libie_ctlq_xn_recv - process control queue receive message + * @params: Xn receive param information to handle a receive message + * + * Process a receive message and update the receive queue buffer. + * Also terminates async transactions for which it failed to receive a response + * within a given timeframe. + * Function is intended to be called periodically from a single task. + * + * Return: remaining budget. + */ +u32 libie_ctlq_xn_recv(struct libie_ctlq_xn_recv_params *params) +{ + struct libie_ctlq_msg ctlq_msg; + u32 budget = params->budget; + + while (budget && libie_ctlq_recv(params->ctlq, &ctlq_msg, 1)) { + budget--; + if (!libie_ctlq_xn_process_recv(params, &ctlq_msg)) + params->ctlq_msg_handler(params->xnm->ctx, &ctlq_msg); + } + + libie_ctlq_post_rx_buffs(params->ctlq); + libie_xn_check_async_timeout(params->xnm); + + return budget; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_recv, "LIBIE_CP"); + +/** + * libie_cp_map_dma_mem - map a given virtual address for DMA + * @dev: device information + * @va: virtual address to be mapped + * @size: size of the memory + * @direction: DMA direction either from/to device + * @dma_mem: memory for DMA information to be stored + * + * Return: true on success, false on DMA map failure. + */ +static bool libie_cp_map_dma_mem(struct device *dev, void *va, size_t size, + int direction, + struct libie_cp_dma_mem *dma_mem) +{ + dma_mem->pa = dma_map_single(dev, va, size, direction); + + return dma_mapping_error(dev, dma_mem->pa) ? false : true; +} + +/** + * libie_cp_unmap_dma_mem - unmap previously mapped DMA address + * @dev: device information + * @dma_mem: DMA memory information + */ +static void libie_cp_unmap_dma_mem(struct device *dev, + const struct libie_cp_dma_mem *dma_mem) +{ + dma_unmap_single(dev, dma_mem->pa, dma_mem->size, + dma_mem->direction); +} + +/** + * libie_ctlq_xn_process_send - process and send a control queue message + * @params: Xn send param information for sending a control queue message + * @xn: Assigned Xn entry for tracking the control queue message + * + * Return: %0 on success, -%errno on failure. + */ +static +int libie_ctlq_xn_process_send(struct libie_ctlq_xn_send_params *params, + struct libie_ctlq_xn *xn) +{ + size_t buf_len = params->send_buf.iov_len; + struct device *dev = params->ctlq->dev; + void *buf = params->send_buf.iov_base; + struct libie_cp_dma_mem *dma_mem; + u16 cookie; + + if (!buf || !buf_len) + return -EOPNOTSUPP; + + if (libie_cp_can_send_onstack(buf_len)) { + dma_mem = &xn->small_dma_mem; + memcpy(dma_mem->va, buf, buf_len); + } else { + dma_mem = &xn->send_dma_mem; + dma_mem->va = buf; + dma_mem->size = buf_len; + dma_mem->direction = DMA_TO_DEVICE; + + if (!libie_cp_map_dma_mem(dev, buf, buf_len, DMA_TO_DEVICE, + dma_mem)) + return -ENOMEM; + } + + cookie = FIELD_PREP(LIBIE_CTLQ_XN_COOKIE_M, xn->cookie) | + FIELD_PREP(LIBIE_CTLQ_XN_INDEX_M, xn->index); + + scoped_guard(spinlock, ¶ms->ctlq->lock) { + struct libie_ctlq_info *ctlq = params->ctlq; + struct libie_ctlq_msg *ctlq_msg; + + if (!libie_ctlq_send_desc_avail(ctlq)) { + if (!libie_cp_can_send_onstack(buf_len)) + libie_cp_unmap_dma_mem(dev, dma_mem); + + return -EBUSY; + } + + ctlq_msg = ctlq->tx_msg[ctlq->next_to_use]; + xn->tx_msg = dma_mem == &xn->small_dma_mem ? ctlq_msg : NULL; + if (params->ctlq_msg) + *ctlq_msg = *params->ctlq_msg; + else + /* Unused ctlq messages are already zeroed */ + ctlq_msg->opcode = LIBIE_CTLQ_SEND_MSG_TO_CP; + + ctlq_msg->sw_cookie = cookie; + ctlq_msg->send_mem = *dma_mem; + ctlq_msg->data_len = buf_len; + ctlq_msg->chnl_opcode = params->chnl_opcode; + libie_ctlq_send(params->ctlq, 1); + } + + return 0; +} + +/** + * libie_ctlq_xn_send - send a control queue message, initiating a transaction + * @params: Xn send param information for sending a control queue message + * + * Send a control queue (mailbox or config) message. + * Based on the params value, the call can be completed synchronously or + * asynchronously. + * + * Return: %0 on success, -%errno on failure. + */ +int libie_ctlq_xn_send(struct libie_ctlq_xn_send_params *params) +{ + bool free_send = !libie_cp_can_send_onstack(params->send_buf.iov_len); + struct libie_ctlq_xn *xn; + int ret; + + if (params->send_buf.iov_len > LIBIE_CTLQ_MAX_BUF_LEN) { + ret = -EINVAL; + goto free_buf; + } + + xn = libie_ctlq_xn_pop_free(params->xnm); + /* no free transactions available */ + if (unlikely(!xn)) { + ret = -EAGAIN; + goto free_buf; + } + + spin_lock(&xn->xn_lock); + if (xn->state == LIBIE_CTLQ_XN_SHUTDOWN) { + ret = -ENXIO; + goto unlock_xn; + } + + xn->state = params->resp_cb ? LIBIE_CTLQ_XN_ASYNC : + LIBIE_CTLQ_XN_WAITING; + xn->virtchnl_opcode = params->chnl_opcode; + + if (params->resp_cb) { + xn->send_ctx = params->send_ctx; + xn->resp_cb = params->resp_cb; + xn->timeout_ms = params->timeout_ms; + xn->timestamp = ktime_get(); + } + + ret = libie_ctlq_xn_process_send(params, xn); + if (ret) + goto release_xn; + else + free_send = false; + + spin_unlock(&xn->xn_lock); + + if (params->resp_cb) + return 0; + + wait_for_completion_timeout(&xn->cmd_completion_event, + msecs_to_jiffies(params->timeout_ms)); + + spin_lock(&xn->xn_lock); + switch (xn->state) { + case LIBIE_CTLQ_XN_WAITING: + ret = -ETIMEDOUT; + break; + case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: + params->recv_mem = xn->recv_mem; + break; + default: + ret = -EBADMSG; + break; + } + + /* Free the receive buffer in case of failure. On timeout, receive + * buffer is not allocated. + */ + if (ret && ret != -ETIMEDOUT) + libie_ctlq_release_rx_buf(&xn->recv_mem); + +release_xn: + xn->state = LIBIE_CTLQ_XN_IDLE; + reinit_completion(&xn->cmd_completion_event); +unlock_xn: + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(params->xnm, xn); +free_buf: + if (free_send) + params->rel_tx_buf(params->send_buf.iov_base); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send, "LIBIE_CP"); + +/** + * struct libie_ctlq_xn_rel_tx_ctx - context needed to release xn Tx message + * @dev: device for which DMA was mapped + * @rel_tx_buf: freeing function for non-small buffers + */ +struct libie_ctlq_xn_rel_tx_ctx { + struct device *dev; + void (*rel_tx_buf)(const void *buf_va); +}; + +/** + * libie_ctlq_xn_rel_tx_buf - release xn-controlled Tx message buffer + * @ctx: context, namely DMA device and freeing function + * @dma_mem: DMA memory to reclaim/unmap + */ +static void libie_ctlq_xn_rel_tx_buf(const void *ctx, + struct libie_cp_dma_mem *dma_mem) +{ + const struct libie_ctlq_xn_rel_tx_ctx *rel_ctx = ctx; + + if (!libie_cp_can_send_onstack(dma_mem->size)) { + libie_cp_unmap_dma_mem(rel_ctx->dev, dma_mem); + rel_ctx->rel_tx_buf(dma_mem->va); + } +} + +/** + * libie_ctlq_xn_send_clean - clean xn-controlled Tx messages + * @ctlq: control queue to clean + * @rel_tx_buf: driver callback to free the buffer + * @force: clean regardless of DD + * + * Return: number of completed/released messages. + */ +u32 libie_ctlq_xn_send_clean(struct libie_ctlq_info *ctlq, + void (*rel_tx_buf)(const void *buf_va), + bool force) +{ + struct libie_ctlq_xn_rel_tx_ctx rel_ctx = { + .dev = ctlq->dev, + .rel_tx_buf = rel_tx_buf, + }; + struct libie_ctlq_clean_params params = { + .ctlq = ctlq, + .force = force, + .num_msgs = ctlq->ring_len, + .rel_ctx = &rel_ctx, + .rel_dma_mem = libie_ctlq_xn_rel_tx_buf, + }; + + return libie_ctlq_send_clean(¶ms); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send_clean, "LIBIE_CP"); + +/** + * libie_ctlq_xn_shutdown - terminate control queue transactions + * @xnm: pointer to the transaction manager + * + * Synchronously terminate existing transactions and stop accepting new ones. + * Async transactions are discarded without invoking resp_cb. + */ +void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm) +{ + bool must_wait = false; + u32 i; + + /* Should be no new clear bits after this */ + spin_lock(&xnm->free_xns_bm_lock); + xnm->shutdown = true; + + for_each_clear_bit(i, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + spin_lock(&xn->xn_lock); + + switch (xn->state) { + /* if an idle xn is not free, it is about to be either + * freed or initialized, prevent the latter and wait + */ + case LIBIE_CTLQ_XN_IDLE: + xn->state = LIBIE_CTLQ_XN_SHUTDOWN; + fallthrough; + /* waiting thread possibly needs a push to return the xn, + * transaction will be reported as timed out + */ + case LIBIE_CTLQ_XN_WAITING: + complete(&xn->cmd_completion_event); + fallthrough; + /* these states will return the xn soon */ + case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: + case LIBIE_CTLQ_XN_COMPLETED_FAILED: + case LIBIE_CTLQ_XN_SHUTDOWN: + must_wait = true; + break; + /* no thread should reference async xns at this point */ + case LIBIE_CTLQ_XN_ASYNC: + xn->state = LIBIE_CTLQ_XN_IDLE; + __libie_ctlq_xn_push_free(xnm, xn); + break; + } + + spin_unlock(&xn->xn_lock); + } + + spin_unlock(&xnm->free_xns_bm_lock); + + if (must_wait) + wait_for_completion(&xnm->can_destroy); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_shutdown, "LIBIE_CP"); + +/** + * libie_ctlq_xn_deinit - deallocate and free the transaction manager resources + * @xnm: pointer to the transaction manager + * @ctx: libie CP context information + * + * Rx processing must be stopped beforehand via cancelling tasks. + * Tx processing must be stopped beforehand via libie_ctlq_xn_shutdown(), + * all buffers must be force-cleaned from the send queue. + */ +void libie_ctlq_xn_deinit(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_ctx *ctx) +{ + libie_ctlq_xn_deinit_dma(xnm, LIBIE_CTLQ_MAX_XN_ENTRIES); + kvfree(xnm); + libie_ctlq_deinit(ctx); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_deinit, "LIBIE_CP"); + +/** + * libie_ctlq_xn_init - initialize the Xn transaction manager + * @params: Xn init param information for allocating Xn manager resources + * + * Return: %0 on success, -%errno on failure. + */ +int libie_ctlq_xn_init(struct libie_ctlq_xn_init_params *params) +{ + struct libie_ctlq_xn_manager *xnm; + int ret; + + ret = libie_ctlq_init(params->ctx, params->cctlq_info, params->num_qs); + if (ret) + return ret; + + xnm = kvzalloc_obj(*xnm); + if (!xnm) + goto ctlq_deinit; + + ret = libie_ctlq_xn_init_dma(¶ms->ctx->mmio_info.pdev->dev, xnm); + if (ret) + goto free_xnm; + + spin_lock_init(&xnm->free_xns_bm_lock); + init_completion(&xnm->can_destroy); + bitmap_fill(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES); + + for (u32 i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + xn->index = i; + init_completion(&xn->cmd_completion_event); + spin_lock_init(&xn->xn_lock); + } + xnm->ctx = params->ctx; + params->xnm = xnm; + + return 0; + +free_xnm: + kvfree(xnm); +ctlq_deinit: + libie_ctlq_deinit(params->ctx); + + return -ENOMEM; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_init, "LIBIE_CP"); + +MODULE_DESCRIPTION("Control Plane communication API"); +MODULE_IMPORT_NS("LIBETH"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/intel/libie/pci.c b/drivers/net/ethernet/intel/libie/pci.c new file mode 100644 index 000000000000..b756c1186ce9 --- /dev/null +++ b/drivers/net/ethernet/intel/libie/pci.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include + +/** + * libie_find_mmio_region - find MMIO region containing a range + * @mmio_list: list that contains MMIO region info + * @offset: range start offset + * @size: range size + * @bar_idx: BAR index containing the range to search + * + * MMIO regions mappings are traversed from oldest to newest. + * + * Return: pointer to a MMIO region overlapping with the range in any way or + * NULL if no such region is mapped. + */ +static struct libie_pci_mmio_region * +libie_find_mmio_region(const struct list_head *mmio_list, + resource_size_t offset, resource_size_t size, + int bar_idx) +{ + resource_size_t end_offset = offset + size; + struct libie_pci_mmio_region *mr; + + list_for_each_entry(mr, mmio_list, list) { + resource_size_t mr_end = mr->offset + mr->size; + resource_size_t mr_start = mr->offset; + + if (mr->bar_idx != bar_idx) + continue; + if (offset < mr_end && end_offset > mr_start) + return mr; + } + + return NULL; +} + +/** + * __libie_pci_get_mmio_addr - get the MMIO virtual address + * @mmio_info: contains list of MMIO regions + * @offset: register offset to find + * @num_args: number of additional arguments present + * @...: optional BAR index (0 by default) + * + * This function finds the virtual address of a register offset by iterating + * through the non-linear MMIO regions that are mapped by the driver. + * + * The list is traversed oldest to newest, so accessing an older mapping + * via this function while deleting a newer one is allowed. + * + * Return: valid MMIO virtual address or NULL. + */ +void __iomem *__libie_pci_get_mmio_addr(struct libie_mmio_info *mmio_info, + resource_size_t offset, + int num_args, ...) +{ + struct libie_pci_mmio_region *mr; + int bar_idx = 0; + va_list args; + + if (num_args) { + va_start(args, num_args); + bar_idx = va_arg(args, int); + va_end(args); + } + + list_for_each_entry(mr, &mmio_info->mmio_list, list) + if (bar_idx == mr->bar_idx && offset >= mr->offset && + offset < mr->offset + mr->size) { + offset -= mr->offset; + + return mr->addr + offset; + } + + WARN_ONCE(true, "Access to an unmapped MMIO region (BAR%d, offset %pa)", + bar_idx, &offset); + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(__libie_pci_get_mmio_addr, "LIBIE_PCI"); + +/** + * __libie_pci_map_mmio_region - map PCI device MMIO region + * @mmio_info: struct to store the mapped MMIO region + * @offset: MMIO region start offset + * @size: MMIO region size + * @num_args: number of additional arguments present + * @...: optional BAR index (0 by default) + * + * Return: true if the requested address range is accessible through + * new or existing mapping, false otherwise. + */ +bool __libie_pci_map_mmio_region(struct libie_mmio_info *mmio_info, + resource_size_t offset, + resource_size_t size, int num_args, ...) +{ + struct pci_dev *pdev = mmio_info->pdev; + struct libie_pci_mmio_region *mr; + resource_size_t end_offset; + void __iomem *va; + int bar_idx = 0; + va_list args; + + if (num_args) { + va_start(args, num_args); + bar_idx = va_arg(args, int); + va_end(args); + } + + if (bar_idx >= PCI_STD_NUM_BARS || bar_idx < 0 || + !pci_resource_is_mem(pdev, bar_idx)) + return false; + + /* pci_iomap_range() silently maps less + * if the requested length is too big + */ + if (!size || check_add_overflow(offset, size, &end_offset) || + end_offset > pci_resource_len(pdev, bar_idx)) + return false; + + mr = libie_find_mmio_region(&mmio_info->mmio_list, offset, size, + bar_idx); + if (mr) { + pci_warn(pdev, + "Mapping of BAR%u (offset=%llu, size=%llu) intersecting region (offset=%llu, size=%llu) already exists\n", + bar_idx, (unsigned long long)mr->offset, + (unsigned long long)mr->size, + (unsigned long long)offset, (unsigned long long)size); + return mr->offset <= offset && + mr->offset + mr->size >= end_offset; + } + + va = pci_iomap_range(mmio_info->pdev, bar_idx, offset, size); + if (!va) { + pci_err(pdev, "Failed to map BAR%u region\n", bar_idx); + return false; + } + + mr = kvzalloc_obj(*mr); + if (!mr) { + pci_iounmap(pdev, va); + return false; + } + + mr->addr = va; + mr->offset = offset; + mr->size = size; + mr->bar_idx = bar_idx; + + list_add_tail(&mr->list, &mmio_info->mmio_list); + + return true; +} +EXPORT_SYMBOL_NS_GPL(__libie_pci_map_mmio_region, "LIBIE_PCI"); + +/** + * libie_pci_unmap_fltr_regs - unmap selected PCI device MMIO regions + * @mmio_info: contains list of MMIO regions to unmap + * @fltr: returns true, if region is to be unmapped + */ +void libie_pci_unmap_fltr_regs(struct libie_mmio_info *mmio_info, + bool (*fltr)(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg)) +{ + struct libie_pci_mmio_region *mr, *tmp; + + list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) { + if (!fltr(mmio_info, mr)) + continue; + list_del(&mr->list); + pci_iounmap(mmio_info->pdev, mr->addr); + kvfree(mr); + } +} +EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_fltr_regs, "LIBIE_PCI"); + +/** + * libie_pci_unmap_all_mmio_regions - unmap all PCI device MMIO regions + * @mmio_info: contains list of MMIO regions to unmap + */ +void libie_pci_unmap_all_mmio_regions(struct libie_mmio_info *mmio_info) +{ + struct libie_pci_mmio_region *mr, *tmp; + + list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) { + list_del(&mr->list); + pci_iounmap(mmio_info->pdev, mr->addr); + kvfree(mr); + } +} +EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_all_mmio_regions, "LIBIE_PCI"); + +/** + * libie_pci_init_dev - enable and configure the device + * @pdev: PCI device information + * + * Enable the device, request memory regions, set 64-bit DMA mask + * and coherent DMA mask, and enable bus-mastering + * + * Return: %0 on success, -%errno on failure. + */ +int libie_pci_init_dev(struct pci_dev *pdev) +{ + int err; + + err = pcim_enable_device(pdev); + if (err) + return err; + + for (int bar = 0; bar < PCI_STD_NUM_BARS; bar++) + if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { + err = pcim_request_region(pdev, bar, pci_name(pdev)); + if (err) + return err; + } + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (err) + return err; + + pci_set_master(pdev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(libie_pci_init_dev, "LIBIE_PCI"); + +MODULE_DESCRIPTION("Common Ethernet PCI library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 744d6585a949..543e566425c1 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -5678,6 +5678,24 @@ static int mvneta_probe(struct platform_device *pdev) "use SW buffer management\n"); mvneta_bm_put(pp->bm_priv); pp->bm_priv = NULL; + } else if (!device_link_add(&pdev->dev, + &pp->bm_priv->pdev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + /* + * Link guarantees BM resumes before mvneta. + * Without it, BM may not be ready when + * mvneta_bm_port_init() runs on resume, + * causing stale buffer addresses and a crash. + * Fall back to SW management to be safe. + */ + dev_warn(&pdev->dev, + "failed to link to BM, use SW buffer management\n"); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_long, 1 << pp->id); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_short, 1 << pp->id); + mvneta_bm_put(pp->bm_priv); + pp->bm_priv = NULL; } } /* Set RX packet offset correction for platforms, whose diff --git a/drivers/net/ethernet/marvell/mvneta_bm.c b/drivers/net/ethernet/marvell/mvneta_bm.c index 6bb380494919..d6d76fbf27b6 100644 --- a/drivers/net/ethernet/marvell/mvneta_bm.c +++ b/drivers/net/ethernet/marvell/mvneta_bm.c @@ -129,6 +129,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); return -ENOMEM; @@ -139,6 +140,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (err < 0) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; return err; } @@ -395,9 +397,20 @@ static void mvneta_bm_put_sram(struct mvneta_bm *priv) struct mvneta_bm *mvneta_bm_get(struct device_node *node) { - struct platform_device *pdev = of_find_device_by_node(node); + struct platform_device *pdev; + struct mvneta_bm *priv; - return pdev ? platform_get_drvdata(pdev) : NULL; + pdev = of_find_device_by_node(node); + if (!pdev) + return NULL; + + priv = platform_get_drvdata(pdev); + if (!priv) { + platform_device_put(pdev); + return NULL; + } + + return priv; } EXPORT_SYMBOL_GPL(mvneta_bm_get); @@ -477,6 +490,75 @@ static void mvneta_bm_remove(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } +static int mvneta_bm_suspend(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i; + + /* Drain buffers and free pool resources while BM is still clocked */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; + int size_bytes; + + if (bm_pool->type == MVNETA_BM_FREE) + continue; + + mvneta_bm_bufs_free(priv, bm_pool, bm_pool->port_map); + if (bm_pool->hwbm_pool.buf_num) + dev_warn(&priv->pdev->dev, + "pool %d: %d buffers not freed\n", + bm_pool->id, bm_pool->hwbm_pool.buf_num); + + mvneta_bm_pool_disable(priv, bm_pool->id); + + if (bm_pool->virt_addr) { + size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size; + dma_free_coherent(&priv->pdev->dev, size_bytes, + bm_pool->virt_addr, + bm_pool->phys_addr); + bm_pool->virt_addr = NULL; + } + /* + * Safe to destroy: device_link guarantees all mvneta ports + * have already suspended, so no hwbm_pool_add() can be in + * progress holding buf_lock. Pairs with mutex_init() in + * mvneta_bm_pool_use() on resume. + */ + mutex_destroy(&bm_pool->hwbm_pool.buf_lock); + bm_pool->type = MVNETA_BM_FREE; + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); + clk_disable_unprepare(priv->clk); + return 0; +} + +static int mvneta_bm_resume(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i, err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + /* Reinitialize BM hardware; pools are refilled by mvneta_resume() */ + mvneta_bm_default_set(priv); + + /* Restore pool registers lost during clock gating */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), + priv->bm_pools[i].hwbm_pool.size); + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mvneta_bm_pm_ops, mvneta_bm_suspend, mvneta_bm_resume); + static const struct of_device_id mvneta_bm_match[] = { { .compatible = "marvell,armada-380-neta-bm" }, { } @@ -489,6 +571,7 @@ static struct platform_driver mvneta_bm_driver = { .driver = { .name = MVNETA_BM_DRIVER_NAME, .of_match_table = mvneta_bm_match, + .pm = pm_sleep_ptr(&mvneta_bm_pm_ops), }, }; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 714e47f68d93..cece197d1074 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -309,6 +309,8 @@ M(NPC_MCAM_GET_DFT_RL_IDXS, 0x601e, npc_get_dft_rl_idxs, \ M(NPC_MCAM_GET_NPC_PFL_INFO, 0x601f, npc_get_pfl_info, \ msg_req, \ npc_get_pfl_info_rsp) \ +M(NPC_MCAM_READ_DEFAULT_RULE, 0x6021, npc_read_default_rule, msg_req, \ + npc_mcam_read_base_rule_rsp) \ /* NIX mbox IDs (range 0x8000 - 0xFFFF) */ \ M(NIX_LF_ALLOC, 0x8000, nix_lf_alloc, \ nix_lf_alloc_req, nix_lf_alloc_rsp) \ @@ -362,6 +364,7 @@ M(NIX_CPT_BP_ENABLE, 0x8020, nix_cpt_bp_enable, nix_bp_cfg_req, \ nix_bp_cfg_rsp) \ M(NIX_CPT_BP_DISABLE, 0x8021, nix_cpt_bp_disable, nix_bp_cfg_req, \ msg_rsp) \ +M(NIX_RX_SW_SYNC, 0x8022, nix_rx_sw_sync, msg_req, msg_rsp) \ M(NIX_READ_INLINE_IPSEC_CFG, 0x8023, nix_read_inline_ipsec_cfg, \ msg_req, nix_inline_ipsec_cfg) \ M(NIX_MCAST_GRP_CREATE, 0x802b, nix_mcast_grp_create, nix_mcast_grp_create_req, \ @@ -803,6 +806,7 @@ struct npc_set_pkind { */ u8 var_len_off_mask; /* Mask for length with in offset */ u8 shift_dir; /* shift direction to get length of the header at var_len_off */ + u8 skip_size; /* l2 size to skip */ }; /* NPA mbox message formats */ @@ -951,6 +955,7 @@ enum nix_af_status { NIX_AF_ERR_INVALID_MCAST_GRP = -436, NIX_AF_ERR_INVALID_MCAST_DEL_REQ = -437, NIX_AF_ERR_NON_CONTIG_MCE_LIST = -438, + NIX_AF_ERR_RX_SW_SYNC_FAIL = -439, }; /* For NIX RX vtag action */ @@ -1262,11 +1267,13 @@ struct nix_rss_flowkey_cfg { #define NIX_FLOW_KEY_TYPE_INNR_UDP BIT(15) #define NIX_FLOW_KEY_TYPE_INNR_SCTP BIT(16) #define NIX_FLOW_KEY_TYPE_INNR_ETH_DMAC BIT(17) +#define NIX_FLOW_KEY_TYPE_CH_LEN_90B BIT(18) #define NIX_FLOW_KEY_TYPE_CUSTOM0 BIT(19) #define NIX_FLOW_KEY_TYPE_VLAN BIT(20) #define NIX_FLOW_KEY_TYPE_IPV4_PROTO BIT(21) #define NIX_FLOW_KEY_TYPE_AH BIT(22) #define NIX_FLOW_KEY_TYPE_ESP BIT(23) +#define NIX_FLOW_KEY_TYPE_ROCEV2 BIT(24) #define NIX_FLOW_KEY_TYPE_L4_DST_ONLY BIT(28) #define NIX_FLOW_KEY_TYPE_L4_SRC_ONLY BIT(29) #define NIX_FLOW_KEY_TYPE_L3_DST_ONLY BIT(30) @@ -1461,7 +1468,7 @@ struct nix_inline_ipsec_lf_cfg { struct nix_hw_info { struct mbox_msghdr hdr; - u16 rsvs16; + u16 vwqe_delay; u16 max_mtu; u16 min_mtu; u32 rpm_dwrr_mtu; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c index d98b49f47970..fce22e314cac 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c @@ -856,7 +856,7 @@ int rvu_mbox_handler_mcs_ctrl_pkt_rule_write(struct rvu *rvu, static void rvu_mcs_set_lmac_bmap(struct rvu *rvu) { struct mcs *mcs = mcs_get_pdata(0); - unsigned long lmac_bmap; + unsigned long lmac_bmap = 0; int cgx, lmac, port; for (port = 0; port < mcs->hw->lmac_cnt; port++) { diff --git a/drivers/net/ethernet/marvell/octeontx2/af/npc.h b/drivers/net/ethernet/marvell/octeontx2/af/npc.h index eaed172f1606..719b3618eeb5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/npc.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/npc.h @@ -161,10 +161,12 @@ enum npc_kpu_lh_ltype { * Software assigns pkind for each incoming port such as CGX * Ethernet interfaces, LBK interfaces, etc. */ -#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_CPT_HDR_PTP_PKIND +#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_SKIP_SIZE_PKIND enum npc_pkind_type { NPC_RX_LBK_PKIND = 0ULL, + NPC_RX_SKIP_SIZE_PKIND = 46ULL, + NPC_RX_CPT_SKIP_SIZE_PKIND = 50ULL, NPC_RX_CPT_HDR_PTP_PKIND = 54ULL, NPC_RX_CUSTOM_PRE_L2_PKIND = 55ULL, NPC_RX_VLAN_EXDSA_PKIND = 56ULL, diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h index 9d5b7b51bdfa..094227404ef9 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h @@ -1183,7 +1183,7 @@ void rvu_switch_enable_lbk_link(struct rvu *rvu, u16 pcifunc, bool ena); int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir); + u8 shift_dir, u8 skip_size); int rvu_get_hwvf(struct rvu *rvu, int pcifunc); /* CN10K MCS */ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 964bcaae098e..b81c47ea023b 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -269,15 +269,19 @@ u32 convert_bytes_to_dwrr_mtu(u32 bytes) return 0; } -static void nix_rx_sync(struct rvu *rvu, int blkaddr) +static int nix_rx_sync(struct rvu *rvu, int blkaddr) { int err; + mutex_lock(&rvu->rsrc_lock); + /* Sync all in flight RX packets to LLC/DRAM */ rvu_write64(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0)); err = rvu_poll_reg(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0), true); - if (err) + if (err) { dev_err(rvu->dev, "SYNC1: NIX RX software sync failed\n"); + goto unlock; + } /* SW_SYNC ensures all existing transactions are finished and pkts * are written to LLC/DRAM, queues should be teared down after @@ -289,6 +293,10 @@ static void nix_rx_sync(struct rvu *rvu, int blkaddr) err = rvu_poll_reg(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0), true); if (err) dev_err(rvu->dev, "SYNC2: NIX RX software sync failed\n"); + +unlock: + mutex_unlock(&rvu->rsrc_lock); + return err; } static bool is_valid_txschq(struct rvu *rvu, int blkaddr, @@ -3950,6 +3958,11 @@ int rvu_mbox_handler_nix_get_hw_info(struct rvu *rvu, struct msg_req *req, if (blkaddr < 0) return NIX_AF_ERR_AF_LF_INVALID; + rsp->vwqe_delay = 0; + if (!is_rvu_otx2(rvu)) + rsp->vwqe_delay = rvu_read64(rvu, blkaddr, NIX_AF_VWQE_TIMER) & + GENMASK_ULL(9, 0); + if (is_lbk_vf(rvu, pcifunc)) rvu_get_lbk_link_max_frs(rvu, &rsp->max_mtu); else @@ -4297,6 +4310,13 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) field->ltype_match = NPC_LT_LC_CUSTOM0; field->ltype_mask = 0xF; break; + case NIX_FLOW_KEY_TYPE_CH_LEN_90B: + field->lid = NPC_LID_LA; + field->hdr_offset = 24; + field->bytesm1 = 1; /* 2 Bytes*/ + field->ltype_match = NPC_LT_LA_CUSTOM_L2_90B_ETHER; + field->ltype_mask = 0xF; + break; case NIX_FLOW_KEY_TYPE_VLAN: field->lid = NPC_LID_LB; field->hdr_offset = 2; /* Skip TPID (2-bytes) */ @@ -4319,6 +4339,13 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) keyoff_marker = false; } break; + case NIX_FLOW_KEY_TYPE_ROCEV2: + field->hdr_offset = 5; + field->bytesm1 = 2; /* Destination QP */ + field->ltype_mask = 0xF; + field->lid = NPC_LID_LE; + field->ltype_match = NPC_LT_LE_ROCEV2; + break; } field->ena = 1; @@ -5406,7 +5433,7 @@ void rvu_nix_lf_teardown(struct rvu *rvu, u16 pcifunc, int blkaddr, int nixlf) /* reset HW config done for Switch headers */ rvu_npc_set_parse_mode(rvu, pcifunc, OTX2_PRIV_FLAGS_DEFAULT, - (PKIND_TX | PKIND_RX), 0, 0, 0, 0); + (PKIND_TX | PKIND_RX), 0, 0, 0, 0, 0); /* Disabling CGX and NPC config done for PTP */ if (pfvf->hw_rx_tstamp_en) { @@ -6331,6 +6358,25 @@ int rvu_mbox_handler_nix_bandprof_get_hwinfo(struct rvu *rvu, struct msg_req *re return 0; } +int rvu_mbox_handler_nix_rx_sw_sync(struct rvu *rvu, struct msg_req *req, + struct msg_rsp *rsp) +{ + int blkaddr, nixlf, err; + + /* NIX_AF_RX_SW_SYNC is global per NIX block; nix_rx_sync() serializes + * access under rvu->rsrc_lock across mbox and teardown paths. + */ + err = nix_get_nixlf(rvu, req->hdr.pcifunc, &nixlf, &blkaddr); + if (err) + return err; + + err = nix_rx_sync(rvu, blkaddr); + if (err) + return NIX_AF_ERR_RX_SW_SYNC_FAIL; + + return 0; +} + static struct nix_mcast_grp_elem *rvu_nix_mcast_find_grp_elem(struct nix_mcast_grp *mcast_grp, u32 mcast_grp_idx) { diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index 38554d51164e..60922944675b 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -4195,10 +4195,40 @@ npc_set_var_len_offset_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, return 0; } +static int npc_set_skip_size_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, + u8 skip_size) +{ + struct npc_kpu_action0 *act0; + int blkaddr; + u64 val; + + blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, pcifunc); + if (blkaddr < 0) { + dev_err(rvu->dev, "%s: NPC block not implemented\n", __func__); + return -EINVAL; + } + + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = skip_size; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind), val); + + /* Update CPT_HR new PKIND */ + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = (skip_size + 40); + act0->next_state = NPC_S_KPU1_CPT_HDR; + act0->var_len_offset = (skip_size + 6); + act0->var_len_mask = 0xe0; + act0->var_len_shift = 0x5; + act0->var_len_right = 0x1; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4), val); + return 0; +} + int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir) - + u8 shift_dir, u8 skip_size) { struct rvu_pfvf *pfvf = rvu_get_pfvf(rvu, pcifunc); int pf = rvu_get_pf(rvu->pdev, pcifunc); @@ -4220,6 +4250,12 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, shift_dir); if (rc) return rc; + } else if (pkind >= NPC_RX_SKIP_SIZE_PKIND && + pkind <= NPC_RX_SKIP_SIZE_PKIND + 3) { + rc = npc_set_skip_size_pkind(rvu, pcifunc, pkind, + skip_size); + if (rc) + return rc; } rxpkind = pkind; txpkind = pkind; @@ -4263,7 +4299,45 @@ int rvu_mbox_handler_npc_set_pkind(struct rvu *rvu, struct npc_set_pkind *req, { return rvu_npc_set_parse_mode(rvu, req->hdr.pcifunc, req->mode, req->dir, req->pkind, req->var_len_off, - req->var_len_off_mask, req->shift_dir); + req->var_len_off_mask, req->shift_dir, + req->skip_size); +} + +int rvu_mbox_handler_npc_read_default_rule(struct rvu *rvu, + struct msg_req *req, + struct npc_mcam_read_base_rule_rsp *rsp) +{ + struct npc_mcam *mcam = &rvu->hw->mcam; + int index, blkaddr, nixlf, rc; + u16 pcifunc = req->hdr.pcifunc; + u8 intf, enable; + + if (is_cn20k(rvu->pdev)) + return NPC_MCAM_INVALID_REQ; + + blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, 0); + if (blkaddr < 0) + return NPC_MCAM_INVALID_REQ; + + rc = nix_get_nixlf(rvu, pcifunc, &nixlf, NULL); + if (rc < 0) + return rc; + + /* Read the default ucast entry */ + mutex_lock(&mcam->lock); + index = npc_get_nixlf_mcam_index(mcam, pcifunc, nixlf, + NIXLF_UCAST_ENTRY); + if (index < 0) { + mutex_unlock(&mcam->lock); + return NIX_AF_ERR_AF_LF_INVALID; + } + + /* Read the mcam entry */ + npc_read_mcam_entry(rvu, mcam, blkaddr, index, &rsp->entry, &intf, + &enable); + mutex_unlock(&mcam->lock); + + return 0; } int rvu_mbox_handler_npc_read_base_steer_rule(struct rvu *rvu, diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c index 91b5947dae06..d422bdd5e8f8 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c @@ -729,7 +729,12 @@ static void npc_set_features(struct rvu *rvu, int blkaddr, u8 intf) if (!npc_check_field(rvu, blkaddr, NPC_LB, intf)) *features &= ~BIT_ULL(NPC_OUTER_VID); - /* Allow extracting SPI field from AH and ESP headers at same offset */ + /* Warn on unrelated MKEX fields colliding with SPI key bits. AH/ESP + * sharing the same SPI key offset is valid; use npc_is_field_present(), + * not npc_check_field(), to advertise the feature. + */ + if (npc_check_overlap(rvu, blkaddr, NPC_IPSEC_SPI, 0, intf)) + dev_warn(rvu->dev, "Overlap detected the field NPC_IPSEC_SPI\n"); if (npc_is_field_present(rvu, NPC_IPSEC_SPI, intf) && (*features & (BIT_ULL(NPC_IPPROTO_ESP) | BIT_ULL(NPC_IPPROTO_AH)))) *features |= BIT_ULL(NPC_IPSEC_SPI); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index dbf173196608..8e41431c7f9c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -656,6 +656,7 @@ static int cn20k_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 3d253132a17f..ca73a94db794 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -1035,7 +1035,6 @@ int otx2_sq_init(struct otx2_nic *pfvf, u16 qidx, u16 sqb_aura) if (qidx > pfvf->hw.xdp_queues) otx2_attach_xsk_buff(pfvf, sq, (qidx - pfvf->hw.xdp_queues)); - chan_offset = qidx % pfvf->hw.tx_chan_cnt; err = pfvf->hw_ops->sq_aq_init(pfvf, qidx, chan_offset, sqb_aura); if (err) { @@ -1517,6 +1516,7 @@ int otx2_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c index 625bb5a05344..8d2d607bc92f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c @@ -750,7 +750,7 @@ static void otx2_sqe_add_ext(struct otx2_nic *pfvf, struct otx2_snd_queue *sq, ext->lso_format = pfvf->hw.lso_udpv6_idx; } - udph->len = htons(sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct udphdr)); } } else if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { ext->tstmp = 1; diff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c index b036b173a308..0c4f462baa6e 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_router.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c @@ -1302,10 +1302,8 @@ static int __prestera_inetaddr_port_event(struct net_device *port_dev, dev_hold(port_dev); break; case NETDEV_DOWN: - if (!re) { - NL_SET_ERR_MSG_MOD(extack, "Can't find RIF"); - return -EEXIST; - } + if (!re) + return 0; prestera_rif_entry_destroy(port->sw, re); dev_put(port_dev); break; diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 351444fb4871..be3bd025c41a 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -5030,10 +5031,14 @@ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev) continue; list_add_tail(&dev->close_list, &dev_list); + netdev_lock_ops(dev); } netif_close_many(&dev_list, false); + list_for_each_entry(dev, &dev_list, close_list) + netdev_unlock_ops(dev); + eth->dma_dev = dma_dev; list_for_each_entry_safe(dev, tmp, &dev_list, close_list) { diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c index cc8c4ef8038f..99b28aaa7cc4 100644 --- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c @@ -92,6 +92,9 @@ static int mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info) { struct net_device_path_stack stack; + struct net_device_path_ctx ctx = { + .dev = dev, + }; struct net_device_path *path; int err; @@ -101,23 +104,29 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED)) return -1; + ether_addr_copy(ctx.daddr, addr); + rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(&ctx, &stack); rcu_read_unlock(); if (err) return err; path = &stack.path[stack.num_paths - 1]; - if (path->type != DEV_PATH_MTK_WDMA) - return -1; + if (path->type != DEV_PATH_MTK_WDMA) { + err = -EINVAL; + goto err_out; + } info->wdma_idx = path->mtk_wdma.wdma_idx; info->queue = path->mtk_wdma.queue; info->bss = path->mtk_wdma.bss; info->wcid = path->mtk_wdma.wcid; info->amsdu = path->mtk_wdma.amsdu; +err_out: + dev_fill_forward_path_release(&stack); - return 0; + return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index 4fe9d7d4f143..a92cf545bdaf 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -48,13 +48,6 @@ #define MLX5_FRAG_BUF_POOLS_NUM \ (PAGE_SHIFT - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + 1) -struct mlx5_db_pgdir { - struct list_head list; - unsigned long *bitmap; - __be32 *db_page; - dma_addr_t db_dma; -}; - struct mlx5_dma_pool { /* Protects page_list and per-page allocation bitmaps. */ struct mutex lock; @@ -444,106 +437,100 @@ void mlx5_frag_buf_free(struct mlx5_core_dev *dev, struct mlx5_frag_buf *buf) } EXPORT_SYMBOL_GPL(mlx5_frag_buf_free); -static struct mlx5_db_pgdir *mlx5_alloc_db_pgdir(struct mlx5_core_dev *dev, - int node) +static int mlx5_db_dma_pools_debugfs_show(struct seq_file *file, void *priv) { - u32 db_per_page = PAGE_SIZE / cache_line_size(); - struct mlx5_db_pgdir *pgdir; + struct mlx5_core_dev *dev = file->private; + int node; - pgdir = kzalloc_node(sizeof(*pgdir), GFP_KERNEL, node); - if (!pgdir) - return NULL; + mlx5_dma_pools_debugfs_print_header(file); - pgdir->bitmap = bitmap_zalloc_node(db_per_page, GFP_KERNEL, node); - if (!pgdir->bitmap) { - kfree(pgdir); - return NULL; + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_dma_pool *pool = dev->priv.db_node_pools[node]; + + if (!pool) + continue; + + mlx5_dma_pool_debugfs_stats_print(file, pool); } - bitmap_fill(pgdir->bitmap, db_per_page); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mlx5_db_dma_pools_debugfs); - pgdir->db_page = mlx5_dma_zalloc_coherent_node(dev, PAGE_SIZE, - &pgdir->db_dma, node); - if (!pgdir->db_page) { - bitmap_free(pgdir->bitmap); - kfree(pgdir); - return NULL; - } +void mlx5_db_pools_cleanup(struct mlx5_core_dev *dev) +{ + struct mlx5_priv *priv = &dev->priv; + int node; - return pgdir; + debugfs_remove(priv->dbg.db_dma_pools_debugfs); + priv->dbg.db_dma_pools_debugfs = NULL; + + for_each_node_state(node, N_POSSIBLE) + if (priv->db_node_pools[node]) + mlx5_dma_pool_destroy(priv->db_node_pools[node]); + + kfree(priv->db_node_pools); + priv->db_node_pools = NULL; } -static int mlx5_alloc_db_from_pgdir(struct mlx5_db_pgdir *pgdir, - struct mlx5_db *db) +int mlx5_db_pools_init(struct mlx5_core_dev *dev) { - u32 db_per_page = PAGE_SIZE / cache_line_size(); - int offset; - int i; + struct mlx5_priv *priv = &dev->priv; + int node; - i = find_first_bit(pgdir->bitmap, db_per_page); - if (i >= db_per_page) + priv->db_node_pools = kzalloc_objs(*priv->db_node_pools, nr_node_ids); + if (!priv->db_node_pools) return -ENOMEM; - __clear_bit(i, pgdir->bitmap); + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_dma_pool *pool; - db->u.pgdir = pgdir; - db->index = i; - offset = db->index * cache_line_size(); - db->db = pgdir->db_page + offset / sizeof(*pgdir->db_page); - db->dma = pgdir->db_dma + offset; + pool = mlx5_dma_pool_create(dev, node, + order_base_2(cache_line_size())); + if (!pool) { + mlx5_db_pools_cleanup(dev); + return -ENOMEM; + } + priv->db_node_pools[node] = pool; + } - db->db[0] = 0; - db->db[1] = 0; + priv->dbg.db_dma_pools_debugfs = + debugfs_create_file("db_dma_pools", 0444, priv->dbg.dbg_root, + dev, &mlx5_db_dma_pools_debugfs_fops); return 0; } int mlx5_db_alloc_node(struct mlx5_core_dev *dev, struct mlx5_db *db, int node) { - struct mlx5_db_pgdir *pgdir; - int ret = 0; + struct mlx5_dma_pool_page *page; + struct mlx5_dma_pool *pool; + unsigned long idx; + int offset; - mutex_lock(&dev->priv.pgdir_mutex); + node = node == NUMA_NO_NODE ? numa_mem_id() : node; - list_for_each_entry(pgdir, &dev->priv.pgdir_list, list) - if (!mlx5_alloc_db_from_pgdir(pgdir, db)) - goto out; + pool = dev->priv.db_node_pools[node]; + page = mlx5_dma_pool_alloc(pool, &idx); + if (!page) + return -ENOMEM; - pgdir = mlx5_alloc_db_pgdir(dev, node); - if (!pgdir) { - ret = -ENOMEM; - goto out; - } + offset = idx << pool->block_shift; + db->u.pool_page = page; + db->index = idx; + db->db = (__be32 *)((u8 *)page->buf + offset); + db->dma = page->dma + offset; - list_add(&pgdir->list, &dev->priv.pgdir_list); - - /* This should never fail -- we just allocated an empty page: */ - WARN_ON(mlx5_alloc_db_from_pgdir(pgdir, db)); - -out: - mutex_unlock(&dev->priv.pgdir_mutex); - - return ret; + return 0; } EXPORT_SYMBOL_GPL(mlx5_db_alloc_node); void mlx5_db_free(struct mlx5_core_dev *dev, struct mlx5_db *db) { - u32 db_per_page = PAGE_SIZE / cache_line_size(); + struct mlx5_dma_pool_page *page = db->u.pool_page; + struct mlx5_dma_pool *pool = page->pool; - mutex_lock(&dev->priv.pgdir_mutex); - - __set_bit(db->index, db->u.pgdir->bitmap); - - if (bitmap_full(db->u.pgdir->bitmap, db_per_page)) { - dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, - db->u.pgdir->db_page, db->u.pgdir->db_dma); - list_del(&db->u.pgdir->list); - bitmap_free(db->u.pgdir->bitmap); - kfree(db->u.pgdir); - } - - mutex_unlock(&dev->priv.pgdir_mutex); + mlx5_dma_pool_free(pool, page, db->index); } EXPORT_SYMBOL_GPL(mlx5_db_free); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index c31e05529fc4..b9026cc64383 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -383,6 +383,7 @@ static const struct devlink_ops mlx5_devlink_ops = { .rate_node_del = mlx5_esw_devlink_rate_node_del, .rate_leaf_parent_set = mlx5_esw_devlink_rate_leaf_parent_set, .rate_node_parent_set = mlx5_esw_devlink_rate_node_parent_set, + .supported_cross_device_rate_nodes = true, #endif #ifdef CONFIG_MLX5_SF_MANAGER .port_new = mlx5_devlink_sf_port_new, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c b/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c index e770088de129..8044419fb5eb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c @@ -130,7 +130,7 @@ struct mlx5_rsc_dump_cmd *mlx5_rsc_dump_cmd_create(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd; int sgmt_type; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return ERR_PTR(-EOPNOTSUPP); sgmt_type = dev->rsc_dump->fw_segment_type[key->rsc]; @@ -165,7 +165,7 @@ int mlx5_rsc_dump_next(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd, bool more_dump; int err; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return -EOPNOTSUPP; err = mlx5_rsc_dump_trigger(dev, cmd, page); @@ -257,14 +257,14 @@ struct mlx5_rsc_dump *mlx5_rsc_dump_create(struct mlx5_core_dev *dev) } rsc_dump = kzalloc_obj(*rsc_dump); if (!rsc_dump) - return ERR_PTR(-ENOMEM); + return NULL; return rsc_dump; } void mlx5_rsc_dump_destroy(struct mlx5_core_dev *dev) { - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return; kfree(dev->rsc_dump); } @@ -274,7 +274,7 @@ int mlx5_rsc_dump_init(struct mlx5_core_dev *dev) struct mlx5_rsc_dump *rsc_dump = dev->rsc_dump; int err; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return 0; err = mlx5_core_alloc_pd(dev, &rsc_dump->pdn); @@ -303,7 +303,7 @@ int mlx5_rsc_dump_init(struct mlx5_core_dev *dev) void mlx5_rsc_dump_cleanup(struct mlx5_core_dev *dev) { - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return; mlx5_core_destroy_mkey(dev, dev->rsc_dump->mkey); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index 822859c360e7..a3945195f454 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -1027,7 +1027,7 @@ struct mlx5e_profile { void (*cleanup_rx)(struct mlx5e_priv *priv); int (*init_tx)(struct mlx5e_priv *priv); void (*cleanup_tx)(struct mlx5e_priv *priv); - void (*enable)(struct mlx5e_priv *priv); + int (*enable)(struct mlx5e_priv *priv); void (*disable)(struct mlx5e_priv *priv); int (*update_rx)(struct mlx5e_priv *priv); void (*update_stats)(struct mlx5e_priv *priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h b/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h index 091b80a67189..4973fb473ff0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h @@ -88,13 +88,18 @@ enum { #ifdef CONFIG_MLX5_EN_ARFS MLX5E_ARFS_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, #endif -#if defined(CONFIG_MLX5_EN_IPSEC) || defined(CONFIG_MLX5_EN_PSP) +#if defined(CONFIG_MLX5_EN_IPSEC) MLX5E_ACCEL_FS_ESP_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL, MLX5E_ACCEL_FS_POL_FT_LEVEL, MLX5E_ACCEL_FS_POL_MISS_FT_LEVEL, MLX5E_ACCEL_FS_ESP_FT_ROCE_LEVEL, #endif +#if defined(CONFIG_MLX5_EN_PSP) + MLX5E_ACCEL_FS_PSP_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, + MLX5E_ACCEL_FS_PSP_ERR_FT_LEVEL, + MLX5E_ACCEL_FS_PSP_RX_FT_LEVEL, +#endif }; struct mlx5e_flow_steering; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/health.c b/drivers/net/ethernet/mellanox/mlx5/core/en/health.c index cb972b2d46e2..45574f8b10eb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/health.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/health.c @@ -186,7 +186,7 @@ int mlx5e_health_rsc_fmsg_dump(struct mlx5e_priv *priv, struct mlx5_rsc_key *key struct page *page; int size; - if (IS_ERR_OR_NULL(mdev->rsc_dump)) + if (!mdev->rsc_dump) return -EOPNOTSUPP; page = alloc_page(GFP_KERNEL); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/params.c b/drivers/net/ethernet/mellanox/mlx5/core/en/params.c index 1f4a547917ba..5caf7820a136 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/params.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/params.c @@ -545,8 +545,8 @@ bool slow_pci_heuristic(struct mlx5_core_dev *mdev) mlx5_port_max_linkspeed(mdev, &link_speed); pci_bw = pcie_bandwidth_available(mdev->pdev, NULL, NULL, NULL); - mlx5_core_dbg_once(mdev, "Max link speed = %d, PCI BW = %d\n", - link_speed, pci_bw); + mlx5_core_dbg(mdev, "Max link speed = %d, PCI BW = %d\n", link_speed, + pci_bw); #define MLX5E_SLOW_PCI_RATIO (2) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h index b526b3898c22..3f212e46fc2f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h @@ -220,18 +220,7 @@ static inline void mlx5e_accel_tx_finish(struct mlx5e_txqsq *sq, static inline int mlx5e_accel_init_rx(struct mlx5e_priv *priv) { - int err; - - err = mlx5_accel_psp_fs_init_rx_tables(priv); - if (err) - goto out; - - err = mlx5e_ktls_init_rx(priv); - if (err) - mlx5_accel_psp_fs_cleanup_rx_tables(priv); - -out: - return err; + return mlx5e_ktls_init_rx(priv); } static inline void mlx5e_accel_cleanup_rx(struct mlx5e_priv *priv) @@ -242,12 +231,6 @@ static inline void mlx5e_accel_cleanup_rx(struct mlx5e_priv *priv) static inline int mlx5e_accel_init_tx(struct mlx5e_priv *priv) { - int err; - - err = mlx5_accel_psp_fs_init_tx_tables(priv); - if (err) - return err; - return mlx5e_ktls_init_tx(priv); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index d9adb993e64d..73b232379263 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -21,51 +21,52 @@ enum accel_psp_syndrome { PSP_BAD_TRAILER, }; -struct mlx5e_psp_tx { +struct mlx5e_psp_tx_table { struct mlx5_flow_namespace *ns; struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; struct mlx5_flow_handle *rule; - struct mutex mutex; /* Protect PSP TX steering */ - u32 refcnt; - struct mlx5_fc *tx_counter; }; -struct mlx5e_psp_rx_err { +struct mlx5e_psp_rx_check_table { struct mlx5_flow_table *ft; + struct mlx5_flow_group *drop_group; struct mlx5_flow_handle *rule; struct mlx5_flow_handle *auth_fail_rule; struct mlx5_flow_handle *err_rule; struct mlx5_flow_handle *bad_rule; - struct mlx5_modify_hdr *copy_modify_hdr; }; -struct mlx5e_accel_fs_psp_prot { +struct mlx5e_psp_rx_decrypt_table { struct mlx5_flow_table *ft; struct mlx5_flow_group *miss_group; struct mlx5_flow_handle *miss_rule; struct mlx5_modify_hdr *rx_modify_hdr; - struct mlx5_flow_destination default_dest; - struct mlx5e_psp_rx_err rx_err; - u32 refcnt; - struct mutex prot_mutex; /* protect ESP4/ESP6 protocol */ - struct mlx5_flow_handle *def_rule; + struct mlx5_flow_handle *rule; }; -struct mlx5e_accel_fs_psp { - struct mlx5e_accel_fs_psp_prot fs_prot[ACCEL_FS_PSP_NUM_TYPES]; - struct mlx5_fc *rx_counter; - struct mlx5_fc *rx_auth_fail_counter; - struct mlx5_fc *rx_err_counter; - struct mlx5_fc *rx_bad_counter; +struct mlx5e_psp_rx_table { + struct mlx5_flow_table *ft; + struct mlx5_flow_group *miss_group; + struct mlx5_flow_handle *miss_rule; + struct mlx5_flow_handle *udp_rules[ACCEL_FS_PSP_NUM_TYPES]; }; struct mlx5e_psp_fs { struct mlx5_core_dev *mdev; - struct mlx5e_psp_tx *tx_fs; - /* Rx manage */ + struct mlx5_fc *tx_counter; + struct mlx5e_psp_tx_table tx; + + /* Rx */ struct mlx5e_flow_steering *fs; - struct mlx5e_accel_fs_psp *rx_fs; + struct mlx5_fc *rx_counter; + struct mlx5_fc *rx_auth_fail_counter; + struct mlx5_fc *rx_err_counter; + struct mlx5_fc *rx_bad_counter; + + struct mlx5e_psp_rx_decrypt_table decrypt[ACCEL_FS_PSP_NUM_TYPES]; + struct mlx5e_psp_rx_check_table check; + struct mlx5e_psp_rx_table rx; }; /* PSP RX flow steering */ @@ -77,46 +78,202 @@ static enum mlx5_traffic_types fs_psp2tt(enum accel_fs_psp_type i) return MLX5_TT_IPV6_UDP; } -static void accel_psp_fs_rx_err_del_rules(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_err *rx_err) +static int accel_psp_fs_create_ft(struct mlx5e_psp_fs *fs, + struct mlx5_flow_table_attr *ft_attr, + struct mlx5_flow_table **ft) { - if (rx_err->bad_rule) { - mlx5_del_flow_rules(rx_err->bad_rule); - rx_err->bad_rule = NULL; + struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); + int err = 0; + + *ft = mlx5_create_auto_grouped_flow_table(ns, ft_attr); + if (IS_ERR(*ft)) { + err = PTR_ERR(*ft); + *ft = NULL; } - if (rx_err->err_rule) { - mlx5_del_flow_rules(rx_err->err_rule); - rx_err->err_rule = NULL; - } + return err; +} - if (rx_err->auth_fail_rule) { - mlx5_del_flow_rules(rx_err->auth_fail_rule); - rx_err->auth_fail_rule = NULL; - } - - if (rx_err->rule) { - mlx5_del_flow_rules(rx_err->rule); - rx_err->rule = NULL; - } - - if (rx_err->copy_modify_hdr) { - mlx5_modify_header_dealloc(fs->mdev, rx_err->copy_modify_hdr); - rx_err->copy_modify_hdr = NULL; +static void accel_psp_fs_destroy_ft(struct mlx5_flow_table **table) +{ + if (*table) { + mlx5_destroy_flow_table(*table); + *table = NULL; } } -static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_err *rx_err) +static void accel_psp_fs_del_flow_rule(struct mlx5_flow_handle **rule) { - accel_psp_fs_rx_err_del_rules(fs, rx_err); - - if (rx_err->ft) { - mlx5_destroy_flow_table(rx_err->ft); - rx_err->ft = NULL; + if (*rule) { + mlx5_del_flow_rules(*rule); + *rule = NULL; } } +static int accel_psp_fs_create_miss_group(struct mlx5_flow_table *ft, + struct mlx5_flow_group **group) +{ + int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); + u32 *in = kvzalloc(inlen, GFP_KERNEL); + int err = 0; + + if (!in) + return -ENOMEM; + + MLX5_SET(create_flow_group_in, in, start_flow_index, ft->max_fte - 1); + MLX5_SET(create_flow_group_in, in, end_flow_index, ft->max_fte - 1); + *group = mlx5_create_flow_group(ft, in); + if (IS_ERR(*group)) { + err = PTR_ERR(*group); + *group = NULL; + } + kvfree(in); + + return err; +} + +static void accel_psp_fs_destroy_flow_group(struct mlx5_flow_group **group) +{ + if (*group) { + mlx5_destroy_flow_group(*group); + *group = NULL; + } +} + +static int accel_psp_fs_create_counter(struct mlx5_core_dev *dev, + struct mlx5_fc **counter) +{ + *counter = mlx5_fc_create(dev, false); + if (IS_ERR(*counter)) { + int err = PTR_ERR(*counter); + + *counter = NULL; + return err; + } + + return 0; +} + +static void accel_psp_fs_destroy_counter(struct mlx5_core_dev *dev, + struct mlx5_fc **counter) +{ + if (*counter) { + mlx5_fc_destroy(dev, *counter); + *counter = NULL; + } +} + +static void accel_psp_fs_rx_ft_destroy(struct mlx5e_psp_rx_table *rx) +{ + int i; + + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) + accel_psp_fs_del_flow_rule(&rx->udp_rules[i]); + accel_psp_fs_del_flow_rule(&rx->miss_rule); + accel_psp_fs_destroy_flow_group(&rx->miss_group); + accel_psp_fs_destroy_ft(&rx->ft); +} + +static int accel_psp_fs_rx_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_table *rx) +{ + struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); + struct mlx5_flow_destination dest[2] = {}; + struct mlx5_flow_table_attr ft_attr = {}; + struct mlx5_core_dev *mdev = fs->mdev; + MLX5_DECLARE_FLOW_ACT(flow_act); + struct mlx5_flow_handle *rule; + struct mlx5_flow_spec *spec; + int i, err = 0; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + + ft_attr.max_fte = 1 + ACCEL_FS_PSP_NUM_TYPES; + ft_attr.level = MLX5E_ACCEL_FS_PSP_RX_FT_LEVEL; + ft_attr.prio = MLX5E_NIC_PRIO; + ft_attr.autogroup.num_reserved_entries = 1; + err = accel_psp_fs_create_ft(fs, &ft_attr, &rx->ft); + if (err) { + mlx5_core_err(mdev, "fail to create psp rx ft err=%d\n", err); + goto out_err; + } + + err = accel_psp_fs_create_miss_group(rx->ft, &rx->miss_group); + if (err) { + mlx5_core_err(mdev, "fail to create psp rx miss_group err=%d\n", + err); + goto out_err; + } + + /* Add miss rule */ + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + flow_act.flags = FLOW_ACT_IGNORE_FLOW_LEVEL; + dest[0].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest[0].ft = mlx5_get_ttc_flow_table(ttc); + dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest[1].counter = fs->rx_counter; + rule = mlx5_add_flow_rules(rx->ft, NULL, &flow_act, dest, 2); + if (IS_ERR(rule)) { + err = PTR_ERR(rule); + mlx5_core_err(mdev, "fail to create psp rx rule, err=%d\n", + err); + goto out_err; + } + rx->miss_rule = rule; + + /* Add UDP v4/v6 rules */ + spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS; + MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, + outer_headers.ip_version); + MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, spec->match_criteria, + ip_protocol); + MLX5_SET(fte_match_set_lyr_2_4, spec->match_value, ip_protocol, + IPPROTO_UDP); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + flow_act.flags = 0; + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + int version = i == ACCEL_FS_PSP4 ? 4 : 6; + + MLX5_SET(fte_match_param, spec->match_value, + outer_headers.ip_version, version); + dest[0] = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); + dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest[1].counter = fs->rx_counter; + rule = mlx5_add_flow_rules(rx->ft, spec, &flow_act, dest, + 2); + if (IS_ERR(rule)) { + err = PTR_ERR(rule); + mlx5_core_err(mdev, + "fail to create psp rx UDP%d rule err=%d\n", + version, err); + goto out_err; + } + rx->udp_rules[i] = rule; + } + goto out_spec; + +out_err: + accel_psp_fs_rx_ft_destroy(rx); +out_spec: + kvfree(spec); + return err; +} + +static +void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_rx_check_table *check) +{ + accel_psp_fs_del_flow_rule(&check->bad_rule); + accel_psp_fs_del_flow_rule(&check->err_rule); + accel_psp_fs_del_flow_rule(&check->auth_fail_rule); + accel_psp_fs_del_flow_rule(&check->rule); + accel_psp_fs_destroy_flow_group(&check->drop_group); + accel_psp_fs_destroy_ft(&check->ft); +} + static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, enum accel_psp_syndrome syndrome) { @@ -129,15 +286,35 @@ static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, MLX5_SET(fte_match_set_misc2, misc_params_2, psp_syndrome, syndrome); } -static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot, - struct mlx5e_psp_rx_err *rx_err) +static int accel_psp_add_drop_rule(struct mlx5_flow_table *ft, + struct mlx5_flow_spec *spec, + struct mlx5_fc *counter, + struct mlx5_flow_handle **rule) { - u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; - struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_flow_destination dest[2]; + struct mlx5_flow_destination dest = {}; + struct mlx5_flow_act flow_act = {}; + int err = 0; + + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest.counter = counter; + *rule = mlx5_add_flow_rules(ft, spec, &flow_act, &dest, 1); + if (IS_ERR(*rule)) { + err = PTR_ERR(*rule); + *rule = NULL; + } + return err; +} + +static +int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_check_table *check) +{ + struct mlx5_flow_table_attr ft_attr = {}; + struct mlx5_flow_destination dest = {}; + struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_act flow_act = {}; - struct mlx5_modify_hdr *modify_hdr; struct mlx5_flow_handle *fte; struct mlx5_flow_spec *spec; int err = 0; @@ -146,174 +323,97 @@ static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, if (!spec) return -ENOMEM; - /* Action to copy 7 bit psp_syndrome to regB[23:29] */ - MLX5_SET(copy_action_in, action, action_type, MLX5_ACTION_TYPE_COPY); - MLX5_SET(copy_action_in, action, src_field, MLX5_ACTION_IN_FIELD_PSP_SYNDROME); - MLX5_SET(copy_action_in, action, src_offset, 0); - MLX5_SET(copy_action_in, action, length, 7); - MLX5_SET(copy_action_in, action, dst_field, MLX5_ACTION_IN_FIELD_METADATA_REG_B); - MLX5_SET(copy_action_in, action, dst_offset, 23); + ft_attr.max_fte = 4; + ft_attr.autogroup.num_reserved_entries = 1; + ft_attr.autogroup.max_num_groups = 2; + ft_attr.level = MLX5E_ACCEL_FS_PSP_ERR_FT_LEVEL; + ft_attr.prio = MLX5E_NIC_PRIO; + err = accel_psp_fs_create_ft(fs, &ft_attr, &check->ft); + if (err) { + mlx5_core_err(fs->mdev, + "fail to create psp rx check ft err=%d\n", err); + goto out_err; + } - modify_hdr = mlx5_modify_header_alloc(mdev, MLX5_FLOW_NAMESPACE_KERNEL, - 1, action); - if (IS_ERR(modify_hdr)) { - err = PTR_ERR(modify_hdr); - mlx5_core_err(mdev, - "fail to alloc psp copy modify_header_id err=%d\n", err); - goto out_spec; + err = accel_psp_fs_create_miss_group(check->ft, &check->drop_group); + if (err) { + mlx5_core_err(fs->mdev, + "fail to create psp rx check drop group err=%d\n", + err); + goto out_err; } accel_psp_setup_syndrome_match(spec, PSP_OK); /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_MOD_HDR | - MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - flow_act.modify_hdr = modify_hdr; - dest[0].type = fs_prot->default_dest.type; - dest[0].ft = fs_prot->default_dest.ft; - dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[1].counter = fs->rx_fs->rx_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 2); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; + dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest.ft = fs->rx.ft; + fte = mlx5_add_flow_rules(check->ft, spec, &flow_act, &dest, 1); if (IS_ERR(fte)) { err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx err copy rule err=%d\n", err); - goto out; + mlx5_core_err(mdev, "fail to add psp rx check ok rule err=%d\n", + err); + goto out_err; } - rx_err->rule = fte; + check->rule = fte; /* add auth fail drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); accel_psp_setup_syndrome_match(spec, PSP_ICV_FAIL); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_auth_fail_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx auth fail drop rule err=%d\n", + err = accel_psp_add_drop_rule(check->ft, spec, + fs->rx_auth_fail_counter, + &check->auth_fail_rule); + if (err) { + mlx5_core_err(mdev, + "fail to add psp rx check auth fail drop rule err=%d\n", err); - goto out_drop_rule; + goto out_err; } - rx_err->auth_fail_rule = fte; /* add framing drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); accel_psp_setup_syndrome_match(spec, PSP_BAD_TRAILER); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_err_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx framing err drop rule err=%d\n", + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_err_counter, + &check->err_rule); + if (err) { + mlx5_core_err(mdev, + "fail to add psp rx check framing drop rule err=%d\n", err); - goto out_drop_auth_fail_rule; + goto out_err; } - rx_err->err_rule = fte; /* add misc. errors drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_bad_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx misc. err drop rule err=%d\n", + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_bad_counter, + &check->bad_rule); + if (err) { + mlx5_core_err(mdev, + "fail to add psp rx check misc. err drop rule err=%d\n", err); - goto out_drop_error_rule; + goto out_err; } - rx_err->bad_rule = fte; - - rx_err->copy_modify_hdr = modify_hdr; goto out_spec; -out_drop_error_rule: - mlx5_del_flow_rules(rx_err->err_rule); - rx_err->err_rule = NULL; -out_drop_auth_fail_rule: - mlx5_del_flow_rules(rx_err->auth_fail_rule); - rx_err->auth_fail_rule = NULL; -out_drop_rule: - mlx5_del_flow_rules(rx_err->rule); - rx_err->rule = NULL; -out: - mlx5_modify_header_dealloc(mdev, modify_hdr); +out_err: + accel_psp_fs_rx_check_ft_destroy(check); out_spec: kfree(spec); return err; } -static int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot, - struct mlx5e_psp_rx_err *rx_err) +static void +accel_psp_fs_rx_decrypt_ft_destroy(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_decrypt_table *decrypt) { - struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); - struct mlx5_flow_table_attr ft_attr = {}; - struct mlx5_flow_table *ft; - int err; - - ft_attr.max_fte = 2; - ft_attr.autogroup.max_num_groups = 2; - ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; // MLX5E_ACCEL_FS_TCP_FT_LEVEL - ft_attr.prio = MLX5E_NIC_PRIO; - ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); - if (IS_ERR(ft)) { - err = PTR_ERR(ft); - mlx5_core_err(fs->mdev, "fail to create psp rx inline ft err=%d\n", err); - return err; - } - - rx_err->ft = ft; - err = accel_psp_fs_rx_err_add_rule(fs, fs_prot, rx_err); - if (err) - goto out_err; - - return 0; - -out_err: - mlx5_destroy_flow_table(ft); - rx_err->ft = NULL; - return err; -} - -static void accel_psp_fs_rx_fs_destroy(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot) -{ - if (fs_prot->def_rule) { - mlx5_del_flow_rules(fs_prot->def_rule); - fs_prot->def_rule = NULL; - } - - if (fs_prot->rx_modify_hdr) { - mlx5_modify_header_dealloc(fs->mdev, fs_prot->rx_modify_hdr); - fs_prot->rx_modify_hdr = NULL; - } - - if (fs_prot->miss_rule) { - mlx5_del_flow_rules(fs_prot->miss_rule); - fs_prot->miss_rule = NULL; - } - - if (fs_prot->miss_group) { - mlx5_destroy_flow_group(fs_prot->miss_group); - fs_prot->miss_group = NULL; - } - - if (fs_prot->ft) { - mlx5_destroy_flow_table(fs_prot->ft); - fs_prot->ft = NULL; + accel_psp_fs_del_flow_rule(&decrypt->rule); + if (decrypt->rx_modify_hdr) { + mlx5_modify_header_dealloc(fs->mdev, decrypt->rx_modify_hdr); + decrypt->rx_modify_hdr = NULL; } + accel_psp_fs_del_flow_rule(&decrypt->miss_rule); + accel_psp_fs_destroy_flow_group(&decrypt->miss_group); + accel_psp_fs_destroy_ft(&decrypt->ft); } static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) @@ -325,70 +425,64 @@ static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) MLX5_SET(fte_match_set_lyr_2_4, spec->match_value, ip_protocol, IPPROTO_UDP); } -static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot) +static int +accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_decrypt_table *decrypt, + struct mlx5_flow_destination *default_dest) { - struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; - int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_modify_hdr *modify_hdr = NULL; struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_flow_destination dest = {}; struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_flow_group *miss_group; MLX5_DECLARE_FLOW_ACT(flow_act); struct mlx5_flow_handle *rule; struct mlx5_flow_spec *spec; - struct mlx5_flow_table *ft; - u32 *flow_group_in; int err = 0; - flow_group_in = kvzalloc(inlen, GFP_KERNEL); spec = kvzalloc_obj(*spec); - if (!flow_group_in || !spec) { - err = -ENOMEM; - goto out; - } + if (!spec) + return -ENOMEM; /* Create FT */ ft_attr.max_fte = 2; - ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_LEVEL; - ft_attr.prio = MLX5E_NIC_PRIO; + ft_attr.level = MLX5E_ACCEL_FS_PSP_FT_LEVEL; ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 1; - ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); - if (IS_ERR(ft)) { - err = PTR_ERR(ft); - mlx5_core_err(mdev, "fail to create psp rx ft err=%d\n", err); + ft_attr.prio = MLX5E_NIC_PRIO; + err = accel_psp_fs_create_ft(fs, &ft_attr, &decrypt->ft); + if (err) { + mlx5_core_err(mdev, "fail to create psp rx decrypt ft err=%d\n", + err); goto out_err; } - fs_prot->ft = ft; /* Create miss_group */ - MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, ft->max_fte - 1); - MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, ft->max_fte - 1); - miss_group = mlx5_create_flow_group(ft, flow_group_in); - if (IS_ERR(miss_group)) { - err = PTR_ERR(miss_group); - mlx5_core_err(mdev, "fail to create psp rx miss_group err=%d\n", err); + err = accel_psp_fs_create_miss_group(decrypt->ft, &decrypt->miss_group); + if (err) { + mlx5_core_err(mdev, + "fail to create psp rx decrypt miss_group err=%d\n", + err); goto out_err; } - fs_prot->miss_group = miss_group; /* Create miss rule */ - rule = mlx5_add_flow_rules(ft, spec, &flow_act, &fs_prot->default_dest, 1); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; + rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, default_dest, + 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); - mlx5_core_err(mdev, "fail to create psp rx miss_rule err=%d\n", err); + mlx5_core_err(mdev, + "fail to create psp rx decrypt miss_rule err=%d\n", + err); goto out_err; } - fs_prot->miss_rule = rule; + decrypt->miss_rule = rule; - /* Add default Rx psp rule */ + /* Add PSP RX decrypt rule */ setup_fte_udp_psp(spec, PSP_DEFAULT_UDP_PORT); flow_act.crypto.type = MLX5_FLOW_CONTEXT_ENCRYPT_DECRYPT_TYPE_PSP; /* Set bit[31, 30] PSP marker */ - /* Set bit[29-23] psp_syndrome is set in error FT */ #define MLX5E_PSP_MARKER_BIT (BIT(30) | BIT(31)) MLX5_SET(set_action_in, action, action_type, MLX5_ACTION_TYPE_SET); MLX5_SET(set_action_in, action, field, MLX5_ACTION_IN_FIELD_METADATA_REG_B); @@ -403,275 +497,159 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, modify_hdr = NULL; goto out_err; } - fs_prot->rx_modify_hdr = modify_hdr; + decrypt->rx_modify_hdr = modify_hdr; flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_CRYPTO_DECRYPT | MLX5_FLOW_CONTEXT_ACTION_MOD_HDR; flow_act.modify_hdr = modify_hdr; dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - dest.ft = fs_prot->rx_err.ft; - rule = mlx5_add_flow_rules(fs_prot->ft, spec, &flow_act, &dest, 1); + dest.ft = fs->check.ft; + rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, &dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); - mlx5_core_err(mdev, - "fail to add psp rule Rx decryption, err=%d, flow_act.action = %#04X\n", - err, flow_act.action); + mlx5_core_err(mdev, "fail to add psp rx decrypt rule, err=%d\n", + err); goto out_err; } - fs_prot->def_rule = rule; - goto out; + decrypt->rule = rule; + goto out_spec; out_err: - accel_psp_fs_rx_fs_destroy(fs, fs_prot); -out: - kvfree(flow_group_in); + accel_psp_fs_rx_decrypt_ft_destroy(fs, decrypt); +out_spec: kvfree(spec); return err; } -static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) -{ - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - - accel_psp = fs->rx_fs; - - /* The netdev unreg already happened, so all offloaded rule are already removed */ - fs_prot = &accel_psp->fs_prot[type]; - - accel_psp_fs_rx_fs_destroy(fs, fs_prot); - - accel_psp_fs_rx_err_destroy_ft(fs, &fs_prot->rx_err); - - return 0; -} - -static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) +static void accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs) { struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - int err; - - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; - - fs_prot->default_dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(type)); - - err = accel_psp_fs_rx_err_create_ft(fs, fs_prot, &fs_prot->rx_err); - if (err) - return err; - - err = accel_psp_fs_rx_create_ft(fs, fs_prot); - if (err) - accel_psp_fs_rx_err_destroy_ft(fs, &fs_prot->rx_err); - - return err; -} - -static int accel_psp_fs_rx_ft_get(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) -{ - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5_flow_destination dest = {}; - struct mlx5e_accel_fs_psp *accel_psp; - struct mlx5_ttc_table *ttc; - int err = 0; - - if (!fs || !fs->rx_fs) - return -EINVAL; - - ttc = mlx5e_fs_get_ttc(fs->fs, false); - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; - mutex_lock(&fs_prot->prot_mutex); - if (fs_prot->refcnt++) - goto out; - - /* create FT */ - err = accel_psp_fs_rx_create(fs, type); - if (err) { - fs_prot->refcnt--; - goto out; - } - - /* connect */ - dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - dest.ft = fs_prot->ft; - mlx5_ttc_fwd_dest(ttc, fs_psp2tt(type), &dest); - -out: - mutex_unlock(&fs_prot->prot_mutex); - return err; -} - -static void accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) -{ - struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; - mutex_lock(&fs_prot->prot_mutex); - if (--fs_prot->refcnt) - goto out; + int i; /* disconnect */ - mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(type)); - - /* remove FT */ - accel_psp_fs_rx_destroy(fs, type); - -out: - mutex_unlock(&fs_prot->prot_mutex); + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + accel_psp_fs_rx_decrypt_ft_destroy(fs, &fs->decrypt[i]); + } + accel_psp_fs_rx_check_ft_destroy(&fs->check); + accel_psp_fs_rx_ft_destroy(&fs->rx); } -static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, + struct netlink_ext_ack *extack) { - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - enum accel_fs_psp_type i; + struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); + int i, err; - if (!fs->rx_fs) - return; - - accel_psp = fs->rx_fs; - mlx5_fc_destroy(fs->mdev, accel_psp->rx_bad_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_err_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_auth_fail_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_counter); - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - mutex_destroy(&fs_prot->prot_mutex); - WARN_ON(fs_prot->refcnt); + err = accel_psp_fs_rx_ft_create(fs, &fs->rx); + if (err) { + NL_SET_ERR_MSG(extack, "Failed creating RX steering table"); + return err; } - kfree(fs->rx_fs); - fs->rx_fs = NULL; -} -static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) -{ - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_fc *flow_counter; - enum accel_fs_psp_type i; - int err; - - accel_psp = kzalloc_obj(*accel_psp); - if (!accel_psp) - return -ENOMEM; + err = accel_psp_fs_rx_check_ft_create(fs, &fs->check); + if (err) { + NL_SET_ERR_MSG(extack, + "Failed creating RX check steering table"); + goto err_ft; + } for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - mutex_init(&fs_prot->prot_mutex); - } + struct mlx5_flow_destination dest; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { - mlx5_core_warn(mdev, - "fail to create psp rx flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_err; - } - accel_psp->rx_counter = flow_counter; + dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); + err = accel_psp_fs_rx_decrypt_ft_create(fs, &fs->decrypt[i], + &dest); + if (err) { + NL_SET_ERR_MSG(extack, + "Failed creating RX decrypt steering table"); + goto err_decrypt_ft; + } - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { - mlx5_core_warn(mdev, - "fail to create psp rx auth fail flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_counter_err; + dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest.ft = fs->decrypt[i].ft; + mlx5_ttc_fwd_dest(ttc, fs_psp2tt(i), &dest); } - accel_psp->rx_auth_fail_counter = flow_counter; - - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { - mlx5_core_warn(mdev, - "fail to create psp rx error flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_auth_fail_counter_err; - } - accel_psp->rx_err_counter = flow_counter; - - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { - mlx5_core_warn(mdev, - "fail to create psp rx bad flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_err_counter_err; - } - accel_psp->rx_bad_counter = flow_counter; - - fs->rx_fs = accel_psp; return 0; -out_err_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_err_counter); - accel_psp->rx_err_counter = NULL; -out_auth_fail_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_auth_fail_counter); - accel_psp->rx_auth_fail_counter = NULL; -out_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_counter); - accel_psp->rx_counter = NULL; -out_err: - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - mutex_destroy(&fs_prot->prot_mutex); +err_decrypt_ft: + while (--i >= 0) { + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + accel_psp_fs_rx_decrypt_ft_destroy(fs, &fs->decrypt[i]); } - kfree(accel_psp); - fs->rx_fs = NULL; + accel_psp_fs_rx_check_ft_destroy(&fs->check); +err_ft: + accel_psp_fs_rx_ft_destroy(&fs->rx); + return err; +} +static void accel_psp_fs_rx_cleanup(struct mlx5e_psp_fs *fs) +{ + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_bad_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_err_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_auth_fail_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_counter); +} + +static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) +{ + struct mlx5_core_dev *mdev = fs->mdev; + int err; + + err = accel_psp_fs_create_counter(mdev, &fs->rx_counter); + if (err) { + mlx5_core_warn(mdev, + "fail to create psp rx flow counter err=%d\n", + err); + goto out_err; + } + + err = accel_psp_fs_create_counter(mdev, &fs->rx_auth_fail_counter); + if (err) { + mlx5_core_warn(mdev, + "fail to create psp rx auth fail flow counter err=%d\n", + err); + goto out_err; + } + + err = accel_psp_fs_create_counter(mdev, &fs->rx_err_counter); + if (err) { + mlx5_core_warn(mdev, + "fail to create psp rx error flow counter err=%d\n", + err); + goto out_err; + } + + err = accel_psp_fs_create_counter(mdev, &fs->rx_bad_counter); + if (err) { + mlx5_core_warn(mdev, + "fail to create psp rx bad flow counter err=%d\n", + err); + goto out_err; + } + + return 0; + +out_err: + accel_psp_fs_rx_cleanup(fs); return err; } void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) { - int i; - if (!priv->psp) return; - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) - accel_psp_fs_rx_ft_put(priv->psp->fs, i); + netdev_lock(priv->netdev); + accel_psp_fs_rx_destroy(priv->psp->fs); + netdev_unlock(priv->netdev); } -int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) -{ - struct mlx5e_psp_fs *fs; - int err, i; - - if (!priv->psp) - return 0; - - fs = priv->psp->fs; - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - err = accel_psp_fs_rx_ft_get(fs, i); - if (err) - goto out_err; - } - - return 0; - -out_err: - i--; - while (i >= 0) { - accel_psp_fs_rx_ft_put(fs, i); - --i; - } - - return err; -} - -static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_tx_table *tx) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_table_attr ft_attr = {}; @@ -681,7 +659,6 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) u32 *in, *mc, *outer_headers_c; struct mlx5_flow_handle *rule; struct mlx5_flow_spec *spec; - struct mlx5e_psp_tx *tx_fs; struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; int err = 0; @@ -700,8 +677,7 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) ft_attr.level = MLX5E_PSP_LEVEL; ft_attr.autogroup.max_num_groups = 1; - tx_fs = fs->tx_fs; - ft = mlx5_create_flow_table(tx_fs->ns, &ft_attr); + ft = mlx5_create_flow_table(tx->ns, &ft_attr); if (IS_ERR(ft)) { err = PTR_ERR(ft); mlx5_core_err(mdev, "PSP: fail to add psp tx flow table, err = %d\n", err); @@ -727,7 +703,7 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) MLX5_FLOW_CONTEXT_ACTION_CRYPTO_ENCRYPT | MLX5_FLOW_CONTEXT_ACTION_COUNT; dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest.counter = tx_fs->tx_counter; + dest.counter = fs->tx_counter; rule = mlx5_add_flow_rules(ft, spec, &flow_act, &dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); @@ -735,9 +711,9 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) goto err_add_flow_rule; } - tx_fs->ft = ft; - tx_fs->fg = fg; - tx_fs->rule = rule; + tx->ft = ft; + tx->fg = fg; + tx->rule = rule; goto out; err_add_flow_rule: @@ -750,87 +726,35 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) return err; } -static void accel_psp_fs_tx_destroy(struct mlx5e_psp_tx *tx_fs) +static void accel_psp_fs_tx_ft_destroy(struct mlx5e_psp_tx_table *tx) { - if (!tx_fs->ft) - return; - - mlx5_del_flow_rules(tx_fs->rule); - mlx5_destroy_flow_group(tx_fs->fg); - mlx5_destroy_flow_table(tx_fs->ft); + accel_psp_fs_del_flow_rule(&tx->rule); + accel_psp_fs_destroy_flow_group(&tx->fg); + accel_psp_fs_destroy_ft(&tx->ft); } -static int accel_psp_fs_tx_ft_get(struct mlx5e_psp_fs *fs) +static void accel_psp_fs_tx_cleanup(struct mlx5e_psp_fs *fs) { - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - int err = 0; - - mutex_lock(&tx_fs->mutex); - if (tx_fs->refcnt++) - goto out; - - err = accel_psp_fs_tx_create_ft_table(fs); - if (err) - tx_fs->refcnt--; -out: - mutex_unlock(&tx_fs->mutex); - return err; + accel_psp_fs_destroy_counter(fs->mdev, &fs->tx_counter); } -static void accel_psp_fs_tx_ft_put(struct mlx5e_psp_fs *fs) -{ - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - - mutex_lock(&tx_fs->mutex); - if (--tx_fs->refcnt) - goto out; - - accel_psp_fs_tx_destroy(tx_fs); -out: - mutex_unlock(&tx_fs->mutex); -} - -static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) -{ - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - - if (!tx_fs) - return; - - mlx5_fc_destroy(fs->mdev, tx_fs->tx_counter); - mutex_destroy(&tx_fs->mutex); - WARN_ON(tx_fs->refcnt); - kfree(tx_fs); - fs->tx_fs = NULL; -} - -static int accel_psp_fs_init_tx(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_tx_init(struct mlx5e_psp_fs *fs) { struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_flow_namespace *ns; - struct mlx5_fc *flow_counter; - struct mlx5e_psp_tx *tx_fs; + int err; - ns = mlx5_get_flow_namespace(mdev, MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); - if (!ns) + fs->tx.ns = mlx5_get_flow_namespace(mdev, + MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); + if (!fs->tx.ns) return -EOPNOTSUPP; - tx_fs = kzalloc_obj(*tx_fs); - if (!tx_fs) - return -ENOMEM; - - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, &fs->tx_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp tx flow counter err=%pe\n", - flow_counter); - kfree(tx_fs); - return PTR_ERR(flow_counter); + "fail to create psp tx flow counter err=%d\n", + err); + return err; } - tx_fs->tx_counter = flow_counter; - mutex_init(&tx_fs->mutex); - tx_fs->ns = ns; - fs->tx_fs = tx_fs; return 0; } @@ -838,32 +762,29 @@ static void mlx5e_accel_psp_fs_get_stats_fill(struct mlx5e_priv *priv, struct mlx5e_psp_stats *stats) { - struct mlx5e_psp_tx *tx_fs = priv->psp->fs->tx_fs; + struct mlx5e_psp_fs *fs = priv->psp->fs; struct mlx5_core_dev *mdev = priv->mdev; - struct mlx5e_accel_fs_psp *accel_psp; - accel_psp = (struct mlx5e_accel_fs_psp *)priv->psp->fs->rx_fs; - - if (tx_fs->tx_counter) - mlx5_fc_query(mdev, tx_fs->tx_counter, &stats->psp_tx_pkts, + if (fs->tx_counter) + mlx5_fc_query(mdev, fs->tx_counter, &stats->psp_tx_pkts, &stats->psp_tx_bytes); - if (accel_psp->rx_counter) - mlx5_fc_query(mdev, accel_psp->rx_counter, &stats->psp_rx_pkts, + if (fs->rx_counter) + mlx5_fc_query(mdev, fs->rx_counter, &stats->psp_rx_pkts, &stats->psp_rx_bytes); - if (accel_psp->rx_auth_fail_counter) - mlx5_fc_query(mdev, accel_psp->rx_auth_fail_counter, + if (fs->rx_auth_fail_counter) + mlx5_fc_query(mdev, fs->rx_auth_fail_counter, &stats->psp_rx_pkts_auth_fail, &stats->psp_rx_bytes_auth_fail); - if (accel_psp->rx_err_counter) - mlx5_fc_query(mdev, accel_psp->rx_err_counter, + if (fs->rx_err_counter) + mlx5_fc_query(mdev, fs->rx_err_counter, &stats->psp_rx_pkts_frame_err, &stats->psp_rx_bytes_frame_err); - if (accel_psp->rx_bad_counter) - mlx5_fc_query(mdev, accel_psp->rx_bad_counter, + if (fs->rx_bad_counter) + mlx5_fc_query(mdev, fs->rx_bad_counter, &stats->psp_rx_pkts_drop, &stats->psp_rx_bytes_drop); } @@ -873,21 +794,15 @@ void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; - accel_psp_fs_tx_ft_put(priv->psp->fs); -} - -int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) -{ - if (!priv->psp) - return 0; - - return accel_psp_fs_tx_ft_get(priv->psp->fs); + netdev_lock(priv->netdev); + accel_psp_fs_tx_ft_destroy(&priv->psp->fs->tx); + netdev_unlock(priv->netdev); } static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs) { - accel_psp_fs_cleanup_rx(fs); - accel_psp_fs_cleanup_tx(fs); + accel_psp_fs_rx_cleanup(fs); + accel_psp_fs_tx_cleanup(fs); kfree(fs); } @@ -901,29 +816,63 @@ static struct mlx5e_psp_fs *mlx5e_accel_psp_fs_init(struct mlx5e_priv *priv) return ERR_PTR(-ENOMEM); fs->mdev = priv->mdev; - err = accel_psp_fs_init_tx(fs); + err = accel_psp_fs_tx_init(fs); if (err) goto err_tx; fs->fs = priv->fs; - err = accel_psp_fs_init_rx(fs); + err = accel_psp_fs_rx_init(fs); if (err) goto err_rx; return fs; err_rx: - accel_psp_fs_cleanup_tx(fs); + accel_psp_fs_tx_cleanup(fs); err_tx: kfree(fs); return ERR_PTR(err); } +static int accel_psp_fs_create(struct mlx5e_priv *priv, + struct netlink_ext_ack *extack) +{ + int err; + + err = accel_psp_fs_rx_create(priv->psp->fs, extack); + if (err) + return err; + + err = accel_psp_fs_tx_ft_create(priv->psp->fs, &priv->psp->fs->tx); + if (err) { + NL_SET_ERR_MSG(extack, "Failed creating TX steering table"); + accel_psp_fs_rx_destroy(priv->psp->fs); + } + return err; +} + +static void accel_psp_fs_destroy(struct mlx5e_priv *priv) +{ + accel_psp_fs_tx_ft_destroy(&priv->psp->fs->tx); + accel_psp_fs_rx_destroy(priv->psp->fs); +} + static int mlx5e_psp_set_config(struct psp_dev *psd, struct psp_dev_config *conf, struct netlink_ext_ack *extack) { - return 0; /* TODO: this should actually do things to the device */ + struct mlx5e_priv *priv = netdev_priv(psd->main_netdev); + bool psp_enabled = psd->config.versions; + bool enable_psp = conf->versions; + int err = 0; + + netdev_lock(priv->netdev); + if (!psp_enabled && enable_psp) + err = accel_psp_fs_create(priv, extack); + else if (psp_enabled && !enable_psp) + accel_psp_fs_destroy(priv); + netdev_unlock(priv->netdev); + return err; } static int @@ -1072,21 +1021,21 @@ void mlx5e_psp_unregister(struct mlx5e_priv *priv) { struct mlx5e_psp *psp = priv->psp; - if (!psp || !psp->psp) + if (!psp || !psp->psd) return; - psp_dev_unregister(psp->psp); - psp->psp = NULL; + psp_dev_unregister(psp->psd); + psp->psd = NULL; } -void mlx5e_psp_register(struct mlx5e_priv *priv) +int mlx5e_psp_register(struct mlx5e_priv *priv) { struct mlx5e_psp *psp = priv->psp; struct psp_dev *psd; /* FW Caps missing */ if (!priv->psp) - return; + return 0; psp->caps.assoc_drv_spc = sizeof(u32); psp->caps.versions = 1 << PSP_VERSION_HDR0_AES_GCM_128; @@ -1098,9 +1047,11 @@ void mlx5e_psp_register(struct mlx5e_priv *priv) if (IS_ERR(psd)) { mlx5_core_err(priv->mdev, "PSP failed to register due to %pe\n", psd); - return; + return PTR_ERR(psd); } - psp->psp = psd; + psp->psd = psd; + + return 0; } int mlx5e_psp_init(struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h index 6b62fef0d9a7..3f441e7dd55a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h @@ -23,7 +23,7 @@ struct mlx5e_psp_stats { }; struct mlx5e_psp { - struct psp_dev *psp; + struct psp_dev *psd; struct psp_dev_caps caps; struct mlx5e_psp_fs *fs; atomic_t tx_key_cnt; @@ -43,33 +43,21 @@ static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) return true; } -int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv); void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv); -int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv); void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv); -void mlx5e_psp_register(struct mlx5e_priv *priv); +int mlx5e_psp_register(struct mlx5e_priv *priv); void mlx5e_psp_unregister(struct mlx5e_priv *priv); int mlx5e_psp_init(struct mlx5e_priv *priv); void mlx5e_psp_cleanup(struct mlx5e_priv *priv); #else -static inline int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) -{ - return 0; -} - static inline void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) { } -static inline int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) -{ - return 0; -} - static inline void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) { } static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) { return false; } -static inline void mlx5e_psp_register(struct mlx5e_priv *priv) { } +static inline int mlx5e_psp_register(struct mlx5e_priv *priv) { return 0; } static inline void mlx5e_psp_unregister(struct mlx5e_priv *priv) { } static inline int mlx5e_psp_init(struct mlx5e_priv *priv) { return 0; } static inline void mlx5e_psp_cleanup(struct mlx5e_priv *priv) { } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c index ef7f5338540f..348fd7a96261 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c @@ -14,12 +14,6 @@ #include "en_accel/psp_rxtx.h" #include "en_accel/psp.h" -enum { - MLX5E_PSP_OFFLOAD_RX_SYNDROME_DECRYPTED, - MLX5E_PSP_OFFLOAD_RX_SYNDROME_AUTH_FAILED, - MLX5E_PSP_OFFLOAD_RX_SYNDROME_BAD_TRAILER, -}; - static void mlx5e_psp_set_swp(struct sk_buff *skb, struct mlx5e_accel_tx_psp_state *psp_st, struct mlx5_wqe_eth_seg *eseg) @@ -122,16 +116,11 @@ static bool mlx5e_psp_set_state(struct mlx5e_priv *priv, bool mlx5e_psp_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff *skb, struct mlx5_cqe64 *cqe) { - u32 psp_meta_data = be32_to_cpu(cqe->ft_metadata); struct mlx5e_priv *priv = netdev_priv(netdev); - u16 dev_id = priv->psp->psp->id; + u16 dev_id = priv->psp->psd->id; bool strip_icv = true; u8 generation = 0; - /* TBD: report errors as SW counters to ethtool, any further handling ? */ - if (MLX5_PSP_METADATA_SYNDROME(psp_meta_data) != MLX5E_PSP_OFFLOAD_RX_SYNDROME_DECRYPTED) - goto drop; - if (psp_dev_rcv(skb, dev_id, generation, strip_icv)) goto drop; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h index 70289c921bd6..2b080c39cc37 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h @@ -10,9 +10,8 @@ #include "en.h" #include "en/txrx.h" -/* Bit30: PSP marker, Bit29-23: PSP syndrome, Bit22-0: PSP obj id */ +/* Bit30: PSP marker, Bit22-0: PSP obj id */ #define MLX5_PSP_METADATA_MARKER(metadata) ((((metadata) >> 30) & 0x3) == 0x3) -#define MLX5_PSP_METADATA_SYNDROME(metadata) (((metadata) >> 23) & GENMASK(6, 0)) #define MLX5_PSP_METADATA_HANDLE(metadata) ((metadata) & GENMASK(22, 0)) struct mlx5e_accel_tx_psp_state { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index f0407a850ea8..fc110a7d16e8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -695,7 +695,7 @@ static void mlx5e_free_mpwqe_rq_drop_page(struct mlx5e_rq *rq) dma_unmap_page(rq->pdev, rq->wqe_overflow.addr, page_size, rq->buff.map_dir); - __free_pages(rq->wqe_overflow.page, page_order); + __free_pages(rq->wqe_overflow.page, page_order); } static int mlx5e_init_rxq_rq(struct mlx5e_channel *c, struct mlx5e_params *params, @@ -3245,9 +3245,9 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv) int i; old_num_txqs = netdev->real_num_tx_queues; - old_ntc = netdev->num_tc ? : 1; + old_ntc = netdev_get_num_tc(netdev) ? : 1; for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++) - old_tc_to_txq[i] = netdev->tc_to_txq[i]; + old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined); nch = priv->channels.params.num_channels; ntc = priv->channels.params.mqprio.num_tc; @@ -6195,7 +6195,7 @@ static int mlx5e_init_nic_tx(struct mlx5e_priv *priv) return 0; } -static void mlx5e_nic_enable(struct mlx5e_priv *priv) +static int mlx5e_nic_enable(struct mlx5e_priv *priv) { struct net_device *netdev = priv->netdev; struct mlx5_core_dev *mdev = priv->mdev; @@ -6203,7 +6203,9 @@ static void mlx5e_nic_enable(struct mlx5e_priv *priv) mlx5e_fs_init_l2_addr(priv->fs, netdev); mlx5e_ipsec_init(priv); - mlx5e_psp_register(priv); + err = mlx5e_psp_register(priv); + if (err) + goto out_ipsec_cleanup; err = mlx5e_macsec_init(priv); if (err) @@ -6226,7 +6228,7 @@ static void mlx5e_nic_enable(struct mlx5e_priv *priv) mlx5e_pcie_cong_event_init(priv); mlx5e_hv_vhca_stats_create(priv); if (netdev->reg_state != NETREG_REGISTERED) - return; + return 0; mlx5e_dcbnl_init_app(priv); mlx5e_nic_set_rx_mode(priv); @@ -6239,6 +6241,12 @@ static void mlx5e_nic_enable(struct mlx5e_priv *priv) netdev_unlock(netdev); netif_device_attach(netdev); rtnl_unlock(); + + return 0; + +out_ipsec_cleanup: + mlx5e_ipsec_cleanup(priv); + return err; } static void mlx5e_nic_disable(struct mlx5e_priv *priv) @@ -6613,13 +6621,18 @@ int mlx5e_attach_netdev(struct mlx5e_priv *priv) if (err) goto err_cleanup_tx; - if (profile->enable) - profile->enable(priv); + if (profile->enable) { + err = profile->enable(priv); + if (err) + goto err_cleanup_rx; + } mlx5e_update_features(priv->netdev); return 0; +err_cleanup_rx: + profile->cleanup_rx(priv); err_cleanup_tx: profile->cleanup_tx(priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index c8b76d301c92..603051ab1eaa 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -1262,9 +1262,11 @@ static void mlx5e_cleanup_rep_tx(struct mlx5e_priv *priv) mlx5e_rep_neigh_cleanup(rpriv); } -static void mlx5e_rep_enable(struct mlx5e_priv *priv) +static int mlx5e_rep_enable(struct mlx5e_priv *priv) { mlx5e_set_netdev_mtu_boundaries(priv); + + return 0; } static void mlx5e_rep_disable(struct mlx5e_priv *priv) @@ -1322,7 +1324,7 @@ static int uplink_rep_async_event(struct notifier_block *nb, unsigned long event return NOTIFY_DONE; } -static void mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) +static int mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) { struct net_device *netdev = priv->netdev; struct mlx5_core_dev *mdev = priv->mdev; @@ -1357,6 +1359,8 @@ static void mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) netdev_unlock(netdev); netif_device_attach(netdev); rtnl_unlock(); + + return 0; } static void mlx5e_uplink_rep_disable(struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 6fbc0441c4b8..206cf9db3466 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -1081,7 +1081,7 @@ static void mlx5e_shampo_update_ipv4_udp_hdr(struct mlx5e_rq *rq, struct iphdr * struct udphdr *uh; uh = (struct udphdr *)(skb->data + udp_off); - uh->len = htons(skb->len - udp_off); + udp_set_len_short(uh, skb->len - udp_off); if (uh->check) uh->check = ~udp_v4_check(skb->len - udp_off, ipv4->saddr, @@ -1100,7 +1100,7 @@ static void mlx5e_shampo_update_ipv6_udp_hdr(struct mlx5e_rq *rq, struct ipv6hdr struct udphdr *uh; uh = (struct udphdr *)(skb->data + udp_off); - uh->len = htons(skb->len - udp_off); + udp_set_len_short(uh, skb->len - udp_off); if (uh->check) uh->check = ~udp_v6_check(skb->len - udp_off, &ipv6->saddr, @@ -1178,9 +1178,6 @@ static void mlx5e_shampo_update_ipv4_tcp_hdr(struct mlx5e_rq *rq, struct iphdr * skb->csum_start = (unsigned char *)tcp - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); - - if (tcp->cwr) - skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; } static void mlx5e_shampo_update_ipv6_tcp_hdr(struct mlx5e_rq *rq, struct ipv6hdr *ipv6, @@ -1199,9 +1196,6 @@ static void mlx5e_shampo_update_ipv6_tcp_hdr(struct mlx5e_rq *rq, struct ipv6hdr skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6; skb->csum_start = (unsigned char *)tcp - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); - - if (tcp->cwr) - skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; } static void mlx5e_shampo_update_hdr(struct mlx5e_rq *rq, struct mlx5_cqe64 *cqe, bool match) @@ -2263,6 +2257,11 @@ static void mlx5e_handle_rx_cqe_mpwrq_shampo(struct mlx5e_rq *rq, struct mlx5_cq data_offset = wqe_offset & (page_size - 1); page_idx = wqe_offset >> rq->mpwqe.page_shift; + if (unlikely(cqe_bcnt <= ETH_ZLEN + 2 * VLAN_HLEN)) { + match = false; + flush = true; + } + if (*skb && !(match && mlx5e_hw_gro_skb_has_enough_space(*skb, data_bcnt, page_size))) { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c b/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c index accc26d1a872..1dcdb86690bb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c @@ -113,7 +113,7 @@ static struct sk_buff *mlx5e_test_get_udp_skb(struct mlx5e_priv *priv) /* Fill UDP header */ udph->source = htons(9); udph->dest = htons(9); /* Discard Protocol */ - udph->len = htons(sizeof(struct mlx5ehdr) + sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct mlx5ehdr) + sizeof(struct udphdr)); udph->check = 0; /* Fill IP header */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c index 24b1ca4e4ff8..981ce4463582 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c @@ -113,8 +113,8 @@ static void esw_acl_egress_ofld_rules_destroy(struct mlx5_vport *vport) esw_acl_egress_ofld_bounce_rules_destroy(vport); } -static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, - struct mlx5_vport *vport) +static int esw_acl_egress_ofld_fwd_grp_create(struct mlx5_eswitch *esw, + struct mlx5_vport *vport) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_group *fwd_grp; @@ -122,22 +122,12 @@ static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, u32 flow_index = 0; int ret = 0; - if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) { - ret = esw_acl_egress_vlan_grp_create(esw, vport); - if (ret) - return ret; - + if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) flow_index++; - } - - if (!mlx5_esw_acl_egress_fwd2vport_supported(esw)) - goto out; flow_group_in = kvzalloc(inlen, GFP_KERNEL); - if (!flow_group_in) { - ret = -ENOMEM; - goto fwd_grp_err; - } + if (!flow_group_in) + return -ENOMEM; /* This group holds 1 FTE to forward all packets to other vport * when bond vports is supported. @@ -150,16 +140,15 @@ static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, esw_warn(esw->dev, "Failed to create vport[%d] egress fwd2vport flow group, err(%d)\n", vport->vport, ret); - kvfree(flow_group_in); - goto fwd_grp_err; + goto out; } vport->egress.offloads.fwd_grp = fwd_grp; - kvfree(flow_group_in); - return 0; + esw_debug(esw->dev, + "lazy-created fwd_grp for vport %d (flow_index=%u)\n", + vport->vport, flow_index); -fwd_grp_err: - esw_acl_egress_vlan_grp_destroy(vport); out: + kvfree(flow_group_in); return ret; } @@ -185,22 +174,23 @@ static bool esw_acl_egress_needed(struct mlx5_eswitch *esw, u16 vport_num) int esw_acl_egress_ofld_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport) { - int table_size = 0; + int table_size = 1; int err; - if (!mlx5_esw_acl_egress_fwd2vport_supported(esw) && - !MLX5_CAP_GEN(esw->dev, prio_tag_required)) - return 0; - if (!esw_acl_egress_needed(esw, vport->vport)) return 0; + /* The fwd2vport FT/group is created lazily on bond events; if prio_tag + * is not required, skip eager FT allocation here entirely. + */ + if (!MLX5_CAP_GEN(esw->dev, prio_tag_required)) + return 0; + esw_acl_egress_ofld_rules_destroy(vport); + /* Reserve an extra FTE so the fwd_grp can be added lazily later. */ if (mlx5_esw_acl_egress_fwd2vport_supported(esw)) table_size++; - if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) - table_size++; vport->egress.acl = esw_acl_table_create(esw, vport, MLX5_FLOW_NAMESPACE_ESW_EGRESS, table_size); if (IS_ERR(vport->egress.acl)) { @@ -210,21 +200,21 @@ int esw_acl_egress_ofld_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport } vport->egress.type = VPORT_EGRESS_ACL_TYPE_DEFAULT; - err = esw_acl_egress_ofld_groups_create(esw, vport); + err = esw_acl_egress_vlan_grp_create(esw, vport); if (err) - goto group_err; + goto table_err; esw_debug(esw->dev, "vport[%d] configure egress rules\n", vport->vport); err = esw_acl_egress_ofld_rules_create(esw, vport, NULL); if (err) - goto rules_err; + goto vlan_grp_err; return 0; -rules_err: - esw_acl_egress_ofld_groups_destroy(vport); -group_err: +vlan_grp_err: + esw_acl_egress_vlan_grp_destroy(vport); +table_err: esw_acl_egress_table_destroy(vport); return err; } @@ -236,18 +226,53 @@ void esw_acl_egress_ofld_cleanup(struct mlx5_vport *vport) esw_acl_egress_table_destroy(vport); } +/* Lazily allocate the egress ACL table and fwd_grp for a vport that is about + * to receive a fwd2vport rule. The table is otherwise created eagerly only + * when prio_tag is required. + */ +static int esw_acl_egress_ofld_fwd2vport_setup(struct mlx5_eswitch *esw, + struct mlx5_vport *vport) +{ + struct mlx5_flow_table *acl = NULL; + int err; + + if (!vport->egress.acl) { + acl = esw_acl_table_create(esw, vport, + MLX5_FLOW_NAMESPACE_ESW_EGRESS, 1); + if (IS_ERR(acl)) + return PTR_ERR(acl); + vport->egress.acl = acl; + vport->egress.type = VPORT_EGRESS_ACL_TYPE_DEFAULT; + } + + if (vport->egress.offloads.fwd_grp) + return 0; + + err = esw_acl_egress_ofld_fwd_grp_create(esw, vport); + if (err && acl) + esw_acl_egress_table_destroy(vport); + return err; +} + int mlx5_esw_acl_egress_vport_bond(struct mlx5_eswitch *esw, u16 active_vport_num, u16 passive_vport_num) { struct mlx5_vport *passive_vport = mlx5_eswitch_get_vport(esw, passive_vport_num); struct mlx5_vport *active_vport = mlx5_eswitch_get_vport(esw, active_vport_num); struct mlx5_flow_destination fwd_dest = {}; + int err; if (IS_ERR(active_vport)) return PTR_ERR(active_vport); if (IS_ERR(passive_vport)) return PTR_ERR(passive_vport); + mutex_lock(&esw->state_lock); + + err = esw_acl_egress_ofld_fwd2vport_setup(esw, passive_vport); + if (err) + goto unlock; + /* Cleanup and recreate rules WITHOUT fwd2vport of active vport */ esw_acl_egress_ofld_rules_destroy(active_vport); esw_acl_egress_ofld_rules_create(esw, active_vport, NULL); @@ -259,16 +284,24 @@ int mlx5_esw_acl_egress_vport_bond(struct mlx5_eswitch *esw, u16 active_vport_nu fwd_dest.vport.vhca_id = MLX5_CAP_GEN(esw->dev, vhca_id); fwd_dest.vport.flags = MLX5_FLOW_DEST_VPORT_VHCA_ID; - return esw_acl_egress_ofld_rules_create(esw, passive_vport, &fwd_dest); + err = esw_acl_egress_ofld_rules_create(esw, passive_vport, &fwd_dest); + +unlock: + mutex_unlock(&esw->state_lock); + return err; } int mlx5_esw_acl_egress_vport_unbond(struct mlx5_eswitch *esw, u16 vport_num) { struct mlx5_vport *vport = mlx5_eswitch_get_vport(esw, vport_num); + int err; if (IS_ERR(vport)) return PTR_ERR(vport); + mutex_lock(&esw->state_lock); esw_acl_egress_ofld_rules_destroy(vport); - return esw_acl_egress_ofld_rules_create(esw, vport, NULL); + err = esw_acl_egress_ofld_rules_create(esw, vport, NULL); + mutex_unlock(&esw->state_lock); + return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 6e50311faa27..36b00a856bc2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -74,7 +74,7 @@ static void mlx5_esw_offloads_pf_vf_devlink_port_attrs_set(struct mlx5_eswitch * memcpy(dl_port->attrs.switch_id.id, ppid.id, ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; devlink_port_attrs_pci_pf_set(dl_port, controller_num, pfnum, - true); + false); } } @@ -134,13 +134,16 @@ static void mlx5_esw_offloads_sf_devlink_port_attrs_set(struct mlx5_eswitch *esw { struct mlx5_core_dev *dev = esw->dev; struct netdev_phys_item_id ppid = {}; + u32 hpf_ctrl; u16 pfnum; pfnum = mlx5_esw_sf_controller_to_pfnum(dev, controller); + hpf_ctrl = mlx5_esw_get_hpf_host_number(dev) + 1; mlx5_esw_get_port_parent_id(dev, &ppid); memcpy(dl_port->attrs.switch_id.id, &ppid.id[0], ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; - devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, !!controller); + devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, + controller == hpf_ctrl); } int mlx5_esw_offloads_sf_devlink_port_init(struct mlx5_eswitch *esw, struct mlx5_vport *vport, @@ -268,7 +271,6 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) dl_port = vport->dl_port; mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); - mlx5_esw_qos_vport_update_parent(vport, NULL, NULL); devl_rate_leaf_destroy(&dl_port->dl_port); devl_port_unregister(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index faccc60fc93a..0d20f51b9702 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -11,57 +11,8 @@ /* Minimum supported BW share value by the HW is 1 Mbit/sec */ #define MLX5_MIN_BW_SHARE 1 -/* Holds rate nodes associated with an E-Switch. */ -struct mlx5_qos_domain { - /* Serializes access to all qos changes in the qos domain. */ - struct mutex lock; - /* List of all mlx5_esw_sched_nodes. */ - struct list_head nodes; -}; - -static void esw_qos_lock(struct mlx5_eswitch *esw) -{ - mutex_lock(&esw->qos.domain->lock); -} - -static void esw_qos_unlock(struct mlx5_eswitch *esw) -{ - mutex_unlock(&esw->qos.domain->lock); -} - -static void esw_assert_qos_lock_held(struct mlx5_eswitch *esw) -{ - lockdep_assert_held(&esw->qos.domain->lock); -} - -static struct mlx5_qos_domain *esw_qos_domain_alloc(void) -{ - struct mlx5_qos_domain *qos_domain; - - qos_domain = kzalloc_obj(*qos_domain); - if (!qos_domain) - return NULL; - - mutex_init(&qos_domain->lock); - INIT_LIST_HEAD(&qos_domain->nodes); - - return qos_domain; -} - -static int esw_qos_domain_init(struct mlx5_eswitch *esw) -{ - esw->qos.domain = esw_qos_domain_alloc(); - - return esw->qos.domain ? 0 : -ENOMEM; -} - -static void esw_qos_domain_release(struct mlx5_eswitch *esw) -{ - kfree(esw->qos.domain); - esw->qos.domain = NULL; -} - enum sched_node_type { + SCHED_NODE_TYPE_ROOT, SCHED_NODE_TYPE_VPORTS_TSAR, SCHED_NODE_TYPE_VPORT, SCHED_NODE_TYPE_TC_ARBITER_TSAR, @@ -94,7 +45,9 @@ struct mlx5_esw_sched_node { enum sched_node_type type; /* The eswitch this node belongs to. */ struct mlx5_eswitch *esw; - /* The children nodes of this node, empty list for leaf nodes. */ + /* The children nodes of this node, empty list for leaf nodes. + * Can be from multiple E-Switches. + */ struct list_head children; /* Valid only if this node is associated with a vport. */ struct mlx5_vport *vport; @@ -106,16 +59,63 @@ struct mlx5_esw_sched_node { u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; -static void esw_qos_node_attach_to_parent(struct mlx5_esw_sched_node *node) +/* Locking notes: + * QoS changes are normally protected by the shd lock. But on older HW shd + * might not be created at all, so there needs to be a fallback serialization + * mechanism. This is esw->state_lock. + * Callers into QoS hold a combination of RTNL, devlink instance lock and + * esw->state_lock. Devlink rate ops additionally hold the shd lock if it + * exists. + * - VF rate ops use esw_qos_lock/esw_qos_unlock. + * - callers with esw->state_lock held use esw_qos_shd_lock/esw_qos_shd_unlock. + * - devlink callers use esw_qos_devlink_lock/esw_qos_devlink_unlock. + */ +static void esw_assert_qos_lock_held(struct mlx5_core_dev *dev) { - if (!node->parent) { - /* Root children are assigned a depth level of 2. */ - node->level = 2; - list_add_tail(&node->entry, &node->esw->qos.domain->nodes); - } else { - node->level = node->parent->level + 1; - list_add_tail(&node->entry, &node->parent->children); - } + if (dev->shd) + devl_assert_locked(dev->shd); + else + lockdep_assert_held(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); + else + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); + else + mutex_unlock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_shd_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); +} + +static void esw_qos_shd_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); +} + +static void esw_qos_devlink_lock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_devlink_unlock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_unlock(&dev->priv.eswitch->state_lock); } static int esw_qos_num_tcs(struct mlx5_core_dev *dev) @@ -125,14 +125,14 @@ static int esw_qos_num_tcs(struct mlx5_core_dev *dev) return num_tcs < DEVLINK_RATE_TCS_MAX ? num_tcs : DEVLINK_RATE_TCS_MAX; } -static void -esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, struct mlx5_esw_sched_node *parent) +static void esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, + struct mlx5_esw_sched_node *parent) { - list_del_init(&node->entry); node->parent = parent; - if (parent) - node->esw = parent->esw; - esw_qos_node_attach_to_parent(node); + node->esw = parent->esw; + node->level = parent->level + 1; + list_del(&node->entry); + list_add_tail(&node->entry, &parent->children); } static void esw_qos_nodes_set_parent(struct list_head *nodes, @@ -321,22 +321,19 @@ static int esw_qos_create_rate_limit_element(struct mlx5_esw_sched_node *node, return esw_qos_node_create_sched_element(node, sched_ctx, extack); } -static u32 esw_qos_calculate_min_rate_divider(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent) +static u32 +esw_qos_calculate_min_rate_divider(struct mlx5_esw_sched_node *parent) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 fw_max_bw_share = MLX5_CAP_QOS(esw->dev, max_tsar_bw_share); + u32 fw_max_bw_share = MLX5_CAP_QOS(parent->esw->dev, max_tsar_bw_share); struct mlx5_esw_sched_node *node; u32 max_guarantee = 0; /* Find max min_rate across all nodes. * This will correspond to fw_max_bw_share in the final bw_share calculation. */ - list_for_each_entry(node, nodes, entry) { - if (node->esw == esw && node->ix != esw->qos.root_tsar_ix && - node->min_rate > max_guarantee) + list_for_each_entry(node, &parent->children, entry) + if (node->min_rate > max_guarantee) max_guarantee = node->min_rate; - } if (max_guarantee) return max_t(u32, max_guarantee / fw_max_bw_share, 1); @@ -368,18 +365,13 @@ static void esw_qos_update_sched_node_bw_share(struct mlx5_esw_sched_node *node, esw_qos_sched_elem_config(node, node->max_rate, bw_share, extack); } -static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent, +static void esw_qos_normalize_min_rate(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 divider = esw_qos_calculate_min_rate_divider(esw, parent); + u32 divider = esw_qos_calculate_min_rate_divider(parent); struct mlx5_esw_sched_node *node; - list_for_each_entry(node, nodes, entry) { - if (node->esw != esw || node->ix == esw->qos.root_tsar_ix) - continue; - + list_for_each_entry(node, &parent->children, entry) { /* Vports TC TSARs don't have a minimum rate configured, * so there's no need to update the bw_share on them. */ @@ -391,7 +383,7 @@ static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, if (list_empty(&node->children)) continue; - esw_qos_normalize_min_rate(node->esw, node, extack); + esw_qos_normalize_min_rate(node, extack); } } @@ -412,14 +404,11 @@ static u32 esw_qos_calculate_tc_bw_divider(u32 *tc_bw) static int esw_qos_set_node_min_rate(struct mlx5_esw_sched_node *node, u32 min_rate, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = node->esw; - if (min_rate == node->min_rate) return 0; node->min_rate = min_rate; - esw_qos_normalize_min_rate(esw, node->parent, extack); - + esw_qos_normalize_min_rate(node->parent, extack); return 0; } @@ -460,6 +449,7 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, struct mlx5_esw_sched_node *parent = vport_node->parent; u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_node->esw->dev; + struct mlx5_vport *vport = vport_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -471,11 +461,17 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_element, attr, vport_number, vport_node->vport->vport); - MLX5_SET(scheduling_context, sched_ctx, parent_element_id, - parent ? parent->ix : vport_node->esw->qos.root_tsar_ix); + MLX5_SET(vport_element, attr, vport_number, vport->vport); + MLX5_SET(scheduling_context, sched_ctx, parent_element_id, parent->ix); MLX5_SET(scheduling_context, sched_ctx, max_average_bw, vport_node->max_rate); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_node, sched_ctx, extack); } @@ -487,6 +483,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, { u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_tc_node->esw->dev; + struct mlx5_vport *vport = vport_tc_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -498,8 +495,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT_TC); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_tc_element, attr, vport_number, - vport_tc_node->vport->vport); + MLX5_SET(vport_tc_element, attr, vport_number, vport->vport); MLX5_SET(vport_tc_element, attr, traffic_class, vport_tc_node->tc); MLX5_SET(scheduling_context, sched_ctx, max_bw_obj_id, rate_limit_elem_ix); @@ -507,13 +503,20 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, vport_tc_node->parent->ix); MLX5_SET(scheduling_context, sched_ctx, bw_share, vport_tc_node->bw_share); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_tc_node, sched_ctx, extack); } static struct mlx5_esw_sched_node * -__esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type type, +__esw_qos_alloc_node(u32 tsar_ix, enum sched_node_type type, struct mlx5_esw_sched_node *parent) { struct mlx5_esw_sched_node *node; @@ -522,20 +525,12 @@ __esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type if (!node) return NULL; - node->esw = esw; node->ix = tsar_ix; node->type = type; - node->parent = parent; INIT_LIST_HEAD(&node->children); - esw_qos_node_attach_to_parent(node); - if (!parent) { - /* The caller is responsible for inserting the node into the - * parent list if necessary. This function can also be used with - * a NULL parent, which doesn't necessarily indicate that it - * refers to the root scheduling element. - */ - list_del_init(&node->entry); - } + INIT_LIST_HEAD(&node->entry); + if (parent) + esw_qos_node_set_parent(node, parent); return node; } @@ -570,7 +565,7 @@ static int esw_qos_create_vports_tc_node(struct mlx5_esw_sched_node *parent, SCHEDULING_HIERARCHY_E_SWITCH)) return -EOPNOTSUPP; - vports_tc_node = __esw_qos_alloc_node(parent->esw, 0, + vports_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORTS_TC_TSAR, parent); if (!vports_tc_node) { @@ -665,7 +660,6 @@ static int esw_qos_create_tc_arbiter_sched_elem( struct netlink_ext_ack *extack) { u32 tsar_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; - u32 tsar_parent_ix; void *attr; if (!mlx5_qos_tsar_type_supported(tc_arbiter_node->esw->dev, @@ -678,10 +672,8 @@ static int esw_qos_create_tc_arbiter_sched_elem( attr = MLX5_ADDR_OF(scheduling_context, tsar_ctx, element_attributes); MLX5_SET(tsar_element, attr, tsar_type, TSAR_ELEMENT_TSAR_TYPE_TC_ARB); - tsar_parent_ix = tc_arbiter_node->parent ? tc_arbiter_node->parent->ix : - tc_arbiter_node->esw->qos.root_tsar_ix; MLX5_SET(scheduling_context, tsar_ctx, parent_element_id, - tsar_parent_ix); + tc_arbiter_node->parent->ix); MLX5_SET(scheduling_context, tsar_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_TSAR); MLX5_SET(scheduling_context, tsar_ctx, max_average_bw, @@ -694,37 +686,36 @@ static int esw_qos_create_tc_arbiter_sched_elem( } static struct mlx5_esw_sched_node * -__esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct mlx5_esw_sched_node *parent, +__esw_qos_create_vports_sched_node(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node; - u32 tsar_ix; int err; + u32 ix; - err = esw_qos_create_node_sched_elem(esw->dev, esw->qos.root_tsar_ix, 0, - 0, &tsar_ix); + err = esw_qos_create_node_sched_elem(parent->esw->dev, parent->ix, 0, 0, + &ix); if (err) { NL_SET_ERR_MSG_MOD(extack, "E-Switch create TSAR for node failed"); return ERR_PTR(err); } - node = __esw_qos_alloc_node(esw, tsar_ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); + node = __esw_qos_alloc_node(ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); if (!node) { NL_SET_ERR_MSG_MOD(extack, "E-Switch alloc node failed"); err = -ENOMEM; goto err_alloc_node; } - list_add_tail(&node->entry, &esw->qos.domain->nodes); - esw_qos_normalize_min_rate(esw, NULL, extack); - trace_mlx5_esw_node_qos_create(esw->dev, node, node->ix); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_node_qos_create(parent->esw->dev, node, node->ix); return node; err_alloc_node: - if (mlx5_destroy_scheduling_element_cmd(esw->dev, + if (mlx5_destroy_scheduling_element_cmd(parent->esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, - tsar_ix)) + ix)) NL_SET_ERR_MSG_MOD(extack, "E-Switch destroy TSAR for node failed"); return ERR_PTR(err); } @@ -738,7 +729,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac struct mlx5_esw_sched_node *node; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!MLX5_CAP_QOS(esw->dev, log_esw_max_sched_depth)) return ERR_PTR(-EOPNOTSUPP); @@ -746,7 +737,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac if (err) return ERR_PTR(err); - node = __esw_qos_create_vports_sched_node(esw, NULL, extack); + node = __esw_qos_create_vports_sched_node(esw->qos.root, extack); if (IS_ERR(node)) esw_qos_put(esw); @@ -762,45 +753,54 @@ static void __esw_qos_destroy_node(struct mlx5_esw_sched_node *node, struct netl trace_mlx5_esw_node_qos_destroy(esw->dev, node, node->ix); esw_qos_destroy_node(node, extack); - esw_qos_normalize_min_rate(esw, NULL, extack); + esw_qos_normalize_min_rate(esw->qos.root, extack); } static int esw_qos_create(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = esw->dev; + struct mlx5_esw_sched_node *root; + u32 root_ix; int err; if (!MLX5_CAP_GEN(dev, qos) || !MLX5_CAP_QOS(dev, esw_scheduling)) return -EOPNOTSUPP; - err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, - &esw->qos.root_tsar_ix); + err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, &root_ix); if (err) { esw_warn(dev, "E-Switch create root TSAR failed (%d)\n", err); return err; } + root = __esw_qos_alloc_node(root_ix, SCHED_NODE_TYPE_ROOT, NULL); + if (!root) { + esw_warn(dev, "E-Switch create root node failed\n"); + err = -ENOMEM; + goto out_err; + } + root->esw = esw; + root->level = 1; + esw->qos.root = root; refcount_set(&esw->qos.refcnt, 1); return 0; +out_err: + mlx5_destroy_scheduling_element_cmd(dev, SCHEDULING_HIERARCHY_E_SWITCH, + root_ix); + return err; } static void esw_qos_destroy(struct mlx5_eswitch *esw) { - int err; - - err = mlx5_destroy_scheduling_element_cmd(esw->dev, - SCHEDULING_HIERARCHY_E_SWITCH, - esw->qos.root_tsar_ix); - if (err) - esw_warn(esw->dev, "E-Switch destroy root TSAR failed (%d)\n", err); + esw_qos_destroy_node(esw->qos.root, NULL); + esw->qos.root = NULL; } static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { int err = 0; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!refcount_inc_not_zero(&esw->qos.refcnt)) { /* esw_qos_create() set refcount to 1 only on success. * No need to decrement on failure. @@ -813,7 +813,7 @@ static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) static void esw_qos_put(struct mlx5_eswitch *esw) { - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (refcount_dec_and_test(&esw->qos.refcnt)) esw_qos_destroy(esw); } @@ -866,8 +866,7 @@ esw_qos_create_vport_tc_sched_node(struct mlx5_vport *vport, u8 tc = vports_tc_node->tc; int err; - vport_tc_node = __esw_qos_alloc_node(vport_node->esw, 0, - SCHED_NODE_TYPE_VPORT_TC, + vport_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORT_TC, vports_tc_node); if (!vport_tc_node) return -ENOMEM; @@ -959,7 +958,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, /* Increase the parent's level by 2 to account for both the * TC arbiter and the vports TC scheduling element. */ - new_level = (parent ? parent->level : 2) + 2; + new_level = parent->level + 2; max_level = 1 << MLX5_CAP_QOS(vport_node->esw->dev, log_esw_max_sched_depth); if (new_level > max_level) { @@ -970,7 +969,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, } } - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (type == SCHED_NODE_TYPE_RATE_LIMITER) err = esw_qos_create_rate_limit_element(vport_node, extack); @@ -997,7 +996,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, err_sched_nodes: if (type == SCHED_NODE_TYPE_RATE_LIMITER) { esw_qos_node_destroy_sched_element(vport_node, NULL); - esw_qos_node_attach_to_parent(vport_node); + esw_qos_node_set_parent(vport_node, vport_node->parent); } else { esw_qos_tc_arbiter_scheduling_teardown(vport_node, NULL); } @@ -1055,7 +1054,7 @@ static void esw_qos_vport_disable(struct mlx5_vport *vport, struct netlink_ext_a vport_node->bw_share = 0; memset(vport_node->tc_bw, 0, sizeof(vport_node->tc_bw)); list_del_init(&vport_node->entry); - esw_qos_normalize_min_rate(vport_node->esw, vport_node->parent, extack); + esw_qos_normalize_min_rate(vport_node->parent, extack); trace_mlx5_esw_vport_qos_destroy(vport_node->esw->dev, vport); } @@ -1068,7 +1067,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); esw_qos_node_set_parent(vport_node, parent); if (type == SCHED_NODE_TYPE_VPORT) @@ -1079,9 +1078,10 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, return err; vport_node->type = type; - esw_qos_normalize_min_rate(vport_node->esw, parent, extack); - trace_mlx5_esw_vport_qos_create(vport->dev, vport, vport_node->max_rate, - vport_node->bw_share); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_vport_qos_create(vport_node->esw->dev, vport, + vport_node->bw_share, + vport_node->max_rate); return 0; } @@ -1092,22 +1092,20 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *sched_node; - struct mlx5_eswitch *parent_esw; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); err = esw_qos_get(esw, extack); if (err) return err; - parent_esw = parent ? parent->esw : esw; - sched_node = __esw_qos_alloc_node(parent_esw, 0, type, parent); + if (!parent) + parent = esw->qos.root; + sched_node = __esw_qos_alloc_node(0, type, parent); if (!sched_node) { esw_qos_put(esw); return -ENOMEM; } - if (!parent) - list_add_tail(&sched_node->entry, &esw->qos.domain->nodes); sched_node->max_rate = max_rate; sched_node->min_rate = min_rate; @@ -1125,33 +1123,23 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t static void mlx5_esw_qos_vport_disable_locked(struct mlx5_vport *vport) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); if (!vport->qos.sched_node) return; esw_qos_vport_disable(vport, NULL); mlx5_esw_qos_vport_qos_free(vport); - esw_qos_put(esw); + esw_qos_put(vport->dev->priv.eswitch); } void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - struct mlx5_esw_sched_node *parent; lockdep_assert_held(&esw->state_lock); - esw_qos_lock(esw); - if (!vport->qos.sched_node) - goto unlock; - - parent = vport->qos.sched_node->parent; - WARN(parent, "Disabling QoS on port before detaching it from node"); - + esw_qos_shd_lock(vport->dev); mlx5_esw_qos_vport_disable_locked(vport); -unlock: - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); } static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rate, @@ -1159,7 +1147,7 @@ static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, max_rate, 0, @@ -1174,7 +1162,7 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, 0, min_rate, @@ -1187,29 +1175,27 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *vport, u32 max_rate, u32 min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; int err; - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, min_rate, NULL); if (!err) err = mlx5_esw_qos_set_vport_max_rate(vport, max_rate, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; bool enabled; - esw_qos_lock(esw); + esw_qos_shd_lock(vport->dev); enabled = !!vport->qos.sched_node; if (enabled) { *max_rate = vport->qos.sched_node->max_rate; *min_rate = vport->qos.sched_node->min_rate; } - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); return enabled; } @@ -1234,6 +1220,28 @@ static int esw_qos_vport_tc_check_type(enum sched_node_type curr_type, return 0; } +static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, + u32 *tc_bw) +{ + int i, num_tcs = esw_qos_num_tcs(esw->dev); + + for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) + if (tc_bw[i]) + return false; + + return true; +} + +static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, + u32 *tc_bw) +{ + struct mlx5_esw_sched_node *node = vport->qos.sched_node; + struct mlx5_eswitch *esw = node ? + node->parent->esw : vport->dev->priv.eswitch; + + return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); +} + static int esw_qos_vport_update(struct mlx5_vport *vport, enum sched_node_type type, struct mlx5_esw_sched_node *parent, @@ -1245,7 +1253,7 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, u32 curr_tc_bw[DEVLINK_RATE_TCS_MAX] = {0}; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (curr_type == type && curr_parent == parent) return 0; @@ -1253,8 +1261,15 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, if (err) return err; - if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) + if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) { esw_qos_tc_arbiter_get_bw_shares(vport_node, curr_tc_bw); + if (!esw_qos_validate_unsupported_tc_bw(parent->esw, + curr_tc_bw)) { + NL_SET_ERR_MSG_MOD(extack, + "Unsupported traffic classes on the new device"); + return -EOPNOTSUPP; + } + } esw_qos_vport_disable(vport, extack); @@ -1275,11 +1290,10 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *curr_parent; enum sched_node_type type; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); curr_parent = vport->qos.sched_node->parent; if (curr_parent == parent) return 0; @@ -1287,10 +1301,9 @@ static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw /* Set vport QoS type based on parent node type if different from * default QoS; otherwise, use the vport's current QoS type. */ - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_RATE_LIMITER; - else if (curr_parent && - curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + else if (curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_VPORT; else type = vport->qos.sched_node->type; @@ -1319,11 +1332,9 @@ static int esw_qos_switch_tc_arbiter_node_to_vports( struct mlx5_esw_sched_node *node, struct netlink_ext_ack *extack) { - u32 parent_tsar_ix = node->parent ? - node->parent->ix : node->esw->qos.root_tsar_ix; int err; - err = esw_qos_create_node_sched_elem(node->esw->dev, parent_tsar_ix, + err = esw_qos_create_node_sched_elem(node->esw->dev, node->parent->ix, node->max_rate, node->bw_share, &node->ix); if (err) { @@ -1378,8 +1389,8 @@ esw_qos_move_node(struct mlx5_esw_sched_node *curr_node) { struct mlx5_esw_sched_node *new_node; - new_node = __esw_qos_alloc_node(curr_node->esw, curr_node->ix, - curr_node->type, NULL); + new_node = __esw_qos_alloc_node(curr_node->ix, curr_node->type, + curr_node->parent); if (!new_node) return ERR_PTR(-ENOMEM); @@ -1489,41 +1500,16 @@ static int esw_qos_node_enable_tc_arbitration(struct mlx5_esw_sched_node *node, return err; } -static u32 mlx5_esw_qos_lag_link_speed_get(struct mlx5_core_dev *mdev, - bool take_rtnl) -{ - struct ethtool_link_ksettings lksettings; - struct net_device *slave, *master; - u32 speed = SPEED_UNKNOWN; - - slave = mlx5_uplink_netdev_get(mdev); - if (!slave) - goto out; - - if (take_rtnl) - rtnl_lock(); - master = netdev_master_upper_dev_get(slave); - if (master && !__ethtool_get_link_ksettings(master, &lksettings)) - speed = lksettings.base.speed; - if (take_rtnl) - rtnl_unlock(); - -out: - mlx5_uplink_netdev_put(mdev, slave); - return speed; -} - static int mlx5_esw_qos_max_link_speed_get(struct mlx5_core_dev *mdev, u32 *link_speed_max, - bool take_rtnl, struct netlink_ext_ack *extack) { int err; - if (!mlx5_lag_is_active(mdev)) + if (!mlx5_lag_is_active(mdev) || + mlx5_lag_query_bond_speed(mdev, link_speed_max) < 0 || + *link_speed_max == 0) goto skip_lag; - *link_speed_max = mlx5_esw_qos_lag_link_speed_get(mdev, take_rtnl); - if (*link_speed_max != (u32)SPEED_UNKNOWN) return 0; @@ -1560,7 +1546,8 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return PTR_ERR(vport); if (rate_mbps) { - err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, false, NULL); + err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, + NULL); if (err) return err; @@ -1570,9 +1557,9 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return err; } - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, rate_mbps, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } @@ -1598,7 +1585,7 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return -EINVAL; } - err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, true, extack); + err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, extack); if (err) return err; @@ -1610,30 +1597,6 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return 0; } -static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, - u32 *tc_bw) -{ - int i, num_tcs = esw_qos_num_tcs(esw->dev); - - for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) { - if (tc_bw[i]) - return false; - } - - return true; -} - -static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, - u32 *tc_bw) -{ - struct mlx5_esw_sched_node *node = vport->qos.sched_node; - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw = (node && node->parent) ? node->parent->esw : esw; - - return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); -} - static bool esw_qos_tc_bw_disabled(u32 *tc_bw) { int i; @@ -1650,55 +1613,38 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return; - if (vport_node->parent || vport_node->max_rate || - vport_node->min_rate || !esw_qos_tc_bw_disabled(vport_node->tc_bw)) + if (vport_node->parent != vport_node->esw->qos.root || + vport_node->max_rate || vport_node->min_rate || + !esw_qos_tc_bw_disabled(vport_node->tc_bw)) return; mlx5_esw_qos_vport_disable_locked(vport); } -int mlx5_esw_qos_init(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - return 0; /* Nothing to change. */ - - return esw_qos_domain_init(esw); -} - -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - esw_qos_domain_release(esw); -} - /* Eswitch devlink rate API */ int mlx5_esw_devlink_rate_leaf_tx_share_set(struct devlink_rate *rate_leaf, void *priv, u64 tx_share, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_share", &tx_share, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, tx_share, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1706,24 +1652,20 @@ int mlx5_esw_devlink_rate_leaf_tx_max_set(struct devlink_rate *rate_leaf, void * u64 tx_max, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_max", &tx_max, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, tx_max, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1734,16 +1676,14 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, { struct mlx5_esw_sched_node *vport_node; struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; bool disable; int err = 0; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); if (!esw_qos_vport_validate_unsupported_tc_bw(vport, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, @@ -1777,7 +1717,7 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, if (!err) esw_qos_set_tc_arbiter_bw_shares(vport_node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1787,18 +1727,17 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node = priv; - struct mlx5_eswitch *esw = node->esw; bool disable; int err; - if (!esw_qos_validate_unsupported_tc_bw(esw, tc_bw)) { + if (!esw_qos_validate_unsupported_tc_bw(node->esw, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, "E-Switch traffic classes number is not supported"); return -EOPNOTSUPP; } disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(node->esw->dev); if (disable) { err = esw_qos_node_disable_tc_arbitration(node, extack); goto unlock; @@ -1808,7 +1747,7 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, if (!err) esw_qos_set_tc_arbiter_bw_shares(node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(node->esw->dev); return err; } @@ -1823,9 +1762,9 @@ int mlx5_esw_devlink_rate_node_tx_share_set(struct devlink_rate *rate_node, void if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_set_node_min_rate(node, tx_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1840,9 +1779,9 @@ int mlx5_esw_devlink_rate_node_tx_max_set(struct devlink_rate *rate_node, void * if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_sched_elem_config(node, tx_max, node->bw_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1857,7 +1796,7 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, if (IS_ERR(esw)) return PTR_ERR(esw); - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); if (esw->mode != MLX5_ESWITCH_OFFLOADS) { NL_SET_ERR_MSG_MOD(extack, "Rate node creation supported only in switchdev mode"); @@ -1870,10 +1809,9 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, err = PTR_ERR(node); goto unlock; } - *priv = node; unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1883,25 +1821,53 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, struct mlx5_esw_sched_node *node = priv; struct mlx5_eswitch *esw = node->esw; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); __esw_qos_destroy_node(node, extack); esw_qos_put(esw); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); + return 0; } -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, - struct netlink_ext_ack *extack) +static int +mlx5_esw_validate_cross_esw_scheduling(struct mlx5_eswitch *esw, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - int err = 0; + if (!parent || esw == parent->esw) + return 0; - if (parent && parent->esw != esw) { - NL_SET_ERR_MSG_MOD(extack, "Cross E-Switch scheduling is not supported"); + if (!MLX5_CAP_QOS(esw->dev, esw_cross_esw_sched)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling is not supported"); + return -EOPNOTSUPP; + } + if (!esw->dev->shd || esw->dev->shd != parent->esw->dev->shd) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add vport to a parent belonging to a different device"); + return -EOPNOTSUPP; + } + if (!mlx5_lag_is_active(esw->dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling requires LAG to be activated"); return -EOPNOTSUPP; } - esw_qos_lock(esw); + return 0; +} + +static int +mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) +{ + struct mlx5_eswitch *esw = vport->dev->priv.eswitch; + int err; + + err = mlx5_esw_validate_cross_esw_scheduling(esw, parent, extack); + if (err) + return err; + if (!vport->qos.sched_node && parent) { enum sched_node_type type; @@ -1910,9 +1876,11 @@ int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_s err = mlx5_esw_qos_vport_enable(vport, type, parent, 0, 0, extack); } else if (vport->qos.sched_node) { - err = esw_qos_vport_update_parent(vport, parent, extack); + err = esw_qos_vport_update_parent(vport, + parent ? : esw->qos.root, + extack); } - esw_qos_unlock(esw); + return err; } @@ -1925,14 +1893,11 @@ int mlx5_esw_devlink_rate_leaf_parent_set(struct devlink_rate *devlink_rate, struct mlx5_vport *vport = priv; int err; + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_vport_update_parent(vport, node, extack); - if (!err) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_qos_lock(esw); + if (!err) esw_vport_qos_prune_empty(vport); - esw_qos_unlock(esw); - } + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1958,7 +1923,7 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, { u8 new_level, max_level; - if (parent && parent->esw != node->esw) { + if (parent->esw != node->esw) { NL_SET_ERR_MSG_MOD(extack, "Cannot assign node to another E-Switch"); return -EOPNOTSUPP; @@ -1970,13 +1935,13 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, return -EOPNOTSUPP; } - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { NL_SET_ERR_MSG_MOD(extack, "Cannot attach a node to a parent with TC bandwidth configured"); return -EOPNOTSUPP; } - new_level = parent ? parent->level + 1 : 2; + new_level = parent->level + 1; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { /* Increase by one to account for the vports TC scheduling * element. @@ -2027,14 +1992,12 @@ static int esw_qos_vports_node_update_parent(struct mlx5_esw_sched_node *node, { struct mlx5_esw_sched_node *curr_parent = node->parent; struct mlx5_eswitch *esw = node->esw; - u32 parent_ix; int err; - parent_ix = parent ? parent->ix : node->esw->qos.root_tsar_ix; mlx5_destroy_scheduling_element_cmd(esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, node->ix); - err = esw_qos_create_node_sched_elem(esw->dev, parent_ix, + err = esw_qos_create_node_sched_elem(esw->dev, parent->ix, node->max_rate, 0, &node->ix); if (err) { NL_SET_ERR_MSG_MOD(extack, @@ -2061,12 +2024,15 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, struct mlx5_eswitch *esw = node->esw; int err; + esw_qos_devlink_lock(esw->dev); + curr_parent = node->parent; + if (!parent) + parent = esw->qos.root; + err = mlx5_esw_qos_node_validate_set_parent(node, parent, extack); if (err) - return err; + goto out; - esw_qos_lock(esw); - curr_parent = node->parent; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { err = esw_qos_tc_arbiter_node_update_parent(node, parent, extack); @@ -2077,12 +2043,11 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, if (err) goto out; - esw_qos_normalize_min_rate(esw, curr_parent, extack); - esw_qos_normalize_min_rate(esw, parent, extack); + esw_qos_normalize_min_rate(curr_parent, extack); + esw_qos_normalize_min_rate(parent, extack); out: - esw_qos_unlock(esw); - + esw_qos_devlink_unlock(esw->dev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h index 0a50982b0e27..f275e850d2c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h @@ -6,9 +6,6 @@ #ifdef CONFIG_MLX5_ESWITCH -int mlx5_esw_qos_init(struct mlx5_eswitch *esw); -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw); - int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *evport, u32 max_rate, u32 min_rate); bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate); void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index a0e2ca87b8d8..b6e2c153b4f7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1885,10 +1885,6 @@ int mlx5_eswitch_enable_locked(struct mlx5_eswitch *esw, int num_vfs) MLX5_NB_INIT(&esw->nb, eswitch_vport_event, NIC_VPORT_CHANGE); mlx5_eq_notifier_register(esw->dev, &esw->nb); - err = mlx5_esw_qos_init(esw); - if (err) - goto err_esw_init; - if (esw->mode == MLX5_ESWITCH_LEGACY) { err = esw_legacy_enable(esw); } else { @@ -1990,6 +1986,13 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); mlx5_eswitch_invalidate_wq(esw); + + if (esw->mode == MLX5_ESWITCH_OFFLOADS) { + struct devlink *devlink = priv_to_devlink(esw->dev); + + devl_rate_nodes_destroy(devlink); + } + mlx5_esw_reps_block(esw); if (!mlx5_core_is_ecpf(esw->dev)) { @@ -2003,12 +2006,6 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) } mlx5_esw_reps_unblock(esw); - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) { - struct devlink *devlink = priv_to_devlink(esw->dev); - - devl_rate_nodes_destroy(devlink); - } /* Destroy legacy fdb when disabling sriov in legacy mode. */ if (esw->mode == MLX5_ESWITCH_LEGACY) mlx5_eswitch_disable_locked(esw); @@ -2039,6 +2036,9 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS", esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); + if (esw->mode == MLX5_ESWITCH_OFFLOADS) + devl_rate_nodes_destroy(devlink); + if (esw->fdb_table.flags & MLX5_ESW_FDB_CREATED) { esw->fdb_table.flags &= ~MLX5_ESW_FDB_CREATED; if (esw->mode == MLX5_ESWITCH_OFFLOADS) @@ -2047,9 +2047,6 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw_legacy_disable(esw); mlx5_esw_acls_ns_cleanup(esw); } - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) - devl_rate_nodes_destroy(devlink); } void mlx5_eswitch_disable(struct mlx5_eswitch *esw) @@ -2554,9 +2551,6 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev) goto reps_err; esw->mode = MLX5_ESWITCH_LEGACY; - err = mlx5_esw_qos_init(esw); - if (err) - goto reps_err; mutex_init(&esw->offloads.encap_tbl_lock); hash_init(esw->offloads.encap_tbl); @@ -2611,7 +2605,6 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw) mlx5_eswitch_invalidate_wq(esw); destroy_workqueue(esw->work_queue); - mlx5_esw_qos_cleanup(esw); WARN_ON(refcount_read(&esw->qos.refcnt)); mutex_destroy(&esw->state_lock); WARN_ON(!xa_empty(&esw->offloads.vhca_map)); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index fea72b1dedab..c655f6e8da1c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -234,8 +234,10 @@ struct mlx5_vport { struct mlx5_vport_info info; - /* Protected with the E-Switch qos domain lock. The Vport QoS can - * either be disabled (sched_node is NULL) or in one of three states: + /* Protected by either the shared devlink (dev->shd) lock or by + * esw->state_lock. See esw_assert_qos_lock_held() for more details. + * The Vport QoS can either be disabled (sched_node is NULL) or in one + * of three states: * 1. Regular QoS (sched_node is a vport node). * 2. TC QoS enabled on the vport (sched_node is a TC arbiter). * 3. TC QoS enabled on the vport's parent node @@ -382,7 +384,6 @@ enum { }; struct dentry; -struct mlx5_qos_domain; struct mlx5_eswitch { struct mlx5_core_dev *dev; @@ -411,12 +412,15 @@ struct mlx5_eswitch { atomic64_t user_count; wait_queue_head_t work_queue_wait; - /* Protected with the E-Switch qos domain lock. */ + /* QoS changes are serialized by either the shared devlink (dev->shd) + * lock or by esw->state_lock. See esw_assert_qos_lock_held() for more + * details. + */ struct { /* Initially 0, meaning no QoS users and QoS is disabled. */ refcount_t refcnt; - u32 root_tsar_ix; - struct mlx5_qos_domain *domain; + /* The root node of the hierarchy. */ + struct mlx5_esw_sched_node *root; } qos; struct mlx5_esw_bridge_offloads *br_offloads; @@ -482,8 +486,6 @@ int mlx5_eswitch_set_vport_trust(struct mlx5_eswitch *esw, u16 vport_num, bool setting); int mlx5_eswitch_set_vport_rate(struct mlx5_eswitch *esw, u16 vport, u32 max_rate, u32 min_rate); -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *node, - struct netlink_ext_ack *extack); int mlx5_eswitch_set_vepa(struct mlx5_eswitch *esw, u8 setting); int mlx5_eswitch_get_vepa(struct mlx5_eswitch *esw, u8 *setting); int mlx5_eswitch_get_vport_config(struct mlx5_eswitch *esw, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h index dbaf33b537f7..906584345a02 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h @@ -214,17 +214,7 @@ struct mlx5_ft_underlay_qp { u32 qpn; }; -#define MLX5_FTE_MATCH_PARAM_RESERVED reserved_at_e00 -/* Calculate the fte_match_param length and without the reserved length. - * Make sure the reserved field is the last. - */ -#define MLX5_ST_SZ_DW_MATCH_PARAM \ - ((MLX5_BYTE_OFF(fte_match_param, MLX5_FTE_MATCH_PARAM_RESERVED) / sizeof(u32)) + \ - BUILD_BUG_ON_ZERO(MLX5_ST_SZ_BYTES(fte_match_param) != \ - MLX5_FLD_SZ_BYTES(fte_match_param, \ - MLX5_FTE_MATCH_PARAM_RESERVED) +\ - MLX5_BYTE_OFF(fte_match_param, \ - MLX5_FTE_MATCH_PARAM_RESERVED))) +#define MLX5_ST_SZ_DW_MATCH_PARAM MLX5_ST_SZ_DW(fte_match_param) struct fs_fte_action { int modify_mask; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c index 305752dab7bd..4c4cf6da519f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c @@ -44,12 +44,12 @@ struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev) hv_vhca = kzalloc_obj(*hv_vhca); if (!hv_vhca) - return ERR_PTR(-ENOMEM); + return NULL; hv_vhca->work_queue = create_singlethread_workqueue("mlx5_hv_vhca"); if (!hv_vhca->work_queue) { kfree(hv_vhca); - return ERR_PTR(-ENOMEM); + return NULL; } hv_vhca->dev = dev; @@ -60,7 +60,7 @@ struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev) void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca) { - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return; destroy_workqueue(hv_vhca->work_queue); @@ -198,28 +198,26 @@ static void mlx5_hv_vhca_control_agent_destroy(struct mlx5_hv_vhca_agent *agent) mlx5_hv_vhca_agent_destroy(agent); } -int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) +void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) { struct mlx5_hv_vhca_agent *agent; int err; - if (IS_ERR_OR_NULL(hv_vhca)) - return IS_ERR_OR_NULL(hv_vhca); + if (!hv_vhca) + return; err = mlx5_hv_register_invalidate(hv_vhca->dev, hv_vhca, mlx5_hv_vhca_invalidate); if (err) - return err; + return; agent = mlx5_hv_vhca_control_agent_create(hv_vhca); if (IS_ERR_OR_NULL(agent)) { mlx5_hv_unregister_invalidate(hv_vhca->dev); - return IS_ERR_OR_NULL(agent); + return; } hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL] = agent; - - return 0; } void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) @@ -227,7 +225,7 @@ void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) struct mlx5_hv_vhca_agent *agent; int i; - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return; agent = hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL]; @@ -261,7 +259,7 @@ mlx5_hv_vhca_agent_create(struct mlx5_hv_vhca *hv_vhca, { struct mlx5_hv_vhca_agent *agent; - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return ERR_PTR(-ENOMEM); if (type >= MLX5_HV_VHCA_AGENT_MAX) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h index 8b3974cf0ee4..e0d1c8cd74ef 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h @@ -31,7 +31,7 @@ struct mlx5_hv_vhca_control_block { struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev); void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca); -int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca); +void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca); void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca); void mlx5_hv_vhca_invalidate(void *context, u64 block_mask); @@ -63,9 +63,8 @@ static inline void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca) { } -static inline int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) +static inline void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) { - return 0; } static inline void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c index 4a7275e8b62e..72cc8da5f119 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c @@ -68,7 +68,9 @@ struct mlx5_ifc_mnvda_reg_bits { struct mlx5_ifc_nv_global_pci_conf_bits { u8 sriov_valid[0x1]; - u8 reserved_at_1[0x10]; + u8 reserved_at_1[0xa]; + u8 per_pf_num_sf[0x1]; + u8 reserved_at_c[0x5]; u8 per_pf_total_vf[0x1]; u8 reserved_at_12[0xe]; @@ -93,9 +95,11 @@ struct mlx5_ifc_nv_global_pci_cap_bits { }; struct mlx5_ifc_nv_pf_pci_conf_bits { - u8 reserved_at_0[0x9]; + u8 log_sf_bar_size[0x8]; + u8 pf_total_sf_en[0x1]; u8 pf_total_vf_en[0x1]; - u8 reserved_at_a[0x16]; + u8 reserved_at_a[0x6]; + u8 total_sf[0x10]; u8 reserved_at_20[0x20]; @@ -158,6 +162,8 @@ struct mlx5_ifc_nv_sw_accelerate_conf_bits { #define MLX5_GET_CFG_HDR_LEN(_mnvda_ptr) \ MLX5_GET(mnvda_reg, _mnvda_ptr, configuration_item_header.length) +#define MLX5_DEFAULT_LOG_SF_BAR_SIZE 12 + static int mlx5_nv_param_read(struct mlx5_core_dev *dev, void *mnvda, size_t len) { @@ -755,6 +761,115 @@ static int mlx5_devlink_total_vfs_validate(struct devlink *devlink, u32 id, return 0; } +static int mlx5_devlink_max_sfs_get(struct devlink *devlink, u32 id, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack) +{ + struct mlx5_core_dev *dev = devlink_priv(devlink); + u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {}; + void *data; + int err; + + err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to read global PCI configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + if (!MLX5_GET(nv_global_pci_conf, data, per_pf_num_sf)) { + ctx->val.vu32 = 0; + return 0; + } + + memset(mnvda, 0, sizeof(mnvda)); + err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to read PF configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + if (MLX5_GET(nv_pf_pci_conf, data, pf_total_sf_en)) + ctx->val.vu32 = MLX5_GET(nv_pf_pci_conf, data, total_sf); + else + ctx->val.vu32 = 0; + + return 0; +} + +static int mlx5_devlink_max_sfs_validate(struct devlink *devlink, u32 id, + union devlink_param_value *val, + struct netlink_ext_ack *extack) +{ + if (val->vu32 > U16_MAX) { + NL_SET_ERR_MSG_FMT_MOD(extack, + "Max SFs allowed value is %u", U16_MAX); + return -EINVAL; + } + + return 0; +} + +static int mlx5_devlink_max_sfs_set(struct devlink *devlink, u32 id, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack) +{ + struct mlx5_core_dev *dev = devlink_priv(devlink); + u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {}; + void *data; + int err; + + /* we don't explicitly disable per_pf_num_sf when max_sfs is 0 because + * another PF may be using SFs + */ + if (ctx->val.vu32) { + err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, + sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to read global PCI configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + /* always enable per_pf_num_sf because another PF may use SFs */ + MLX5_SET(nv_global_pci_conf, data, per_pf_num_sf, 1); + + err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to change per_pf_num_sf global PCI configuration"); + return err; + } + memset(mnvda, 0, sizeof(mnvda)); + } + + err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to read PF configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + MLX5_SET(nv_pf_pci_conf, data, log_sf_bar_size, + ctx->val.vu32 ? MLX5_DEFAULT_LOG_SF_BAR_SIZE : 0); + MLX5_SET(nv_pf_pci_conf, data, pf_total_sf_en, !!ctx->val.vu32); + MLX5_SET(nv_pf_pci_conf, data, total_sf, ctx->val.vu32); + + err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to change PF PCI configuration"); + return err; + } + NL_SET_ERR_MSG_MOD(extack, + "Modifying max_sfs requires a FW reset and PCI bus rescan"); + + return 0; +} + static const struct devlink_param mlx5_nv_param_devlink_params[] = { DEVLINK_PARAM_GENERIC(ENABLE_SRIOV, BIT(DEVLINK_PARAM_CMODE_PERMANENT), mlx5_devlink_enable_sriov_get, @@ -763,6 +878,10 @@ static const struct devlink_param mlx5_nv_param_devlink_params[] = { mlx5_devlink_total_vfs_get, mlx5_devlink_total_vfs_set, mlx5_devlink_total_vfs_validate), + DEVLINK_PARAM_GENERIC(MAX_SFS, BIT(DEVLINK_PARAM_CMODE_PERMANENT), + mlx5_devlink_max_sfs_get, + mlx5_devlink_max_sfs_set, + mlx5_devlink_max_sfs_validate), DEVLINK_PARAM_DRIVER(MLX5_DEVLINK_PARAM_ID_CQE_COMPRESSION_TYPE, "cqe_compress_type", DEVLINK_PARAM_TYPE_STRING, BIT(DEVLINK_PARAM_CMODE_PERMANENT), diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c index ee2fdefa1945..4cdc50cd6f03 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c @@ -19,7 +19,7 @@ struct mlx5_sd { u32 group_id; - u8 host_buses; + u8 group_size; struct mlx5_devcom_comp_dev *devcom; struct dentry *dfs; u8 state; @@ -46,14 +46,14 @@ enum mlx5_sd_state { MLX5_SD_STATE_UP, }; -static int mlx5_sd_get_host_buses(struct mlx5_core_dev *dev) +static int mlx5_sd_get_group_size(struct mlx5_core_dev *dev) { struct mlx5_sd *sd = mlx5_get_sd(dev); if (!sd) return 1; - return sd->host_buses; + return sd->group_size; } struct mlx5_core_dev *mlx5_sd_get_primary(struct mlx5_core_dev *dev) @@ -107,7 +107,7 @@ int mlx5_sd_pf_num_get(struct mlx5_core_dev *dev) if (pos == dev) break; - return pf_num * sd->host_buses + i; + return pf_num * sd->group_size + i; } struct mlx5_core_dev * @@ -118,7 +118,7 @@ mlx5_sd_primary_get_peer(struct mlx5_core_dev *primary, int idx) if (idx == 0) return primary; - if (idx >= mlx5_sd_get_host_buses(primary)) + if (idx >= mlx5_sd_get_group_size(primary)) return NULL; sd = mlx5_get_sd(primary); @@ -130,7 +130,7 @@ int mlx5_sd_ch_ix_get_dev_ix(struct mlx5_core_dev *dev, int ch_ix) if (is_mdev_switchdev_mode(dev)) return 0; - return ch_ix % mlx5_sd_get_host_buses(dev); + return ch_ix % mlx5_sd_get_group_size(dev); } int mlx5_sd_ch_ix_get_vec_ix(struct mlx5_core_dev *dev, int ch_ix) @@ -138,7 +138,7 @@ int mlx5_sd_ch_ix_get_vec_ix(struct mlx5_core_dev *dev, int ch_ix) if (is_mdev_switchdev_mode(dev)) return ch_ix; - return ch_ix / mlx5_sd_get_host_buses(dev); + return ch_ix / mlx5_sd_get_group_size(dev); } struct mlx5_core_dev *mlx5_sd_ch_ix_get_dev(struct mlx5_core_dev *primary, int ch_ix) @@ -164,7 +164,7 @@ static bool ft_create_alias_supported(struct mlx5_core_dev *dev) } static int mlx5_query_sd(struct mlx5_core_dev *dev, bool *sdm, - u8 *host_buses) + u8 *group_size) { u32 out[MLX5_ST_SZ_DW(mpir_reg)]; int err; @@ -174,7 +174,7 @@ static int mlx5_query_sd(struct mlx5_core_dev *dev, bool *sdm, return err; *sdm = MLX5_GET(mpir_reg, out, sdm); - *host_buses = MLX5_GET(mpir_reg, out, host_buses); + *group_size = MLX5_GET(mpir_reg, out, host_buses); return 0; } @@ -184,10 +184,10 @@ static u32 mlx5_sd_group_id(struct mlx5_core_dev *dev, u8 sd_group) return (u32)((MLX5_CAP_GEN(dev, native_port_num) << 8) | sd_group); } -static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) +static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 group_size) { /* Honor the SW implementation limit */ - if (host_buses > MLX5_SD_MAX_GROUP_SZ) + if (group_size > MLX5_SD_MAX_GROUP_SZ) return false; /* Disconnect secondaries from the network */ @@ -200,7 +200,7 @@ static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) /* RX steering from primary to secondaries */ if (!MLX5_CAP_GEN(dev, cross_vhca_rqt)) return false; - if (host_buses > MLX5_CAP_GEN_2(dev, max_rqt_vhca_id)) + if (group_size > MLX5_CAP_GEN_2(dev, max_rqt_vhca_id)) return false; /* TX steering from secondaries to primary */ @@ -214,7 +214,7 @@ static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) bool mlx5_sd_is_supported(struct mlx5_core_dev *dev) { - u8 host_buses, sd_group; + u8 group_size = U8_MAX, sd_group; bool sdm; int err; @@ -222,23 +222,25 @@ bool mlx5_sd_is_supported(struct mlx5_core_dev *dev) if (!mlx5_core_is_pf(dev)) return false; - err = mlx5_query_nic_vport_sd_group(dev, &sd_group); - if (err || !sd_group) + err = mlx5_query_nic_vport_sd_group(dev, &sd_group, &group_size); + if (err || !sd_group || group_size < MLX5_SD_MIN_GROUP_SZ) return false; - if (!MLX5_CAP_MCAM_REG(dev, mpir)) - return false; + if (group_size == U8_MAX) { + if (!MLX5_CAP_MCAM_REG(dev, mpir)) + return false; - err = mlx5_query_sd(dev, &sdm, &host_buses); - if (err || !sdm) - return false; + err = mlx5_query_sd(dev, &sdm, &group_size); + if (err || !sdm) + return false; + } - return mlx5_sd_caps_supported(dev, host_buses); + return mlx5_sd_caps_supported(dev, group_size); } static int sd_init(struct mlx5_core_dev *dev) { - u8 host_buses, sd_group; + u8 group_size = U8_MAX, sd_group; struct mlx5_sd *sd; u32 group_id; bool sdm; @@ -248,26 +250,27 @@ static int sd_init(struct mlx5_core_dev *dev) if (!mlx5_core_is_pf(dev)) return 0; - err = mlx5_query_nic_vport_sd_group(dev, &sd_group); + err = mlx5_query_nic_vport_sd_group(dev, &sd_group, &group_size); if (err) return err; - if (!sd_group) + if (!sd_group || group_size < MLX5_SD_MIN_GROUP_SZ) return 0; - if (!MLX5_CAP_MCAM_REG(dev, mpir)) - return 0; + if (group_size == U8_MAX) { + if (!MLX5_CAP_MCAM_REG(dev, mpir)) + return 0; - err = mlx5_query_sd(dev, &sdm, &host_buses); - if (err) - return err; - - if (!sdm) - return 0; + err = mlx5_query_sd(dev, &sdm, &group_size); + if (err) + return err; + if (!sdm) + return 0; + } group_id = mlx5_sd_group_id(dev, sd_group); - if (!mlx5_sd_caps_supported(dev, host_buses)) { + if (!mlx5_sd_caps_supported(dev, group_size)) { sd_warn(dev, "can't support requested netdev combining for group id 0x%x, skipping\n", group_id); return 0; @@ -277,7 +280,7 @@ static int sd_init(struct mlx5_core_dev *dev) if (!sd) return -ENOMEM; - sd->host_buses = host_buses; + sd->group_size = group_size; sd->group_id = group_id; mlx5_set_sd(dev, sd); @@ -540,7 +543,7 @@ static int sd_register(struct mlx5_core_dev *dev) sd->devcom = devcom; mlx5_devcom_comp_lock(devcom); - if (mlx5_devcom_comp_get_size(devcom) != sd->host_buses || + if (mlx5_devcom_comp_get_size(devcom) != sd->group_size || mlx5_devcom_comp_is_ready(devcom)) goto out; @@ -576,7 +579,7 @@ static int sd_register(struct mlx5_core_dev *dev) DEVCOM_CANT_FAIL, primary); primary_sd = mlx5_get_sd(primary); - if (primary_sd->next_secondary_idx + 1 == sd->host_buses) + if (primary_sd->next_secondary_idx + 1 == sd->group_size) mlx5_devcom_comp_set_ready(devcom, true); out: mlx5_devcom_comp_unlock(devcom); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h index cb88bf34079a..bc8dbc299070 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h @@ -6,6 +6,7 @@ #include +#define MLX5_SD_MIN_GROUP_SZ 2 #define MLX5_SD_MAX_GROUP_SZ 2 struct mlx5_sd; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 643b4aac2033..5f28d906c35b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -1819,8 +1819,6 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) INIT_LIST_HEAD(&priv->bfregs.wc_head.list); mutex_init(&priv->alloc_mutex); - mutex_init(&priv->pgdir_mutex); - INIT_LIST_HEAD(&priv->pgdir_list); priv->numa_node = dev_to_node(mlx5_core_dma_dev(dev)); priv->dbg.dbg_root = debugfs_create_dir(dev_name(dev->device), @@ -1830,6 +1828,10 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) if (err) goto err_frag_buf_pools_init; + err = mlx5_db_pools_init(dev); + if (err) + goto err_db_pools_init; + INIT_LIST_HEAD(&priv->traps); err = mlx5_cmd_init(dev); @@ -1891,10 +1893,11 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) err_timeout_init: mlx5_cmd_cleanup(dev); err_cmd_init: + mlx5_db_pools_cleanup(dev); +err_db_pools_init: mlx5_frag_buf_pools_cleanup(dev); err_frag_buf_pools_init: debugfs_remove(dev->priv.dbg.dbg_root); - mutex_destroy(&priv->pgdir_mutex); mutex_destroy(&priv->alloc_mutex); mutex_destroy(&priv->bfregs.wc_head.lock); mutex_destroy(&priv->bfregs.reg_head.lock); @@ -1917,9 +1920,9 @@ void mlx5_mdev_uninit(struct mlx5_core_dev *dev) mlx5_health_cleanup(dev); mlx5_tout_cleanup(dev); mlx5_cmd_cleanup(dev); + mlx5_db_pools_cleanup(dev); mlx5_frag_buf_pools_cleanup(dev); debugfs_remove_recursive(dev->priv.dbg.dbg_root); - mutex_destroy(&priv->pgdir_mutex); mutex_destroy(&priv->alloc_mutex); mutex_destroy(&priv->bfregs.wc_head.lock); mutex_destroy(&priv->bfregs.reg_head.lock); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h index 09e669f83dba..d6713a2ce676 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h @@ -438,6 +438,8 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx); void mlx5_mdev_uninit(struct mlx5_core_dev *dev); int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev); void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev); +int mlx5_db_pools_init(struct mlx5_core_dev *dev); +void mlx5_db_pools_cleanup(struct mlx5_core_dev *dev); int mlx5_init_one(struct mlx5_core_dev *dev); int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev); void mlx5_uninit_one(struct mlx5_core_dev *dev); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c index e624f5da96c8..775dc5c2d7a6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c @@ -398,7 +398,6 @@ int mlx5hws_cmd_rtc_create(struct mlx5_core_dev *mdev, MLX5_SET(rtc, attr, update_method, rtc_attr->fw_gen_wqe); MLX5_SET(rtc, attr, update_index_mode, rtc_attr->update_index_mode); MLX5_SET(rtc, attr, access_index_mode, rtc_attr->access_index_mode); - MLX5_SET(rtc, attr, num_hash_definer, rtc_attr->num_hash_definer); MLX5_SET(rtc, attr, log_depth, rtc_attr->log_depth); MLX5_SET(rtc, attr, log_hash_size, rtc_attr->log_size); MLX5_SET(rtc, attr, table_type, rtc_attr->table_type); @@ -611,9 +610,11 @@ int mlx5hws_cmd_stc_modify(struct mlx5_core_dev *mdev, ret = mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out)); if (ret) - mlx5_core_err(mdev, "Failed to modify STC FW action_type %d\n", - stc_attr->action_type); - + mlx5_core_err(mdev, + "Failed to modify STC action_type %d, err %d, syndrome 0x%x\n", + stc_attr->action_type, ret, + MLX5_GET(general_obj_out_cmd_hdr, + out, syndrome)); return ret; } @@ -1172,19 +1173,13 @@ int mlx5hws_cmd_query_caps(struct mlx5_core_dev *mdev, } if (MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.esw_manager_vport_number_valid)) + capability.e_switch_cap.esw_manager_vport_number_valid)) caps->eswitch_manager_vport_number = MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.esw_manager_vport_number); + capability.e_switch_cap.esw_manager_vport_number); caps->merged_eswitch = MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.merged_eswitch); - } - - ret = mlx5_cmd_exec(mdev, in, sizeof(in), out, out_size); - if (ret) { - mlx5_core_err(mdev, "Failed to query device attributes\n"); - goto out; + capability.e_switch_cap.merged_eswitch); } snprintf(caps->fw_ver, sizeof(caps->fw_ver), "%d.%d.%d", diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c index aed009aec4fe..df3b93499eeb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c @@ -344,6 +344,36 @@ hws_send_engine_update_rule_resize(struct mlx5hws_send_engine *queue, } } +static const char *hws_rule_status_to_string(enum mlx5hws_rule_status status) +{ + switch (status) { + case MLX5HWS_RULE_STATUS_CREATING: return "CREATING"; + case MLX5HWS_RULE_STATUS_UPDATING: return "UPDATING"; + case MLX5HWS_RULE_STATUS_DELETING: return "DELETING"; + case MLX5HWS_RULE_STATUS_FAILING: return "FAILING"; + default: return "NA"; + } +} + +static const char *hws_rule_resize_state_to_string(u8 state) +{ + switch (state) { + case MLX5HWS_RULE_RESIZE_STATE_IDLE: return "IDLE"; + case MLX5HWS_RULE_RESIZE_STATE_WRITING: return "WRITING"; + case MLX5HWS_RULE_RESIZE_STATE_DELETING: return "DELETING"; + default: return "UNKNOWN"; + } +} + +static const char *hws_gta_syndrome_to_string(u8 syndrome) +{ + switch (syndrome) { + case 1: return "SET_FLOW_FAIL"; + case 2: return "DISABLE_FLOW_FAIL"; + default: return "UNKNOWN"; + } +} + static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, struct mlx5hws_send_ring_priv *priv, struct mlx5_cqe64 *cqe) @@ -352,6 +382,7 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, struct mlx5hws_context *ctx = priv->rule->matcher->tbl->ctx; u32 opcode = cqe ? get_cqe_opcode(cqe) : 0; struct mlx5hws_rule *rule = priv->rule; + u8 syndrome; /* If something bad happens and lots of rules are failing, we don't * want to pollute dmesg. Print only the first bad cqe per engine, @@ -364,26 +395,17 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, if (mlx5hws_rule_move_in_progress(rule)) mlx5hws_err(ctx, - "--- rule 0x%08llx: error completion moving rule: phase %s, wqes left %d\n", + "--- rule 0x%08llx: error completion moving rule: phase %s (%d), wqes left %d\n", HWS_PTR_TO_ID(rule), - rule->resize_info->state == - MLX5HWS_RULE_RESIZE_STATE_WRITING ? "WRITING" : - rule->resize_info->state == - MLX5HWS_RULE_RESIZE_STATE_DELETING ? "DELETING" : - "UNKNOWN", + hws_rule_resize_state_to_string + (rule->resize_info->state), + rule->resize_info->state, rule->pending_wqes); else mlx5hws_err(ctx, "--- rule 0x%08llx: error completion %s (%d), wqes left %d\n", HWS_PTR_TO_ID(rule), - rule->status == - MLX5HWS_RULE_STATUS_CREATING ? "CREATING" : - rule->status == - MLX5HWS_RULE_STATUS_DELETING ? "DELETING" : - rule->status == - MLX5HWS_RULE_STATUS_FAILING ? "FAILING" : - rule->status == - MLX5HWS_RULE_STATUS_UPDATING ? "UPDATING" : "NA", + hws_rule_status_to_string(rule->status), rule->status, rule->pending_wqes); @@ -423,6 +445,10 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, " rule 0x%08llx: |--- syndrome = 0x%x\n", HWS_PTR_TO_ID(rule), err_cqe->syndrome); + mlx5hws_err(ctx, + " rule 0x%08llx: |--- WQE_CNT = 0x%04x\n", + HWS_PTR_TO_ID(rule), + (u32)be16_to_cpu(err_cqe->wqe_counter)); } mlx5hws_err(ctx, @@ -433,13 +459,12 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, HWS_PTR_TO_ID(rule), (be32_to_cpu(cqe->byte_cnt) & 0x80000000) ? "FAILURE" : "SUCCESS"); + /* syndrome is in the lower 2 bits of byte_cnt */ + syndrome = be32_to_cpu(cqe->byte_cnt) & 3; mlx5hws_err(ctx, - " rule 0x%08llx: |------- SYNDROME = %s\n", + " rule 0x%08llx: |------- SYNDROME = %s (%u)\n", HWS_PTR_TO_ID(rule), - ((be32_to_cpu(cqe->byte_cnt) & 0x00000003) == 1) ? - "SET_FLOW_FAIL" : - ((be32_to_cpu(cqe->byte_cnt) & 0x00000003) == 2) ? - "DISABLE_FLOW_FAIL" : "UNKNOWN"); + hws_gta_syndrome_to_string(syndrome), syndrome); mlx5hws_err(ctx, " rule 0x%08llx: cqe->sop_drop_qpn = 0x%08x\n", HWS_PTR_TO_ID(rule), be32_to_cpu(cqe->sop_drop_qpn)); @@ -1091,8 +1116,6 @@ static int hws_bwc_send_queues_init(struct mlx5hws_context *ctx) if (!mlx5hws_context_bwc_supported(ctx)) return 0; - ctx->queues += bwc_queues; - ctx->bwc_send_queue_locks = kzalloc_objs(*ctx->bwc_send_queue_locks, bwc_queues); @@ -1104,6 +1127,8 @@ static int hws_bwc_send_queues_init(struct mlx5hws_context *ctx) if (!ctx->bwc_lock_class_keys) goto err_lock_class_keys; + ctx->queues += bwc_queues; + for (i = 0; i < bwc_queues; i++) { mutex_init(&ctx->bwc_send_queue_locks[i]); lockdep_register_key(ctx->bwc_lock_class_keys + i); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 3676e26ac6b0..3d86510af615 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -550,7 +550,8 @@ int mlx5_query_nic_vport_system_image_guid(struct mlx5_core_dev *mdev, } EXPORT_SYMBOL_GPL(mlx5_query_nic_vport_system_image_guid); -int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group) +int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group, + u8 *sd_group_size) { int outlen = MLX5_ST_SZ_BYTES(query_nic_vport_context_out); u32 *out; @@ -566,6 +567,9 @@ int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group) *sd_group = MLX5_GET(query_nic_vport_context_out, out, nic_vport_context.sd_group); + if (MLX5_CAP_GEN(mdev, sd_group_size)) + *sd_group_size = MLX5_GET(query_nic_vport_context_out, out, + nic_vport_context.sd_group_size); out: kvfree(out); return err; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c index 82569162d2e5..815e8d8e3185 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c @@ -663,8 +663,11 @@ static netdev_tx_t mlxsw_sp_port_xmit(struct sk_buff *skb, return NETDEV_TX_OK; } -static void mlxsw_sp_set_rx_mode(struct net_device *dev) +static int mlxsw_sp_set_rx_mode_async(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { + return 0; } static int mlxsw_sp_port_set_mac_address(struct net_device *dev, void *p) @@ -1191,7 +1194,7 @@ static const struct net_device_ops mlxsw_sp_port_netdev_ops = { .ndo_stop = mlxsw_sp_port_stop, .ndo_start_xmit = mlxsw_sp_port_xmit, .ndo_setup_tc = mlxsw_sp_setup_tc, - .ndo_set_rx_mode = mlxsw_sp_set_rx_mode, + .ndo_set_rx_mode_async = mlxsw_sp_set_rx_mode_async, .ndo_set_mac_address = mlxsw_sp_port_set_mac_address, .ndo_change_mtu = mlxsw_sp_port_change_mtu, .ndo_get_stats64 = mlxsw_sp_port_get_stats64, @@ -1549,6 +1552,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u16 local_port, dev->vlan_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; dev->lltx = true; dev->netns_immutable = true; + dev->request_ops_lock = true; dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = MLXSW_PORT_MAX_MTU - MLXSW_PORT_ETH_FRAME_HDR; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c index cbb272a96359..0d0cd093b3c6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c @@ -1118,7 +1118,7 @@ u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, } void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key) + char *enc_key) { u16 start = delta->start; u8 mask = delta->mask; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h index 010204f73ea4..67cc7a5737dd 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h @@ -245,7 +245,7 @@ u8 mlxsw_sp_acl_erp_delta_mask(const struct mlxsw_sp_acl_erp_delta *delta); u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, const char *enc_key); void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key); + char *enc_key); struct mlxsw_sp_acl_erp_mask; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c index 7f78b1ef61cc..3bdb532d833b 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c @@ -1262,6 +1262,8 @@ mlxsw_sp_set_module_power_mode(struct net_device *dev, const struct ethtool_ops mlxsw_sp_port_ethtool_ops = { .cap_link_lanes_supported = true, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = mlxsw_sp_port_get_drvinfo, .get_link = ethtool_op_get_link, .get_link_ext_state = mlxsw_sp_port_get_link_ext_state, diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index e759171bfd76..24ae56a3c9ed 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -42,6 +42,7 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter) u32 strap; int ret; + adapter->is_rmii_en = false; /* Timeout = 100 (i.e. 1 sec (10 msce * 100)) */ ret = lan743x_hs_syslock_acquire(adapter, 100); if (ret < 0) { @@ -73,8 +74,15 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter) adapter->is_sgmii_en = false; } } - netif_dbg(adapter, drv, adapter->netdev, - "SGMII I/F %sable\n", adapter->is_sgmii_en ? "En" : "Dis"); + + if (!adapter->is_sgmii_en && strap & STRAP_READ_USE_RMII_EN_) { + if (strap & STRAP_READ_RMII_EN_) + adapter->is_rmii_en = true; + } + + netif_dbg(adapter, drv, adapter->netdev, "Selected I/F: %s\n", + adapter->is_sgmii_en ? "SGMII" : + adapter->is_rmii_en ? "RMII" : "RGMII"); } static bool is_pci11x1x_chip(struct lan743x_adapter *adapter) @@ -1394,6 +1402,8 @@ static void lan743x_phy_interface_select(struct lan743x_adapter *adapter) if (adapter->is_pci11x1x && adapter->is_sgmii_en) adapter->phy_interface = PHY_INTERFACE_MODE_SGMII; + else if (adapter->is_pci11x1x && adapter->is_rmii_en) + adapter->phy_interface = PHY_INTERFACE_MODE_RMII; else if (id_rev == ID_REV_ID_LAN7430_) adapter->phy_interface = PHY_INTERFACE_MODE_GMII; else if ((id_rev == ID_REV_ID_LAN7431_) && (data & MAC_CR_MII_EN_)) @@ -3182,6 +3192,12 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter) __set_bit(PHY_INTERFACE_MODE_MII, adapter->phylink_config.supported_interfaces); break; + case PHY_INTERFACE_MODE_RMII: + __set_bit(PHY_INTERFACE_MODE_RMII, + adapter->phylink_config.supported_interfaces); + adapter->phylink_config.lpi_capabilities = 0; + break; + default: phy_interface_set_rgmii(adapter->phylink_config.supported_interfaces); } @@ -3189,6 +3205,9 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter) memcpy(adapter->phylink_config.lpi_interfaces, adapter->phylink_config.supported_interfaces, sizeof(adapter->phylink_config.lpi_interfaces)); + if (adapter->phy_interface == PHY_INTERFACE_MODE_RMII) + __clear_bit(PHY_INTERFACE_MODE_RMII, + adapter->phylink_config.lpi_interfaces); pl = phylink_create(&adapter->phylink_config, NULL, adapter->phy_interface, &lan743x_phylink_mac_ops); @@ -3533,6 +3552,7 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter, { struct lan743x_tx *tx; u32 sgmii_ctl; + u32 rmii_ctl; int index; int ret; @@ -3554,6 +3574,12 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter, sgmii_ctl |= SGMII_CTL_SGMII_POWER_DN_; } lan743x_csr_write(adapter, SGMII_CTL, sgmii_ctl); + rmii_ctl = lan743x_csr_read(adapter, RMII_CTL); + if (adapter->is_rmii_en) + rmii_ctl |= RMII_CTL_RMII_ENABLE_; + else + rmii_ctl &= ~RMII_CTL_RMII_ENABLE_; + lan743x_csr_write(adapter, RMII_CTL, rmii_ctl); } else { adapter->max_tx_channels = LAN743X_MAX_TX_CHANNELS; adapter->used_tx_channels = LAN743X_USED_TX_CHANNELS; @@ -3620,8 +3646,9 @@ static int lan743x_mdiobus_init(struct lan743x_adapter *adapter) adapter->mdiobus->name = "lan743x-mdiobus-c45"; dev_dbg(&adapter->pdev->dev, "lan743x-mdiobus-c45\n"); } else { - dev_dbg(&adapter->pdev->dev, "RGMII operation\n"); - // Only C22 support when RGMII I/F + dev_dbg(&adapter->pdev->dev, "%s operation\n", + adapter->is_rmii_en ? "RMII" : "RGMII"); + // Only C22 support when RGMII/RMII I/F adapter->mdiobus->read = lan743x_mdiobus_read_c22; adapter->mdiobus->write = lan743x_mdiobus_write_c22; adapter->mdiobus->name = "lan743x-mdiobus"; diff --git a/drivers/net/ethernet/microchip/lan743x_main.h b/drivers/net/ethernet/microchip/lan743x_main.h index 1573c8f9c993..d9495cf96b41 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.h +++ b/drivers/net/ethernet/microchip/lan743x_main.h @@ -36,7 +36,9 @@ #define FPGA_SGMII_OP BIT(24) #define STRAP_READ (0x0C) +#define STRAP_READ_USE_RMII_EN_ BIT(23) #define STRAP_READ_USE_SGMII_EN_ BIT(22) +#define STRAP_READ_RMII_EN_ BIT(7) #define STRAP_READ_SGMII_EN_ BIT(6) #define STRAP_READ_SGMII_REFCLK_ BIT(5) #define STRAP_READ_SGMII_2_5G_ BIT(4) @@ -323,6 +325,9 @@ #define MAC_WUCSR2_IPV6_TCPSYN_RCD_ BIT(5) #define MAC_WUCSR2_IPV4_TCPSYN_RCD_ BIT(4) +#define RMII_CTL (0x710) +#define RMII_CTL_RMII_ENABLE_ BIT(0) + #define SGMII_ACC (0x720) #define SGMII_ACC_SGMII_BZY_ BIT(31) #define SGMII_ACC_SGMII_WR_ BIT(30) @@ -1072,6 +1077,7 @@ struct lan743x_adapter { struct lan743x_rx rx[LAN743X_USED_RX_CHANNELS]; bool is_pci11x1x; bool is_sgmii_en; + bool is_rmii_en; /* protect ethernet syslock */ spinlock_t eth_syslock_spinlock; bool eth_syslock_en; diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c index 0277d9737369..26a2761332a5 100644 --- a/drivers/net/ethernet/microchip/lan865x/lan865x.c +++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c @@ -346,7 +346,7 @@ static int lan865x_probe(struct spi_device *spi) spi_set_drvdata(spi, priv); INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler); - priv->tc6 = oa_tc6_init(spi, netdev); + priv->tc6 = oa_tc6_init(spi, netdev, NULL); if (!priv->tc6) { ret = -ENODEV; goto free_netdev; diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c index ace4ba21eec4..3fc2c006ba12 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c @@ -95,6 +95,7 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = { [GA_HSCH_SYSTEM] = 37384, [GA_HSCH_MMGT] = 36260, [GA_HSCH_TAS_CONFIG] = 37696, + [GA_HSCH_TAS_PROFILE_CFG] = 37712, [GA_PTP_PTP_CFG] = 512, [GA_PTP_PTP_TOD_DOMAINS] = 528, [GA_PTP_PHASE_DETECTOR_CTRL] = 628, @@ -129,6 +130,7 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = { [GC_GCB_SIO_CTRL] = 1, [GC_HSCH_HSCH_CFG] = 1120, [GC_HSCH_HSCH_DWRR] = 32, + [GC_HSCH_TAS_PROFILE_CFG] = 30, [GC_PTP_PTP_PINS] = 8, [GC_PTP_PHASE_DETECTOR_CTRL] = 8, [GC_REW_PORT] = 35, @@ -144,6 +146,7 @@ const unsigned int lan969x_gsize[GSIZE_LAST] = { [GW_FDMA_FDMA] = 448, [GW_GCB_CHIP_REGS] = 180, [GW_HSCH_TAS_CONFIG] = 16, + [GW_HSCH_TAS_PROFILE_CFG] = 68, [GW_PTP_PHASE_DETECTOR_CTRL] = 12, [GW_QSYS_PAUSE_CFG] = 988, }; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h index d9ef4ef137b8..d34467513648 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h @@ -5369,6 +5369,18 @@ extern const struct sparx5_regs *regs; #define HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY_GET(x)\ FIELD_GET(HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY, x) +/* HSCH:TAS_PROFILE_CFG:TAS_PROFILE_CONFIG */ +#define HSCH_TAS_PROFILE_CONFIG(g) \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_PROFILE_CFG], g, \ + regs->gcnt[GC_HSCH_TAS_PROFILE_CFG], \ + regs->gsize[GW_HSCH_TAS_PROFILE_CFG], 32, 0, 1, 4) + +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED GENMASK(10, 8) +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(x)\ + FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_GET(x)\ + FIELD_GET(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) + /* LAN969X ONLY */ /* HSIOWRAP:XMII_CFG:XMII_CFG */ #define HSIO_WRAP_XMII_CFG(g) \ diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c index 62c49893de3c..ef06bed3a9cc 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c @@ -11,6 +11,7 @@ #include "sparx5_main_regs.h" #include "sparx5_main.h" #include "sparx5_port.h" +#include "sparx5_qos.h" #define SPX5_ETYPE_TAG_C 0x8100 #define SPX5_ETYPE_TAG_S 0x88a8 @@ -1050,6 +1051,9 @@ int sparx5_port_config(struct sparx5 *sparx5, sparx5, QFWD_SWITCH_PORT_MODE(port->portno)); + /* Notify TAS about the speed. */ + sparx5_tas_speed(port, conf->speed); + /* Save the new values */ port->conf = *conf; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c index e580670f3992..972da8a71f5a 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c @@ -9,6 +9,17 @@ #include "sparx5_main.h" #include "sparx5_qos.h" +enum sparx5_tas_link_speed { + TAS_SPEED_NO_GB, + TAS_SPEED_10, + TAS_SPEED_100, + TAS_SPEED_1000, + TAS_SPEED_2500, + TAS_SPEED_5000, + TAS_SPEED_10000, + TAS_SPEED_25000, +}; + /* Calculate new base_time based on cycle_time. * * The hardware requires a base_time that is always in the future. @@ -581,3 +592,41 @@ int sparx5_tc_ets_del(struct sparx5_port *port) return sparx5_dwrr_conf_set(port, &dwrr); } + +void sparx5_tas_speed(struct sparx5_port *port, int speed) +{ + struct sparx5 *sparx5 = port->sparx5; + u8 spd; + + switch (speed) { + case SPEED_10: + spd = TAS_SPEED_10; + break; + case SPEED_100: + spd = TAS_SPEED_100; + break; + case SPEED_1000: + spd = TAS_SPEED_1000; + break; + case SPEED_2500: + spd = TAS_SPEED_2500; + break; + case SPEED_5000: + spd = TAS_SPEED_5000; + break; + case SPEED_10000: + spd = TAS_SPEED_10000; + break; + case SPEED_25000: + spd = TAS_SPEED_25000; + break; + default: + netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed); + return; + } + + spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd), + HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, + sparx5, + HSCH_TAS_PROFILE_CONFIG(port->portno)); +} diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h index 04f76f1e23f6..a92a699c551f 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h @@ -60,6 +60,7 @@ struct sparx5_dwrr { }; int sparx5_qos_init(struct sparx5 *sparx5); +void sparx5_tas_speed(struct sparx5_port *port, int speed); /* Multi-Queue Priority */ int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c index 220e81b714d4..3863f954bd83 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c @@ -95,6 +95,7 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = { [GA_HSCH_SYSTEM] = 184000, [GA_HSCH_MMGT] = 162368, [GA_HSCH_TAS_CONFIG] = 162384, + [GA_HSCH_TAS_PROFILE_CFG] = 188416, [GA_PTP_PTP_CFG] = 320, [GA_PTP_PTP_TOD_DOMAINS] = 336, [GA_PTP_PHASE_DETECTOR_CTRL] = 420, @@ -129,6 +130,7 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = { [GC_GCB_SIO_CTRL] = 3, [GC_HSCH_HSCH_CFG] = 5040, [GC_HSCH_HSCH_DWRR] = 72, + [GC_HSCH_TAS_PROFILE_CFG] = 100, [GC_PTP_PTP_PINS] = 5, [GC_PTP_PHASE_DETECTOR_CTRL] = 5, [GC_REW_PORT] = 70, @@ -144,6 +146,7 @@ const unsigned int sparx5_gsize[GSIZE_LAST] = { [GW_FDMA_FDMA] = 428, [GW_GCB_CHIP_REGS] = 424, [GW_HSCH_TAS_CONFIG] = 12, + [GW_HSCH_TAS_PROFILE_CFG] = 64, [GW_PTP_PHASE_DETECTOR_CTRL] = 8, [GW_QSYS_PAUSE_CFG] = 1128, }; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h index ea28130c2341..585589a31e90 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h @@ -104,6 +104,7 @@ enum sparx5_gaddr_enum { GA_HSCH_SYSTEM, GA_HSCH_MMGT, GA_HSCH_TAS_CONFIG, + GA_HSCH_TAS_PROFILE_CFG, GA_PTP_PTP_CFG, GA_PTP_PTP_TOD_DOMAINS, GA_PTP_PHASE_DETECTOR_CTRL, @@ -139,6 +140,7 @@ enum sparx5_gcnt_enum { GC_GCB_SIO_CTRL, GC_HSCH_HSCH_CFG, GC_HSCH_HSCH_DWRR, + GC_HSCH_TAS_PROFILE_CFG, GC_PTP_PTP_PINS, GC_PTP_PHASE_DETECTOR_CTRL, GC_REW_PORT, @@ -155,6 +157,7 @@ enum sparx5_gsize_enum { GW_FDMA_FDMA, GW_GCB_CHIP_REGS, GW_HSCH_TAS_CONFIG, + GW_HSCH_TAS_PROFILE_CFG, GW_PTP_PHASE_DETECTOR_CTRL, GW_QSYS_PAUSE_CFG, GSIZE_LAST, diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c index ff86cde11a32..788c0728d763 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api.c +++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c @@ -2427,18 +2427,21 @@ struct vcap_rule *vcap_decode_rule(struct vcap_rule_internal *elem) err = vcap_read_rule(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; err = vcap_decode_keyset(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; err = vcap_decode_actionset(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; out: return &ri->data; +err_free_rule: + vcap_free_rule(&ri->data); + return ERR_PTR(err); } struct vcap_rule *vcap_get_rule(struct vcap_control *vctrl, u32 id) diff --git a/drivers/net/ethernet/microsoft/Kconfig b/drivers/net/ethernet/microsoft/Kconfig index 3f36ee6a8ece..e9be18c92ca5 100644 --- a/drivers/net/ethernet/microsoft/Kconfig +++ b/drivers/net/ethernet/microsoft/Kconfig @@ -21,6 +21,7 @@ config MICROSOFT_MANA depends on X86_64 || (ARM64 && !CPU_BIG_ENDIAN) depends on PCI_HYPERV select AUXILIARY_BUS + select DIMLIB select PAGE_POOL select NET_SHAPER help diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index e8b7ffb47eb9..ed9af314e4ed 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* Copyright (c) 2021, Microsoft Corporation. */ +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include @@ -373,28 +375,96 @@ int mana_gd_send_request(struct gdma_context *gc, u32 req_len, const void *req, EXPORT_SYMBOL_NS(mana_gd_send_request, "NET_MANA"); int mana_gd_alloc_memory(struct gdma_context *gc, unsigned int length, - struct gdma_mem_info *gmi) + struct gdma_mem_info *gmi, bool allow_scatter) { + unsigned int npages, i; dma_addr_t dma_handle; + bool can_fallback; void *buf; if (length < MANA_PAGE_SIZE || !is_power_of_2(length)) return -EINVAL; gmi->dev = gc->dev; - buf = dma_alloc_coherent(gmi->dev, length, &dma_handle, GFP_KERNEL); - if (!buf) + + /* An allocation that fits in one page does not benefit from + * fallback. + */ + can_fallback = allow_scatter && length > PAGE_SIZE; + + /* Warn only when there is no fallback to rescue the failure. */ + buf = dma_alloc_coherent(gmi->dev, length, &dma_handle, + GFP_KERNEL | + (can_fallback ? __GFP_NOWARN : 0)); + if (buf) { + gmi->dma_handle = dma_handle; + gmi->virt_addr = buf; + gmi->length = length; + gmi->nr_pages = 0; + return 0; + } + + if (!can_fallback) return -ENOMEM; - gmi->dma_handle = dma_handle; - gmi->virt_addr = buf; + /* length is a power of 2 above PAGE_SIZE, so this divides exactly. */ + npages = length / PAGE_SIZE; + + gmi->pages_va = kvcalloc(npages, sizeof(*gmi->pages_va), GFP_KERNEL); + if (!gmi->pages_va) + return -ENOMEM; + + gmi->pages_dma = kvcalloc(npages, sizeof(*gmi->pages_dma), GFP_KERNEL); + if (!gmi->pages_dma) + goto free_va; + + for (i = 0; i < npages; i++) { + gmi->pages_va[i] = dma_alloc_coherent(gmi->dev, PAGE_SIZE, + &gmi->pages_dma[i], + GFP_KERNEL); + if (!gmi->pages_va[i]) + goto free_pages; + } + + dev_info_ratelimited(gmi->dev, + "contiguous %u-byte DMA alloc failed; using %u scattered pages\n", + length, npages); + + gmi->virt_addr = NULL; + gmi->dma_handle = 0; gmi->length = length; + gmi->nr_pages = npages; return 0; + +free_pages: + while (i--) + dma_free_coherent(gmi->dev, PAGE_SIZE, gmi->pages_va[i], + gmi->pages_dma[i]); + kvfree(gmi->pages_dma); + gmi->pages_dma = NULL; +free_va: + kvfree(gmi->pages_va); + gmi->pages_va = NULL; + return -ENOMEM; } void mana_gd_free_memory(struct gdma_mem_info *gmi) { + unsigned int i; + + if (gmi->nr_pages > 0) { + for (i = 0; i < gmi->nr_pages; i++) + dma_free_coherent(gmi->dev, PAGE_SIZE, gmi->pages_va[i], + gmi->pages_dma[i]); + kvfree(gmi->pages_va); + kvfree(gmi->pages_dma); + gmi->pages_va = NULL; + gmi->pages_dma = NULL; + gmi->nr_pages = 0; + return; + } + dma_free_coherent(gmi->dev, gmi->length, gmi->virt_addr, gmi->dma_handle); } @@ -466,6 +536,7 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) #define DOORBELL_OFFSET_RQ 0x400 #define DOORBELL_OFFSET_CQ 0x800 #define DOORBELL_OFFSET_EQ 0xFF8 +#define DOORBELL_OFFSET_DIM 0x820 static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, enum gdma_queue_type q_type, u32 qid, @@ -506,6 +577,16 @@ static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, addr += DOORBELL_OFFSET_SQ; break; + case GDMA_DIM: + e.dim.id = qid; + e.dim.mod_usec = FIELD_GET(MANA_INTR_MODR_USEC_MAX, tail_ptr); + e.dim.mod_usec_vld = !!(tail_ptr & MANA_INTR_MODR_USEC_VLD); + e.dim.mod_comps = FIELD_GET(MANA_INTR_MODR_COMP_MASK, tail_ptr); + e.dim.mod_comps_vld = num_req; + + addr += DOORBELL_OFFSET_DIM; + break; + default: WARN_ON(1); return; @@ -540,6 +621,23 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit) } EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA"); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld) +{ + struct gdma_context *gc = cq->gdma_dev->gdma_context; + u32 dim_val; + + /* Convert the DIM values to doorbell parameters */ + dim_val = FIELD_PREP(MANA_INTR_MODR_USEC_MAX, mod_usec) | + FIELD_PREP(MANA_INTR_MODR_COMP_MASK, mod_comps); + if (mod_usec_vld) + dim_val |= MANA_INTR_MODR_USEC_VLD; + + mana_gd_ring_doorbell(gc, cq->gdma_dev->doorbell, GDMA_DIM, cq->id, + dim_val, mod_comps_vld); +} +EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA"); + #define MANA_SERVICE_PERIOD 10 static void mana_serv_rescan(struct pci_dev *pdev) @@ -724,11 +822,73 @@ int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type) return 0; } +/* Return the CPU address of byte @offset within a queue's ring buffer. */ +static void *mana_gd_ring_ptr(const struct gdma_queue *q, u32 offset) +{ + const struct gdma_mem_info *gmi = &q->mem_info; + + if (gmi->nr_pages > 0) + return (u8 *)gmi->pages_va[offset / PAGE_SIZE] + + (offset & (PAGE_SIZE - 1)); + + return q->queue_mem_ptr + offset; +} + +/* Number of bytes from @offset to the end of the CPU-contiguous region: the + * rest of the ring, or the rest of the current page when scattered. + */ +static u32 mana_gd_ring_contig_avail(const struct gdma_queue *q, u32 offset) +{ + if (q->mem_info.nr_pages > 0) + return PAGE_SIZE - (offset & (PAGE_SIZE - 1)); + + return q->queue_size - offset; +} + +/* Copy up to @count bytes from ring offset *@pos of @q into user buffer @buf, + * so a scattered ring reads back as if it were contiguous. Returns bytes + * copied, 0 at end of ring, or a negative errno. + */ +ssize_t mana_gd_read_ring(struct gdma_queue *q, char __user *buf, + size_t count, loff_t *pos) +{ + u32 size = q->queue_size; + loff_t off = *pos; + size_t copied = 0; + + if (off < 0) + return -EINVAL; + if (off >= size || !count) + return 0; + count = min_t(size_t, count, size - off); + + while (count) { + u32 offset = off; + u32 avail = mana_gd_ring_contig_avail(q, offset); + size_t chunk = min_t(size_t, count, avail); + size_t left = copy_to_user(buf, mana_gd_ring_ptr(q, offset), + chunk); + + chunk -= left; + buf += chunk; + off += chunk; + copied += chunk; + count -= chunk; + if (left) + break; + } + + if (!copied) + return -EFAULT; + + *pos = off; + return copied; +} + static void mana_gd_process_eqe(struct gdma_queue *eq) { u32 head = eq->head % (eq->queue_size / GDMA_EQE_SIZE); struct gdma_context *gc = eq->gdma_dev->gdma_context; - struct gdma_eqe *eq_eqe_ptr = eq->queue_mem_ptr; union gdma_eqe_info eqe_info; enum gdma_eqe_type type; struct gdma_event event; @@ -736,7 +896,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) struct gdma_eqe *eqe; u32 cq_id; - eqe = &eq_eqe_ptr[head]; + eqe = mana_gd_ring_ptr(eq, head * sizeof(*eqe)); eqe_info.as_uint32 = eqe->eqe_info; type = eqe_info.type; @@ -800,7 +960,6 @@ static void mana_gd_process_eq_events(void *arg) { u32 owner_bits, new_bits, old_bits; union gdma_eqe_info eqe_info; - struct gdma_eqe *eq_eqe_ptr; struct gdma_queue *eq = arg; struct gdma_context *gc; struct gdma_eqe *eqe; @@ -810,11 +969,10 @@ static void mana_gd_process_eq_events(void *arg) gc = eq->gdma_dev->gdma_context; num_eqe = eq->queue_size / GDMA_EQE_SIZE; - eq_eqe_ptr = eq->queue_mem_ptr; /* Process up to 5 EQEs at a time, and update the HW head. */ for (i = 0; i < 5; i++) { - eqe = &eq_eqe_ptr[eq->head % num_eqe]; + eqe = mana_gd_ring_ptr(eq, (eq->head % num_eqe) * sizeof(*eqe)); eqe_info.as_uint32 = eqe->eqe_info; owner_bits = eqe_info.owner_bits; @@ -1078,7 +1236,7 @@ int mana_gd_create_hwc_queue(struct gdma_dev *gd, return -ENOMEM; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, false); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, gdma memory allocation err: %d\n", spec->type, spec->queue_size, err); @@ -1153,7 +1311,7 @@ static int mana_gd_create_dma_region(struct gdma_dev *gd, if (length < MANA_PAGE_SIZE || !is_power_of_2(length)) return -EINVAL; - if (!MANA_PAGE_ALIGNED(gmi->virt_addr)) + if (gmi->nr_pages == 0 && !MANA_PAGE_ALIGNED(gmi->virt_addr)) return -EINVAL; hwc = gc->hwc.driver_data; @@ -1173,8 +1331,24 @@ static int mana_gd_create_dma_region(struct gdma_dev *gd, req->page_count = num_page; req->page_addr_list_len = num_page; - for (i = 0; i < num_page; i++) - req->page_addr_list[i] = gmi->dma_handle + i * MANA_PAGE_SIZE; + if (gmi->nr_pages > 0) { + unsigned int subpages = PAGE_SIZE / MANA_PAGE_SIZE; + unsigned int idx = 0; + unsigned int pg, sub; + + /* Each PAGE_SIZE chunk is physically contiguous and contains + * PAGE_SIZE / MANA_PAGE_SIZE consecutive device pages. + */ + for (pg = 0; pg < gmi->nr_pages; pg++) + for (sub = 0; sub < subpages; sub++) + req->page_addr_list[idx++] = + gmi->pages_dma[pg] + + sub * MANA_PAGE_SIZE; + } else { + for (i = 0; i < num_page; i++) + req->page_addr_list[i] = + gmi->dma_handle + i * MANA_PAGE_SIZE; + } err = mana_gd_send_request(gc, req_msg_size, req, sizeof(resp), &resp); if (err) @@ -1217,7 +1391,7 @@ int mana_gd_create_mana_eq(struct gdma_dev *gd, return -ENOMEM; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, true); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, gdma memory allocation err: %d\n", spec->type, spec->queue_size, err); @@ -1272,7 +1446,7 @@ int mana_gd_create_mana_wq_cq(struct gdma_dev *gd, queue->id = INVALID_QUEUE_ID; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, true); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, memory allocation err: %d\n", spec->type, spec->queue_size, err); @@ -1479,7 +1653,7 @@ u8 *mana_gd_get_wqe_ptr(const struct gdma_queue *wq, u32 wqe_offset) WARN_ON_ONCE((offset + GDMA_WQE_BU_SIZE) > wq->queue_size); - return wq->queue_mem_ptr + offset; + return mana_gd_ring_ptr(wq, offset); } static u32 mana_gd_write_client_oob(const struct gdma_wqe_request *wqe_req, @@ -1525,27 +1699,24 @@ static u32 mana_gd_write_client_oob(const struct gdma_wqe_request *wqe_req, return sizeof(header) + client_oob_size; } -static void mana_gd_write_sgl(struct gdma_queue *wq, u8 *wqe_ptr, +static void mana_gd_write_sgl(struct gdma_queue *wq, u32 sgl_offset, const struct gdma_wqe_request *wqe_req) { + u32 size_to_end = mana_gd_ring_contig_avail(wq, sgl_offset); u32 sgl_size = sizeof(struct gdma_sge) * wqe_req->num_sge; const u8 *address = (u8 *)wqe_req->sgl; - u8 *base_ptr, *end_ptr; - u32 size_to_end; - - base_ptr = wq->queue_mem_ptr; - end_ptr = base_ptr + wq->queue_size; - size_to_end = (u32)(end_ptr - wqe_ptr); if (size_to_end < sgl_size) { - memcpy(wqe_ptr, address, size_to_end); + memcpy(mana_gd_ring_ptr(wq, sgl_offset), address, size_to_end); - wqe_ptr = base_ptr; address += size_to_end; sgl_size -= size_to_end; + sgl_offset += size_to_end; + if (sgl_offset == wq->queue_size) + sgl_offset = 0; } - memcpy(wqe_ptr, address, sgl_size); + memcpy(mana_gd_ring_ptr(wq, sgl_offset), address, sgl_size); } int mana_gd_post_work_request(struct gdma_queue *wq, @@ -1555,8 +1726,12 @@ int mana_gd_post_work_request(struct gdma_queue *wq, u32 client_oob_size = wqe_req->inline_oob_size; u32 sgl_data_size; u32 max_wqe_size; + u32 wqe_offset; + u32 sgl_offset; u32 wqe_size; + u32 oob_len; u8 *wqe_ptr; + u32 head; if (wqe_req->num_sge == 0) return -EINVAL; @@ -1588,13 +1763,17 @@ int mana_gd_post_work_request(struct gdma_queue *wq, if (wqe_info) wqe_info->wqe_size_in_bu = wqe_size / GDMA_WQE_BU_SIZE; - wqe_ptr = mana_gd_get_wqe_ptr(wq, wq->head); - wqe_ptr += mana_gd_write_client_oob(wqe_req, wq->type, client_oob_size, - sgl_data_size, wqe_ptr); - if (wqe_ptr >= (u8 *)wq->queue_mem_ptr + wq->queue_size) - wqe_ptr -= wq->queue_size; + head = wq->head; + wqe_offset = (head * GDMA_WQE_BU_SIZE) & (wq->queue_size - 1); + wqe_ptr = mana_gd_get_wqe_ptr(wq, head); + oob_len = mana_gd_write_client_oob(wqe_req, wq->type, client_oob_size, + sgl_data_size, wqe_ptr); - mana_gd_write_sgl(wq, wqe_ptr, wqe_req); + sgl_offset = wqe_offset + oob_len; + if (sgl_offset >= wq->queue_size) + sgl_offset -= wq->queue_size; + + mana_gd_write_sgl(wq, sgl_offset, wqe_req); wq->head += wqe_size / GDMA_WQE_BU_SIZE; @@ -1624,11 +1803,10 @@ int mana_gd_post_and_ring(struct gdma_queue *queue, static int mana_gd_read_cqe(struct gdma_queue *cq, struct gdma_comp *comp) { unsigned int num_cqe = cq->queue_size / sizeof(struct gdma_cqe); - struct gdma_cqe *cq_cqe = cq->queue_mem_ptr; u32 owner_bits, new_bits, old_bits; struct gdma_cqe *cqe; - cqe = &cq_cqe[cq->head % num_cqe]; + cqe = mana_gd_ring_ptr(cq, (cq->head % num_cqe) * sizeof(*cqe)); owner_bits = cqe->cqe_info.owner_bits; old_bits = (cq->head / num_cqe - 1) & GDMA_CQE_OWNER_MASK; @@ -2427,6 +2605,8 @@ static void mana_gd_remove(struct pci_dev *pdev) { struct gdma_context *gc = pci_get_drvdata(pdev); + pci_disable_sriov(pdev); + mana_rdma_remove(&gc->mana_ib); mana_remove(&gc->mana, false); @@ -2496,6 +2676,27 @@ static void mana_gd_shutdown(struct pci_dev *pdev) pci_disable_device(pdev); } +static int mana_sriov_configure(struct pci_dev *pdev, int numvfs) +{ + int err = 0; + + dev_info(&pdev->dev, "Requested num VFs: %d\n", numvfs); + + if (numvfs > 0) { + err = pci_enable_sriov(pdev, numvfs); + } else { + if (pci_vfs_assigned(pdev)) { + dev_warn(&pdev->dev, + "Cannot disable SR-IOV while VFs are assigned\n"); + return -EPERM; + } + + pci_disable_sriov(pdev); + } + + return err ? err : numvfs; +} + static const struct pci_device_id mana_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF_DEVICE_ID) }, { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF2_DEVICE_ID) }, @@ -2511,6 +2712,7 @@ static struct pci_driver mana_driver = { .suspend = mana_gd_suspend, .resume = mana_gd_resume, .shutdown = mana_gd_shutdown, + .sriov_configure = mana_sriov_configure, }; static int __init mana_driver_init(void) diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c index e3c24d50dad0..263e7c4e2934 100644 --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c @@ -479,7 +479,7 @@ static int mana_hwc_alloc_dma_buf(struct hw_channel_context *hwc, u16 q_depth, buf_size = MANA_PAGE_ALIGN(q_depth * max_msg_size); gmi = &dma_buf->mem_info; - err = mana_gd_alloc_memory(gc, buf_size, gmi); + err = mana_gd_alloc_memory(gc, buf_size, gmi, false); if (err) { dev_err(hwc->dev, "Failed to allocate DMA buffer size: %u, err %d\n", buf_size, err); diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 92bb55935c1c..7a1ac853e3ab 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -40,6 +40,9 @@ static ssize_t mana_dbg_q_read(struct file *filp, char __user *buf, size_t count { struct gdma_queue *gdma_q = filp->private_data; + if (gdma_q->mem_info.nr_pages) + return mana_gd_read_ring(gdma_q, buf, count, pos); + return simple_read_from_buffer(buf, count, pos, gdma_q->queue_mem_ptr, gdma_q->queue_size); } @@ -755,6 +758,25 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +static bool +mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) +{ + /* On some platforms with 4K PAGE_SIZE, page_pool fragment allocation + * in the RX refill path (~2kB buffer) can cause significant throughput + * regression under high connection counts. Allow user to force one RX + * buffer per page via ethtool private flag to bypass the fragment + * path. + */ + 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; +} + /* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, @@ -765,8 +787,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; - /* 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)) { + if (mana_use_single_rxbuf_per_page(apc, mtu)) { if (mana_xdp_get(apc)) { *headroom = XDP_PACKET_HEADROOM; *alloc_size = PAGE_SIZE; @@ -892,6 +913,17 @@ static void mana_tx_timeout(struct net_device *netdev, unsigned int txqueue) struct mana_context *ac = apc->ac; struct gdma_context *gc = ac->gdma_dev->gdma_context; + /* Debug knob for bringup/qualification: when set, log the timeout and + * skip the reset so the failing state is preserved for telemetry. + * Disabled by default; production behaviour is unchanged. + */ + if (READ_ONCE(apc->tx_timeout_skip_reset)) { + netdev_warn(netdev, + "TX timeout on queue %u: reset skipped (tx_timeout_skip_reset enabled)\n", + txqueue); + return; + } + /* Already in service, hence tx queue reset is not required.*/ if (test_bit(GC_IN_SERVICE, &gc->flags)) return; @@ -1230,6 +1262,9 @@ int mana_gd_query_device_cfg(struct gdma_context *gc, u32 proto_major_ver, return err; } + gc->cqe8_coalescing_sup = !!(resp.pf_cap_flags1 & + MANA_PF_FLAG_1_CQE_8_COALESCING_SUPPORTED); + *max_num_vports = resp.max_num_vports; if (resp.hdr.response.msg_version >= GDMA_MESSAGE_V2) { @@ -1415,7 +1450,11 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, mana_gd_init_req_hdr(&req->hdr, MANA_CONFIG_VPORT_RX, req_buf_size, sizeof(resp)); - req->hdr.req.msg_version = GDMA_MESSAGE_V2; + /* Request & response versions can be different. + * HW can handle newer msg versions, by skipping + * new fields. + */ + req->hdr.req.msg_version = GDMA_MESSAGE_V5; req->hdr.resp.msg_version = GDMA_MESSAGE_V2; req->vport = apc->port_handle; @@ -1429,8 +1468,13 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, req->update_indir_tab = update_tab; req->default_rxobj = apc->default_rxobj; - if (rx != TRI_STATE_FALSE) + /* Request-msg v5 requires this field */ + req->rss_hash_types = MANA_HASH_ENABLE_SUPPORTED; + + if (rx != TRI_STATE_FALSE) { req->cqe_coalescing_enable = apc->cqe_coalescing_enable; + req->cqe8_coalescing_enable = apc->cqe8_coalescing_enable; + } if (update_key) memcpy(&req->hashkey, apc->hashkey, MANA_HASH_KEY_SIZE); @@ -1591,6 +1635,15 @@ int mana_create_wq_obj(struct mana_port_context *apc, mana_gd_init_req_hdr(&req.hdr, MANA_CREATE_WQ_OBJ, sizeof(req), sizeof(resp)); + + /* Our driver uses different message versions for request and + * response in this case. + * Our firmware is forward compatible with newer message versions, so + * the old firmware still properly handles this message, just the new + * feature fields are ignored, and queue creation will be successful. + */ + req.hdr.req.msg_version = GDMA_MESSAGE_V3; + req.hdr.resp.msg_version = GDMA_MESSAGE_V2; req.vport = vport; req.wq_type = wq_type; req.wq_gdma_region = wq_spec->gdma_region; @@ -1599,6 +1652,9 @@ int mana_create_wq_obj(struct mana_port_context *apc, req.cq_size = cq_spec->queue_size; req.cq_moderation_ctx_id = cq_spec->modr_ctx_id; req.cq_parent_qid = cq_spec->attached_eq; + req.req_cq_moderation = cq_spec->req_cq_moderation; + req.cq_moderation_comp = cq_spec->cq_moderation_comp; + req.cq_moderation_usec = cq_spec->cq_moderation_usec; err = mana_send_request(apc->ac, &req, sizeof(req), &resp, sizeof(resp)); @@ -1856,6 +1912,7 @@ static void mana_poll_tx_cq(struct mana_cq *cq) struct gdma_posted_wqe_info *wqe_info; unsigned int pkt_transmitted = 0; unsigned int wqe_unit_cnt = 0; + unsigned int tx_bytes = 0; struct mana_txq *txq = cq->txq; struct mana_port_context *apc; struct netdev_queue *net_txq; @@ -1937,6 +1994,8 @@ static void mana_poll_tx_cq(struct mana_cq *cq) mana_unmap_skb(skb, apc); + tx_bytes += skb->len; + napi_consume_skb(skb, cq->budget); pkt_transmitted++; @@ -1967,6 +2026,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq) if (atomic_sub_return(pkt_transmitted, &txq->pending_sends) < 0) WARN_ON_ONCE(1); + /* Feed DIM with the completion rate observed here, in NAPI context. */ + cq->tx_dim_pkts += pkt_transmitted; + cq->tx_dim_bytes += tx_bytes; + cq->work_done = pkt_transmitted; } @@ -2016,16 +2079,14 @@ static struct sk_buff *mana_build_skb(struct mana_rxq *rxq, void *buf_va, static void mana_rx_skb(void *buf_va, bool from_pool, struct mana_rxcomp_oob *cqe, struct mana_rxq *rxq, - int i) + u32 pkt_len, u32 pkt_hash) { struct mana_stats_rx *rx_stats = &rxq->stats; struct net_device *ndev = rxq->ndev; - uint pkt_len = cqe->ppi[i].pkt_len; u16 rxq_idx = rxq->rxq_idx; struct napi_struct *napi; struct xdp_buff xdp = {}; struct sk_buff *skb; - u32 hash_value; u32 act; rxq->rx_cq.work_done++; @@ -2064,12 +2125,10 @@ static void mana_rx_skb(void *buf_va, bool from_pool, } if (cqe->rx_hashtype != 0 && (ndev->features & NETIF_F_RXHASH)) { - hash_value = cqe->ppi[i].pkt_hash; - if (cqe->rx_hashtype & MANA_HASH_L4) - skb_set_hash(skb, hash_value, PKT_HASH_TYPE_L4); + skb_set_hash(skb, pkt_hash, PKT_HASH_TYPE_L4); else - skb_set_hash(skb, hash_value, PKT_HASH_TYPE_L3); + skb_set_hash(skb, pkt_hash, PKT_HASH_TYPE_L3); } if (cqe->rx_vlantag_present) { @@ -2210,6 +2269,43 @@ static void mana_refill_rx_oob(struct device *dev, struct mana_rxq *rxq, rxoob->dma_sync_offset = dma_sync_offset; } +static void mana_process_one_rx_pkt(struct device *dev, struct mana_rxq *rxq, + struct mana_rxcomp_oob *oob, + u32 pktlen, u32 pkt_hash) +{ + struct mana_recv_buf_oob *rxbuf_oob; + struct net_device *ndev = rxq->ndev; + void *old_buf = NULL; + bool old_fp; + + rxbuf_oob = &rxq->rx_oobs[rxq->buf_index]; + WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1); + + if (unlikely(pktlen > rxq->datasize)) { + /* Increase it even if mana_rx_skb() isn't called. */ + rxq->rx_cq.work_done++; + + ++ndev->stats.rx_dropped; + netdev_warn_once(ndev, + "Dropped oversized RX packet: len=%u, datasize=%u\n", + pktlen, rxq->datasize); + + /* Reuse the RX buffer since rxbuf_oob is unchanged. */ + } else { + mana_refill_rx_oob(dev, rxq, rxbuf_oob, pktlen, + &old_buf, &old_fp); + + /* Unsuccessful refill will have old_buf == NULL. + * In this case, mana_rx_skb() will drop the packet. + */ + mana_rx_skb(old_buf, old_fp, oob, rxq, pktlen, pkt_hash); + } + + mana_move_wq_tail(rxq->gdma_rq, rxbuf_oob->wqe_inf.wqe_size_in_bu); + + mana_post_pkt_rxq(rxq); +} + static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, struct gdma_comp *cqe) { @@ -2219,10 +2315,10 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, struct mana_recv_buf_oob *rxbuf_oob; struct mana_port_context *apc; struct device *dev = gc->dev; + bool coalesced_8 = false; bool coalesced = false; - void *old_buf = NULL; - u32 curr, pktlen; - bool old_fp; + u32 pktlen; + int pkt_i; int i; apc = netdev_priv(ndev); @@ -2245,6 +2341,11 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, coalesced = true; break; + case CQE_RX_COALESCED_8: + coalesced = true; + coalesced_8 = true; + break; + case CQE_RX_OBJECT_FENCE: complete(&rxq->fence_event); return; @@ -2256,54 +2357,48 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, return; } + pkt_i = 0; for (i = 0; i < MANA_RXCOMP_OOB_NUM_PPI; i++) { - old_buf = NULL; - pktlen = oob->ppi[i].pkt_len; + u32 pkt_hash; + + if (coalesced_8) { + /* 8-pkt mode: 2 packets per PPI entry */ + pktlen = oob->ppi[i].pkt_len0; + pkt_hash = oob->ppi[i].pkt_hash0; + } else { + pktlen = oob->ppi[i].pkt_len; + pkt_hash = oob->ppi[i].pkt_hash; + } if (pktlen == 0) break; - curr = rxq->buf_index; - rxbuf_oob = &rxq->rx_oobs[curr]; - WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1); - - if (unlikely(pktlen > rxq->datasize)) { - /* Increase it even if mana_rx_skb() isn't called. */ - rxq->rx_cq.work_done++; - - ++ndev->stats.rx_dropped; - netdev_warn_once(ndev, - "Dropped oversized RX packet: len=%u, datasize=%u\n", - pktlen, rxq->datasize); - - /* Reuse the RX buffer since rxbuf_oob is unchanged. */ - } else { - - mana_refill_rx_oob(dev, rxq, rxbuf_oob, pktlen, - &old_buf, &old_fp); - - /* Unsuccessful refill will have old_buf == NULL. - * In this case, mana_rx_skb() will drop the packet. - */ - mana_rx_skb(old_buf, old_fp, oob, rxq, i); - } - - mana_move_wq_tail(rxq->gdma_rq, - rxbuf_oob->wqe_inf.wqe_size_in_bu); - - mana_post_pkt_rxq(rxq); + mana_process_one_rx_pkt(dev, rxq, oob, pktlen, pkt_hash); + pkt_i++; if (!coalesced) break; + + /* Process 2nd packet from the same PPI in 8-pkt mode */ + if (coalesced_8) { + pktlen = oob->ppi[i].pkt_len1; + pkt_hash = oob->ppi[i].pkt_hash1; + if (pktlen == 0) + break; + + mana_process_one_rx_pkt(dev, rxq, oob, pktlen, + pkt_hash); + pkt_i++; + } } /* Collect coalesced CQE count based on packets processed. - * Coalesced CQEs have at least 2 packets, so index is i - 2. + * Coalesced CQEs have at least 2 packets, so index is pkt_i - 2. */ - if (i > 1) { + if (pkt_i > 1) { u64_stats_update_begin(&rxq->stats.syncp); - rxq->stats.coalesced_cqe[i - 2]++; + rxq->stats.coalesced_cqe[pkt_i - 2]++; u64_stats_update_end(&rxq->stats.syncp); - } else if (!i && !pktlen) { + } else if (!pkt_i && !pktlen) { u64_stats_update_begin(&rxq->stats.syncp); rxq->stats.pkt_len0_err++; u64_stats_update_end(&rxq->stats.syncp); @@ -2352,6 +2447,117 @@ static void mana_poll_rx_cq(struct mana_cq *cq) xdp_do_flush(); } +static void mana_rx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +static void mana_tx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_tx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +/* The caller must update apc->rx/tx_dim_enabled before disabling and + * after enabling. And synchronize_net() before draining the DIM work, + * so that NAPI cannot observe a stale flag. + */ +void mana_dim_change(struct mana_cq *cq, bool enable) +{ + bool is_rx = cq->type == MANA_CQ_TYPE_RX; + struct mana_port_context *apc; + work_func_t work_func; + u32 usec, comp; + + if (is_rx) { + apc = netdev_priv(cq->rxq->ndev); + usec = apc->intr_modr_rx_usec; + comp = apc->intr_modr_rx_comp; + work_func = mana_rx_dim_work; + } else { + apc = netdev_priv(cq->txq->ndev); + usec = apc->intr_modr_tx_usec; + comp = apc->intr_modr_tx_comp; + work_func = mana_tx_dim_work; + } + + /* On enable, zero the DIM state so net_dim() starts measuring from + * scratch. + * On disable, drain any pending DIM work and restore the static + * moderation values. + */ + if (enable) { + memset(&cq->dim, 0, sizeof(cq->dim)); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + INIT_WORK(&cq->dim.work, work_func); + } else { + cancel_work_sync(&cq->dim.work); + mana_gd_ring_dim(cq->gdma_cq, usec, true, comp, true); + } +} + +static void mana_update_rx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->rxq->ndev); + struct dim_sample dim_sample = {}; + struct mana_rxq *rxq = cq->rxq; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->rx_dim_enabled)) + return; + + dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets, + rxq->stats.bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + +static void mana_update_tx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->txq->ndev); + struct dim_sample dim_sample = {}; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->tx_dim_enabled)) + return; + + /* cq->tx_dim_pkts/bytes are accumulated in mana_poll_tx_cq(), in the + * same NAPI context as this read, so they track the hardware + * completion rate and need no u64_stats_sync protection. + */ + dim_update_sample(READ_ONCE(cq->dim_event_ctr), cq->tx_dim_pkts, + cq->tx_dim_bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; @@ -2370,6 +2576,15 @@ static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) if (w < cq->budget) { mana_gd_ring_cq(gdma_queue, SET_ARM_BIT); cq->work_done_since_doorbell = 0; + + /* Update DIM before napi_complete_done() to prevent running + * net_dim() concurrently. + */ + if (cq->type == MANA_CQ_TYPE_RX) + mana_update_rx_dim(cq); + else + mana_update_tx_dim(cq); + napi_complete_done(&cq->napi, w); } else if (cq->work_done_since_doorbell >= (cq->gdma_cq->queue_size / COMP_ENTRY_SIZE) * 4) { @@ -2402,6 +2617,7 @@ static void mana_schedule_napi(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; + WRITE_ONCE(cq->dim_event_ctr, cq->dim_event_ctr + 1); napi_schedule_irqoff(&cq->napi); } @@ -2444,6 +2660,7 @@ static void mana_destroy_txq(struct mana_port_context *apc) if (apc->tx_qp[i]->txq.napi_initialized) { napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&apc->tx_qp[i]->tx_cq.dim.work); netif_napi_del_locked(napi); apc->tx_qp[i]->txq.napi_initialized = false; } @@ -2577,6 +2794,11 @@ static int mana_create_txq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_tx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_tx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_SQ, &wq_spec, &cq_spec, &apc->tx_qp[i]->tx_object); @@ -2607,6 +2829,13 @@ static int mana_create_txq(struct mana_port_context *apc, set_bit(NAPI_STATE_NO_BUSY_POLL, &cq->napi.state); netif_napi_add_locked(net, &cq->napi, mana_poll); + + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_tx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); txq->napi_initialized = true; @@ -2644,6 +2873,7 @@ static void mana_destroy_rxq(struct mana_port_context *apc, napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&rxq->rx_cq.dim.work); netif_napi_del_locked(napi); } @@ -2887,6 +3117,11 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_rx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_rx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_RQ, &wq_spec, &cq_spec, &rxq->rxobj); if (err) @@ -2919,6 +3154,12 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, WARN_ON(xdp_rxq_info_reg_mem_model(&rxq->xdp_rxq, MEM_TYPE_PAGE_POOL, rxq->page_pool)); + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_rx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); mana_gd_ring_cq(cq->gdma_cq, SET_ARM_BIT); @@ -3321,6 +3562,9 @@ static int mana_init_port(struct net_device *ndev) &apc->steer_cqe_coalescing); debugfs_create_u32("current_speed", 0400, apc->mana_port_debugfs, &apc->speed); + debugfs_create_bool("tx_timeout_skip_reset", 0600, + apc->mana_port_debugfs, + &apc->tx_timeout_skip_reset); return 0; reset_apc: @@ -3584,6 +3828,17 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc->port_idx = port_idx; apc->link_cfg_error = 1; apc->cqe_coalescing_enable = 0; + apc->cqe8_coalescing_enable = 0; + + /* Initialize interrupt moderation settings if supported by HW */ + if (gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) { + apc->intr_modr_rx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_rx_comp = MANA_INTR_MODR_COMP_DEF; + apc->intr_modr_tx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_tx_comp = MANA_INTR_MODR_COMP_DEF; + apc->rx_dim_enabled = MANA_ADAPTIVE_RX_DEF; + apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF; + } mutex_init(&apc->vport_mutex); apc->vport_use_count = 0; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 881df597d7f9..ece7ff9cc409 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -133,58 +133,94 @@ static const struct mana_stats_desc mana_phy_stats[] = { { "hc_tc7_tx_pause_phy", offsetof(struct mana_ethtool_phy_stats, tx_pause_tc7_phy) }, }; +static const char mana_priv_flags[MANA_PRIV_FLAG_MAX][ETH_GSTRING_LEN] = { + [MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF] = "full-page-rx" +}; + static int mana_get_sset_count(struct net_device *ndev, int stringset) { struct mana_port_context *apc = netdev_priv(ndev); unsigned int num_queues = apc->num_queues; - if (stringset != ETH_SS_STATS) - return -EINVAL; + switch (stringset) { + case ETH_SS_STATS: + return ARRAY_SIZE(mana_eth_stats) + + ARRAY_SIZE(mana_phy_stats) + + ARRAY_SIZE(mana_hc_stats) + + num_queues * (MANA_STATS_RX_COUNT + MANA_STATS_TX_COUNT); - return ARRAY_SIZE(mana_eth_stats) + ARRAY_SIZE(mana_phy_stats) + ARRAY_SIZE(mana_hc_stats) + - num_queues * (MANA_STATS_RX_COUNT + MANA_STATS_TX_COUNT); + case ETH_SS_PRIV_FLAGS: + return MANA_PRIV_FLAG_MAX; + + default: + return -EINVAL; + } +} + +static void mana_get_strings_stats(struct mana_port_context *apc, u8 **data) +{ + unsigned int num_queues = apc->num_queues; + int i, j; + + for (i = 0; i < ARRAY_SIZE(mana_eth_stats); i++) + ethtool_puts(data, mana_eth_stats[i].name); + + for (i = 0; i < ARRAY_SIZE(mana_hc_stats); i++) + ethtool_puts(data, mana_hc_stats[i].name); + + for (i = 0; i < ARRAY_SIZE(mana_phy_stats); i++) + ethtool_puts(data, mana_phy_stats[i].name); + + for (i = 0; i < num_queues; i++) { + ethtool_sprintf(data, "rx_%d_packets", i); + ethtool_sprintf(data, "rx_%d_bytes", i); + ethtool_sprintf(data, "rx_%d_xdp_drop", i); + ethtool_sprintf(data, "rx_%d_xdp_tx", i); + ethtool_sprintf(data, "rx_%d_xdp_redirect", i); + ethtool_sprintf(data, "rx_%d_pkt_len0_err", i); + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) + ethtool_sprintf(data, + "rx_%d_coalesced_cqe_%d", + i, + j + 2); + } + + for (i = 0; i < num_queues; i++) { + ethtool_sprintf(data, "tx_%d_packets", i); + ethtool_sprintf(data, "tx_%d_bytes", i); + ethtool_sprintf(data, "tx_%d_xdp_xmit", i); + ethtool_sprintf(data, "tx_%d_tso_packets", i); + ethtool_sprintf(data, "tx_%d_tso_bytes", i); + ethtool_sprintf(data, "tx_%d_tso_inner_packets", i); + ethtool_sprintf(data, "tx_%d_tso_inner_bytes", i); + ethtool_sprintf(data, "tx_%d_long_pkt_fmt", i); + ethtool_sprintf(data, "tx_%d_short_pkt_fmt", i); + ethtool_sprintf(data, "tx_%d_csum_partial", i); + ethtool_sprintf(data, "tx_%d_mana_map_err", i); + } +} + +static void mana_get_strings_priv_flags(u8 **data) +{ + int i; + + for (i = 0; i < MANA_PRIV_FLAG_MAX; i++) + ethtool_puts(data, mana_priv_flags[i]); } static void mana_get_strings(struct net_device *ndev, u32 stringset, u8 *data) { struct mana_port_context *apc = netdev_priv(ndev); - unsigned int num_queues = apc->num_queues; - int i, j; - if (stringset != ETH_SS_STATS) - return; - for (i = 0; i < ARRAY_SIZE(mana_eth_stats); i++) - ethtool_puts(&data, mana_eth_stats[i].name); - - for (i = 0; i < ARRAY_SIZE(mana_hc_stats); i++) - ethtool_puts(&data, mana_hc_stats[i].name); - - for (i = 0; i < ARRAY_SIZE(mana_phy_stats); i++) - ethtool_puts(&data, mana_phy_stats[i].name); - - for (i = 0; i < num_queues; i++) { - ethtool_sprintf(&data, "rx_%d_packets", i); - ethtool_sprintf(&data, "rx_%d_bytes", i); - ethtool_sprintf(&data, "rx_%d_xdp_drop", i); - ethtool_sprintf(&data, "rx_%d_xdp_tx", i); - ethtool_sprintf(&data, "rx_%d_xdp_redirect", i); - ethtool_sprintf(&data, "rx_%d_pkt_len0_err", i); - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) - ethtool_sprintf(&data, "rx_%d_coalesced_cqe_%d", i, j + 2); - } - - for (i = 0; i < num_queues; i++) { - ethtool_sprintf(&data, "tx_%d_packets", i); - ethtool_sprintf(&data, "tx_%d_bytes", i); - ethtool_sprintf(&data, "tx_%d_xdp_xmit", i); - ethtool_sprintf(&data, "tx_%d_tso_packets", i); - ethtool_sprintf(&data, "tx_%d_tso_bytes", i); - ethtool_sprintf(&data, "tx_%d_tso_inner_packets", i); - ethtool_sprintf(&data, "tx_%d_tso_inner_bytes", i); - ethtool_sprintf(&data, "tx_%d_long_pkt_fmt", i); - ethtool_sprintf(&data, "tx_%d_short_pkt_fmt", i); - ethtool_sprintf(&data, "tx_%d_csum_partial", i); - ethtool_sprintf(&data, "tx_%d_mana_map_err", i); + switch (stringset) { + case ETH_SS_STATS: + mana_get_strings_stats(apc, &data); + break; + case ETH_SS_PRIV_FLAGS: + mana_get_strings_priv_flags(&data); + break; + default: + break; } } @@ -205,7 +241,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, u64 xdp_drop; u64 xdp_tx; u64 pkt_len0_err; - u64 coalesced_cqe[MANA_RXCOMP_OOB_NUM_PPI - 1]; + u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1]; u64 tso_packets; u64 tso_bytes; u64 tso_inner_packets; @@ -245,7 +281,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, xdp_tx = rx_stats->xdp_tx; xdp_redirect = rx_stats->xdp_redirect; pkt_len0_err = rx_stats->pkt_len0_err; - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) coalesced_cqe[j] = rx_stats->coalesced_cqe[j]; } while (u64_stats_fetch_retry(&rx_stats->syncp, start)); @@ -255,7 +291,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, data[i++] = xdp_tx; data[i++] = xdp_redirect; data[i++] = pkt_len0_err; - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) data[i++] = coalesced_cqe[j]; } @@ -408,6 +444,7 @@ static int mana_get_coalesce(struct net_device *ndev, struct mana_port_context *apc = netdev_priv(ndev); kernel_coal->rx_cqe_frames = + apc->cqe8_coalescing_enable ? MANA_CQE_COAL_PKTS_8 : apc->cqe_coalescing_enable ? MANA_RXCOMP_OOB_NUM_PPI : 1; kernel_coal->rx_cqe_nsecs = apc->cqe_coalescing_timeout_ns; @@ -419,6 +456,15 @@ static int mana_get_coalesce(struct net_device *ndev, !kernel_coal->rx_cqe_nsecs) kernel_coal->rx_cqe_nsecs = MANA_RX_CQE_NSEC_DEF; + ec->rx_coalesce_usecs = apc->intr_modr_rx_usec; + ec->rx_max_coalesced_frames = apc->intr_modr_rx_comp; + + ec->tx_coalesce_usecs = apc->intr_modr_tx_usec; + ec->tx_max_coalesced_frames = apc->intr_modr_tx_comp; + + ec->use_adaptive_rx_coalesce = apc->rx_dim_enabled; + ec->use_adaptive_tx_coalesce = apc->tx_dim_enabled; + return 0; } @@ -428,30 +474,178 @@ static int mana_set_coalesce(struct net_device *ndev, struct netlink_ext_ack *extack) { struct mana_port_context *apc = netdev_priv(ndev); - u8 saved_cqe_coalescing_enable; + struct { + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + u8 cqe_coalescing_enable; + u8 cqe8_coalescing_enable; + bool rx_dim_enabled; + bool tx_dim_enabled; + } saved; + bool modr_changed = false; + bool dim_changed = false; + struct gdma_context *gc; + u32 max_cqe_frames; int err; + gc = apc->ac->gdma_dev->gdma_context; + max_cqe_frames = gc->cqe8_coalescing_sup ? MANA_CQE_COAL_PKTS_8 : + MANA_RXCOMP_OOB_NUM_PPI; + + /* Both static and dynamic interrupt moderation (DIM) rely on the + * same HW capability advertised by the PF. + */ + if ((ec->use_adaptive_rx_coalesce || ec->use_adaptive_tx_coalesce || + ec->rx_coalesce_usecs || ec->tx_coalesce_usecs || + ec->rx_max_coalesced_frames || ec->tx_max_coalesced_frames) && + !(gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)) { + NL_SET_ERR_MSG(extack, + "Interrupt Moderation is not supported by HW"); + return -EOPNOTSUPP; + } + if (kernel_coal->rx_cqe_frames != 1 && - kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI) { + kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI && + kernel_coal->rx_cqe_frames != max_cqe_frames) { NL_SET_ERR_MSG_FMT(extack, - "rx-frames must be 1 or %u, got %u", + "rx-frames must be 1 or %u%s, got %u", MANA_RXCOMP_OOB_NUM_PPI, + gc->cqe8_coalescing_sup ? " or 8" : "", kernel_coal->rx_cqe_frames); return -EINVAL; } - saved_cqe_coalescing_enable = apc->cqe_coalescing_enable; + if (ec->rx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX || + ec->tx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce usecs must be <= %lu", + MANA_INTR_MODR_USEC_MAX); + return -EINVAL; + } + + if (ec->rx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX || + ec->tx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce frames must be <= %lu", + MANA_INTR_MODR_COMP_MAX); + return -EINVAL; + } + + if (ec->rx_coalesce_usecs != apc->intr_modr_rx_usec || + ec->rx_max_coalesced_frames != apc->intr_modr_rx_comp || + ec->tx_coalesce_usecs != apc->intr_modr_tx_usec || + ec->tx_max_coalesced_frames != apc->intr_modr_tx_comp) + modr_changed = true; + + saved.intr_modr_rx_usec = apc->intr_modr_rx_usec; + saved.intr_modr_rx_comp = apc->intr_modr_rx_comp; + saved.intr_modr_tx_usec = apc->intr_modr_tx_usec; + saved.intr_modr_tx_comp = apc->intr_modr_tx_comp; + + apc->intr_modr_rx_usec = ec->rx_coalesce_usecs; + apc->intr_modr_rx_comp = ec->rx_max_coalesced_frames; + apc->intr_modr_tx_usec = ec->tx_coalesce_usecs; + apc->intr_modr_tx_comp = ec->tx_max_coalesced_frames; + + if (!!ec->use_adaptive_rx_coalesce != apc->rx_dim_enabled || + !!ec->use_adaptive_tx_coalesce != apc->tx_dim_enabled) + dim_changed = true; + + saved.rx_dim_enabled = apc->rx_dim_enabled; + saved.tx_dim_enabled = apc->tx_dim_enabled; + + saved.cqe_coalescing_enable = apc->cqe_coalescing_enable; + saved.cqe8_coalescing_enable = apc->cqe8_coalescing_enable; apc->cqe_coalescing_enable = - kernel_coal->rx_cqe_frames == MANA_RXCOMP_OOB_NUM_PPI; + kernel_coal->rx_cqe_frames >= MANA_RXCOMP_OOB_NUM_PPI; + apc->cqe8_coalescing_enable = + kernel_coal->rx_cqe_frames == MANA_CQE_COAL_PKTS_8; - if (!apc->port_is_up) + if (!apc->port_is_up) { + WRITE_ONCE(apc->rx_dim_enabled, !!ec->use_adaptive_rx_coalesce); + WRITE_ONCE(apc->tx_dim_enabled, !!ec->use_adaptive_tx_coalesce); return 0; + } - err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); - if (err) - apc->cqe_coalescing_enable = saved_cqe_coalescing_enable; + if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable || + apc->cqe8_coalescing_enable != saved.cqe8_coalescing_enable) { + /* CQE coalescing setting is applied via RSS configuration. */ + err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); + if (err) { + netdev_err(ndev, "Change CQE coalescing failed: %d\n", + err); + apc->cqe_coalescing_enable = + saved.cqe_coalescing_enable; + apc->cqe8_coalescing_enable = + saved.cqe8_coalescing_enable; + apc->intr_modr_rx_usec = saved.intr_modr_rx_usec; + apc->intr_modr_rx_comp = saved.intr_modr_rx_comp; + apc->intr_modr_tx_usec = saved.intr_modr_tx_usec; + apc->intr_modr_tx_comp = saved.intr_modr_tx_comp; + return err; + } + } - return err; + if (modr_changed || dim_changed) { + bool new_rx_dim = !!ec->use_adaptive_rx_coalesce; + bool new_tx_dim = !!ec->use_adaptive_tx_coalesce; + bool disable_rx_dim = saved.rx_dim_enabled && !new_rx_dim; + bool disable_tx_dim = saved.tx_dim_enabled && !new_tx_dim; + bool enable_rx_dim = !saved.rx_dim_enabled && new_rx_dim; + bool enable_tx_dim = !saved.tx_dim_enabled && new_tx_dim; + int q; + + /* On disable: clear the per-port flag first and + * synchronize_net() so any in-flight NAPI poll observes + * the new value and will not schedule further DIM work; + * then drain pending work and restore the static + * moderation values. + */ + if (disable_rx_dim) + WRITE_ONCE(apc->rx_dim_enabled, false); + if (disable_tx_dim) + WRITE_ONCE(apc->tx_dim_enabled, false); + if (disable_rx_dim || disable_tx_dim) + synchronize_net(); + + for (q = 0; q < apc->num_queues; q++) { + struct mana_cq *rx_cq = &apc->rxqs[q]->rx_cq; + struct mana_cq *tx_cq = &apc->tx_qp[q]->tx_cq; + + if (disable_rx_dim) + mana_dim_change(rx_cq, false); + else if (enable_rx_dim) + mana_dim_change(rx_cq, true); + else if (!new_rx_dim && modr_changed) + mana_gd_ring_dim(rx_cq->gdma_cq, + apc->intr_modr_rx_usec, true, + apc->intr_modr_rx_comp, true); + + if (disable_tx_dim) + mana_dim_change(tx_cq, false); + else if (enable_tx_dim) + mana_dim_change(tx_cq, true); + else if (!new_tx_dim && modr_changed) + mana_gd_ring_dim(tx_cq->gdma_cq, + apc->intr_modr_tx_usec, true, + apc->intr_modr_tx_comp, true); + } + + /* Publish the enable flag with release semantics so a + * concurrent NAPI poll that observes it set also sees the DIM + * (re)init done by mana_dim_change() above. + */ + if (enable_rx_dim) + /* pairs with smp_load_acquire() in mana_update_rx_dim() */ + smp_store_release(&apc->rx_dim_enabled, true); + if (enable_tx_dim) + /* pairs with smp_load_acquire() in mana_update_tx_dim() */ + smp_store_release(&apc->tx_dim_enabled, true); + } + + return 0; } /* mana_set_channels - change the number of queues on a port @@ -594,10 +788,89 @@ static int mana_get_link_ksettings(struct net_device *ndev, return 0; } +static u32 mana_get_priv_flags(struct net_device *ndev) +{ + struct mana_port_context *apc = netdev_priv(ndev); + + return apc->priv_flags; +} + +static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) +{ + struct mana_port_context *apc = netdev_priv(ndev); + u32 changed = apc->priv_flags ^ priv_flags; + u32 old_priv_flags = apc->priv_flags; + int err = 0; + + if (!changed) + return 0; + + /* Reject unknown bits */ + if (priv_flags & ~GENMASK(MANA_PRIV_FLAG_MAX - 1, 0)) + return -EINVAL; + + apc->priv_flags = priv_flags; + + if (changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) { + 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 (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || + mana_xdp_get(apc)) + return 0; + + /* Block RDMA from grabbing the vport during detach/attach */ + mutex_lock(&apc->vport_mutex); + apc->channel_changing = true; + mutex_unlock(&apc->vport_mutex); + + err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues); + if (err) { + netdev_err(ndev, + "Insufficient memory for new allocations\n"); + apc->priv_flags = old_priv_flags; + goto clear_flag; + } + + err = mana_detach(ndev, false); + if (err) { + netdev_err(ndev, "mana_detach failed: %d\n", err); + apc->priv_flags = old_priv_flags; + goto out; + } + + err = mana_attach(ndev); + if (err) { + netdev_err(ndev, "mana_attach failed: %d\n", err); + apc->priv_flags = old_priv_flags; + } + } + +out: + mana_pre_dealloc_rxbufs(apc); +clear_flag: + mutex_lock(&apc->vport_mutex); + apc->channel_changing = false; + mutex_unlock(&apc->vport_mutex); + + return err; +} + const struct ethtool_ops mana_ethtool_ops = { - .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES, + .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES | + ETHTOOL_COALESCE_RX_USECS | + ETHTOOL_COALESCE_RX_MAX_FRAMES | + ETHTOOL_COALESCE_TX_USECS | + ETHTOOL_COALESCE_TX_MAX_FRAMES | + ETHTOOL_COALESCE_USE_ADAPTIVE_RX | + ETHTOOL_COALESCE_USE_ADAPTIVE_TX, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_SPFLAGS | ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_ethtool_stats = mana_get_ethtool_stats, .get_sset_count = mana_get_sset_count, @@ -615,4 +888,6 @@ const struct ethtool_ops mana_ethtool_ops = { .set_ringparam = mana_set_ringparam, .get_link_ksettings = mana_get_link_ksettings, .get_link = ethtool_op_get_link, + .get_priv_flags = mana_get_priv_flags, + .set_priv_flags = mana_set_priv_flags, }; diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 0727d53345a3..417c15d1ff42 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -12,46 +12,6 @@ #include #include -/* OPEN Alliance TC6 registers */ -/* Standard Capabilities Register */ -#define OA_TC6_REG_STDCAP 0x0002 -#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) - -/* Reset Control and Status Register */ -#define OA_TC6_REG_RESET 0x0003 -#define RESET_SWRESET BIT(0) /* Software Reset */ - -/* Configuration Register #0 */ -#define OA_TC6_REG_CONFIG0 0x0004 -#define CONFIG0_SYNC BIT(15) -#define CONFIG0_ZARFE_ENABLE BIT(12) - -/* Status Register #0 */ -#define OA_TC6_REG_STATUS0 0x0008 -#define STATUS0_RESETC BIT(6) /* Reset Complete */ -#define STATUS0_HEADER_ERROR BIT(5) -#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4) -#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) -#define STATUS0_TX_PROTOCOL_ERROR BIT(0) - -/* Buffer Status Register */ -#define OA_TC6_REG_BUFFER_STATUS 0x000B -#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) -#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) - -/* Interrupt Mask Register #0 */ -#define OA_TC6_REG_INT_MASK0 0x000C -#define INT_MASK0_HEADER_ERR_MASK BIT(5) -#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) -#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) -#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) -#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ - GENMASK(12, 7)) - -/* PHY Clause 22 registers base address and mask */ -#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 -#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F - /* Control command header */ #define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31) #define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29) @@ -81,23 +41,17 @@ #define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8) #define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1) -/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the - * OPEN Alliance specification. - */ -#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ -#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ -#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ -#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ -#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ - +#define OA_TC6_CTRL_PROT_REPLY_SIZE 4 #define OA_TC6_CTRL_HEADER_SIZE 4 #define OA_TC6_CTRL_REG_VALUE_SIZE 4 #define OA_TC6_CTRL_IGNORED_SIZE 4 #define OA_TC6_CTRL_MAX_REGISTERS 128 -#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ - (OA_TC6_CTRL_MAX_REGISTERS *\ - OA_TC6_CTRL_REG_VALUE_SIZE) +\ - OA_TC6_CTRL_IGNORED_SIZE) +#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ + (OA_TC6_CTRL_MAX_REGISTERS *\ + (OA_TC6_CTRL_REG_VALUE_SIZE +\ + OA_TC6_CTRL_PROT_REPLY_SIZE)) +\ + OA_TC6_CTRL_IGNORED_SIZE) + #define OA_TC6_CHUNK_PAYLOAD_SIZE 64 #define OA_TC6_DATA_HEADER_SIZE 4 #define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\ @@ -108,6 +62,8 @@ #define STATUS0_RESETC_POLL_DELAY 1000 #define STATUS0_RESETC_POLL_TIMEOUT 1000000 +#define OA_TC6_REG_MMS_MASK GENMASK(19, 16) + /* Internal structure for MAC-PHY drivers */ struct oa_tc6 { struct net_device *netdev; @@ -130,6 +86,8 @@ struct oa_tc6 { bool rx_buf_overflow; bool int_flag; bool disable_traffic; + bool prot_ctrl; + enum oa_tc6_quirk_flag quirk_flags; }; enum oa_tc6_header_type { @@ -213,25 +171,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[], { __be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { *tx_buf++ = cpu_to_be32(value[i]); + if (tc6->prot_ctrl) + *tx_buf++ = cpu_to_be32(~value[i]); + } } -static u16 oa_tc6_calculate_ctrl_buf_size(u8 length) +static u16 oa_tc6_calculate_ctrl_buf_size(u8 length, bool ctrl_prot) { + u32 reply_size = OA_TC6_CTRL_REG_VALUE_SIZE; + + if (ctrl_prot) + reply_size += OA_TC6_CTRL_PROT_REPLY_SIZE; + /* Control command consists 4 bytes header + 4 bytes register value for - * each register + 4 bytes ignored value. + * each register (+ 4 bytes for the register value complement in case + * protected mode is used) + 4 bytes ignored value. */ - return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length + + return OA_TC6_CTRL_HEADER_SIZE + reply_size * length + OA_TC6_CTRL_IGNORED_SIZE; } static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length, - enum oa_tc6_register_op reg_op) + enum oa_tc6_register_op reg_op, + u16 buf_size) { __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + memset(tx_buf, 0, buf_size); *tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op); if (reg_op == OA_TC6_CTRL_REG_WRITE) @@ -254,10 +223,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size) return 0; } -static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) +static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 length) { - u32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; - u32 *tx_buf = tc6->spi_ctrl_tx_buf; + __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; + __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + u32 complement; + u32 reply; /* The echoed control read header must match with the one that was * transmitted. @@ -265,6 +236,20 @@ static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) if (*tx_buf != *rx_buf) return -EPROTO; + if (tc6->prot_ctrl) { + /* Skip past the echoed header to the value/complement pairs */ + rx_buf += 1; + for (int i = 0; i < length; i++) { + reply = be32_to_cpu(rx_buf[0]); + complement = be32_to_cpu(rx_buf[1]); + + if (complement != ~reply) + return -EPROTO; + + rx_buf += 2; + } + } + return 0; } @@ -274,8 +259,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *tc6, u32 value[], __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { value[i] = be32_to_cpu(*rx_buf++); + + /* skip complement word */ + if (tc6->prot_ctrl) + rx_buf++; + } } static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], @@ -284,10 +274,10 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], u16 size; int ret; - /* Prepare control command and copy to SPI control buffer */ - oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op); + size = oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl); - size = oa_tc6_calculate_ctrl_buf_size(length); + /* Prepare control command and copy to SPI control buffer */ + oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op, size); /* Perform SPI transfer */ ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size); @@ -302,7 +292,7 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], return oa_tc6_check_ctrl_write_reply(tc6, size); /* Check echoed/received control read command reply for errors */ - ret = oa_tc6_check_ctrl_read_reply(tc6, size); + ret = oa_tc6_check_ctrl_read_reply(tc6, length); if (ret) return ret; @@ -355,6 +345,27 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value) } EXPORT_SYMBOL_GPL(oa_tc6_read_register); +/** + * oa_tc6_read_register_mms - function for reading a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be read. + * @value: value read from the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_read_registers(tc6, mms_reg, value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms); + /** * oa_tc6_write_registers - function for writing multiple consecutive registers. * @tc6: oa_tc6 struct. @@ -399,6 +410,27 @@ int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value) } EXPORT_SYMBOL_GPL(oa_tc6_write_register); +/** + * oa_tc6_write_register_mms - function for writing a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be written. + * @value: value to be written in the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_write_registers(tc6, mms_reg, &value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms); + static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) { u32 regval; @@ -408,7 +440,7 @@ static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) if (ret) return ret; - if (!(regval & STDCAP_DIRECT_PHY_REG_ACCESS)) + if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS)) return -ENODEV; return 0; @@ -462,36 +494,39 @@ static int oa_tc6_get_phy_c45_mms(int devnum) } } -static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, - int regnum) +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum) { struct oa_tc6 *tc6 = bus->priv; u32 regval; + int mms; int ret; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val); + ret = oa_tc6_read_register_mms(tc6, mms, regnum, ®val); if (ret) return ret; return regval; } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_read_c45); -static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, - int regnum, u16 val) +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val) { struct oa_tc6 *tc6 = bus->priv; - int ret; + int mms; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - return oa_tc6_write_register(tc6, (ret << 16) | regnum, val); + return oa_tc6_write_register_mms(tc6, mms, regnum, val); } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45); static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) { @@ -544,6 +579,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) { int ret; + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return 0; + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6); if (ret) { netdev_err(tc6->netdev, @@ -580,6 +618,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) static void oa_tc6_phy_exit(struct oa_tc6 *tc6) { + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return; + phy_disconnect(tc6->phydev); oa_tc6_mdiobus_unregister(tc6); } @@ -601,7 +642,7 @@ static int oa_tc6_read_status0(struct oa_tc6 *tc6) static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) { - u32 regval = RESET_SWRESET; + u32 regval = OA_TC6_RESET_SWRESET; int ret; ret = oa_tc6_write_register(tc6, OA_TC6_REG_RESET, regval); @@ -610,7 +651,7 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) /* Poll for soft reset complete for every 1ms until 1s timeout */ ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval, - regval & STATUS0_RESETC, + regval & OA_TC6_STATUS0_RESETC, STATUS0_RESETC_POLL_DELAY, STATUS0_RESETC_POLL_TIMEOUT); if (ret) @@ -629,10 +670,10 @@ static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6) if (ret) return ret; - regval &= ~(INT_MASK0_TX_PROTOCOL_ERR_MASK | - INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | - INT_MASK0_LOSS_OF_FRAME_ERR_MASK | - INT_MASK0_HEADER_ERR_MASK); + regval &= ~(OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK | + OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | + OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK | + OA_TC6_INT_MASK0_HEADER_ERR_MASK); return oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); } @@ -647,7 +688,7 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6) return ret; /* Enable configuration synchronization for data transfer */ - value |= CONFIG0_SYNC; + value |= OA_TC6_CONFIG0_SYNC; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value); } @@ -692,7 +733,7 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) */ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) { - u32 regval = INT_MASK0_ALL_INTERRUPTS; + u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS; tc6->disable_traffic = true; oa_tc6_free_pending_skbs(tc6); @@ -722,25 +763,25 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } - if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { tc6->rx_buf_overflow = true; oa_tc6_cleanup_ongoing_rx_skb(tc6); net_err_ratelimited("%s: Receive buffer overflow error\n", tc6->netdev->name); return -EAGAIN; } - if (FIELD_GET(STATUS0_TX_PROTOCOL_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_TX_PROTOCOL_ERROR, value)) { netdev_err(tc6->netdev, "Transmit protocol error\n"); return -ENODEV; } /* TODO: Currently loss of frame and header errors are treated as * non-recoverable errors. They will be handled in the next version. */ - if (FIELD_GET(STATUS0_LOSS_OF_FRAME_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR, value)) { netdev_err(tc6->netdev, "Loss of frame error\n"); return -ENODEV; } - if (FIELD_GET(STATUS0_HEADER_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_HEADER_ERROR, value)) { netdev_err(tc6->netdev, "Header error\n"); return -ENODEV; } @@ -1187,9 +1228,10 @@ static int oa_tc6_update_buffer_status_from_register(struct oa_tc6 *tc6) if (ret) return ret; - tc6->tx_credits = FIELD_GET(BUFFER_STATUS_TX_CREDITS_AVAILABLE, value); - tc6->rx_chunks_available = FIELD_GET(BUFFER_STATUS_RX_CHUNKS_AVAILABLE, - value); + tc6->tx_credits = FIELD_GET(OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE, + value); + tc6->rx_chunks_available = + FIELD_GET(OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE, value); return 0; } @@ -1233,7 +1275,7 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6) return ret; /* Set Zero-Align Receive Frame Enable */ - regval |= CONFIG0_ZARFE_ENABLE; + regval |= OA_TC6_CONFIG0_ZARFE_ENABLE; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval); } @@ -1272,15 +1314,31 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) } EXPORT_SYMBOL_GPL(oa_tc6_start_xmit); +static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) +{ + u32 regval; + int ret; + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val); + if (ret) + return ret; + + tc6->prot_ctrl = FIELD_GET(OA_TC6_CONFIG0_PROTE, regval); + + return 0; +} + /** * oa_tc6_init - allocates and initializes oa_tc6 structure. * @spi: device with which data will be exchanged. * @netdev: network device interface structure. + * @quirks: device specific modifiers for the OA TC6 protocol. * * Return: pointer reference to the oa_tc6 structure if the MAC-PHY * initialization is successful otherwise NULL. */ -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks) { struct oa_tc6 *tc6; int ret; @@ -1295,6 +1353,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) mutex_init(&tc6->spi_ctrl_lock); spin_lock_init(&tc6->tx_skb_lock); + if (quirks) + tc6->quirk_flags = quirks->quirk_flags; + /* Set the SPI controller to pump at realtime priority */ tc6->spi->rt = true; if (spi_setup(tc6->spi) < 0) @@ -1324,6 +1385,14 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) if (!tc6->spi_data_rx_buf) return NULL; + /* Check the PROTE bit status so that we can reset the device */ + ret = oa_tc6_check_ctrl_protection(tc6); + if (ret) { + dev_err(&tc6->spi->dev, + "Failed to check the protection mode: %d\n", ret); + return NULL; + } + ret = oa_tc6_sw_reset_macphy(tc6); if (ret) { dev_err(&tc6->spi->dev, diff --git a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c index 48b94ce77490..88c5c52e0e38 100644 --- a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c +++ b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #define PCH_GBE_MAR_ENTRIES 16 #define PCH_GBE_SHORT_PKT 64 diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.c b/drivers/net/ethernet/pensando/ionic/ionic_lif.c index fd3ee9820531..abc8e3530435 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_lif.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.c @@ -920,8 +920,21 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq) }; int err; - q->partner = &lif->txqcqs[q->index]->q; - q->partner->partner = q; + q->partner = NULL; + + /* Only normal RX queues have matching TX queue partners. */ + if (q->index < lif->nxqs) { + if (!lif->txqcqs || + q->index >= lif->ionic->ntxqs_per_lif || + !lif->txqcqs[q->index]) { + dev_err(dev, "missing TX queue partner for RX queue %u\n", + q->index); + return -ENXIO; + } + + q->partner = &lif->txqcqs[q->index]->q; + q->partner->partner = q; + } if (!lif->xdp_prog || (lif->xdp_prog->aux && lif->xdp_prog->aux->xdp_has_frags)) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c index 6e6f3ed07271..10501be9ef95 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_main.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c @@ -269,6 +269,8 @@ bool ionic_notifyq_service(struct ionic_cq *cq) if ((s64)(eid - lif->last_eid) <= 0) return false; + dma_rmb(); + lif->last_eid = eid; dev_dbg(lif->ionic->dev, "notifyq event:\n"); @@ -314,6 +316,8 @@ bool ionic_adminq_service(struct ionic_cq *cq) if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; diff --git a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c index 528114877677..c999754afb5f 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c @@ -558,18 +558,15 @@ struct sync_item { void ionic_rx_filter_sync(struct ionic_lif *lif) { struct device *dev = lif->ionic->dev; - struct list_head sync_add_list; - struct list_head sync_del_list; struct sync_item *sync_item; struct ionic_rx_filter *f; + LIST_HEAD(sync_add_list); + LIST_HEAD(sync_del_list); struct hlist_head *head; struct hlist_node *tmp; struct sync_item *spos; unsigned int i; - INIT_LIST_HEAD(&sync_add_list); - INIT_LIST_HEAD(&sync_del_list); - clear_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); /* Copy the filters to be added and deleted diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 301ebee2fdc5..e436e3231e86 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -545,13 +545,18 @@ static bool ionic_run_xdp(struct ionic_rx_stats *stats, break; case XDP_TX: + txq = rxq->partner; + if (unlikely(!txq)) { + err = -EIO; + break; + } + xdpf = xdp_convert_buff_to_frame(&xdp_buf); if (!xdpf) { err = -ENOSPC; break; } - txq = rxq->partner; nq = netdev_get_tx_queue(netdev, txq->index); __netif_tx_lock(nq, smp_processor_id()); txq_trans_cond_update(nq); @@ -701,11 +706,7 @@ static void ionic_rx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_rxq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -729,11 +730,18 @@ static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog) struct ionic_queue *q = cq->bound_q; struct ionic_rxq_comp *comp; - comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->pkt_type_color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; @@ -1180,7 +1188,6 @@ static void ionic_tx_clean(struct ionic_queue *q, bool in_napi) { struct ionic_tx_stats *stats = q_to_tx_stats(q); - struct ionic_qcq *qcq = q_to_qcq(q); struct sk_buff *skb; if (desc_info->xdpf) { @@ -1205,11 +1212,7 @@ static void ionic_tx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_txq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -1244,11 +1247,18 @@ static bool ionic_tx_service(struct ionic_cq *cq, unsigned int pkts = 0; u16 index; - comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* clean the related q entries, there could be * several q entries completed for each cq completion */ diff --git a/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c b/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c index fff8dc84212d..e96a268067f2 100644 --- a/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c +++ b/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c @@ -2478,7 +2478,6 @@ static int netxen_parse_md_template(struct netxen_adapter *adapter) static int netxen_collect_minidump(struct netxen_adapter *adapter) { - int ret = 0; struct netxen_minidump_template_hdr *hdr; hdr = (struct netxen_minidump_template_hdr *) adapter->mdump.md_template; @@ -2486,11 +2485,7 @@ netxen_collect_minidump(struct netxen_adapter *adapter) hdr->driver_timestamp = ktime_get_seconds(); hdr->driver_info_word2 = adapter->fw_version; hdr->driver_info_word3 = NXRD32(adapter, CRB_DRIVER_VERSION); - ret = netxen_parse_md_template(adapter); - if (ret) - return ret; - - return ret; + return netxen_parse_md_template(adapter); } diff --git a/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c b/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c index 5ee2bd9d6886..67d9bf69f8f2 100644 --- a/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c +++ b/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c @@ -3447,13 +3447,24 @@ static struct pci_driver netxen_driver = { static int __init netxen_init_module(void) { + int ret; + printk(KERN_INFO "%s\n", netxen_nic_driver_string); #ifdef CONFIG_INET register_netdevice_notifier(&netxen_netdev_cb); register_inetaddr_notifier(&netxen_inetaddr_cb); #endif - return pci_register_driver(&netxen_driver); + + ret = pci_register_driver(&netxen_driver); +#ifdef CONFIG_INET + if (ret) { + unregister_inetaddr_notifier(&netxen_inetaddr_cb); + unregister_netdevice_notifier(&netxen_netdev_cb); + } +#endif + + return ret; } module_init(netxen_init_module); diff --git a/drivers/net/ethernet/qlogic/qed/qed_dcbx.c b/drivers/net/ethernet/qlogic/qed/qed_dcbx.c index 3a5c25026858..19c2b870feed 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_dcbx.c +++ b/drivers/net/ethernet/qlogic/qed/qed_dcbx.c @@ -639,7 +639,7 @@ qed_dcbx_get_operational_params(struct qed_hwfn *p_hwfn, flags = p_hwfn->p_dcbx_info->operational.flags; /* If DCBx version is non zero, then negotiation - * was successfuly performed + * was successfully performed */ p_operational = ¶ms->operational; enabled = !!(QED_MFW_GET_FIELD(flags, DCBX_CONFIG_VERSION) != diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c index 45ed8705c7ca..47cd9ec665ee 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c @@ -1618,11 +1618,7 @@ static int qlcnic_83xx_check_hw_status(struct qlcnic_adapter *p_dev) if (err) return err; - err = qlcnic_83xx_check_heartbeat(p_dev); - if (err) - return err; - - return err; + return qlcnic_83xx_check_heartbeat(p_dev); } static int qlcnic_83xx_poll_reg(struct qlcnic_adapter *p_dev, u32 addr, diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c index 537fd26da904..761ef3bc8193 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c @@ -29,7 +29,7 @@ #define QLCNIC_FLAGS_VLAN_OOB 0x40 #define qlcnic_set_tx_vlan_tci(cmd_desc, v) \ - (cmd_desc)->vlan_TCI = cpu_to_le16(v); + (cmd_desc)->vlan_TCI = cpu_to_le16(v) #define qlcnic_set_cmd_desc_port(cmd_desc, var) \ ((cmd_desc)->port_ctxid |= ((var) & 0x0F)) #define qlcnic_set_cmd_desc_ctxid(cmd_desc, var) \ diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c index be747510d947..3c301e609d3e 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c @@ -106,7 +106,6 @@ static const struct regmap_config regmap_config_ipq9574 = { .rd_table = &ppe_reg_table, .wr_table = &ppe_reg_table, .max_register = 0xbef800, - .fast_io = true, }; static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev) diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c index e9a0e22907a6..125b73be92b1 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c @@ -1367,7 +1367,8 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, * * Return: 0 on success, negative error code on failure. */ -int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_map) +int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, + const u32 *queue_map) { u32 reg, queue_bitmap_val[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT]; @@ -1380,7 +1381,7 @@ int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_m } static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id, - const struct ppe_bm_port_config port_cfg) + struct ppe_bm_port_config port_cfg) { u32 reg, val, bm_fc_val[2]; int ret; @@ -1586,7 +1587,7 @@ static int ppe_config_qm(struct ppe_device *ppe_dev) } static int ppe_node_scheduler_config(struct ppe_device *ppe_dev, - const struct ppe_scheduler_port_config config) + struct ppe_scheduler_port_config config) { struct ppe_scheduler_cfg sch_cfg; int ret, i; diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h index 4bb45ca40144..60493e51e0a4 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h @@ -313,5 +313,5 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, struct ppe_rss_hash_cfg hash_cfg); int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, - u32 *queue_map); + const u32 *queue_map); #endif diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c index 78d4df55740a..bed6f63facf2 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c @@ -423,7 +423,8 @@ struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id) { struct rmnet_endpoint *ep; - hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) { + hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode, + lockdep_rtnl_is_held()) { if (ep->mux_id == mux_id) return ep; } diff --git a/drivers/net/ethernet/realtek/8139cp.c b/drivers/net/ethernet/realtek/8139cp.c index 5652da8a178c..9016527e229a 100644 --- a/drivers/net/ethernet/realtek/8139cp.c +++ b/drivers/net/ethernet/realtek/8139cp.c @@ -2066,7 +2066,7 @@ static int __maybe_unused cp_suspend(struct device *device) /* Disable Rx and Tx */ cpw16 (IntrMask, 0); - cpw8 (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn)); + cpw8 (Cmd, cpr8 (Cmd) & ~(RxOn | TxOn)); spin_unlock_irqrestore (&cp->lock, flags); diff --git a/drivers/net/ethernet/realtek/r8169.h b/drivers/net/ethernet/realtek/r8169.h index 0b9c1d4eb48b..fb772cc04310 100644 --- a/drivers/net/ethernet/realtek/r8169.h +++ b/drivers/net/ethernet/realtek/r8169.h @@ -74,7 +74,7 @@ enum mac_version { RTL_GIGA_MAC_VER_80, RTL_GIGA_MAC_NONE, RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1, - RTL_GIGA_MAC_VER_EXTENDED + RTL_GIGA_MAC_VER_EXTENDED = RTL_GIGA_MAC_NONE + 1 }; struct rtl8169_private; diff --git a/drivers/net/ethernet/realtek/r8169_leds.c b/drivers/net/ethernet/realtek/r8169_leds.c index 1999e81f0bca..5a2067be3095 100644 --- a/drivers/net/ethernet/realtek/r8169_leds.c +++ b/drivers/net/ethernet/realtek/r8169_leds.c @@ -31,6 +31,7 @@ struct r8169_led_classdev { struct led_classdev led; struct net_device *ndev; int index; + char name[LED_MAX_NAME_SIZE]; }; #define lcdev_to_r8169_ldev(lcdev) container_of(lcdev, struct r8169_led_classdev, led) @@ -131,13 +132,12 @@ static void rtl8168_setup_ldev(struct r8169_led_classdev *ldev, { struct rtl8169_private *tp = netdev_priv(ndev); struct led_classdev *led_cdev = &ldev->led; - char led_name[LED_MAX_NAME_SIZE]; ldev->ndev = ndev; ldev->index = index; - r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE); - led_cdev->name = led_name; + r8169_get_led_name(tp, index, ldev->name, sizeof(ldev->name)); + led_cdev->name = ldev->name; led_cdev->hw_control_trigger = "netdev"; led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; led_cdev->hw_control_is_supported = rtl8168_led_hw_control_is_supported; @@ -230,13 +230,12 @@ static void rtl8125_setup_led_ldev(struct r8169_led_classdev *ldev, { struct rtl8169_private *tp = netdev_priv(ndev); struct led_classdev *led_cdev = &ldev->led; - char led_name[LED_MAX_NAME_SIZE]; ldev->ndev = ndev; ldev->index = index; - r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE); - led_cdev->name = led_name; + r8169_get_led_name(tp, index, ldev->name, sizeof(ldev->name)); + led_cdev->name = ldev->name; led_cdev->hw_control_trigger = "netdev"; led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; led_cdev->hw_control_is_supported = rtl8125_led_hw_control_is_supported; diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index 5e56ec9b1013..3ee4c6108189 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -1028,6 +1028,7 @@ struct ravb_ptp_perout { struct ravb_ptp { struct ptp_clock *clock; struct ptp_clock_info info; + int phc_index; u32 default_addend; u32 current_addend; int extts[N_EXT_TS]; @@ -1123,6 +1124,8 @@ struct ravb_private { int msg_enable; int speed; int emac_irq; + int err_irq; + int mgmt_irq; unsigned no_avb_link:1; unsigned avb_link_active_low:1; diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index 5f88733094d0..ea1c7e536791 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -1779,7 +1779,7 @@ static int ravb_get_ts_info(struct net_device *ndev, (1 << HWTSTAMP_FILTER_NONE) | (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | (1 << HWTSTAMP_FILTER_ALL); - info->phc_index = ptp_clock_index(priv->ptp.clock); + info->phc_index = READ_ONCE(priv->ptp.phc_index); } return 0; @@ -2885,11 +2885,13 @@ static int ravb_setup_irqs(struct ravb_private *priv) return error; if (info->err_mgmt_irqs) { - error = ravb_setup_irq(priv, "err_a", "err_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "err_a", "err_a", &priv->err_irq, + ravb_multi_interrupt); if (error) return error; - error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", &priv->mgmt_irq, + ravb_multi_interrupt); if (error) return error; } @@ -2953,6 +2955,7 @@ static int ravb_probe(struct platform_device *pdev) priv->rstc = rstc; priv->ndev = ndev; priv->pdev = pdev; + priv->ptp.phc_index = -1; priv->num_tx_ring[RAVB_BE] = BE_TX_RING_SIZE; priv->num_rx_ring[RAVB_BE] = BE_RX_RING_SIZE; if (info->nc_queues) { diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c index 226c6c0ab945..43218bc15b15 100644 --- a/drivers/net/ethernet/renesas/ravb_ptp.c +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -289,16 +289,17 @@ static const struct ptp_clock_info ravb_ptp_info = { void ravb_ptp_interrupt(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock = READ_ONCE(priv->ptp.clock); u32 gis = ravb_read(ndev, GIS); gis &= ravb_read(ndev, GIC); - if (gis & GIS_PTCF) { + if ((gis & GIS_PTCF) && clock) { struct ptp_clock_event event; event.type = PTP_CLOCK_EXTTS; event.index = 0; event.timestamp = ravb_read(ndev, GCPT); - ptp_clock_event(priv->ptp.clock, &event); + ptp_clock_event(clock, &event); } if (gis & GIS_PTMF) { struct ravb_ptp_perout *perout = priv->ptp.perout; @@ -315,6 +316,7 @@ void ravb_ptp_interrupt(struct net_device *ndev) void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock; unsigned long flags; priv->ptp.info = ravb_ptp_info; @@ -327,15 +329,45 @@ void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) ravb_modify(ndev, GCCR, GCCR_TCSS, GCCR_TCSS_ADJGPTP); spin_unlock_irqrestore(&priv->lock, flags); - priv->ptp.clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + if (IS_ERR(clock)) { + netdev_err(ndev, "failed to register PTP clock: %pe\n", clock); + clock = NULL; + } + + WRITE_ONCE(priv->ptp.clock, clock); + if (clock) + WRITE_ONCE(priv->ptp.phc_index, ptp_clock_index(clock)); +} + +static void ravb_ptp_disable(struct net_device *ndev) +{ + ravb_write(ndev, 0, GIC); + ravb_write(ndev, 0, GIS); +} + +static void ravb_ptp_sync_irqs(struct net_device *ndev) +{ + struct ravb_private *priv = netdev_priv(ndev); + + synchronize_irq(ndev->irq); + if (priv->info->err_mgmt_irqs) { + synchronize_irq(priv->err_irq); + synchronize_irq(priv->mgmt_irq); + } } void ravb_ptp_stop(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock; - ravb_write(ndev, 0, GIC); - ravb_write(ndev, 0, GIS); + WRITE_ONCE(priv->ptp.phc_index, -1); + clock = xchg(&priv->ptp.clock, NULL); - ptp_clock_unregister(priv->ptp.clock); + ravb_ptp_disable(ndev); + ravb_ptp_sync_irqs(ndev); + + if (clock) + ptp_clock_unregister(clock); } diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c index ee8381b60b8d..f7beeb73eb16 100644 --- a/drivers/net/ethernet/renesas/rtsn.c +++ b/drivers/net/ethernet/renesas/rtsn.c @@ -685,7 +685,6 @@ static void rtsn_set_rate(struct rtsn_private *priv) static int rtsn_rmac_init(struct rtsn_private *priv) { const u8 *mac_addr = priv->ndev->dev_addr; - int ret; /* Set MAC address */ rtsn_write(priv, MRMAC0, (mac_addr[0] << 8) | mac_addr[1]); @@ -702,11 +701,7 @@ static int rtsn_rmac_init(struct rtsn_private *priv) /* Link verification */ rtsn_modify(priv, MLVC, MLVC_PLV, MLVC_PLV); - ret = rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); - if (ret) - return ret; - - return ret; + return rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); } static int rtsn_hw_init(struct rtsn_private *priv) diff --git a/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c b/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c index 2eccc7617507..e4701b29e1a0 100644 --- a/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c +++ b/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c @@ -82,7 +82,6 @@ static int sxgbe_platform_probe(struct platform_device *pdev) void __iomem *addr; struct sxgbe_priv_data *priv = NULL; struct sxgbe_plat_data *plat_dat = NULL; - struct net_device *ndev = platform_get_drvdata(pdev); struct device_node *node = dev->of_node; /* Get memory resource */ @@ -158,7 +157,7 @@ static int sxgbe_platform_probe(struct platform_device *pdev) irq_dispose_mapping(priv->txq[i]->irq_no); irq_dispose_mapping(priv->irq); err_drv_remove: - sxgbe_drv_remove(ndev); + sxgbe_drv_remove(priv->dev); err_out: return -ENODEV; } diff --git a/drivers/net/ethernet/sfc/Kconfig b/drivers/net/ethernet/sfc/Kconfig index c4c43434f314..979f2801e2a8 100644 --- a/drivers/net/ethernet/sfc/Kconfig +++ b/drivers/net/ethernet/sfc/Kconfig @@ -66,6 +66,15 @@ config SFC_MCDI_LOGGING Driver-Interface) commands and responses, allowing debugging of driver/firmware interaction. The tracing is actually enabled by a sysfs file 'mcdi_logging' under the PCI device. +config SFC_CXL + bool "Solarflare SFC9100-family CXL support" + depends on SFC && CXL_BUS >= SFC + default SFC + help + This enables SFC CXL support if the kernel is configuring CXL for + using CTPIO with CXL.mem. The SFC device with CXL support and + with a CXL-aware firmware can be used for minimizing latencies + when sending through CTPIO. source "drivers/net/ethernet/sfc/falcon/Kconfig" source "drivers/net/ethernet/sfc/siena/Kconfig" diff --git a/drivers/net/ethernet/sfc/Makefile b/drivers/net/ethernet/sfc/Makefile index d99039ec468d..bb0f1891cde6 100644 --- a/drivers/net/ethernet/sfc/Makefile +++ b/drivers/net/ethernet/sfc/Makefile @@ -13,6 +13,7 @@ sfc-$(CONFIG_SFC_SRIOV) += sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \ mae.o tc.o tc_bindings.o tc_counters.o \ tc_encap_actions.o tc_conntrack.o +sfc-$(CONFIG_SFC_CXL) += efx_cxl.o obj-$(CONFIG_SFC) += sfc.o obj-$(CONFIG_SFC_FALCON) += falcon/ diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c index 7e04f115bbaa..73bc064929f6 100644 --- a/drivers/net/ethernet/sfc/ef10.c +++ b/drivers/net/ethernet/sfc/ef10.c @@ -24,6 +24,7 @@ #include #include #include +#include "efx_cxl.h" /* Hardware control for EF10 architecture including 'Huntington'. */ @@ -106,7 +107,7 @@ static int efx_ef10_get_vf_index(struct efx_nic *efx) static int efx_ef10_init_datapath_caps(struct efx_nic *efx) { - MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V4_OUT_LEN); + MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V7_OUT_LEN); struct efx_ef10_nic_data *nic_data = efx->nic_data; size_t outlen; int rc; @@ -177,6 +178,12 @@ static int efx_ef10_init_datapath_caps(struct efx_nic *efx) efx->num_mac_stats); } + if (outlen < MC_CMD_GET_CAPABILITIES_V7_OUT_LEN) + nic_data->datapath_caps3 = 0; + else + nic_data->datapath_caps3 = MCDI_DWORD(outbuf, + GET_CAPABILITIES_V7_OUT_FLAGS3); + return 0; } @@ -1140,6 +1147,9 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) unsigned int channel_vis, pio_write_vi_base, max_vis; struct efx_ef10_nic_data *nic_data = efx->nic_data; unsigned int uc_mem_map_size, wc_mem_map_size; +#ifdef CONFIG_SFC_CXL + struct efx_probe_data *probe_data; +#endif void __iomem *membase; int rc; @@ -1263,8 +1273,23 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) iounmap(efx->membase); efx->membase = membase; - /* Set up the WC mapping if needed */ - if (wc_mem_map_size) { + if (!wc_mem_map_size) + goto skip_pio; + + /* Set up the WC mapping */ + +#ifdef CONFIG_SFC_CXL + probe_data = container_of(efx, struct efx_probe_data, efx); + if ((nic_data->datapath_caps3 & + (1 << MC_CMD_GET_CAPABILITIES_V7_OUT_CXL_CONFIG_ENABLE_LBN)) && + probe_data->cxl_pio_initialised) { + /* Using PIO through CXL mapping */ + nic_data->pio_write_base = probe_data->cxl->ctpio_cxl; + nic_data->pio_write_vi_base = pio_write_vi_base; + } else +#endif + { + /* Using legacy PIO BAR mapping */ nic_data->wc_membase = ioremap_wc(efx->membase_phys + uc_mem_map_size, wc_mem_map_size); @@ -1279,12 +1304,14 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) nic_data->wc_membase + (pio_write_vi_base * efx->vi_stride + ER_DZ_TX_PIOBUF - uc_mem_map_size); - - rc = efx_ef10_link_piobufs(efx); - if (rc) - efx_ef10_free_piobufs(efx); } + rc = efx_ef10_link_piobufs(efx); + if (rc) + efx_ef10_free_piobufs(efx); + +skip_pio: + netif_dbg(efx, probe, efx->net_dev, "memory BAR at %pa (virtual %p+%x UC, %p+%x WC)\n", &efx->membase_phys, efx->membase, uc_mem_map_size, diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 8f136a11d396..3806cd3dd7f4 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -34,6 +34,7 @@ #include "selftest.h" #include "sriov.h" #include "efx_devlink.h" +#include "efx_cxl.h" #include "mcdi_port_common.h" #include "mcdi_pcol.h" @@ -981,12 +982,15 @@ static void efx_pci_remove(struct pci_dev *pci_dev) efx_pci_remove_main(efx); efx_fini_io(efx); + + probe_data = container_of(efx, struct efx_probe_data, efx); + efx_cxl_exit(probe_data); + pci_dbg(efx->pci_dev, "shutdown successful\n"); efx_fini_devlink_and_unlock(efx); efx_fini_struct(efx); free_netdev(efx->net_dev); - probe_data = container_of(efx, struct efx_probe_data, efx); kfree(probe_data); }; @@ -1190,6 +1194,17 @@ static int efx_pci_probe(struct pci_dev *pci_dev, if (rc) goto fail2; + /* A successful cxl initialization implies a CXL region created to be + * used for PIO buffers. If there is no CXL support legacy PIO buffers + * defined at specific PCI BAR regions will be used. If there is CXL + * support and the cxl initialization fails, the driver probe fails. + */ + rc = efx_cxl_init(probe_data); + if (rc) { + pci_err(pci_dev, "CXL initialization failed with error %d\n", rc); + goto fail3; + } + rc = efx_pci_probe_post_io(efx); if (rc) { /* On failure, retry once immediately. @@ -1228,6 +1243,7 @@ static int efx_pci_probe(struct pci_dev *pci_dev, return 0; fail3: + efx_cxl_exit(probe_data); efx_fini_io(efx); fail2: efx_fini_struct(efx); diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c new file mode 100644 index 000000000000..348d7404cd7a --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + * + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + */ + +#include + +#include +#include +#include "net_driver.h" +#include "efx_cxl.h" + +#define EFX_CTPIO_BUFFER_SIZE SZ_256M + +int efx_cxl_init(struct efx_probe_data *probe_data) +{ + struct efx_nic *efx = &probe_data->efx; + struct pci_dev *pci_dev = efx->pci_dev; + struct range cxl_pio_range; + struct efx_cxl *cxl; + u16 dvsec; + int rc; + + /* Is the device configured with and using CXL? */ + if (!pcie_is_cxl(pci_dev)) + return 0; + + dvsec = pci_find_dvsec_capability(pci_dev, PCI_VENDOR_ID_CXL, + PCI_DVSEC_CXL_DEVICE); + if (!dvsec) { + pci_info(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability not found\n"); + return 0; + } + + pci_dbg(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability found\n"); + + /* Create a cxl_dev_state embedded in the cxl struct using cxl core api + * specifying no mbox available. + */ + cxl = devm_cxl_dev_state_create(&pci_dev->dev, CXL_DEVTYPE_DEVMEM, + pci_get_dsn(pci_dev), dvsec, + struct efx_cxl, cxlds, false); + + if (!cxl) + return -ENOMEM; + + rc = cxl_pci_setup_regs(pci_dev, CXL_REGLOC_RBI_COMPONENT, + &cxl->cxlds.reg_map); + if (rc) { + pci_err(pci_dev, "No component registers\n"); + return rc; + } + + if (!cxl->cxlds.reg_map.component_map.hdm_decoder.valid) { + pci_err(pci_dev, "Expected HDM component register not found\n"); + return -ENODEV; + } + + if (!cxl->cxlds.reg_map.component_map.ras.valid) { + pci_err(pci_dev, "Expected RAS component register not found\n"); + return -ENODEV; + } + + /* Set media ready explicitly as there are neither mailbox for checking + * this state nor the CXL register involved, both not mandatory for + * type2. + */ + cxl->cxlds.media_ready = true; + + if (cxl_set_capacity(&cxl->cxlds, EFX_CTPIO_BUFFER_SIZE)) { + pci_err(pci_dev, "dpa capacity setup failed\n"); + return -ENODEV; + } + + cxl->cxlmd = devm_cxl_probe_mem(&cxl->cxlds, &cxl_pio_range); + if (IS_ERR(cxl->cxlmd)) { + pci_err(pci_dev, "CXL accel memdev creation failed\n"); + return PTR_ERR(cxl->cxlmd); + } + + cxl->ctpio_cxl = ioremap_wc(cxl_pio_range.start, + range_len(&cxl_pio_range)); + if (!cxl->ctpio_cxl) { + pci_err(pci_dev, "CXL ioremap region (%pra) failed\n", + &cxl_pio_range); + return -ENOMEM; + } + + probe_data->cxl_pio_initialised = true; + probe_data->cxl = cxl; + + return 0; +} + +void efx_cxl_exit(struct efx_probe_data *probe_data) +{ + if (!probe_data->cxl) + return; + + iounmap(probe_data->cxl->ctpio_cxl); +} + +MODULE_IMPORT_NS("CXL"); diff --git a/drivers/net/ethernet/sfc/efx_cxl.h b/drivers/net/ethernet/sfc/efx_cxl.h new file mode 100644 index 000000000000..3e2705cb063f --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/**************************************************************************** + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#ifndef EFX_CXL_H +#define EFX_CXL_H + +#ifdef CONFIG_SFC_CXL + +#include + +struct efx_probe_data; + +struct efx_cxl { + struct cxl_dev_state cxlds; + struct cxl_memdev *cxlmd; + void __iomem *ctpio_cxl; +}; + +int efx_cxl_init(struct efx_probe_data *probe_data); +void efx_cxl_exit(struct efx_probe_data *probe_data); +#else +static inline int efx_cxl_init(struct efx_probe_data *probe_data) { return 0; } +static inline void efx_cxl_exit(struct efx_probe_data *probe_data) {} +#endif +#endif diff --git a/drivers/net/ethernet/sfc/falcon/net_driver.h b/drivers/net/ethernet/sfc/falcon/net_driver.h index 7ab0db44720d..63016bbae115 100644 --- a/drivers/net/ethernet/sfc/falcon/net_driver.h +++ b/drivers/net/ethernet/sfc/falcon/net_driver.h @@ -1208,7 +1208,7 @@ ef4_channel_get_tx_queue(struct ef4_channel *channel, unsigned type) static inline bool ef4_tx_queue_used(struct ef4_tx_queue *tx_queue) { - return !(tx_queue->efx->net_dev->num_tc < 2 && + return !(netdev_get_num_tc(tx_queue->efx->net_dev) < 2 && tx_queue->queue & EF4_TXQ_TYPE_HIGHPRI); } diff --git a/drivers/net/ethernet/sfc/falcon/selftest.c b/drivers/net/ethernet/sfc/falcon/selftest.c index db4dd7fb77f5..4d29e0baf2eb 100644 --- a/drivers/net/ethernet/sfc/falcon/selftest.c +++ b/drivers/net/ethernet/sfc/falcon/selftest.c @@ -401,8 +401,8 @@ static void ef4_iterate_state(struct ef4_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct ef4_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct ef4_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c index 9e18aaf44bad..2103b6fdf968 100644 --- a/drivers/net/ethernet/sfc/falcon/tx.c +++ b/drivers/net/ethernet/sfc/falcon/tx.c @@ -435,15 +435,19 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } - if (num_tc > net_dev->num_tc) { + if (num_tc > netdev_get_num_tc(net_dev)) { /* Initialise high-priority queues as necessary */ ef4_for_each_channel(channel, efx) { ef4_for_each_possible_channel_tx_queue(tx_queue, @@ -462,7 +466,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, } } else { /* Reduce number of classes before number of queues */ - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); } rc = netif_set_real_num_tx_queues(net_dev, @@ -477,7 +481,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, * it to ef4_fini_channels(). */ - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); return 0; } diff --git a/drivers/net/ethernet/sfc/mcdi.c b/drivers/net/ethernet/sfc/mcdi.c index e65db9b70724..b806d3d90c42 100644 --- a/drivers/net/ethernet/sfc/mcdi.c +++ b/drivers/net/ethernet/sfc/mcdi.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "net_driver.h" #include "nic.h" #include "io.h" @@ -71,7 +72,7 @@ int efx_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = mcdi_logging_default; @@ -112,7 +113,7 @@ int efx_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -138,7 +139,7 @@ void efx_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h index b98c259f672d..3964b2c56609 100644 --- a/drivers/net/ethernet/sfc/net_driver.h +++ b/drivers/net/ethernet/sfc/net_driver.h @@ -1197,14 +1197,24 @@ struct efx_nic { atomic_t n_rx_noskb_drops; }; +#ifdef CONFIG_SFC_CXL +struct efx_cxl; +#endif + /** * struct efx_probe_data - State after hardware probe * @pci_dev: The PCI device * @efx: Efx NIC details + * @cxl: details of related cxl objects + * @cxl_pio_initialised: cxl initialization outcome. */ struct efx_probe_data { struct pci_dev *pci_dev; struct efx_nic efx; +#ifdef CONFIG_SFC_CXL + struct efx_cxl *cxl; + bool cxl_pio_initialised; +#endif }; static inline struct efx_nic *efx_netdev_priv(struct net_device *dev) diff --git a/drivers/net/ethernet/sfc/nic.h b/drivers/net/ethernet/sfc/nic.h index ec3b2df43b68..7480f9995dfb 100644 --- a/drivers/net/ethernet/sfc/nic.h +++ b/drivers/net/ethernet/sfc/nic.h @@ -152,6 +152,8 @@ enum { * %MC_CMD_GET_CAPABILITIES response) * @datapath_caps2: Further Capabilities of datapath firmware (FLAGS2 field of * %MC_CMD_GET_CAPABILITIES response) + * @datapath_caps3: Further Capabilities of datapath firmware (FLAGS3 field of + * %MC_CMD_GET_CAPABILITIES response) * @rx_dpcpu_fw_id: Firmware ID of the RxDPCPU * @tx_dpcpu_fw_id: Firmware ID of the TxDPCPU * @must_probe_vswitching: Flag: vswitching has yet to be setup after MC reboot @@ -187,6 +189,7 @@ struct efx_ef10_nic_data { bool must_check_datapath_caps; u32 datapath_caps; u32 datapath_caps2; + u32 datapath_caps3; unsigned int rx_dpcpu_fw_id; unsigned int tx_dpcpu_fw_id; bool must_probe_vswitching; diff --git a/drivers/net/ethernet/sfc/selftest.c b/drivers/net/ethernet/sfc/selftest.c index 8ec76329237a..dc716feb79cb 100644 --- a/drivers/net/ethernet/sfc/selftest.c +++ b/drivers/net/ethernet/sfc/selftest.c @@ -398,8 +398,8 @@ static void efx_iterate_state(struct efx_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct efx_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct efx_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/siena/mcdi.c b/drivers/net/ethernet/sfc/siena/mcdi.c index 4d0d6bd5d3d1..048c1e6017c0 100644 --- a/drivers/net/ethernet/sfc/siena/mcdi.c +++ b/drivers/net/ethernet/sfc/siena/mcdi.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "net_driver.h" #include "nic.h" #include "io.h" @@ -73,7 +74,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = efx_siena_mcdi_logging_default; @@ -116,7 +117,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -142,7 +143,7 @@ void efx_siena_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); diff --git a/drivers/net/ethernet/sfc/siena/selftest.c b/drivers/net/ethernet/sfc/siena/selftest.c index 930643612df5..c74cf5131364 100644 --- a/drivers/net/ethernet/sfc/siena/selftest.c +++ b/drivers/net/ethernet/sfc/siena/selftest.c @@ -399,8 +399,8 @@ static void efx_iterate_state(struct efx_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct efx_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct efx_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c index 91e87594ed1e..67c77d67d984 100644 --- a/drivers/net/ethernet/sfc/siena/tx.c +++ b/drivers/net/ethernet/sfc/siena/tx.c @@ -376,15 +376,19 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type, mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); return netif_set_real_num_tx_queues(net_dev, max_t(int, num_tc, 1) * diff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c index db222abef53b..c2ad3a358d20 100644 --- a/drivers/net/ethernet/sfc/tc_encap_actions.c +++ b/drivers/net/ethernet/sfc/tc_encap_actions.c @@ -311,7 +311,7 @@ static void efx_gen_tun_header_udp(struct efx_tc_encap_action *encap, u8 len) encap->encap_hdr_len += sizeof(*udp); udp->dest = key->tp_dst; - udp->len = cpu_to_be16(sizeof(*udp) + len); + udp_set_len_short(udp, sizeof(*udp) + len); } static void efx_gen_tun_header_vxlan(struct efx_tc_encap_action *encap) diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c index 39731069d99e..009f37105eaf 100644 --- a/drivers/net/ethernet/sgi/ioc3-eth.c +++ b/drivers/net/ethernet/sgi/ioc3-eth.c @@ -967,11 +967,12 @@ static void ioc3eth_remove(struct platform_device *pdev) struct net_device *dev = platform_get_drvdata(pdev); struct ioc3_private *ip = netdev_priv(dev); + unregister_netdev(dev); + timer_delete_sync(&ip->ioc3_timer); + dma_free_coherent(ip->dma_dev, RX_RING_SIZE, ip->rxr, ip->rxr_dma); dma_free_coherent(ip->dma_dev, TX_RING_SIZE + SZ_16K - 1, ip->tx_ring, ip->txr_dma); - unregister_netdev(dev); - timer_delete_sync(&ip->ioc3_timer); free_netdev(dev); } @@ -1061,9 +1062,9 @@ static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev) d1 = dma_map_single(ip->dma_dev, skb->data, s1, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d1)) goto drop_packet; - d2 = dma_map_single(ip->dma_dev, (void *)b2, s1, DMA_TO_DEVICE); + d2 = dma_map_single(ip->dma_dev, (void *)b2, s2, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d2)) { - dma_unmap_single(ip->dma_dev, d1, len, DMA_TO_DEVICE); + dma_unmap_single(ip->dma_dev, d1, s1, DMA_TO_DEVICE); goto drop_packet; } desc->p1 = cpu_to_be64(ioc3_map(d1, PCI64_ATTR_PREF)); diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index fc04a23342cf..66025e2509e9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -20,9 +20,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, unsigned int nopaged_len = skb_headlen(skb); struct stmmac_priv *priv = tx_q->priv_data; unsigned int entry = tx_q->cur_tx; - unsigned int bmax, buf_len, des2; + unsigned int bmax, buf_len; unsigned int i = 1, len; struct dma_desc *desc; + dma_addr_t des2; desc = tx_q->dma_tx + entry; @@ -36,7 +37,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data, buf_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; @@ -54,7 +55,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, (skb->data + bmax * i), bmax, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; @@ -67,7 +68,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, (skb->data + bmax * i), len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index 4ac979d874d6..eab8c13fbdcc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -28,11 +28,15 @@ /* * TX/RX Clock Delay Bit Masks: - * - TX Delay: bits [14:8] — TX_CLK delay (unit: 0.02ns per bit) - * - RX Delay: bits [30:24] — RX_CLK delay (unit: 0.02ns per bit) + * - TX Delay: bits [14:8] - TX_CLK delay (unit: 0.02ns per bit) + * - TX Invert : bit [15] + * - RX Delay: bits [30:24] - RX_CLK delay (unit: 0.02ns per bit) + * - RX Invert : bit [31] */ #define EIC7700_ETH_TX_ADJ_DELAY GENMASK(14, 8) #define EIC7700_ETH_RX_ADJ_DELAY GENMASK(30, 24) +#define EIC7700_ETH_TX_INV_DELAY BIT(15) +#define EIC7700_ETH_RX_INV_DELAY BIT(31) #define EIC7700_MAX_DELAY_STEPS 0x7F #define EIC7700_DELAY_STEP_PS 20 @@ -43,7 +47,14 @@ static const char * const eic7700_clk_names[] = { "tx", "axi", "cfg", }; +struct eic7700_dwmac_data { + bool rgmii_rx_clk_invert; + bool has_internal_tx_delay; + u32 tx_clk_inherent_skew_ps; +}; + struct eic7700_qos_priv { + struct device *dev; struct plat_stmmacenet_data *plat_dat; struct regmap *eic7700_hsp_regmap; u32 eth_axi_lp_ctrl_offset; @@ -54,6 +65,7 @@ struct eic7700_qos_priv { u32 eth_clk_dly_param; bool has_txd_offset; bool has_rxd_offset; + bool eth_rx_clk_inv; }; static int eic7700_clks_config(void *priv, bool enabled) @@ -97,9 +109,6 @@ static int eic7700_dwmac_init(struct device *dev, void *priv) if (dwc->has_rxd_offset) regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_rxd_offset, 0); - regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, - dwc->eth_clk_dly_param); - return 0; } @@ -126,8 +135,38 @@ static int eic7700_dwmac_resume(struct device *dev, void *priv) return ret; } +/* + * eth1 requires RX clock inversion at 1000Mbps due to silicon-inherent + * RX sampling skew at MAC input. + * + * The configuration is updated in fix_mac_speed() because the required + * sampling behavior depends on the negotiated link speed. + */ +static void eic7700_dwmac_fix_speed(void *priv, phy_interface_t interface, + int speed, unsigned int mode) +{ + struct eic7700_qos_priv *dwc = (struct eic7700_qos_priv *)priv; + u32 dly_param = dwc->eth_clk_dly_param; + + switch (speed) { + case SPEED_1000: + if (dwc->eth_rx_clk_inv) + dly_param |= EIC7700_ETH_RX_INV_DELAY; + break; + case SPEED_100: + case SPEED_10: + break; + default: + dev_warn(dwc->dev, "unsupported speed %u\n", speed); + return; + } + + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, dly_param); +} + static int eic7700_dwmac_probe(struct platform_device *pdev) { + const struct eic7700_dwmac_data *data; struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; struct eic7700_qos_priv *dwc_priv; @@ -148,6 +187,30 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) if (!dwc_priv) return -ENOMEM; + dwc_priv->dev = &pdev->dev; + + data = device_get_match_data(&pdev->dev); + if (!data) + return dev_err_probe(&pdev->dev, + -EINVAL, "no match data found\n"); + + dwc_priv->eth_rx_clk_inv = data->rgmii_rx_clk_invert; + /* + * The MAC silicon unconditionally adds ~2 ns TX delay; prevent + * the PHY from also adding TX delay to avoid doubling it. + * + * DT specifies rgmii-id (TX from MAC silicon, RX from PHY); + * override to rgmii-rxid so the PHY only adds its RX delay. + */ + if (data->has_internal_tx_delay) { + plat_dat->phy_interface = + phy_fix_phy_mode_for_mac_delays(plat_dat->phy_interface, + true, false); + if (plat_dat->phy_interface == PHY_INTERFACE_MODE_NA) + return dev_err_probe(&pdev->dev, -EINVAL, + "phy interface mode is NA\n"); + } + /* Read rx-internal-delay-ps and update rx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps", &delay_ps)) { @@ -165,12 +228,15 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val); - } else { - return dev_err_probe(&pdev->dev, -EINVAL, - "missing required property rx-internal-delay-ps\n"); } - /* Read tx-internal-delay-ps and update tx_clk delay */ + /* Read tx-internal-delay-ps and update tx_clk delay. + * + * For eswin,eic7700-qos-eth-clk-inversion, the DT property describes + * the effective TX delay at the MAC output, including the inherent + * silicon delay. Subtract the fixed component to obtain the + * programmable delay value. + */ if (!of_property_read_u32(pdev->dev.of_node, "tx-internal-delay-ps", &delay_ps)) { if (delay_ps % EIC7700_DELAY_STEP_PS) @@ -178,18 +244,22 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) "tx delay must be multiple of %dps\n", EIC7700_DELAY_STEP_PS); + if (delay_ps < data->tx_clk_inherent_skew_ps) + return dev_err_probe(&pdev->dev, -EINVAL, + "tx delay %ups below inherent skew %ups\n", + delay_ps, data->tx_clk_inherent_skew_ps); + + delay_ps -= data->tx_clk_inherent_skew_ps; + if (delay_ps > EIC7700_MAX_DELAY_PS) return dev_err_probe(&pdev->dev, -EINVAL, - "tx delay out of range\n"); + "tx delay out of programmable range\n"); val = delay_ps / EIC7700_DELAY_STEP_PS; dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val); - } else { - return dev_err_probe(&pdev->dev, -EINVAL, - "missing required property tx-internal-delay-ps\n"); } dwc_priv->eic7700_hsp_regmap = @@ -260,12 +330,31 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) plat_dat->exit = eic7700_dwmac_exit; plat_dat->suspend = eic7700_dwmac_suspend; plat_dat->resume = eic7700_dwmac_resume; + plat_dat->fix_mac_speed = eic7700_dwmac_fix_speed; return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res); } +static const struct eic7700_dwmac_data eic7700_dwmac_data = { + .rgmii_rx_clk_invert = false, + .has_internal_tx_delay = false, + .tx_clk_inherent_skew_ps = 0, +}; + +static const struct eic7700_dwmac_data eic7700_dwmac_data_clk_inversion = { + .rgmii_rx_clk_invert = true, + .has_internal_tx_delay = true, + .tx_clk_inherent_skew_ps = 2000, +}; + static const struct of_device_id eic7700_dwmac_match[] = { - { .compatible = "eswin,eic7700-qos-eth" }, + { .compatible = "eswin,eic7700-qos-eth", + .data = &eic7700_dwmac_data, + }, + { + .compatible = "eswin,eic7700-qos-eth-clk-inversion", + .data = &eic7700_dwmac_data_clk_inversion, + }, { } }; MODULE_DEVICE_TABLE(of, eic7700_dwmac_match); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c index 4d207f41a43b..f5f9fa67ecd7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c @@ -1348,6 +1348,7 @@ static int intel_eth_pci_probe(struct pci_dev *pdev, err_alloc_irq: clk_disable_unprepare(plat->stmmac_clk); clk_unregister_fixed_rate(plat->stmmac_clk); + pci_free_irq_vectors(pdev); return ret; } @@ -1367,6 +1368,7 @@ static void intel_eth_pci_remove(struct pci_dev *pdev) clk_disable_unprepare(priv->plat->stmmac_clk); clk_unregister_fixed_rate(priv->plat->stmmac_clk); + pci_free_irq_vectors(pdev); } #define PCI_DEVICE_ID_INTEL_QUARK 0x0937 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 61b6d45a02f5..f8ab347f7b5b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -320,6 +320,8 @@ /* DMA Registers */ #define XGMAC_DMA_MODE 0x00003000 #define XGMAC_SWR BIT(0) +#define XGMAC_INTM_MASK GENMASK(13, 12) +#define XGMAC_INTM_MODE1 0x1 #define XGMAC_DMA_SYSBUS_MODE 0x00003004 #define XGMAC_WR_OSR_LMT GENMASK(29, 24) #define XGMAC_RD_OSR_LMT GENMASK(21, 16) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 03437f1cf3df..ff83858ebc1f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -31,6 +31,16 @@ static void dwxgmac2_dma_init(void __iomem *ioaddr, value |= XGMAC_EAME; writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE); + + /* + * Route DMA interrupts to per-channel lines when multi-MSI enabled. + */ + if (dma_cfg->multi_msi_en) { + value = readl(ioaddr + XGMAC_DMA_MODE); + value = u32_replace_bits(value, XGMAC_INTM_MODE1, + XGMAC_INTM_MASK); + writel(value, ioaddr + XGMAC_DMA_MODE); + } } static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 511b0fd5e834..265671170bf6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -328,18 +328,6 @@ int stmmac_hwif_init(struct stmmac_priv *priv) /* Save ID for later use */ priv->synopsys_id = version.snpsver; - /* Lets assume some safe values first */ - if (core_type == DWMAC_CORE_GMAC4) { - priv->ptpaddr = priv->ioaddr + PTP_GMAC4_OFFSET; - priv->mmcaddr = priv->ioaddr + MMC_GMAC4_OFFSET; - priv->estaddr = priv->ioaddr + EST_GMAC4_OFFSET; - } else { - priv->ptpaddr = priv->ioaddr + PTP_GMAC3_X_OFFSET; - priv->mmcaddr = priv->ioaddr + MMC_GMAC3_X_OFFSET; - if (core_type == DWMAC_CORE_XGMAC) - priv->estaddr = priv->ioaddr + EST_XGMAC_OFFSET; - } - mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL); if (!mac) return -ENOMEM; diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index 78fc6aa5bbe9..f7949419eb9f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -20,8 +20,9 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, unsigned int nopaged_len = skb_headlen(skb); struct stmmac_priv *priv = tx_q->priv_data; unsigned int entry = tx_q->cur_tx; - unsigned int bmax, len, des2; + unsigned int bmax, len; struct dma_desc *desc; + dma_addr_t des2; if (priv->extend_desc) desc = (struct dma_desc *)(tx_q->dma_etx + entry); @@ -39,7 +40,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data, bmax, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; @@ -47,7 +48,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, tx_q->tx_skbuff_dma[entry].len = bmax; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum, STMMAC_RING_MODE, 0, false, skb->len); tx_q->tx_skbuff[entry] = NULL; @@ -60,27 +61,27 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data + bmax, len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; tx_q->tx_skbuff_dma[entry].len = len; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 0, len, csum, STMMAC_RING_MODE, 1, !skb_is_nonlinear(skb), skb->len); } else { des2 = dma_map_single(priv->device, skb->data, nopaged_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; tx_q->tx_skbuff_dma[entry].len = nopaged_len; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, csum, STMMAC_RING_MODE, 0, !skb_is_nonlinear(skb), skb->len); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 92585d27ab88..154cc0c7623d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -759,7 +759,10 @@ static u32 stmmac_usec2riwt(u32 usec, struct stmmac_priv *priv) return 0; } - return (usec * (clk / 1000000)) / 256; + /* Receive Interrupt Watchdog Timer (riwt) has a resolution of 256 + * ticks. + */ + return DIV_ROUND_CLOSEST(usec * (clk / USEC_PER_SEC), 256); } static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) @@ -772,7 +775,7 @@ static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) return 0; } - return (riwt * 256) / (clk / 1000000); + return DIV_ROUND_CLOSEST(riwt * 256, clk / USEC_PER_SEC); } static int __stmmac_get_coalesce(struct net_device *dev, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c index c54c70224351..c889204a7aa5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c @@ -217,8 +217,11 @@ int dwmac5_fpe_map_preemption_class(struct net_device *ndev, * and is direct one-to-one mapping." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); @@ -275,8 +278,11 @@ int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev, * any of the scheduling algorithms." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a71f0df26378..b2b7d0242dd3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1330,6 +1330,10 @@ static int stmmac_init_phy(struct net_device *dev) struct phy_device *phydev; if (addr < 0) { + /* If a custom PCS is in use, no PHY is needed */ + if (priv->hw->phylink_pcs) + return 0; + netdev_err(priv->dev, "no phy found\n"); return -ENODEV; } @@ -4622,6 +4626,8 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) set_ic = true; else if (!priv->tx_coal_frames[queue]) set_ic = false; + else if (!netdev_xmit_more()) + set_ic = true; else if (tx_packets > priv->tx_coal_frames[queue]) set_ic = true; else if ((tx_q->tx_count_frames % @@ -4906,6 +4912,8 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) set_ic = true; else if (!priv->tx_coal_frames[queue]) set_ic = false; + else if (!netdev_xmit_more()) + set_ic = true; else if (tx_packets > priv->tx_coal_frames[queue]) set_ic = true; else if ((tx_q->tx_count_frames % @@ -6367,28 +6375,17 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) * @rq: An IOCTL specific structure, that can contain a pointer to * a proprietary structure used to pass information to the driver. * @cmd: IOCTL command - * Description: - * Currently it supports the phy_mii_ioctl(...) and HW time stamping. + * Description: Forward the PHY ioctls to phylink + * Return: Zero on success or negative error code. */ static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct stmmac_priv *priv = netdev_priv (dev); - int ret = -EOPNOTSUPP; if (!netif_running(dev)) return -EINVAL; - switch (cmd) { - case SIOCGMIIPHY: - case SIOCGMIIREG: - case SIOCSMIIREG: - ret = phylink_mii_ioctl(priv->phylink, rq, cmd); - break; - default: - break; - } - - return ret; + return phylink_mii_ioctl(priv->phylink, rq, cmd); } static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index a0c75886587c..1df26c217f9a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -154,9 +153,9 @@ static struct sk_buff *stmmac_test_get_udp_skb(struct stmmac_priv *priv, } else { uhdr->source = htons(attr->sport); uhdr->dest = htons(attr->dport); - uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); + udp_set_len_short(uhdr, sizeof(*shdr) + sizeof(*uhdr) + attr->size); if (attr->max_size) - uhdr->len = htons(attr->max_size - + udp_set_len_short(uhdr, attr->max_size - (sizeof(*ihdr) + sizeof(*ehdr))); uhdr->check = 0; } @@ -374,25 +373,6 @@ static int stmmac_test_mac_loopback(struct stmmac_priv *priv) return __stmmac_test_loopback(priv, &attr); } -static int stmmac_test_phy_loopback(struct stmmac_priv *priv) -{ - struct stmmac_packet_attrs attr = { }; - int ret; - - if (!priv->dev->phydev) - return -EOPNOTSUPP; - - ret = phy_loopback(priv->dev->phydev, true, 0); - if (ret) - return ret; - - attr.dst = priv->dev->dev_addr; - ret = __stmmac_test_loopback(priv, &attr); - - phy_loopback(priv->dev->phydev, false, 0); - return ret; -} - static int stmmac_test_mmc(struct stmmac_priv *priv) { struct stmmac_counters initial, final; @@ -735,13 +715,13 @@ static int stmmac_test_flowctrl_validate(struct sk_buff *skb, static int stmmac_test_flowctrl(struct stmmac_priv *priv) { unsigned char paddr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x01}; - struct phy_device *phydev = priv->dev->phydev; u32 rx_cnt = priv->plat->rx_queues_to_use; + struct mac_device_info *mac = priv->hw; struct stmmac_test_priv *tpriv; unsigned int pkt_count; int i, ret = 0; - if (!phydev || (!phydev->pause && !phydev->asym_pause)) + if (!(mac->link.caps & MAC_SYM_PAUSE)) return -EOPNOTSUPP; tpriv = kzalloc_obj(*tpriv); @@ -1815,10 +1795,6 @@ static int stmmac_test_tbs(struct stmmac_priv *priv) return ret; } -#define STMMAC_LOOPBACK_NONE 0 -#define STMMAC_LOOPBACK_MAC 1 -#define STMMAC_LOOPBACK_PHY 2 - static const struct stmmac_test { char name[ETH_GSTRING_LEN]; int lb; @@ -1826,131 +1802,96 @@ static const struct stmmac_test { } stmmac_selftests[] = { { .name = "MAC Loopback ", - .lb = STMMAC_LOOPBACK_MAC, .fn = stmmac_test_mac_loopback, - }, { - .name = "PHY Loopback ", - .lb = STMMAC_LOOPBACK_NONE, /* Test will handle it */ - .fn = stmmac_test_phy_loopback, }, { .name = "MMC Counters ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mmc, }, { .name = "EEE ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_eee, }, { .name = "Hash Filter MC ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_hfilt, }, { .name = "Perfect Filter UC ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_pfilt, }, { .name = "MC Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mcfilt, }, { .name = "UC Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_ucfilt, }, { .name = "Flow Control ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_flowctrl, }, { .name = "RSS ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_rss, }, { .name = "VLAN Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanfilt, }, { .name = "VLAN Filtering (perf) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanfilt_perfect, }, { .name = "Double VLAN Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_dvlanfilt, }, { .name = "Double VLAN Filter (perf) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_dvlanfilt_perfect, }, { .name = "Flexible RX Parser ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_rxp, }, { .name = "SA Insertion (desc) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_desc_sai, }, { .name = "SA Replacement (desc) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_desc_sar, }, { .name = "SA Insertion (reg) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_reg_sai, }, { .name = "SA Replacement (reg) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_reg_sar, }, { .name = "VLAN TX Insertion ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanoff, }, { .name = "SVLAN TX Insertion ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_svlanoff, }, { .name = "L3 DA Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l3filt_da, }, { .name = "L3 SA Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l3filt_sa, }, { .name = "L4 DA TCP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_da_tcp, }, { .name = "L4 SA TCP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_sa_tcp, }, { .name = "L4 DA UDP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_da_udp, }, { .name = "L4 SA UDP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_sa_udp, }, { .name = "ARP Offload ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_arpoffload, }, { .name = "Jumbo Frame ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_jumbo, }, { .name = "Multichannel Jumbo ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mjumbo, }, { .name = "Split Header ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_sph, }, { .name = "TBS (ETF Scheduler) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_tbs, }, }; @@ -1978,57 +1919,21 @@ void stmmac_selftest_run(struct net_device *dev, /* Wait for queues drain */ msleep(200); + ret = stmmac_set_mac_loopback(priv, priv->ioaddr, true); + if (ret) { + netdev_err(priv->dev, "Loopback is not supported\n"); + etest->flags |= ETH_TEST_FL_FAILED; + return; + } + for (i = 0; i < count; i++) { - ret = 0; - - switch (stmmac_selftests[i].lb) { - case STMMAC_LOOPBACK_PHY: - ret = -EOPNOTSUPP; - if (dev->phydev) - ret = phy_loopback(dev->phydev, true, 0); - if (!ret) - break; - fallthrough; - case STMMAC_LOOPBACK_MAC: - ret = stmmac_set_mac_loopback(priv, priv->ioaddr, true); - break; - case STMMAC_LOOPBACK_NONE: - break; - default: - ret = -EOPNOTSUPP; - break; - } - - /* - * First tests will always be MAC / PHY loopback. If any of - * them is not supported we abort earlier. - */ - if (ret) { - netdev_err(priv->dev, "Loopback is not supported\n"); - etest->flags |= ETH_TEST_FL_FAILED; - break; - } - ret = stmmac_selftests[i].fn(priv); if (ret && (ret != -EOPNOTSUPP)) etest->flags |= ETH_TEST_FL_FAILED; buf[i] = ret; - - switch (stmmac_selftests[i].lb) { - case STMMAC_LOOPBACK_PHY: - ret = -EOPNOTSUPP; - if (dev->phydev) - ret = phy_loopback(dev->phydev, false, 0); - if (!ret) - break; - fallthrough; - case STMMAC_LOOPBACK_MAC: - stmmac_set_mac_loopback(priv, priv->ioaddr, false); - break; - default: - break; - } } + + stmmac_set_mac_loopback(priv, priv->ioaddr, false); } void stmmac_selftest_get_strings(struct stmmac_priv *priv, u8 *data) diff --git a/drivers/net/ethernet/sun/niu.c b/drivers/net/ethernet/sun/niu.c index 88df15e6dd74..54dd7281191d 100644 --- a/drivers/net/ethernet/sun/niu.c +++ b/drivers/net/ethernet/sun/niu.c @@ -6021,11 +6021,11 @@ static void niu_set_irq_name(struct niu *np) int port = np->port; int i, j = 1; - sprintf(np->irq_name[0], "%s:MAC", np->dev->name); + snprintf(np->irq_name[0], sizeof(np->irq_name[0]), "%s:MAC", np->dev->name); if (port == 0) { - sprintf(np->irq_name[1], "%s:MIF", np->dev->name); - sprintf(np->irq_name[2], "%s:SYSERR", np->dev->name); + snprintf(np->irq_name[1], sizeof(np->irq_name[1]), "%s:MIF", np->dev->name); + snprintf(np->irq_name[2], sizeof(np->irq_name[2]), "%s:SYSERR", np->dev->name); j = 3; } diff --git a/drivers/net/ethernet/sun/niu.h b/drivers/net/ethernet/sun/niu.h index d8368043fc3b..676d31499ac3 100644 --- a/drivers/net/ethernet/sun/niu.h +++ b/drivers/net/ethernet/sun/niu.h @@ -3262,7 +3262,7 @@ struct niu { #define NIU_FLAGS_XMAC 0x00010000 /* 0=BMAC 1=XMAC */ u32 msg_enable; - char irq_name[NIU_NUM_RXCHAN+NIU_NUM_TXCHAN+3][IFNAMSIZ + 6]; + char irq_name[NIU_NUM_RXCHAN + NIU_NUM_TXCHAN + 3][IFNAMSIZ + 7]; /* Protects hw programming, and ring state. */ spinlock_t lock; diff --git a/drivers/net/ethernet/ti/cpsw_priv.c b/drivers/net/ethernet/ti/cpsw_priv.c index 1f6f374551cb..0580a7885d33 100644 --- a/drivers/net/ethernet/ti/cpsw_priv.c +++ b/drivers/net/ethernet/ti/cpsw_priv.c @@ -949,7 +949,7 @@ static int cpsw_set_cbs(struct net_device *ndev, * limited first and for compliance with CPDMA rate limited channels * that also used in bacward order. FIFO0 cannot be rate limited. */ - fifo = cpsw_tc_to_fifo(tc, ndev->num_tc); + fifo = cpsw_tc_to_fifo(tc, netdev_get_num_tc(ndev)); if (!fifo) { dev_err(priv->dev, "Last tc%d can't be rate limited", tc); return -EINVAL; diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile index a71b0ad77de3..c8724bb129aa 100644 --- a/drivers/net/ethernet/wangxun/libwx/Makefile +++ b/drivers/net/ethernet/wangxun/libwx/Makefile @@ -4,5 +4,5 @@ obj-$(CONFIG_LIBWX) += libwx.o -libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o +libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o wx_err.o libwx-objs += wx_vf.o wx_vf_lib.o wx_vf_common.o diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c new file mode 100644 index 000000000000..b56fbdc959de --- /dev/null +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd. */ +/* Copyright (c) 1999 - 2026 Intel Corporation. */ + +#include +#include +#include + +#include "wx_type.h" +#include "wx_lib.h" +#include "wx_err.h" + +/** + * wx_io_error_detected - called when PCI error is detected + * @pdev: Pointer to PCI device + * @state: The current pci connection state + * + * Return: pci_ers_result_t. + * + * This function is called after a PCI bus error affecting + * this device has been detected. + */ +static pci_ers_result_t wx_io_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct wx *wx = pci_get_drvdata(pdev); + struct net_device *netdev; + + if (!wx) + return PCI_ERS_RESULT_DISCONNECT; + + netdev = wx->netdev; + if (!netif_device_present(netdev)) + return PCI_ERS_RESULT_DISCONNECT; + + rtnl_lock(); + netif_device_detach(netdev); + set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + wx_soft_quiesce(wx); + + if (state == pci_channel_io_perm_failure) { + rtnl_unlock(); + return PCI_ERS_RESULT_DISCONNECT; + } + + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); + rtnl_unlock(); + + /* Request a slot reset. */ + return PCI_ERS_RESULT_NEED_RESET; +} + +/** + * wx_io_slot_reset - called after the pci bus has been reset. + * @pdev: Pointer to PCI device + * + * Return: pci_ers_result_t. + * + * Restart the card from scratch, as if from a cold-boot. + */ +static pci_ers_result_t wx_io_slot_reset(struct pci_dev *pdev) +{ + struct wx *wx = pci_get_drvdata(pdev); + + if (pci_enable_device_mem(pdev)) { + wx_err(wx, "Cannot re-enable PCI device after reset.\n"); + return PCI_ERS_RESULT_DISCONNECT; + } + + /* make all memory operations done before clearing the flag */ + smp_mb__before_atomic(); + clear_bit(WX_STATE_DISABLED, wx->state); + clear_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + pci_set_master(pdev); + pci_restore_state(pdev); + pci_wake_from_d3(pdev, false); + + rtnl_lock(); + if (netif_running(wx->netdev) && wx->down_suspend) + wx->down_suspend(wx); + if (wx->do_reset) + wx->do_reset(wx->netdev, false); + rtnl_unlock(); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * wx_io_resume - called when traffic can start flowing again. + * @pdev: Pointer to PCI device + * + * This callback is called when the error recovery driver tells us that + * its OK to resume normal operation. + */ +static void wx_io_resume(struct pci_dev *pdev) +{ + struct wx *wx = pci_get_drvdata(pdev); + struct net_device *netdev; + int err; + + netdev = wx->netdev; + rtnl_lock(); + if (netif_running(netdev)) { + err = netdev->netdev_ops->ndo_open(netdev); + if (err) { + wx_err(wx, "Failed to open netdev after reset\n"); + goto out; + } + } + netif_device_attach(netdev); +out: + rtnl_unlock(); +} + +const struct pci_error_handlers wx_err_handler = { + .error_detected = wx_io_error_detected, + .slot_reset = wx_io_slot_reset, + .resume = wx_io_resume, +}; +EXPORT_SYMBOL(wx_err_handler); + +static bool wx_check_pcie_error(struct wx *wx) +{ + u16 vid, pci_cmd; + + pci_read_config_word(wx->pdev, PCI_VENDOR_ID, &vid); + pci_read_config_word(wx->pdev, PCI_COMMAND, &pci_cmd); + + /* PCIe link loss or memory space can't access */ + if (vid == U16_MAX || !(pci_cmd & PCI_COMMAND_MEMORY)) + return true; + + return false; +} + +static void wx_pf_reset_subtask(struct wx *wx) +{ + if (!test_and_clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags)) + return; + + wx_warn(wx, "Reset adapter.\n"); + if (wx->do_reset) + wx->do_reset(wx->netdev, true); +} + +static void wx_reset_task(struct work_struct *work) +{ + struct wx *wx = container_of(work, struct wx, reset_task); + + rtnl_lock(); + + /* If the device has been detached (e.g., due to AER error handling), + * abort the reset task to prevent operating on a dead or unmanaged + * hardware. + */ + if (!netif_device_present(wx->netdev)) + goto out; + + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) { + /* Double check: Verify if the PCIe error is still present. */ + if (wx_check_pcie_error(wx)) + wx_soft_quiesce(wx); + else + clear_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + goto out; + } + + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + goto out; + + wx_pf_reset_subtask(wx); + +out: + rtnl_unlock(); +} + +void wx_check_err_subtask(struct wx *wx) +{ + if (test_bit(WX_FLAG_NEED_DO_RESET, wx->flags)) + queue_work(wx->reset_wq, &wx->reset_task); +} +EXPORT_SYMBOL(wx_check_err_subtask); + +int wx_init_err_task(struct wx *wx) +{ + wx->reset_wq = alloc_workqueue("%s_reset_wq_%x", WQ_UNBOUND | WQ_HIGHPRI, + 1, wx->driver_name, pci_dev_id(wx->pdev)); + if (!wx->reset_wq) { + wx_err(wx, "Failed to create wx_reset_wq workqueue\n"); + return -ENOMEM; + } + + INIT_WORK(&wx->reset_task, wx_reset_task); + return 0; +} +EXPORT_SYMBOL(wx_init_err_task); + +static bool wx_ring_tx_pending(struct wx *wx) +{ + int i; + + for (i = 0; i < wx->num_tx_queues; i++) { + struct wx_ring *tx_ring = wx->tx_ring[i]; + + if (tx_ring->next_to_use != tx_ring->next_to_clean) + return true; + } + + return false; +} + +static bool wx_vf_tx_pending(struct wx *wx) +{ + struct wx_ring_feature *vmdq = &wx->ring_feature[RING_F_VMDQ]; + u32 q_per_pool = __ALIGN_MASK(1, ~vmdq->mask); + u32 i, j; + + if (!wx->num_vfs) + return false; + + for (i = 0; i < wx->num_vfs; i++) { + for (j = 0; j < q_per_pool; j++) { + u32 h, t; + + h = rd32(wx, WX_PX_TR_RP_PV(q_per_pool, i, j)); + t = rd32(wx, WX_PX_TR_WP_PV(q_per_pool, i, j)); + + if (h != t) + return true; + } + } + + return false; +} + +static void wx_watchdog_flush_tx(struct wx *wx) +{ + if (!netif_running(wx->netdev)) + return; + if (netif_carrier_ok(wx->netdev)) + return; + + if (wx_ring_tx_pending(wx) || wx_vf_tx_pending(wx)) { + /* We've lost link, so the controller stops DMA, + * but we've got queued Tx work that's never going + * to get done, so reset controller to flush Tx. + * (Do the reset outside of interrupt context). + */ + wx_warn(wx, "initiating reset due to lost link with pending Tx work\n"); + set_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + } +} + +static void wx_detect_tx_hang(struct wx *wx) +{ + int i; + + /* If we're down or resetting, just bail */ + if (!netif_running(wx->netdev) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + + /* Force detection of hung controller */ + if (netif_carrier_ok(wx->netdev)) { + for (i = 0; i < wx->num_tx_queues; i++) + set_bit(WX_TX_DETECT_HANG, wx->tx_ring[i]->state); + } +} + +void wx_check_hang_subtask(struct wx *wx) +{ + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + + wx_watchdog_flush_tx(wx); + wx_detect_tx_hang(wx); +} +EXPORT_SYMBOL(wx_check_hang_subtask); + +static void wx_tx_timeout_recovery(struct wx *wx) +{ + /* + * When a PCIe hardware error occurs, the driver should initiate a PCIe + * recovery mechanism. However, this recovery flow relies on the AER + * driver for current kernel policy. Therefore, a self-contained + * recovery mechanism is not implemented yet. + */ + set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + wx_err(wx, "PCIe error detected during tx timeout\n"); + queue_work(wx->reset_wq, &wx->reset_task); +} + +static void wx_tx_timeout_reset(struct wx *wx) +{ + if (test_bit(WX_STATE_DOWN, wx->state)) + return; + + set_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + wx_warn(wx, "initiating reset due to tx timeout\n"); + wx_service_event_schedule(wx); +} + +void wx_tx_timeout(struct net_device *netdev, unsigned int __always_unused txqueue) +{ + struct wx *wx = netdev_priv(netdev); + + if (wx_check_pcie_error(wx)) + wx_tx_timeout_recovery(wx); + else + wx_tx_timeout_reset(wx); +} +EXPORT_SYMBOL(wx_tx_timeout); + +void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next) +{ + struct wx *wx = netdev_priv(tx_ring->netdev); + + wx_warn(wx, + "Detected Tx Unit Hang: Queue %d, TDH %x, TDT %x, ntu %x, ntc %x, ntc.time_stamp %lx, jiffies %lx\n", + tx_ring->queue_index, + rd32(wx, WX_PX_TR_RP(tx_ring->reg_idx)), + rd32(wx, WX_PX_TR_WP(tx_ring->reg_idx)), + tx_ring->next_to_use, next, + tx_ring->tx_buffer_info[next].time_stamp, jiffies); + + netif_stop_subqueue(tx_ring->netdev, tx_ring->queue_index); + + wx_tx_timeout_reset(wx); +} diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.h b/drivers/net/ethernet/wangxun/libwx/wx_err.h new file mode 100644 index 000000000000..a6a82a263528 --- /dev/null +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * WangXun Gigabit PCI Express Linux driver + * Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd. + */ + +#ifndef _WX_ERR_H_ +#define _WX_ERR_H_ + +extern const struct pci_error_handlers wx_err_handler; + +void wx_check_err_subtask(struct wx *wx); +int wx_init_err_task(struct wx *wx); +void wx_check_hang_subtask(struct wx *wx); +void wx_tx_timeout(struct net_device *netdev, unsigned int txqueue); +void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next); + +#endif /* _WX_ERR_H_ */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c index 5df971aca9e3..940d2e59876c 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c @@ -9,6 +9,8 @@ #include "wx_ethtool.h" #include "wx_hw.h" #include "wx_lib.h" +#include "wx_vf_common.h" +#include "wx_vf_lib.h" struct wx_stats { char stat_string[ETH_GSTRING_LEN]; @@ -395,7 +397,7 @@ static void wx_update_rsc(struct wx *wx) /* reset the device to apply the new RSC setting */ if (need_reset && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); } int wx_set_coalesce(struct net_device *netdev, @@ -487,7 +489,10 @@ int wx_set_coalesce(struct net_device *netdev, else /* rx only or mixed */ q_vector->itr = rx_itr_param; - wx_write_eitr(q_vector); + if (wx->pdev->is_virtfn) + wx_write_eitr_vf(q_vector); + else + wx_write_eitr(q_vector); } wx_update_rsc(wx); @@ -775,6 +780,65 @@ static int wx_get_link_ksettings_vf(struct net_device *netdev, return 0; } +static int wx_set_ringparam_vf(struct net_device *netdev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct wx *wx = netdev_priv(netdev); + u32 new_rx_count, new_tx_count; + struct wx_ring *temp_ring; + int i, err = 0; + + new_tx_count = clamp_t(u32, ring->tx_pending, WX_MIN_TXD, WX_MAX_TXD); + new_tx_count = ALIGN(new_tx_count, WX_REQ_TX_DESCRIPTOR_MULTIPLE); + + new_rx_count = clamp_t(u32, ring->rx_pending, WX_MIN_RXD, WX_MAX_RXD); + new_rx_count = ALIGN(new_rx_count, WX_REQ_RX_DESCRIPTOR_MULTIPLE); + + if (new_tx_count == wx->tx_ring_count && + new_rx_count == wx->rx_ring_count) + return 0; + + mutex_lock(&wx->reset_lock); + set_bit(WX_STATE_RESETTING, wx->state); + + if (!netif_running(wx->netdev)) { + for (i = 0; i < wx->num_tx_queues; i++) + wx->tx_ring[i]->count = new_tx_count; + for (i = 0; i < wx->num_rx_queues; i++) + wx->rx_ring[i]->count = new_rx_count; + wx->tx_ring_count = new_tx_count; + wx->rx_ring_count = new_rx_count; + + goto clear_reset; + } + + /* allocate temporary buffer to store rings in */ + i = max_t(int, wx->num_tx_queues, wx->num_rx_queues); + temp_ring = kvmalloc_objs(struct wx_ring, i); + if (!temp_ring) { + err = -ENOMEM; + goto clear_reset; + } + + wxvf_down(wx); + /* wx_set_ring() may partially apply changes before + * returning an error. The error indicates that not all + * requested ring parameters could be configured. + */ + err = wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring); + if (err) + wx_err(wx, "failed to set ring parameters: %d", err); + wx_configure_vf(wx); + wxvf_up_complete(wx); + kvfree(temp_ring); +clear_reset: + clear_bit(WX_STATE_RESETTING, wx->state); + mutex_unlock(&wx->reset_lock); + return err; +} + static const struct ethtool_ops wx_ethtool_ops_vf = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ | @@ -782,8 +846,10 @@ static const struct ethtool_ops wx_ethtool_ops_vf = { .get_drvinfo = wx_get_drvinfo, .get_link = ethtool_op_get_link, .get_ringparam = wx_get_ringparam, + .set_ringparam = wx_set_ringparam_vf, .get_msglevel = wx_get_msglevel, .get_coalesce = wx_get_coalesce, + .set_coalesce = wx_set_coalesce, .get_ts_info = ethtool_op_get_ts_info, .get_link_ksettings = wx_get_link_ksettings_vf, }; diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c index 260e14d5d541..122c4952d203 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c @@ -1932,6 +1932,7 @@ static void wx_configure_tx_ring(struct wx *wx, else ring->atr_sample_rate = 0; + bitmap_zero(ring->state, WX_RING_STATE_NBITS); /* reinitialize tx_buffer_info */ memset(ring->tx_buffer_info, 0, sizeof(struct wx_tx_buffer) * ring->count); @@ -2851,16 +2852,26 @@ EXPORT_SYMBOL(wx_fc_enable); static void wx_update_xoff_rx_lfc(struct wx *wx) { struct wx_hw_stats *hwstats = &wx->stats; + u64 data; + int i; if (wx->fc.mode != wx_fc_full && wx->fc.mode != wx_fc_rx_pause) return; if (wx->mac.type >= wx_mac_aml) - hwstats->lxoffrxc += rd32_wrap(wx, WX_MAC_LXOFFRXC_AML, - &wx->last_stats.lxoffrxc); + data = rd32_wrap(wx, WX_MAC_LXOFFRXC_AML, + &wx->last_stats.lxoffrxc); else - hwstats->lxoffrxc += rd64(wx, WX_MAC_LXOFFRXC); + data = rd64(wx, WX_MAC_LXOFFRXC); + hwstats->lxoffrxc += data; + + /* refill credits (no tx hang) if we received xoff */ + if (!data) + return; + + for (i = 0; i < wx->num_tx_queues; i++) + clear_bit(WX_HANG_CHECK_ARMED, wx->tx_ring[i]->state); } /** diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 5d99e870de5e..ed5aad7857bd 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -14,6 +14,7 @@ #include "wx_type.h" #include "wx_lib.h" +#include "wx_err.h" #include "wx_ptp.h" #include "wx_hw.h" #include "wx_vf_lib.h" @@ -742,6 +743,37 @@ static struct netdev_queue *wx_txring_txq(const struct wx_ring *ring) return netdev_get_tx_queue(ring->netdev, ring->queue_index); } +static u32 wx_get_tx_pending(struct wx_ring *ring) +{ + unsigned int head, tail; + + head = ring->next_to_clean; + tail = ring->next_to_use; + + return ((head <= tail) ? tail : tail + ring->count) - head; +} + +static bool wx_check_tx_hang(struct wx_ring *ring) +{ + u32 tx_done_old = ring->tx_stats.tx_done_old; + u32 tx_pending = wx_get_tx_pending(ring); + u32 tx_done = ring->stats.packets; + + if (!test_and_clear_bit(WX_TX_DETECT_HANG, ring->state)) + return false; + + if (tx_done_old == tx_done && tx_pending) + /* make sure it is true for two checks in a row */ + return test_and_set_bit(WX_HANG_CHECK_ARMED, ring->state); + + /* update completed stats and continue */ + ring->tx_stats.tx_done_old = tx_done; + /* reset the countdown */ + clear_bit(WX_HANG_CHECK_ARMED, ring->state); + + return false; +} + /** * wx_clean_tx_irq - Reclaim resources after transmit completes * @q_vector: structure containing interrupt and ring information @@ -866,6 +898,11 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector, netdev_tx_completed_queue(wx_txring_txq(tx_ring), total_packets, total_bytes); + if (wx_check_tx_hang(tx_ring)) { + wx_handle_tx_hang(tx_ring, i); + return true; + } + #define TX_WAKE_THRESHOLD (DESC_NEEDED * 2) if (unlikely(total_packets && netif_carrier_ok(tx_ring->netdev) && (wx_desc_unused(tx_ring) >= TX_WAKE_THRESHOLD))) { @@ -3114,7 +3151,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) netdev->features = features; if (changed & NETIF_F_HW_VLAN_CTAG_RX && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER)) wx_set_rx_mode(netdev); @@ -3164,7 +3201,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) out: if (need_reset && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); return 0; } @@ -3230,6 +3267,30 @@ netdev_features_t wx_features_check(struct sk_buff *skb, netdev_features_t features) { struct wx *wx = netdev_priv(netdev); + __be16 type = skb->protocol; + u16 vlan_depth = ETH_HLEN; + u32 vlan_num = 0; + + if (skb_vlan_tag_present(skb)) + vlan_num++; + + while (eth_type_vlan(type)) { + struct vlan_hdr vhdr, *vh; + + vh = skb_header_pointer(skb, vlan_depth, sizeof(vhdr), &vhdr); + if (unlikely(!vh)) + break; + + type = vh->h_vlan_encapsulated_proto; + vlan_depth += VLAN_HLEN; + vlan_num++; + + if (vlan_num > 2) { + features &= ~(NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX); + break; + } + } if (!skb->encapsulation) return features; @@ -3251,8 +3312,8 @@ netdev_features_t wx_features_check(struct sk_buff *skb, } EXPORT_SYMBOL(wx_features_check); -void wx_set_ring(struct wx *wx, u32 new_tx_count, - u32 new_rx_count, struct wx_ring *temp_ring) +int wx_set_ring(struct wx *wx, u32 new_tx_count, + u32 new_rx_count, struct wx_ring *temp_ring) { int i, err = 0; @@ -3274,7 +3335,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, i--; wx_free_tx_resources(&temp_ring[i]); } - return; + return err; } } @@ -3302,7 +3363,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, i--; wx_free_rx_resources(&temp_ring[i]); } - return; + return err; } } @@ -3314,6 +3375,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, wx->rx_ring_count = new_rx_count; } + return 0; } EXPORT_SYMBOL(wx_set_ring); @@ -3348,5 +3410,23 @@ void wx_service_timer(struct timer_list *t) } EXPORT_SYMBOL(wx_service_timer); +void wx_soft_quiesce(struct wx *wx) +{ + if (!netif_running(wx->netdev) || + test_and_set_bit(WX_STATE_DOWN, wx->state)) + return; + + pci_clear_master(wx->pdev); + netif_tx_stop_all_queues(wx->netdev); + netif_carrier_off(wx->netdev); + netif_tx_disable(wx->netdev); + wx_napi_disable_all(wx); + wx_ptp_quiesce(wx); + + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + timer_delete_sync(&wx->service_timer); +} +EXPORT_SYMBOL(wx_soft_quiesce); + MODULE_DESCRIPTION("Common library for Wangxun(R) Ethernet drivers."); MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.h b/drivers/net/ethernet/wangxun/libwx/wx_lib.h index aed6ea8cf0d6..9d85d399e17f 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.h @@ -36,10 +36,11 @@ netdev_features_t wx_fix_features(struct net_device *netdev, netdev_features_t wx_features_check(struct sk_buff *skb, struct net_device *netdev, netdev_features_t features); -void wx_set_ring(struct wx *wx, u32 new_tx_count, - u32 new_rx_count, struct wx_ring *temp_ring); +int wx_set_ring(struct wx *wx, u32 new_tx_count, + u32 new_rx_count, struct wx_ring *temp_ring); void wx_service_event_schedule(struct wx *wx); void wx_service_event_complete(struct wx *wx); void wx_service_timer(struct timer_list *t); +void wx_soft_quiesce(struct wx *wx); #endif /* _WX_LIB_H_ */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 44f3e6505246..3eea647c4742 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -321,6 +321,9 @@ static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp) struct wx *wx = container_of(ptp, struct wx, ptp_caps); int ts_done; + if (!test_bit(WX_STATE_PTP_RUNNING, wx->state)) + return HZ; + ts_done = wx_ptp_tx_hwtstamp_work(wx); wx_ptp_overflow_check(wx); @@ -842,6 +845,30 @@ void wx_ptp_stop(struct wx *wx) } EXPORT_SYMBOL(wx_ptp_stop); +void wx_ptp_quiesce(struct wx *wx) +{ + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state)) + return; + + clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags); + + if (wx->ptp_clock) + ptp_cancel_worker_sync(wx->ptp_clock); + + if (wx->ptp_tx_skb) { + dev_kfree_skb_any(wx->ptp_tx_skb); + wx->ptp_tx_skb = NULL; + } + clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + + if (wx->ptp_clock) { + ptp_clock_unregister(wx->ptp_clock); + wx->ptp_clock = NULL; + dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name); + } +} +EXPORT_SYMBOL(wx_ptp_quiesce); + /** * wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp * @wx: pointer to wx struct diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h index 50db90a6e3ee..ad2f824875d5 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h @@ -10,6 +10,7 @@ void wx_ptp_reset(struct wx *wx); void wx_ptp_init(struct wx *wx); void wx_ptp_suspend(struct wx *wx); void wx_ptp_stop(struct wx *wx); +void wx_ptp_quiesce(struct wx *wx); void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb); int wx_hwtstamp_get(struct net_device *dev, struct kernel_hwtstamp_config *cfg); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 65e3e55db1cf..2eba5ab59925 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -450,6 +450,11 @@ enum WX_MSCA_CMD_value { #define WX_PX_TR_CFG_THRE_SHIFT 8 #define WX_PX_TR_CFG_HEAD_WB BIT(27) +#define WX_PX_TR_RP_PV(q_per_pool, vf_number, vf_q_index) \ + (WX_PX_TR_RP((q_per_pool) * (vf_number) + (vf_q_index))) +#define WX_PX_TR_WP_PV(q_per_pool, vf_number, vf_q_index) \ + (WX_PX_TR_WP((q_per_pool) * (vf_number) + (vf_q_index))) + /* Receive DMA Registers */ #define WX_PX_RR_BAL(_i) (0x01000 + ((_i) * 0x40)) #define WX_PX_RR_BAH(_i) (0x01004 + ((_i) * 0x40)) @@ -1040,6 +1045,7 @@ struct wx_queue_stats { struct wx_tx_queue_stats { u64 restart_queue; u64 tx_busy; + u32 tx_done_old; }; struct wx_rx_queue_stats { @@ -1055,6 +1061,12 @@ struct wx_rx_queue_stats { #define wx_for_each_ring(posm, headm) \ for (posm = (headm).ring; posm; posm = posm->next) +enum wx_ring_state { + WX_TX_DETECT_HANG, + WX_HANG_CHECK_ARMED, + WX_RING_STATE_NBITS +}; + struct wx_ring_container { struct wx_ring *ring; /* pointer to linked list of rings */ unsigned int total_bytes; /* total bytes processed this int */ @@ -1074,6 +1086,7 @@ struct wx_ring { struct wx_tx_buffer *tx_buffer_info; struct wx_rx_buffer *rx_buffer_info; }; + DECLARE_BITMAP(state, WX_RING_STATE_NBITS); u8 __iomem *tail; dma_addr_t dma; /* phys. address of descriptor ring */ dma_addr_t headwb_dma; @@ -1209,6 +1222,8 @@ enum wx_state { WX_STATE_PTP_RUNNING, WX_STATE_PTP_TX_IN_PROGRESS, WX_STATE_SERVICE_SCHED, + WX_STATE_DISABLED, + WX_STATE_RES_FREED, WX_STATE_NBITS /* must be last */ }; @@ -1275,6 +1290,7 @@ enum wx_pf_flags { WX_FLAG_NEED_DO_RESET, WX_FLAG_RX_MERGE_ENABLED, WX_FLAG_TXHEAD_WB_ENABLED, + WX_FLAG_NEED_PCIE_RECOVERY, WX_PF_FLAGS_NBITS /* must be last */ }; @@ -1395,7 +1411,8 @@ struct wx { void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype); void (*configure_fdir)(struct wx *wx); int (*setup_tc)(struct net_device *netdev, u8 tc); - void (*do_reset)(struct net_device *netdev); + void (*do_reset)(struct net_device *netdev, bool reinit); + void (*down_suspend)(struct wx *wx); int (*ptp_setup_sdp)(struct wx *wx); void (*set_num_queues)(struct wx *wx); @@ -1423,6 +1440,8 @@ struct wx { struct timer_list service_timer; struct work_struct service_task; + struct work_struct reset_task; + struct workqueue_struct *reset_wq; struct mutex reset_lock; /* mutex for reset */ }; @@ -1505,7 +1524,8 @@ rd32_wrap(struct wx *wx, u32 reg, u32 *last) #define wx_err(wx, fmt, arg...) \ dev_err(&(wx)->pdev->dev, fmt, ##arg) - +#define wx_warn(wx, fmt, arg...) \ + dev_warn(&(wx)->pdev->dev, fmt, ##arg) #define wx_dbg(wx, fmt, arg...) \ dev_dbg(&(wx)->pdev->dev, fmt, ##arg) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf.h b/drivers/net/ethernet/wangxun/libwx/wx_vf.h index eb6ca3fe4e97..b64a4de089f2 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf.h @@ -41,7 +41,6 @@ #define WX_VF_MAX_RX_QUEUES 4 #define WX_VXITR(i) (0x200 + (4 * (i))) /* i=[0,1] */ -#define WX_VXITR_MASK GENMASK(8, 0) #define WX_VXITR_CNT_WDIS BIT(31) #define WX_VXIVAR_MISC 0x260 #define WX_VXIVAR(i) (0x240 + (4 * (i))) /* i=[0,3] */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c index 0d2db8d38cd5..26de78e9a69e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c @@ -269,7 +269,7 @@ static void wxvf_irq_enable(struct wx *wx) wr32(wx, WX_VXIMC, wx->eims_enable_mask); } -static void wxvf_up_complete(struct wx *wx) +void wxvf_up_complete(struct wx *wx) { /* Always set the carrier off */ netif_carrier_off(wx->netdev); @@ -324,7 +324,7 @@ int wxvf_open(struct net_device *netdev) } EXPORT_SYMBOL(wxvf_open); -static void wxvf_down(struct wx *wx) +void wxvf_down(struct wx *wx) { struct net_device *netdev = wx->netdev; diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h index cbbb1b178cb2..d45d5d8ac3ab 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h @@ -15,7 +15,9 @@ void wx_set_rx_mode_vf(struct net_device *netdev); void wx_configure_vf(struct wx *wx); int wx_set_mac_vf(struct net_device *netdev, void *p); void wxvf_watchdog_update_link(struct wx *wx); +void wxvf_up_complete(struct wx *wx); int wxvf_open(struct net_device *netdev); +void wxvf_down(struct wx *wx); int wxvf_close(struct net_device *netdev); void wxvf_init_service(struct wx *wx); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c index aa8be036956c..7325b475ee10 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c @@ -16,7 +16,18 @@ void wx_write_eitr_vf(struct wx_q_vector *q_vector) int v_idx = q_vector->v_idx; u32 itr_reg; - itr_reg = q_vector->itr & WX_VXITR_MASK; + switch (wx->mac.type) { + case wx_mac_sp: + itr_reg = q_vector->itr & WX_SP_MAX_EITR; + break; + case wx_mac_aml: + case wx_mac_aml40: + itr_reg = (q_vector->itr >> 3) & WX_AML_MAX_EITR; + break; + default: + itr_reg = q_vector->itr & WX_EM_MAX_EITR; + break; + } /* set the WDIS bit to not clear the timer bits and cause an * immediate assertion of the interrupt diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c index b2e191982803..1960f7154151 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c @@ -59,7 +59,6 @@ static int ngbe_set_ringparam(struct net_device *netdev, wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring); kvfree(temp_ring); - wx_configure(wx); ngbe_up(wx); clear_reset: diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index cb653861aad8..855dc963c610 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -14,6 +14,7 @@ #include "../libwx/wx_type.h" #include "../libwx/wx_hw.h" #include "../libwx/wx_lib.h" +#include "../libwx/wx_err.h" #include "../libwx/wx_ptp.h" #include "../libwx/wx_mbx.h" #include "../libwx/wx_sriov.h" @@ -46,6 +47,20 @@ static const struct pci_device_id ngbe_pci_tbl[] = { { } }; +static void ngbe_down_suspend(struct wx *wx) +{ + if (test_and_set_bit(WX_STATE_RES_FREED, wx->state)) + return; + + phylink_stop(wx->phylink); + phylink_disconnect_phy(wx->phylink); + wx_clean_all_tx_rings(wx); + wx_clean_all_rx_rings(wx); + wx_free_irq(wx); + wx_free_isb_resources(wx); + wx_free_resources(wx); +} + /** * ngbe_init_type_code - Initialize the shared code * @wx: pointer to hardware structure @@ -133,6 +148,8 @@ static int ngbe_sw_init(struct wx *wx) wx->mbx.size = WX_VXMAILBOX_SIZE; wx->setup_tc = ngbe_setup_tc; + wx->do_reset = ngbe_do_reset; + wx->down_suspend = ngbe_down_suspend; set_bit(0, &wx->fwd_bitmask); return 0; @@ -147,6 +164,8 @@ static void ngbe_service_task(struct work_struct *work) struct wx *wx = container_of(work, struct wx, service_task); wx_update_stats(wx); + wx_check_hang_subtask(wx); + wx_check_err_subtask(wx); wx_service_event_complete(wx); } @@ -177,7 +196,7 @@ static void ngbe_irq_enable(struct wx *wx, bool queues) wr32(wx, WX_PX_MISC_IEN, mask); - /* mask interrupt */ + /* unmask interrupt */ if (queues) wx_intr_enable(wx, NGBE_INTR_ALL); else if (wx->pdev->msix_enabled) @@ -394,6 +413,7 @@ static void ngbe_disable_device(struct wx *wx) netif_tx_stop_all_queues(netdev); netif_tx_disable(netdev); + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); @@ -410,6 +430,9 @@ static void ngbe_disable_device(struct wx *wx) static void ngbe_reset(struct wx *wx) { + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) + return; + wx_flush_sw_mac_table(wx); wx_mac_set_default_filter(wx, wx->mac.addr); if (test_bit(WX_STATE_PTP_RUNNING, wx->state)) @@ -425,13 +448,14 @@ void ngbe_down(struct wx *wx) wx_clean_all_rx_rings(wx); } -void ngbe_up(struct wx *wx) +static void ngbe_up_complete(struct wx *wx) { wx_configure_vectors(wx); /* make sure to complete pre-operations */ smp_mb__before_atomic(); clear_bit(WX_STATE_DOWN, wx->state); + clear_bit(WX_STATE_RES_FREED, wx->state); wx_napi_enable_all(wx); /* enable transmits */ netif_tx_start_all_queues(wx->netdev); @@ -492,7 +516,7 @@ static int ngbe_open(struct net_device *netdev) wx_ptp_init(wx); - ngbe_up(wx); + ngbe_up_complete(wx); return 0; err_dis_phy: @@ -505,6 +529,12 @@ static int ngbe_open(struct net_device *netdev) return err; } +void ngbe_up(struct wx *wx) +{ + wx_configure(wx); + ngbe_up_complete(wx); +} + /** * ngbe_close - Disables a network interface * @netdev: network interface device structure @@ -520,12 +550,16 @@ static int ngbe_close(struct net_device *netdev) { struct wx *wx = netdev_priv(netdev); + if (test_bit(WX_STATE_RES_FREED, wx->state)) + goto out; + wx_ptp_stop(wx); ngbe_down(wx); wx_free_irq(wx); wx_free_isb_resources(wx); wx_free_resources(wx); phylink_disconnect_phy(wx->phylink); +out: wx_control_hw(wx, false); return 0; @@ -557,7 +591,8 @@ static void ngbe_dev_shutdown(struct pci_dev *pdev, bool *enable_wake) *enable_wake = !!wufc; wx_control_hw(wx, false); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static void ngbe_shutdown(struct pci_dev *pdev) @@ -592,6 +627,8 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc) */ if (netif_running(dev)) ngbe_close(dev); + else + ngbe_reset(wx); wx_clear_interrupt_scheme(wx); @@ -608,11 +645,36 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc) return 0; } +static void ngbe_reinit_locked(struct wx *wx) +{ + netif_trans_update(wx->netdev); + + mutex_lock(&wx->reset_lock); + set_bit(WX_STATE_RESETTING, wx->state); + + ngbe_down(wx); + ngbe_up(wx); + + clear_bit(WX_STATE_RESETTING, wx->state); + mutex_unlock(&wx->reset_lock); +} + +void ngbe_do_reset(struct net_device *netdev, bool reinit) +{ + struct wx *wx = netdev_priv(netdev); + + if (netif_running(netdev) && reinit) + ngbe_reinit_locked(wx); + else + ngbe_reset(wx); +} + static const struct net_device_ops ngbe_netdev_ops = { .ndo_open = ngbe_open, .ndo_stop = ngbe_close, .ndo_change_mtu = wx_change_mtu, .ndo_start_xmit = wx_xmit_frame, + .ndo_tx_timeout = wx_tx_timeout, .ndo_set_rx_mode = wx_set_rx_mode, .ndo_set_features = wx_set_features, .ndo_fix_features = wx_fix_features, @@ -798,6 +860,10 @@ static int ngbe_probe(struct pci_dev *pdev, eth_hw_addr_set(netdev, wx->mac.perm_addr); wx_mac_set_default_filter(wx, wx->mac.perm_addr); + err = wx_init_err_task(wx); + if (err) + goto err_free_mac_table; + ngbe_init_service(wx); err = wx_init_interrupt_scheme(wx); @@ -814,6 +880,7 @@ static int ngbe_probe(struct pci_dev *pdev, goto err_register; pci_set_drvdata(pdev, wx); + pci_save_state(pdev); return 0; @@ -825,6 +892,8 @@ static int ngbe_probe(struct pci_dev *pdev, err_cancel_service: timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); err_free_mac_table: kfree(wx->rss_key); kfree(wx->mac_table); @@ -856,6 +925,8 @@ static void ngbe_remove(struct pci_dev *pdev) timer_shutdown_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); phylink_destroy(wx->phylink); pci_release_selected_regions(pdev, @@ -865,7 +936,8 @@ static void ngbe_remove(struct pci_dev *pdev) kfree(wx->mac_table); wx_clear_interrupt_scheme(wx); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static int ngbe_suspend(struct pci_dev *pdev, pm_message_t state) @@ -892,6 +964,7 @@ static int ngbe_resume(struct pci_dev *pdev) wx_err(wx, "Cannot enable PCI device from suspend\n"); return err; } + clear_bit(WX_STATE_DISABLED, wx->state); pci_set_master(pdev); device_wakeup_disable(&pdev->dev); @@ -916,6 +989,7 @@ static struct pci_driver ngbe_driver = { .resume = ngbe_resume, .shutdown = ngbe_shutdown, .sriov_configure = wx_pci_sriov_configure, + .err_handler = &wx_err_handler, }; module_pci_driver(ngbe_driver); diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h index 7077a0da4c98..c9233dc7ae50 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h @@ -125,5 +125,6 @@ extern char ngbe_driver_name[]; void ngbe_down(struct wx *wx); void ngbe_up(struct wx *wx); int ngbe_setup_tc(struct net_device *dev, u8 tc); +void ngbe_do_reset(struct net_device *netdev, bool reinit); #endif /* _NGBE_TYPE_H_ */ diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index c277863baf67..eb91c4f28ecd 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -14,6 +14,7 @@ #include "../libwx/wx_type.h" #include "../libwx/wx_lib.h" +#include "../libwx/wx_err.h" #include "../libwx/wx_ptp.h" #include "../libwx/wx_hw.h" #include "../libwx/wx_mbx.h" @@ -93,12 +94,24 @@ static void txgbe_module_detection_subtask(struct wx *wx) { int err; + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags)) return; /* wait for SFF module ready */ msleep(200); + /* Re-check state to avoid racing with down/reset paths. + * Module identification is deferred to the next up event, + * so it is safe to bail out here. + */ + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + err = txgbe_identify_module(wx); if (err == -ENODEV) set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags); @@ -106,6 +119,10 @@ static void txgbe_module_detection_subtask(struct wx *wx) static void txgbe_link_config_subtask(struct wx *wx) { + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + if (!test_and_clear_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags)) return; @@ -123,6 +140,8 @@ static void txgbe_service_task(struct work_struct *work) txgbe_module_detection_subtask(wx); txgbe_link_config_subtask(wx); wx_update_stats(wx); + wx_check_hang_subtask(wx); + wx_check_err_subtask(wx); wx_service_event_complete(wx); } @@ -144,6 +163,7 @@ static void txgbe_up_complete(struct wx *wx) /* make sure to complete pre-operations */ smp_mb__before_atomic(); clear_bit(WX_STATE_DOWN, wx->state); + clear_bit(WX_STATE_RES_FREED, wx->state); wx_napi_enable_all(wx); switch (wx->mac.type) { @@ -187,6 +207,9 @@ static void txgbe_reset(struct wx *wx) u8 old_addr[ETH_ALEN]; int err; + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) + return; + err = txgbe_reset_hw(wx); if (err != 0) wx_err(wx, "Hardware Error: %d\n", err); @@ -224,6 +247,7 @@ static void txgbe_disable_device(struct wx *wx) wx_irq_disable(wx); wx_napi_disable_all(wx); + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); @@ -292,6 +316,20 @@ void txgbe_up(struct wx *wx) txgbe_up_complete(wx); } +static void txgbe_down_suspend(struct wx *wx) +{ + if (test_and_set_bit(WX_STATE_RES_FREED, wx->state)) + return; + + phylink_stop(wx->phylink); + wx_clean_all_tx_rings(wx); + wx_clean_all_rx_rings(wx); + wx_free_irq(wx); + txgbe_free_misc_irq(wx->priv); + wx_free_resources(wx); + txgbe_fdir_filter_exit(wx); +} + /** * txgbe_init_type_code - Initialize the shared code * @wx: pointer to hardware structure @@ -408,6 +446,7 @@ static int txgbe_sw_init(struct wx *wx) wx->setup_tc = txgbe_setup_tc; wx->do_reset = txgbe_do_reset; + wx->down_suspend = txgbe_down_suspend; set_bit(0, &wx->fwd_bitmask); switch (wx->mac.type) { @@ -518,12 +557,16 @@ static int txgbe_close(struct net_device *netdev) { struct wx *wx = netdev_priv(netdev); + if (test_bit(WX_STATE_RES_FREED, wx->state)) + goto out; + wx_ptp_stop(wx); txgbe_down(wx); wx_free_irq(wx); txgbe_free_misc_irq(wx->priv); wx_free_resources(wx); txgbe_fdir_filter_exit(wx); +out: wx_control_hw(wx, false); return 0; @@ -544,7 +587,8 @@ static void txgbe_dev_shutdown(struct pci_dev *pdev) wx_control_hw(wx, false); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static void txgbe_shutdown(struct pci_dev *pdev) @@ -606,11 +650,11 @@ static void txgbe_reinit_locked(struct wx *wx) mutex_unlock(&wx->reset_lock); } -void txgbe_do_reset(struct net_device *netdev) +void txgbe_do_reset(struct net_device *netdev, bool reinit) { struct wx *wx = netdev_priv(netdev); - if (netif_running(netdev)) + if (netif_running(netdev) && reinit) txgbe_reinit_locked(wx); else txgbe_reset(wx); @@ -654,6 +698,7 @@ static const struct net_device_ops txgbe_netdev_ops = { .ndo_stop = txgbe_close, .ndo_change_mtu = wx_change_mtu, .ndo_start_xmit = wx_xmit_frame, + .ndo_tx_timeout = wx_tx_timeout, .ndo_set_rx_mode = wx_set_rx_mode, .ndo_set_features = wx_set_features, .ndo_fix_features = wx_fix_features, @@ -814,6 +859,10 @@ static int txgbe_probe(struct pci_dev *pdev, eth_hw_addr_set(netdev, wx->mac.perm_addr); wx_mac_set_default_filter(wx, wx->mac.perm_addr); + err = wx_init_err_task(wx); + if (err) + goto err_free_mac_table; + txgbe_init_service(wx); err = wx_init_interrupt_scheme(wx); @@ -889,6 +938,7 @@ static int txgbe_probe(struct pci_dev *pdev, goto err_remove_phy; pci_set_drvdata(pdev, wx); + pci_save_state(pdev); netif_tx_stop_all_queues(netdev); @@ -916,6 +966,8 @@ static int txgbe_probe(struct pci_dev *pdev, err_cancel_service: timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); err_free_mac_table: kfree(wx->rss_key); kfree(wx->mac_table); @@ -949,6 +1001,8 @@ static void txgbe_remove(struct pci_dev *pdev) timer_shutdown_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); txgbe_remove_phy(txgbe); wx_free_isb_resources(wx); @@ -960,7 +1014,8 @@ static void txgbe_remove(struct pci_dev *pdev) kfree(wx->mac_table); wx_clear_interrupt_scheme(wx); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static struct pci_driver txgbe_driver = { @@ -970,6 +1025,7 @@ static struct pci_driver txgbe_driver = { .remove = txgbe_remove, .shutdown = txgbe_shutdown, .sriov_configure = wx_pci_sriov_configure, + .err_handler = &wx_err_handler, }; module_pci_driver(txgbe_driver); diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h index 877234e3fdc2..3e93a3f309c1 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h @@ -313,7 +313,7 @@ extern char txgbe_driver_name[]; void txgbe_down(struct wx *wx); void txgbe_up(struct wx *wx); int txgbe_setup_tc(struct net_device *dev, u8 tc); -void txgbe_do_reset(struct net_device *netdev); +void txgbe_do_reset(struct net_device *netdev, bool reinit); #define DECLARE_PHY_INTERFACE_MASK_ZERO(name) \ unsigned long name[PHY_INTERFACE_MODE_MAX] = { 0, } diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index fcf517069d16..1722b7038f34 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -2898,7 +2898,10 @@ static int axienet_probe(struct platform_device *pdev) * Here we check for memory allocated for Rx/Tx in the hardware from * the device-tree and accordingly set flags. */ - of_property_read_u32(pdev->dev.of_node, "xlnx,rxmem", &lp->rxmem); + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,rxmem", &lp->rxmem); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to read xlnx,rxmem property\n"); lp->switch_x_sgmii = of_property_read_bool(pdev->dev.of_node, "xlnx,switch-x-sgmii"); diff --git a/drivers/net/ethernet/zte/Kconfig b/drivers/net/ethernet/zte/Kconfig new file mode 100644 index 000000000000..b95c2fc7db77 --- /dev/null +++ b/drivers/net/ethernet/zte/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ZTE driver configuration +# + +config NET_VENDOR_ZTE + bool "ZTE devices" + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Zte cards. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_ZTE + +source "drivers/net/ethernet/zte/dinghai/Kconfig" + +endif # NET_VENDOR_ZTE diff --git a/drivers/net/ethernet/zte/Makefile b/drivers/net/ethernet/zte/Makefile new file mode 100644 index 000000000000..cd9929b61559 --- /dev/null +++ b/drivers/net/ethernet/zte/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the ZTE device drivers +# + +obj-$(CONFIG_DINGHAI) += dinghai/ diff --git a/drivers/net/ethernet/zte/dinghai/Kconfig b/drivers/net/ethernet/zte/dinghai/Kconfig new file mode 100644 index 000000000000..121be3bf7707 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ZTE DingHai Ethernet driver configuration +# + +config DINGHAI + bool "ZTE DingHai Ethernet driver" + depends on PCI + select NET_DEVLINK + help + This driver supports ZTE DingHai Ethernet devices. + + DingHai is a high-performance Ethernet controller that supports + multiple features including hardware offloading, SR-IOV, and + advanced virtualization capabilities. + + If you say Y here, you can select specific driver variants below. + + If unsure, say N. + +if DINGHAI + +config DINGHAI_PF + tristate "ZTE DingHai PF (Physical Function) driver" + help + This driver supports ZTE DingHai PCI Express Ethernet + adapters (PF). + + To compile this driver as a module, choose M here. The module + will be named dinghai10e. + + If unsure, say N. + +endif # DINGHAI diff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile new file mode 100644 index 000000000000..e2f4da33df59 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ZTE DingHai Ethernet driver +# + +obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o +dinghai10e-y := en_pf.o diff --git a/drivers/net/ethernet/zte/dinghai/dh_queue.h b/drivers/net/ethernet/zte/dinghai/dh_queue.h new file mode 100644 index 000000000000..5b63895c1ea1 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/dh_queue.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ZTE DingHai Ethernet driver - PCI capability definitions + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#ifndef __DH_QUEUE_H__ +#define __DH_QUEUE_H__ + +#include + +/* This is the PCI capability header: */ +struct zxdh_pf_pci_cap { + __u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */ + __u8 cap_next; /* Generic PCI field: next ptr. */ + __u8 cap_len; /* Generic PCI field: capability length */ + __u8 cfg_type; /* Identifies the structure. */ + __u8 bar; /* Where to find it. */ + __u8 id; /* Multiple capabilities of the same type */ + __u8 padding[2]; /* Pad to full dword. */ + __le32 offset; /* Offset within bar. */ + __le32 length; /* Length of the structure, in bytes. */ +}; + +/* Fields in ZXDH_PF_PCI_CAP_COMMON_CFG: */ +struct zxdh_pf_pci_common_cfg { + /* About the whole device. */ + __le32 device_feature_select; /* read-write */ + __le32 device_feature; /* read-only */ + __le32 guest_feature_select; /* read-write */ + __le32 guest_feature; /* read-write */ + __le16 msix_config; /* read-write */ + __le16 num_queues; /* read-only */ + __u8 device_status; /* read-write */ + __u8 config_generation; /* read-only */ + + /* About a specific virtqueue. */ + __le16 queue_select; /* read-write */ + __le16 queue_size; /* read-write, power of 2. */ + __le16 queue_msix_vector; /* read-write */ + __le16 queue_enable; /* read-write */ + __le16 queue_notify_off; /* read-only */ + __le32 queue_desc_lo; /* read-write */ + __le32 queue_desc_hi; /* read-write */ + __le32 queue_avail_lo; /* read-write */ + __le32 queue_avail_hi; /* read-write */ + __le32 queue_used_lo; /* read-write */ + __le32 queue_used_hi; /* read-write */ +}; + +struct zxdh_pf_pci_notify_cap { + struct zxdh_pf_pci_cap cap; + __le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */ +}; + +#endif /* __DH_QUEUE_H__ */ diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c new file mode 100644 index 000000000000..86d437408820 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ZTE DingHai Ethernet driver + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#include +#include +#include +#include +#include "en_pf.h" +#include "dh_queue.h" + +MODULE_AUTHOR("Junyang Han "); +MODULE_DESCRIPTION("ZTE DingHai series Ethernet driver"); +MODULE_LICENSE("GPL"); + +static const struct devlink_ops zxdh_pf_devlink_ops = {}; + +static const struct pci_device_id zxdh_pf_pci_table[] = { + { PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_PF_DEVICE_ID) }, + { PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_VF_DEVICE_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, zxdh_pf_pci_table); + +void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size) +{ + void *priv = kzalloc(size, GFP_KERNEL); + + if (priv) + zxdh_dev->priv = priv; + return priv; +} + +void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev) +{ + kfree(zxdh_dev->priv); +} + +static int zxdh_pf_pci_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + int ret; + + pci_set_drvdata(zxdh_dev->pdev, zxdh_dev); + + ret = pci_enable_device(zxdh_dev->pdev); + if (ret) { + dev_err(zxdh_dev->device, "pci_enable_device failed: %d\n", ret); + return ret; + } + + dma_set_mask_and_coherent(zxdh_dev->device, DMA_BIT_MASK(64)); + + ret = pci_request_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM), + "dh-pf"); + if (ret) { + dev_err(zxdh_dev->device, "pci_request_selected_regions failed: %d\n", ret); + goto err_pci; + } + + pci_set_master(zxdh_dev->pdev); + ret = pci_save_state(zxdh_dev->pdev); + if (ret) { + dev_err(zxdh_dev->device, "pci_save_state failed: %d\n", ret); + goto err_pci_save_state; + } + + if (!(pci_resource_flags(zxdh_dev->pdev, 0) & IORESOURCE_MEM)) { + ret = -ENODEV; + dev_err(zxdh_dev->device, "BAR 0 is not an MMIO resource\n"); + goto err_pci_save_state; + } + + pf_dev->pci_ioremap_addr[0] = + ioremap(pci_resource_start(zxdh_dev->pdev, 0), + pci_resource_len(zxdh_dev->pdev, 0)); + if (!pf_dev->pci_ioremap_addr[0]) { + ret = -ENOMEM; + dev_err(zxdh_dev->device, "dh pf pci ioremap failed\n"); + goto err_pci_save_state; + } + + return 0; + +err_pci_save_state: + pci_release_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM)); +err_pci: + pci_disable_device(zxdh_dev->pdev); + return ret; +} + +void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + + iounmap(pf_dev->pci_ioremap_addr[0]); + pci_release_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM)); + pci_disable_device(zxdh_dev->pdev); +} + +int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type, + u32 ioresource_types, int *bars) +{ + int pos; + u8 type; + u8 bar; + + for (pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR); pos > 0; + pos = pci_find_next_capability(pdev, pos, PCI_CAP_ID_VNDR)) { + pci_read_config_byte(pdev, + pos + offsetof(struct zxdh_pf_pci_cap, + cfg_type), &type); + pci_read_config_byte(pdev, + pos + offsetof(struct zxdh_pf_pci_cap, bar), &bar); + + /* ignore structures with reserved BAR values */ + if (bar > ZXDH_PF_MAX_BAR_VAL) + continue; + + if (type == cfg_type) { + if (pci_resource_len(pdev, bar) && + pci_resource_flags(pdev, bar) & ioresource_types) { + *bars |= (1 << bar); + return pos; + } + } + } + + return 0; +} + +void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off, + size_t minlen, u32 align, + u32 start, u32 size, + size_t *len, resource_size_t *pa, + u32 *bar_off) +{ + struct pci_dev *pdev = zxdh_dev->pdev; + void __iomem *p; + u32 offset; + u32 length; + u8 bar; + + pci_read_config_byte(pdev, + off + offsetof(struct zxdh_pf_pci_cap, bar), &bar); + + if (bar > ZXDH_PF_MAX_BAR_VAL) { + dev_err(zxdh_dev->device, "invalid bar %u\n", bar); + return NULL; + } + + pci_read_config_dword(pdev, + off + offsetof(struct zxdh_pf_pci_cap, + offset), &offset); + pci_read_config_dword(pdev, + off + offsetof(struct zxdh_pf_pci_cap, + length), &length); + + if (bar_off) + *bar_off = offset; + + if (length <= start) { + dev_err(zxdh_dev->device, "bad capability len %u (>%u expected)\n", + length, start); + return NULL; + } + + if (length - start < minlen) { + dev_err(zxdh_dev->device, "bad capability len %u (>=%zu expected)\n", + length, minlen); + return NULL; + } + + length -= start; + if (start + offset < offset) { + dev_err(zxdh_dev->device, "map wrap-around %u+%u\n", start, offset); + return NULL; + } + + offset += start; + if (offset & (align - 1)) { + dev_err(zxdh_dev->device, "offset %u not aligned to %u\n", offset, align); + return NULL; + } + + if (length > size) + length = size; + + if (len) + *len = length; + + if (length + offset < offset || + length + offset > pci_resource_len(pdev, bar)) { + dev_err(zxdh_dev->device, + "map %u@%u out of range on bar %u length %lu\n", + length, offset, bar, + (unsigned long)pci_resource_len(pdev, bar)); + return NULL; + } + + p = pci_iomap_range(pdev, bar, offset, length); + if (!p) { + dev_err(zxdh_dev->device, "unable to map custom queue %u@%u on bar %u\n", + length, offset, bar); + } else if (pa) { + *pa = pci_resource_start(pdev, bar) + offset; + } + + return p; +} + +int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int common; + + /* check for a common config: if not, use legacy mode (bar 0). */ + common = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_COMMON_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + if (!common) { + dev_err(zxdh_dev->device, + "missing capabilities, leaving for legacy driver\n"); + return -ENODEV; + } + + pf_dev->common = zxdh_pf_map_capability(zxdh_dev, common, + sizeof(struct zxdh_pf_pci_common_cfg), + ZXDH_PF_ALIGN4, 0, + sizeof(struct zxdh_pf_pci_common_cfg), + NULL, NULL, NULL); + if (!pf_dev->common) { + dev_err(zxdh_dev->device, "pf_dev->common is null\n"); + return -EINVAL; + } + + return 0; +} + +int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + u32 notify_length; + u32 notify_offset; + int notify; + + /* If common is there, these should be too... */ + notify = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_NOTIFY_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + if (!notify) { + dev_err(zxdh_dev->device, "missing notify cfg capability\n"); + return -EINVAL; + } + + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + notify_off_multiplier), + &pf_dev->notify_offset_multiplier); + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + cap.length), ¬ify_length); + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + cap.offset), ¬ify_offset); + + /* We don't know how many VQs we'll map, ahead of the time. + * If notify length is small, map it all now. Otherwise, + * map each VQ individually later. + */ + if (notify_length <= PAGE_SIZE - (notify_offset % PAGE_SIZE)) { + pf_dev->notify_base = zxdh_pf_map_capability(zxdh_dev, notify, + ZXDH_PF_MAP_MINLEN2, + ZXDH_PF_ALIGN2, 0, + notify_length, + &pf_dev->notify_len, + &pf_dev->notify_pa, NULL); + if (!pf_dev->notify_base) { + dev_err(zxdh_dev->device, "pf_dev->notify_base is null\n"); + return -EINVAL; + } + } else { + pf_dev->notify_map_cap = notify; + } + + return 0; +} + +int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int device; + + /* Device capability is only mandatory for + * devices that have device-specific configuration. + */ + device = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_DEVICE_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + + /* we don't know how much we should map, + * but PAGE_SIZE is more than enough for all existing devices. + */ + if (device) { + pf_dev->device = zxdh_pf_map_capability(zxdh_dev, device, 0, + ZXDH_PF_ALIGN4, 0, PAGE_SIZE, + &pf_dev->device_len, NULL, + &pf_dev->dev_cfg_bar_off); + if (!pf_dev->device) { + dev_err(zxdh_dev->device, "pf_dev->device is null\n"); + return -EINVAL; + } + } + return 0; +} + +void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + + if (pf_dev->device) + pci_iounmap(pdev, pf_dev->device); + if (pf_dev->notify_base) + pci_iounmap(pdev, pf_dev->notify_base); + pci_iounmap(pdev, pf_dev->common); +} + +int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int ret; + + ret = zxdh_pf_common_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_common_cfg_init failed: %d\n", ret); + return ret; + } + + ret = zxdh_pf_notify_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_notify_cfg_init failed: %d\n", ret); + goto err_map_notify; + } + + ret = zxdh_pf_device_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_device_cfg_init failed: %d\n", ret); + goto err_map_device; + } + + return 0; + +err_map_device: + if (pf_dev->notify_base) + pci_iounmap(pdev, pf_dev->notify_base); +err_map_notify: + pci_iounmap(pdev, pf_dev->common); + return ret; +} + +static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct zxdh_core_dev *zxdh_dev; + struct zxdh_pf_dev *pf_dev; + struct devlink *devlink; + int ret; + + devlink = devlink_alloc(&zxdh_pf_devlink_ops, sizeof(struct zxdh_core_dev), + &pdev->dev); + if (!devlink) + return -ENOMEM; + + zxdh_dev = devlink_priv(devlink); + zxdh_dev->device = &pdev->dev; + zxdh_dev->pdev = pdev; + zxdh_dev->devlink = devlink; + + pf_dev = zxdh_core_alloc_priv(zxdh_dev, sizeof(*pf_dev)); + if (!pf_dev) { + dev_err(&pdev->dev, "zxdh_pf_dev alloc failed\n"); + ret = -ENOMEM; + goto err_pf_dev; + } + + ret = zxdh_pf_pci_init(zxdh_dev); + if (ret) { + dev_err(&pdev->dev, "zxdh_pf_pci_init failed: %d\n", ret); + goto err_pci_init; + } + + ret = zxdh_pf_modern_cfg_init(zxdh_dev); + if (ret) { + dev_err(&pdev->dev, "zxdh_pf_modern_cfg_init failed: %d\n", ret); + goto err_cfg_init; + } + + devlink_register(devlink); + + return 0; + +err_cfg_init: + zxdh_pf_pci_close(zxdh_dev); +err_pci_init: + zxdh_core_free_priv(zxdh_dev); +err_pf_dev: + devlink_free(devlink); + return ret; +} + +static void zxdh_pf_remove(struct pci_dev *pdev) +{ + struct zxdh_core_dev *zxdh_dev = pci_get_drvdata(pdev); + struct devlink *devlink = priv_to_devlink(zxdh_dev); + + devlink_unregister(devlink); + zxdh_pf_modern_cfg_uninit(zxdh_dev); + zxdh_pf_pci_close(zxdh_dev); + zxdh_core_free_priv(zxdh_dev); + devlink_free(devlink); + pci_set_drvdata(pdev, NULL); +} + +static void zxdh_pf_shutdown(struct pci_dev *pdev) +{ + if (system_state == SYSTEM_POWER_OFF) + pci_set_power_state(pdev, PCI_D3hot); + pci_disable_device(pdev); +} + +static struct pci_driver zxdh_pf_driver = { + .name = "dinghai10e", + .id_table = zxdh_pf_pci_table, + .probe = zxdh_pf_probe, + .remove = zxdh_pf_remove, + .shutdown = zxdh_pf_shutdown, +}; + +module_pci_driver(zxdh_pf_driver); diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h new file mode 100644 index 000000000000..7373dee8d1a9 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/en_pf.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ZTE DingHai Ethernet driver - PF header + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#ifndef __ZXDH_EN_PF_H__ +#define __ZXDH_EN_PF_H__ + +#include + +#define ZXDH_PF_VENDOR_ID 0x1cf2 +#define ZXDH_PF_DEVICE_ID 0x8040 +#define ZXDH_VF_DEVICE_ID 0x8041 + +/* Common configuration */ +#define ZXDH_PCI_CAP_COMMON_CFG 1 +/* Notifications */ +#define ZXDH_PCI_CAP_NOTIFY_CFG 2 +/* ISR access */ +#define ZXDH_PCI_CAP_ISR_CFG 3 +/* Device specific configuration */ +#define ZXDH_PCI_CAP_DEVICE_CFG 4 +/* PCI configuration access */ +#define ZXDH_PCI_CAP_PCI_CFG 5 + +#define ZXDH_PF_MAX_BAR_VAL 0x5 +#define ZXDH_PF_ALIGN4 4 +#define ZXDH_PF_ALIGN2 2 +#define ZXDH_PF_MAP_MINLEN2 2 + +struct zxdh_core_dev { + struct device *device; + struct pci_dev *pdev; + struct devlink *devlink; + void *priv; +}; + +struct zxdh_pf_dev { + struct zxdh_pf_pci_common_cfg __iomem *common; + /* Device-specific data (non-legacy mode) */ + /* Base of vq notifications (non-legacy mode). */ + void __iomem *device; + void __iomem *notify_base; + /* Physical base of vq notifications */ + resource_size_t notify_pa; + /* So we can sanity-check accesses. */ + size_t notify_len; + size_t device_len; + /* Capability for when we need to map notifications per-vq. */ + s32 notify_map_cap; + u32 notify_offset_multiplier; + /* Multiply queue_notify_off by this value. (non-legacy mode). */ + s32 modern_bars; + void __iomem *pci_ioremap_addr[6]; + u32 dev_cfg_bar_off; +}; + +void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size); +void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev); +void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type, + u32 ioresource_types, int *bars); +void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off, + size_t minlen, u32 align, + u32 start, u32 size, + size_t *len, resource_size_t *pa, + u32 *bar_off); +int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev); +void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev); + +#endif /* __ZXDH_EN_PF_H__ */ diff --git a/drivers/net/fjes/fjes_main.c b/drivers/net/fjes/fjes_main.c index 1f0f38980549..a2c77ca0d155 100644 --- a/drivers/net/fjes/fjes_main.c +++ b/drivers/net/fjes/fjes_main.c @@ -1394,16 +1394,15 @@ static void fjes_remove(struct platform_device *plat_dev) fjes_dbg_adapter_exit(adapter); - cancel_delayed_work_sync(&adapter->interrupt_watch_task); - cancel_work_sync(&adapter->unshare_watch_task); - cancel_work_sync(&adapter->raise_intr_rxdata_task); - cancel_work_sync(&adapter->tx_stall_task); + /* Unregister first: .ndo_stop frees the IRQ and cancels the workers. */ + unregister_netdev(netdev); + if (adapter->control_wq) destroy_workqueue(adapter->control_wq); if (adapter->txrx_wq) destroy_workqueue(adapter->txrx_wq); - unregister_netdev(netdev); + cancel_work_sync(&adapter->force_close_task); fjes_hw_exit(hw); diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 72023ebd0e1b..0ab729f055ea 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -69,8 +69,8 @@ struct geneve_skb_cb { /* per-network namespace private data for this module */ struct geneve_net { struct list_head geneve_list; - /* sock_list is protected by rtnl lock */ struct list_head sock_list; + struct mutex lock; }; static unsigned int geneve_net_id; @@ -91,6 +91,7 @@ struct geneve_config { u16 port_min; u16 port_max; + struct rcu_head rcu; /* Must be last --ends in a flexible-array member. */ struct ip_tunnel_info info; }; @@ -109,7 +110,7 @@ struct geneve_dev { #endif struct list_head next; /* geneve's per namespace list */ struct gro_cells gro_cells; - struct geneve_config cfg; + struct geneve_config __rcu *cfg; }; struct geneve_sock { @@ -191,8 +192,10 @@ static struct geneve_dev *geneve_lookup(struct geneve_sock *gs, hash = geneve_net_vni_hash(vni); vni_list_head = &gs->vni_list[hash]; hlist_for_each_entry_rcu(node, vni_list_head, hlist) { - if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) && - addr == node->geneve->cfg.info.key.u.ipv4.dst) + const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg); + + if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) && + addr == cfg->info.key.u.ipv4.dst) return node->geneve; } return NULL; @@ -210,8 +213,10 @@ static struct geneve_dev *geneve6_lookup(struct geneve_sock *gs, hash = geneve_net_vni_hash(vni); vni_list_head = &gs->vni_list[hash]; hlist_for_each_entry_rcu(node, vni_list_head, hlist) { - if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) && - ipv6_addr_equal(&addr6, &node->geneve->cfg.info.key.u.ipv6.dst)) + const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg); + + if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) && + ipv6_addr_equal(&addr6, &cfg->info.key.u.ipv6.dst)) return node->geneve; } return NULL; @@ -395,11 +400,6 @@ static int geneve_init(struct net_device *dev) if (err) return err; - err = dst_cache_init(&geneve->cfg.info.dst_cache, GFP_KERNEL); - if (err) { - gro_cells_destroy(&geneve->gro_cells); - return err; - } netdev_lockdep_set_classes(dev); return 0; } @@ -408,7 +408,6 @@ static void geneve_uninit(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - dst_cache_destroy(&geneve->cfg.info.dst_cache); gro_cells_destroy(&geneve->gro_cells); } @@ -649,7 +648,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, /* Adjust the nested UDP header len and checksum. */ uh = udp_hdr(skb); - uh->len = htons(skb->len - gro_hint->nested_tp_offset); + udp_set_len_short(uh, skb->len - gro_hint->nested_tp_offset); if (uh->check) { len = skb->len - gro_hint->nested_tp_offset; skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM; @@ -668,6 +667,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, /* Callback from net/ipv4/udp.c to receive packets */ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb) { + const struct geneve_config *cfg; struct genevehdr *geneveh; struct geneve_dev *geneve; struct geneve_sock *gs; @@ -693,8 +693,9 @@ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb) inner_proto = geneveh->proto_type; - if (unlikely((!geneve->cfg.inner_proto_inherit && - inner_proto != htons(ETH_P_TEB)))) { + cfg = rcu_dereference(geneve->cfg); + if (unlikely(!cfg || (!cfg->inner_proto_inherit && + inner_proto != htons(ETH_P_TEB)))) { dev_dstats_rx_dropped(geneve->dev); goto drop; } @@ -780,9 +781,10 @@ static int geneve_udp_encap_err_lookup(struct sock *sk, struct sk_buff *skb) } static struct sock *geneve_create_sock(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { - struct ip_tunnel_info *info = &geneve->cfg.info; + const struct ip_tunnel_info *info = &cfg->info; struct udp_port_cfg udp_conf; struct socket *sock; int err; @@ -793,7 +795,7 @@ static struct sock *geneve_create_sock(struct net *net, if (ipv6) { udp_conf.family = AF_INET6; udp_conf.ipv6_v6only = 1; - udp_conf.use_udp6_rx_checksums = geneve->cfg.use_udp6_rx_checksums; + udp_conf.use_udp6_rx_checksums = cfg->use_udp6_rx_checksums; udp_conf.local_ip6 = info->key.u.ipv6.src; } else #endif @@ -1009,7 +1011,8 @@ static int geneve_gro_complete(struct sock *sk, struct sk_buff *skb, /* Create new listen socket if needed */ static struct geneve_sock *geneve_socket_create(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct geneve_net *gn = net_generic(net, geneve_net_id); struct udp_tunnel_sock_cfg tunnel_cfg; @@ -1021,7 +1024,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net, if (!gs) return ERR_PTR(-ENOMEM); - sk = geneve_create_sock(net, geneve, ipv6); + sk = geneve_create_sock(net, geneve, cfg, ipv6); if (IS_ERR(sk)) { kfree(gs); return ERR_CAST(sk); @@ -1032,9 +1035,6 @@ static struct geneve_sock *geneve_socket_create(struct net *net, for (h = 0; h < VNI_HASH_SIZE; ++h) INIT_HLIST_HEAD(&gs->vni_list[h]); - /* Initialize the geneve udp offloads structure */ - udp_tunnel_notify_add_rx_port(sk, UDP_TUNNEL_TYPE_GENEVE); - /* Mark socket as an encapsulation socket */ memset(&tunnel_cfg, 0, sizeof(tunnel_cfg)); tunnel_cfg.sk_user_data = gs; @@ -1049,12 +1049,41 @@ static struct geneve_sock *geneve_socket_create(struct net *net, return gs; } -static void __geneve_sock_release(struct geneve_sock *gs) +static void __geneve_sock_release(struct geneve_dev *geneve, bool ipv6) { - if (!gs || --gs->refcnt) + struct geneve_dev_node *node; + struct geneve_sock *gs; + struct geneve_net *gn; + +#if IS_ENABLED(CONFIG_IPV6) + if (ipv6) { + gs = rtnl_dereference(geneve->sock6); + rcu_assign_pointer(geneve->sock6, NULL); + node = &geneve->hlist6; + } else +#endif + { + gs = rtnl_dereference(geneve->sock4); + rcu_assign_pointer(geneve->sock4, NULL); + node = &geneve->hlist4; + } + + if (!gs) return; + gn = net_generic(sock_net(gs->sk), geneve_net_id); + mutex_lock(&gn->lock); + + hlist_del_init_rcu(&node->hlist); + + if (--gs->refcnt) { + mutex_unlock(&gn->lock); + return; + } + list_del(&gs->list); + mutex_unlock(&gn->lock); + udp_tunnel_notify_del_rx_port(gs->sk, UDP_TUNNEL_TYPE_GENEVE); udp_tunnel_sock_release(gs->sk); kfree_rcu(gs, rcu); @@ -1062,28 +1091,20 @@ static void __geneve_sock_release(struct geneve_sock *gs) static void geneve_sock_release(struct geneve_dev *geneve) { - struct geneve_sock *gs4 = rtnl_dereference(geneve->sock4); #if IS_ENABLED(CONFIG_IPV6) - struct geneve_sock *gs6 = rtnl_dereference(geneve->sock6); - - rcu_assign_pointer(geneve->sock6, NULL); -#endif - - rcu_assign_pointer(geneve->sock4, NULL); - - __geneve_sock_release(gs4); -#if IS_ENABLED(CONFIG_IPV6) - __geneve_sock_release(gs6); + __geneve_sock_release(geneve, true); #endif + __geneve_sock_release(geneve, false); } static struct geneve_sock *geneve_find_sock(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct geneve_net *gn = net_generic(net, geneve_net_id); - struct ip_tunnel_info *info = &geneve->cfg.info; + const struct ip_tunnel_info *info = &cfg->info; sa_family_t family = ipv6 ? AF_INET6 : AF_INET; - bool gro_hint = geneve->cfg.gro_hint; + bool gro_hint = cfg->gro_hint; __be16 dst_port = info->key.tp_dst; struct geneve_sock *gs; @@ -1113,27 +1134,37 @@ static struct geneve_sock *geneve_find_sock(struct net *net, return NULL; } -static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) +static int geneve_sock_add(struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct net *net = geneve->net; struct geneve_dev_node *node; struct geneve_sock *gs; + struct geneve_net *gn; + bool created = false; __u8 vni[3]; + int ret = 0; __u32 hash; - gs = geneve_find_sock(net, geneve, ipv6); + gn = net_generic(net, geneve_net_id); + mutex_lock(&gn->lock); + + gs = geneve_find_sock(net, geneve, cfg, ipv6); if (gs) { gs->refcnt++; - goto out; + } else { + gs = geneve_socket_create(net, geneve, cfg, ipv6); + if (IS_ERR(gs)) { + ret = PTR_ERR(gs); + goto out; + } + + created = true; } - gs = geneve_socket_create(net, geneve, ipv6); - if (IS_ERR(gs)) - return PTR_ERR(gs); + gs->collect_md = cfg->collect_md; + gs->gro_hint = cfg->gro_hint; -out: - gs->collect_md = geneve->cfg.collect_md; - gs->gro_hint = geneve->cfg.gro_hint; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { rcu_assign_pointer(geneve->sock6, gs); @@ -1146,30 +1177,41 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) } node->geneve = geneve; - tunnel_id_to_vni(geneve->cfg.info.key.tun_id, vni); + tunnel_id_to_vni(cfg->info.key.tun_id, vni); hash = geneve_net_vni_hash(vni); hlist_add_head_rcu(&node->hlist, &gs->vni_list[hash]); - return 0; + +out: + mutex_unlock(&gn->lock); + + if (created) { + /* Initialize the geneve udp offloads structure */ + udp_tunnel_notify_add_rx_port(gs->sk, UDP_TUNNEL_TYPE_GENEVE); + } + + return ret; } static int geneve_open(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - bool dualstack = geneve->cfg.dualstack; - bool ipv4, ipv6; + const struct geneve_config *cfg; + bool ipv4, ipv6, dualstack; int ret = 0; - ipv6 = geneve->cfg.info.mode & IP_TUNNEL_INFO_IPV6 || dualstack; + cfg = rtnl_dereference(geneve->cfg); + dualstack = cfg->dualstack; + ipv6 = cfg->info.mode & IP_TUNNEL_INFO_IPV6 || dualstack; ipv4 = !ipv6 || dualstack; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { - ret = geneve_sock_add(geneve, true); + ret = geneve_sock_add(geneve, cfg, true); if (ret < 0 && ret != -EAFNOSUPPORT) ipv4 = false; } #endif if (ipv4) - ret = geneve_sock_add(geneve, false); + ret = geneve_sock_add(geneve, cfg, false); if (ret < 0) geneve_sock_release(geneve); @@ -1180,10 +1222,6 @@ static int geneve_stop(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - hlist_del_init_rcu(&geneve->hlist4.hlist); -#if IS_ENABLED(CONFIG_IPV6) - hlist_del_init_rcu(&geneve->hlist6.hlist); -#endif geneve_sock_release(geneve); return 0; } @@ -1207,6 +1245,7 @@ static void geneve_build_header(struct genevehdr *geneveh, } static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve, + const struct geneve_config *cfg, struct sk_buff *skb) { struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb); @@ -1219,7 +1258,7 @@ static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve, cb->gro_hint_len = 0; /* Try to add the GRO hint only in case of double encap. */ - if (!geneve->cfg.gro_hint || !skb->encapsulation) + if (!cfg->gro_hint || !skb->encapsulation) return 0; /* @@ -1280,10 +1319,11 @@ static void geneve_put_gro_hint_opt(struct genevehdr *gnvh, int opt_size, static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb, const struct ip_tunnel_info *info, - const struct geneve_dev *geneve, int ip_hdr_len) + const struct geneve_dev *geneve, + const struct geneve_config *cfg, int ip_hdr_len) { bool udp_sum = test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags); - bool inner_proto_inherit = geneve->cfg.inner_proto_inherit; + bool inner_proto_inherit = cfg->inner_proto_inherit; bool xnet = !net_eq(geneve->net, dev_net(geneve->dev)); struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb); struct genevehdr *gnvh; @@ -1324,14 +1364,14 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb, } static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev, + const struct geneve_config *cfg, const struct ip_tunnel_info *info, bool *use_cache) { - struct geneve_dev *geneve = netdev_priv(dev); u8 dsfield; dsfield = info->key.tos; - if (dsfield == 1 && !geneve->cfg.collect_md) { + if (cfg && dsfield == 1 && !cfg->collect_md) { dsfield = ip_tunnel_get_dsfield(ip_hdr(skb), skb); *use_cache = false; } @@ -1341,6 +1381,7 @@ static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev, static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, struct geneve_dev *geneve, + const struct geneve_config *cfg, const struct ip_tunnel_info *info) { struct geneve_sock *gs4 = rcu_dereference(geneve->sock4); @@ -1353,35 +1394,35 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, __be16 sport; int err; - if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit)) + if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit)) return -EINVAL; if (!gs4) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - tos = geneve_get_dsfield(skb, dev, info, &use_cache); + tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr, &info->key, - sport, geneve->cfg.info.key.tp_dst, tos, + sport, cfg->info.key.tp_dst, tos, use_cache ? (struct dst_cache *)&info->dst_cache : NULL); if (IS_ERR(rt)) return PTR_ERR(rt); - if (geneve->cfg.info.key.u.ipv4.src && - saddr != geneve->cfg.info.key.u.ipv4.src) { + if (cfg->info.key.u.ipv4.src && + saddr != cfg->info.key.u.ipv4.src) { dst_release(&rt->dst); return -EADDRNOTAVAIL; } err = skb_tunnel_check_pmtu(skb, &rt->dst, GENEVE_IPV4_HLEN + info->options_len + - geneve_build_gro_hint_opt(geneve, skb), + geneve_build_gro_hint_opt(geneve, cfg, skb), netif_is_any_bridge_port(dev)); if (err < 0) { dst_release(&rt->dst); @@ -1415,21 +1456,21 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, } tos = ip_tunnel_ecn_encap(tos, ip_hdr(skb), skb); - if (geneve->cfg.collect_md) { + if (cfg->collect_md) { ttl = key->ttl; df = test_bit(IP_TUNNEL_DONT_FRAGMENT_BIT, key->tun_flags) ? htons(IP_DF) : 0; } else { - if (geneve->cfg.ttl_inherit) + if (cfg->ttl_inherit) ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb); else ttl = key->ttl; ttl = ttl ? : ip4_dst_hoplimit(&rt->dst); - if (geneve->cfg.df == GENEVE_DF_SET) { + if (cfg->df == GENEVE_DF_SET) { df = htons(IP_DF); - } else if (geneve->cfg.df == GENEVE_DF_INHERIT) { + } else if (cfg->df == GENEVE_DF_INHERIT) { struct ethhdr *eth = skb_eth_hdr(skb); if (ntohs(eth->h_proto) == ETH_P_IPV6) { @@ -1443,13 +1484,13 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, } } - err = geneve_build_skb(&rt->dst, skb, info, geneve, + err = geneve_build_skb(&rt->dst, skb, info, geneve, cfg, sizeof(struct iphdr)); if (unlikely(err)) return err; udp_tunnel_xmit_skb(rt, gs4->sk, skb, saddr, info->key.u.ipv4.dst, - tos, ttl, df, sport, geneve->cfg.info.key.tp_dst, + tos, ttl, df, sport, cfg->info.key.tp_dst, !net_eq(geneve->net, dev_net(geneve->dev)), !test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags), 0); @@ -1459,6 +1500,7 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, #if IS_ENABLED(CONFIG_IPV6) static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, struct geneve_dev *geneve, + const struct geneve_config *cfg, const struct ip_tunnel_info *info) { struct geneve_sock *gs6 = rcu_dereference(geneve->sock6); @@ -1470,35 +1512,35 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, __be16 sport; int err; - if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit)) + if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit)) return -EINVAL; if (!gs6) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - prio = geneve_get_dsfield(skb, dev, info, &use_cache); + prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0, &saddr, key, sport, - geneve->cfg.info.key.tp_dst, prio, + cfg->info.key.tp_dst, prio, use_cache ? (struct dst_cache *)&info->dst_cache : NULL); if (IS_ERR(dst)) return PTR_ERR(dst); - if (!ipv6_addr_any(&geneve->cfg.info.key.u.ipv6.src) && - !ipv6_addr_equal(&saddr, &geneve->cfg.info.key.u.ipv6.src)) { + if (!ipv6_addr_any(&cfg->info.key.u.ipv6.src) && + !ipv6_addr_equal(&saddr, &cfg->info.key.u.ipv6.src)) { dst_release(dst); return -EADDRNOTAVAIL; } err = skb_tunnel_check_pmtu(skb, dst, GENEVE_IPV6_HLEN + info->options_len + - geneve_build_gro_hint_opt(geneve, skb), + geneve_build_gro_hint_opt(geneve, cfg, skb), netif_is_any_bridge_port(dev)); if (err < 0) { dst_release(dst); @@ -1531,22 +1573,22 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, } prio = ip_tunnel_ecn_encap(prio, ip_hdr(skb), skb); - if (geneve->cfg.collect_md) { + if (cfg->collect_md) { ttl = key->ttl; } else { - if (geneve->cfg.ttl_inherit) + if (cfg->ttl_inherit) ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb); else ttl = key->ttl; ttl = ttl ? : ip6_dst_hoplimit(dst); } - err = geneve_build_skb(dst, skb, info, geneve, sizeof(struct ipv6hdr)); + err = geneve_build_skb(dst, skb, info, geneve, cfg, sizeof(struct ipv6hdr)); if (unlikely(err)) return err; udp_tunnel6_xmit_skb(dst, gs6->sk, skb, dev, &saddr, &key->u.ipv6.dst, prio, ttl, - info->key.label, sport, geneve->cfg.info.key.tp_dst, + info->key.label, sport, cfg->info.key.tp_dst, !test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags), 0); @@ -1557,28 +1599,31 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - struct ip_tunnel_info *info = NULL; + const struct ip_tunnel_info *info = NULL; + const struct geneve_config *cfg; int err; - if (geneve->cfg.collect_md) { + rcu_read_lock(); + cfg = rcu_dereference(geneve->cfg); + if (cfg->collect_md) { info = skb_tunnel_info(skb); if (unlikely(!info || !(info->mode & IP_TUNNEL_INFO_TX))) { netdev_dbg(dev, "no tunnel metadata\n"); dev_kfree_skb(skb); dev_dstats_tx_dropped(dev); + rcu_read_unlock(); return NETDEV_TX_OK; } } else { - info = &geneve->cfg.info; + info = &cfg->info; } - rcu_read_lock(); #if IS_ENABLED(CONFIG_IPV6) if (info->mode & IP_TUNNEL_INFO_IPV6) - err = geneve6_xmit_skb(skb, dev, geneve, info); + err = geneve6_xmit_skb(skb, dev, geneve, cfg, info); else #endif - err = geneve_xmit_skb(skb, dev, geneve, info); + err = geneve_xmit_skb(skb, dev, geneve, cfg, info); rcu_read_unlock(); if (likely(!err)) @@ -1611,8 +1656,13 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) { struct ip_tunnel_info *info = skb_tunnel_info(skb); struct geneve_dev *geneve = netdev_priv(dev); + const struct geneve_config *cfg; __be16 sport; + cfg = rcu_dereference(geneve->cfg); + if (unlikely(!cfg)) + return -ENODEV; + if (ip_tunnel_info_af(info) == AF_INET) { struct rtable *rt; struct geneve_sock *gs4 = rcu_dereference(geneve->sock4); @@ -1624,14 +1674,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - tos = geneve_get_dsfield(skb, dev, info, &use_cache); + tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr, &info->key, - sport, geneve->cfg.info.key.tp_dst, + sport, cfg->info.key.tp_dst, tos, use_cache ? &info->dst_cache : NULL); if (IS_ERR(rt)) @@ -1651,14 +1701,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - prio = geneve_get_dsfield(skb, dev, info, &use_cache); + prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0, &saddr, &info->key, sport, - geneve->cfg.info.key.tp_dst, prio, + cfg->info.key.tp_dst, prio, use_cache ? &info->dst_cache : NULL); if (IS_ERR(dst)) return PTR_ERR(dst); @@ -1671,7 +1721,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) } info->key.tp_src = sport; - info->key.tp_dst = geneve->cfg.info.key.tp_dst; + info->key.tp_dst = cfg->info.key.tp_dst; return 0; } @@ -1716,6 +1766,8 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) ASSERT_RTNL(); + mutex_lock(&gn->lock); + list_for_each_entry(gs, &gn->sock_list, list) { if (push) { udp_tunnel_push_rx_port(dev, gs->sk, @@ -1725,9 +1777,54 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) UDP_TUNNEL_TYPE_GENEVE); } } + + mutex_unlock(&gn->lock); +} + +static struct geneve_config *geneve_config_alloc(const struct geneve_config *src) +{ + struct geneve_config *cfg; + int err; + + cfg = kmemdup(src, sizeof(*src), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + + cfg->info.dst_cache.cache = NULL; + err = dst_cache_init(&cfg->info.dst_cache, GFP_KERNEL); + if (err) { + kfree(cfg); + return ERR_PTR(err); + } + + return cfg; +} + +static void geneve_config_free(struct geneve_config *cfg) +{ + if (cfg) { + dst_cache_destroy(&cfg->info.dst_cache); + kfree(cfg); + } +} + +static void geneve_config_free_rcu(struct rcu_head *head) +{ + struct geneve_config *cfg = container_of(head, struct geneve_config, rcu); + + geneve_config_free(cfg); } /* Initialize the device structure. */ +static void geneve_free_dev(struct net_device *dev) +{ + struct geneve_dev *geneve = netdev_priv(dev); + struct geneve_config *cfg = rcu_dereference_protected(geneve->cfg, 1); + + geneve_config_free(cfg); + RCU_INIT_POINTER(geneve->cfg, NULL); +} + static void geneve_setup(struct net_device *dev) { ether_setup(dev); @@ -1735,6 +1832,7 @@ static void geneve_setup(struct net_device *dev) dev->netdev_ops = &geneve_netdev_ops; dev->ethtool_ops = &geneve_ethtool_ops; dev->needs_free_netdev = true; + dev->priv_destructor = geneve_free_dev; SET_NETDEV_DEVTYPE(dev, &geneve_type); @@ -1763,6 +1861,8 @@ static void geneve_setup(struct net_device *dev) dev->max_mtu = IP_MAX_MTU - GENEVE_BASE_HLEN - dev->hard_header_len; netif_keep_dst(dev); + netif_set_tso_max_size(dev, GSO_MAX_SIZE); + dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE; dev->lltx = true; @@ -1895,18 +1995,26 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, *tun_on_same_port = false; *tun_collect_md = false; + + mutex_lock(&gn->lock); + list_for_each_entry(geneve, &gn->geneve_list, next) { - if (info->key.tp_dst == geneve->cfg.info.key.tp_dst && - (cfg->dualstack || geneve->cfg.dualstack || - geneve_saddr_conflict(info, &geneve->cfg.info))) { - *tun_collect_md |= geneve->cfg.collect_md; + const struct geneve_config *gcfg = rtnl_dereference(geneve->cfg); + + if (info->key.tp_dst == gcfg->info.key.tp_dst && + (cfg->dualstack || gcfg->dualstack || + geneve_saddr_conflict(info, &gcfg->info))) { + *tun_collect_md |= gcfg->collect_md; *tun_on_same_port = true; } - if (info->key.tun_id == geneve->cfg.info.key.tun_id && - info->key.tp_dst == geneve->cfg.info.key.tp_dst && - !memcmp(&info->key.u, &geneve->cfg.info.key.u, sizeof(info->key.u))) + if (info->key.tun_id == gcfg->info.key.tun_id && + info->key.tp_dst == gcfg->info.key.tp_dst && + !memcmp(&info->key.u, &gcfg->info.key.u, sizeof(info->key.u))) t = geneve; } + + mutex_unlock(&gn->lock); + return t; } @@ -1940,6 +2048,7 @@ static int geneve_configure(struct net *net, struct net_device *dev, struct geneve_dev *t, *geneve = netdev_priv(dev); const struct ip_tunnel_info *info = &cfg->info; bool tun_collect_md, tun_on_same_port; + struct geneve_config *new_cfg; int err, encap_len; if (cfg->collect_md && !is_tnl_info_zero(info)) { @@ -1980,10 +2089,13 @@ static int geneve_configure(struct net *net, struct net_device *dev, } } - dst_cache_reset(&geneve->cfg.info.dst_cache); - memcpy(&geneve->cfg, cfg, sizeof(*cfg)); + new_cfg = geneve_config_alloc(cfg); + if (IS_ERR(new_cfg)) + return PTR_ERR(new_cfg); - if (geneve->cfg.inner_proto_inherit) { + rcu_assign_pointer(geneve->cfg, new_cfg); + + if (cfg->inner_proto_inherit) { dev->header_ops = NULL; dev->type = ARPHRD_NONE; dev->hard_header_len = 0; @@ -1992,10 +2104,15 @@ static int geneve_configure(struct net *net, struct net_device *dev, } err = register_netdevice(dev); - if (err) + if (err) { + geneve_free_dev(dev); return err; + } + mutex_lock(&gn->lock); list_add(&geneve->next, &gn->geneve_list); + mutex_unlock(&gn->lock); + return 0; } @@ -2341,57 +2458,17 @@ static int geneve_newlink(struct net_device *dev, return 0; } -/* Quiesces the geneve device data path for both TX and RX. - * - * On transmit geneve checks for non-NULL geneve_sock before it proceeds. - * So, if we set that socket to NULL under RCU and wait for synchronize_net() - * to complete for the existing set of in-flight packets to be transmitted, - * then we would have quiesced the transmit data path. All the future packets - * will get dropped until we unquiesce the data path. - * - * On receive geneve dereference the geneve_sock stashed in the socket. So, - * if we set that to NULL under RCU and wait for synchronize_net() to - * complete, then we would have quiesced the receive data path. +/* Update the device configuration under RTNL. + * We use RCU swap to update the configuration atomically, so the data path + * (both TX and RX) can continue running without interruption or packet loss. */ -static void geneve_quiesce(struct geneve_dev *geneve, struct geneve_sock **gs4, - struct geneve_sock **gs6) -{ - *gs4 = rtnl_dereference(geneve->sock4); - rcu_assign_pointer(geneve->sock4, NULL); - if (*gs4) - rcu_assign_sk_user_data((*gs4)->sk, NULL); -#if IS_ENABLED(CONFIG_IPV6) - *gs6 = rtnl_dereference(geneve->sock6); - rcu_assign_pointer(geneve->sock6, NULL); - if (*gs6) - rcu_assign_sk_user_data((*gs6)->sk, NULL); -#else - *gs6 = NULL; -#endif - synchronize_net(); -} - -/* Resumes the geneve device data path for both TX and RX. */ -static void geneve_unquiesce(struct geneve_dev *geneve, struct geneve_sock *gs4, - struct geneve_sock __maybe_unused *gs6) -{ - rcu_assign_pointer(geneve->sock4, gs4); - if (gs4) - rcu_assign_sk_user_data(gs4->sk, gs4); -#if IS_ENABLED(CONFIG_IPV6) - rcu_assign_pointer(geneve->sock6, gs6); - if (gs6) - rcu_assign_sk_user_data(gs6->sk, gs6); -#endif -} - static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { struct geneve_dev *geneve = netdev_priv(dev); - struct geneve_sock *gs4, *gs6; - struct geneve_config cfg; + struct geneve_config *old_cfg = rtnl_dereference(geneve->cfg); + struct geneve_config *cfg; int err; if (!rtnl_dev_link_net_capable(dev, geneve->net)) @@ -2400,33 +2477,51 @@ static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], /* If the geneve device is configured for metadata (or externally * controlled, for example, OVS), then nothing can be changed. */ - if (geneve->cfg.collect_md) + if (old_cfg->collect_md) return -EOPNOTSUPP; /* Start with the existing info. */ - memcpy(&cfg, &geneve->cfg, sizeof(cfg)); - err = geneve_nl2info(tb, data, extack, &cfg, true); + cfg = geneve_config_alloc(old_cfg); + if (IS_ERR(cfg)) + return PTR_ERR(cfg); + + err = geneve_nl2info(tb, data, extack, cfg, true); if (err) - return err; + goto err_free_cfg; - if (!geneve_dst_addr_equal(&geneve->cfg.info, &cfg.info)) { - dst_cache_reset(&cfg.info.dst_cache); - geneve_link_config(dev, &cfg.info, tb); - } + if (!geneve_dst_addr_equal(&old_cfg->info, &cfg->info)) + geneve_link_config(dev, &cfg->info, tb); - geneve_quiesce(geneve, &gs4, &gs6); - memcpy(&geneve->cfg, &cfg, sizeof(cfg)); - geneve_unquiesce(geneve, gs4, gs6); + rcu_assign_pointer(geneve->cfg, cfg); + call_rcu_hurry(&old_cfg->rcu, geneve_config_free_rcu); return 0; + +err_free_cfg: + geneve_config_free(cfg); + return err; +} + +static void __geneve_dellink(struct net *net, struct net_device *dev, + struct list_head *head) +{ + struct geneve_dev *geneve = netdev_priv(dev); + + list_del_init(&geneve->next); + unregister_netdevice_queue_net(net, dev, head); } static void geneve_dellink(struct net_device *dev, struct list_head *head) { struct geneve_dev *geneve = netdev_priv(dev); + struct geneve_net *gn; - list_del(&geneve->next); - unregister_netdevice_queue(dev, head); + gn = net_generic(geneve->net, geneve_net_id); + + mutex_lock(&gn->lock); + if (!list_empty(&geneve->next)) + __geneve_dellink(dev_net(dev), dev, head); + mutex_unlock(&gn->lock); } static size_t geneve_get_size(const struct net_device *dev) @@ -2452,16 +2547,27 @@ static size_t geneve_get_size(const struct net_device *dev) static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct geneve_dev *geneve = netdev_priv(dev); - struct ip_tunnel_info *info = &geneve->cfg.info; - bool ttl_inherit = geneve->cfg.ttl_inherit; - bool metadata = geneve->cfg.collect_md; - struct ifla_geneve_port_range ports = { - .low = htons(geneve->cfg.port_min), - .high = htons(geneve->cfg.port_max), - }; + const struct geneve_dev *geneve = netdev_priv(dev); + struct ifla_geneve_port_range ports; + const struct geneve_config *cfg; + const struct ip_tunnel_info *info; + bool ttl_inherit, metadata; __u8 tmp_vni[3]; __u32 vni; + int err = 0; + + rcu_read_lock(); + cfg = rcu_dereference(geneve->cfg); + if (!cfg) { + err = -ENODEV; + goto out; + } + + info = &cfg->info; + ttl_inherit = cfg->ttl_inherit; + metadata = cfg->collect_md; + ports.low = htons(cfg->port_min); + ports.high = htons(cfg->port_max); tunnel_id_to_vni(info->key.tun_id, tmp_vni); vni = (tmp_vni[0] << 16) | (tmp_vni[1] << 8) | tmp_vni[2]; @@ -2489,17 +2595,17 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) #endif } - if (!geneve->cfg.dualstack) { + if (!cfg->dualstack) { if (ip_tunnel_info_af(info) == AF_INET) { if ((info->key.u.ipv4.src || - geneve->cfg.collect_md) && + metadata) && nla_put_in_addr(skb, IFLA_GENEVE_LOCAL, info->key.u.ipv4.src)) goto nla_put_failure; #if IS_ENABLED(CONFIG_IPV6) } else { if ((!ipv6_addr_any(&info->key.u.ipv6.src) || - geneve->cfg.collect_md) && + metadata) && nla_put_in6_addr(skb, IFLA_GENEVE_LOCAL6, &info->key.u.ipv6.src)) goto nla_put_failure; @@ -2512,7 +2618,7 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) nla_put_be32(skb, IFLA_GENEVE_LABEL, info->key.label)) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_GENEVE_DF, geneve->cfg.df)) + if (nla_put_u8(skb, IFLA_GENEVE_DF, cfg->df)) goto nla_put_failure; if (nla_put_be16(skb, IFLA_GENEVE_PORT, info->key.tp_dst)) @@ -2523,28 +2629,31 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) #if IS_ENABLED(CONFIG_IPV6) if (nla_put_u8(skb, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, - !geneve->cfg.use_udp6_rx_checksums)) + !cfg->use_udp6_rx_checksums)) goto nla_put_failure; #endif if (nla_put_u8(skb, IFLA_GENEVE_TTL_INHERIT, ttl_inherit)) goto nla_put_failure; - if (geneve->cfg.inner_proto_inherit && + if (cfg->inner_proto_inherit && nla_put_flag(skb, IFLA_GENEVE_INNER_PROTO_INHERIT)) goto nla_put_failure; if (nla_put(skb, IFLA_GENEVE_PORT_RANGE, sizeof(ports), &ports)) goto nla_put_failure; - if (geneve->cfg.gro_hint && + if (cfg->gro_hint && nla_put_flag(skb, IFLA_GENEVE_GRO_HINT)) goto nla_put_failure; - return 0; +out: + rcu_read_unlock(); + return err; nla_put_failure: - return -EMSGSIZE; + err = -EMSGSIZE; + goto out; } static struct rtnl_link_ops geneve_link_ops __read_mostly = { @@ -2561,55 +2670,6 @@ static struct rtnl_link_ops geneve_link_ops __read_mostly = { .fill_info = geneve_fill_info, }; -struct net_device *geneve_dev_create_fb(struct net *net, const char *name, - u8 name_assign_type, u16 dst_port) -{ - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - LIST_HEAD(list_kill); - int err; - struct geneve_config cfg = { - .df = GENEVE_DF_UNSET, - .use_udp6_rx_checksums = true, - .ttl_inherit = false, - .collect_md = true, - .dualstack = true, - .port_min = 1, - .port_max = USHRT_MAX, - }; - - memset(tb, 0, sizeof(tb)); - dev = rtnl_create_link(net, name, name_assign_type, - &geneve_link_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - init_tnl_info(&cfg.info, dst_port); - err = geneve_configure(net, dev, NULL, &cfg); - if (err) { - free_netdev(dev); - return ERR_PTR(err); - } - - /* openvswitch users expect packet sizes to be unrestricted, - * so set the largest MTU we can. - */ - err = geneve_change_mtu(dev, IP_MAX_MTU); - if (err) - goto err; - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) - goto err; - - return dev; -err: - geneve_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(geneve_dev_create_fb); - static int geneve_netdevice_event(struct notifier_block *unused, unsigned long event, void *ptr) { @@ -2633,6 +2693,8 @@ static __net_init int geneve_init_net(struct net *net) INIT_LIST_HEAD(&gn->geneve_list); INIT_LIST_HEAD(&gn->sock_list); + mutex_init(&gn->lock); + return 0; } @@ -2642,8 +2704,12 @@ static void __net_exit geneve_exit_rtnl_net(struct net *net, struct geneve_net *gn = net_generic(net, geneve_net_id); struct geneve_dev *geneve, *next; + mutex_lock(&gn->lock); + list_for_each_entry_safe(geneve, next, &gn->geneve_list, next) - geneve_dellink(geneve->dev, dev_to_kill); + __geneve_dellink(net, geneve->dev, dev_to_kill); + + mutex_unlock(&gn->lock); } static void __net_exit geneve_exit_net(struct net *net) @@ -2692,6 +2758,7 @@ static void __exit geneve_cleanup_module(void) rtnl_link_unregister(&geneve_link_ops); unregister_netdevice_notifier(&geneve_notifier_block); unregister_pernet_subsys(&geneve_net_ops); + rcu_barrier(); } module_exit(geneve_cleanup_module); diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 80f84fc87008..8d05ad480438 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -69,6 +69,7 @@ struct ipvl_dev { DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE); netdev_features_t sfeatures; u32 msg_enable; + bool dying; }; struct ipvl_addr { @@ -91,12 +92,13 @@ struct ipvl_port { struct hlist_head hlhead[IPVLAN_HASH_SIZE]; spinlock_t addrs_lock; /* guards hash-table and addrs */ struct list_head ipvlans; + struct mutex pnodes_lock; u16 mode; u16 flags; u16 dev_id_start; struct work_struct wq; struct sk_buff_head backlog; - int count; + refcount_t count; struct ida ida; netdevice_tracker dev_tracker; }; @@ -168,7 +170,8 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan, unsigned int len, bool success, bool mcast); int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack); -void ipvlan_link_delete(struct net_device *dev, struct list_head *head); +void __ipvlan_link_delete(struct net *net, struct net_device *dev, + struct list_head *head); void ipvlan_link_setup(struct net_device *dev); int ipvlan_link_register(struct rtnl_link_ops *ops); #ifdef CONFIG_IPVLAN_L3S @@ -207,4 +210,9 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev) return rcu_access_pointer(dev->rx_handler) == ipvlan_handle_frame; } +#if IS_ENABLED(CONFIG_IPVTAP) +extern void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev, + struct list_head *head); +#endif + #endif /* __IPVLAN_H */ diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 92d3d340281f..4939cf67b336 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -7,6 +7,12 @@ #include "ipvlan.h" +#if IS_ENABLED(CONFIG_IPVTAP) +void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev, + struct list_head *head); +EXPORT_SYMBOL(__ipvtap_dellink_ptr); +#endif + static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, struct netlink_ext_ack *extack) { @@ -16,6 +22,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, ASSERT_RTNL(); if (port->mode != nval) { + mutex_lock(&port->pnodes_lock); + list_for_each_entry(ipvlan, &port->ipvlans, pnode) { flags = ipvlan->dev->flags; if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) { @@ -40,6 +48,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, ipvlan_l3s_unregister(port); } port->mode = nval; + + mutex_unlock(&port->pnodes_lock); } return 0; @@ -56,6 +66,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, NULL); } + mutex_unlock(&port->pnodes_lock); + return err; } @@ -76,6 +88,7 @@ static int ipvlan_port_create(struct net_device *dev) INIT_HLIST_HEAD(&port->hlhead[idx]); spin_lock_init(&port->addrs_lock); + mutex_init(&port->pnodes_lock); skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); ida_init(&port->ida); @@ -86,6 +99,7 @@ static int ipvlan_port_create(struct net_device *dev) goto err; netdev_hold(dev, &port->dev_tracker, GFP_KERNEL); + return 0; err: @@ -93,16 +107,18 @@ static int ipvlan_port_create(struct net_device *dev) return err; } -static void ipvlan_port_destroy(struct net_device *dev) +static void ipvlan_port_destroy(struct ipvl_port *port) { - struct ipvl_port *port = ipvlan_port_get_rtnl(dev); + struct net_device *dev = port->dev; struct sk_buff *skb; - netdev_put(dev, &port->dev_tracker); if (port->mode == IPVLAN_MODE_L3S) ipvlan_l3s_unregister(port); + netdev_rx_handler_unregister(dev); cancel_work_sync(&port->wq); + netdev_put(dev, &port->dev_tracker); + while ((skb = __skb_dequeue(&port->backlog)) != NULL) { dev_put(skb->dev); kfree_skb(skb); @@ -111,6 +127,27 @@ static void ipvlan_port_destroy(struct net_device *dev) kfree(port); } +static void ipvlan_port_put(struct ipvl_port *port) +{ + if (refcount_dec_and_test(&port->count)) + ipvlan_port_destroy(port); +} + +static struct ipvl_port *ipvlan_port_get(struct net_device *dev) +{ + struct ipvl_port *port = NULL; + + rcu_read_lock(); + if (netif_is_ipvlan_port(dev)) { + port = ipvlan_port_get_rcu(dev); + if (!refcount_inc_not_zero(&port->count)) + port = NULL; + } + rcu_read_unlock(); + + return port; +} + #define IPVLAN_ALWAYS_ON_OFLOADS \ (NETIF_F_SG | NETIF_F_HW_CSUM | \ NETIF_F_GSO_ROBUST | NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL) @@ -155,30 +192,45 @@ static int ipvlan_init(struct net_device *dev) if (!ipvlan->pcpu_stats) return -ENOMEM; + netdev_lock(phy_dev); + if (!netif_is_ipvlan_port(phy_dev)) { err = ipvlan_port_create(phy_dev); if (err < 0) { + netdev_unlock(phy_dev); free_percpu(ipvlan->pcpu_stats); return err; } + port = ipvlan_port_get_rtnl(phy_dev); + refcount_set(&port->count, 1); + } else { + port = ipvlan_port_get_rtnl(phy_dev); + refcount_inc(&port->count); } - port = ipvlan_port_get_rtnl(phy_dev); - port->count += 1; + + netdev_unlock(phy_dev); + + ipvlan->port = port; + return 0; } static void ipvlan_uninit(struct net_device *dev) { struct ipvl_dev *ipvlan = netdev_priv(dev); - struct net_device *phy_dev = ipvlan->phy_dev; - struct ipvl_port *port; + netdevice_tracker dev_tracker; + struct net_device *phy_dev; free_percpu(ipvlan->pcpu_stats); - port = ipvlan_port_get_rtnl(phy_dev); - port->count -= 1; - if (!port->count) - ipvlan_port_destroy(port->dev); + phy_dev = ipvlan->phy_dev; + netdev_hold(phy_dev, &dev_tracker, GFP_KERNEL); + netdev_lock(phy_dev); + + ipvlan_port_put(ipvlan->port); + + netdev_unlock(phy_dev); + netdev_put(phy_dev, &dev_tracker); } static int ipvlan_open(struct net_device *dev) @@ -596,9 +648,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, if (err < 0) return err; - /* ipvlan_init() would have created the port, if required */ - port = ipvlan_port_get_rtnl(phy_dev); - ipvlan->port = port; + port = ipvlan->port; /* If the port-id base is at the MAX value, then wrap it around and * begin from 0x1 again. This may be due to a busy system where lots @@ -641,7 +691,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, if (err) goto unlink_netdev; + mutex_lock(&port->pnodes_lock); list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans); + mutex_unlock(&port->pnodes_lock); + netif_stacked_transfer_operstate(phy_dev, dev); return 0; @@ -655,7 +708,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, } EXPORT_SYMBOL_GPL(ipvlan_link_new); -void ipvlan_link_delete(struct net_device *dev, struct list_head *head) +void __ipvlan_link_delete(struct net *net, struct net_device *dev, + struct list_head *head) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_addr *addr, *next; @@ -670,10 +724,20 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head) ida_free(&ipvlan->port->ida, dev->dev_id); list_del_rcu(&ipvlan->pnode); - unregister_netdevice_queue(dev, head); + unregister_netdevice_queue_net(net, dev, head); netdev_upper_dev_unlink(ipvlan->phy_dev, dev); } -EXPORT_SYMBOL_GPL(ipvlan_link_delete); +EXPORT_SYMBOL(__ipvlan_link_delete); + +static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) +{ + struct ipvl_dev *ipvlan = netdev_priv(dev); + + mutex_lock(&ipvlan->port->pnodes_lock); + if (!ipvlan->dying) + __ipvlan_link_delete(dev_net(dev), dev, head); + mutex_unlock(&ipvlan->port->pnodes_lock); +} void ipvlan_link_setup(struct net_device *dev) { @@ -731,14 +795,19 @@ static int ipvlan_device_event(struct notifier_block *unused, struct netdev_notifier_pre_changeaddr_info *prechaddr_info; struct net_device *dev = netdev_notifier_info_to_dev(ptr); struct ipvl_dev *ipvlan, *next; + int err, ret = NOTIFY_DONE; struct ipvl_port *port; LIST_HEAD(lst_kill); - int err; - if (!netif_is_ipvlan_port(dev)) - return NOTIFY_DONE; + if (event == NETDEV_PRECHANGEUPPER || + event == NETDEV_CHANGEUPPER) + return ret; - port = ipvlan_port_get_rtnl(dev); + port = ipvlan_port_get(dev); + if (!port) + return ret; + + mutex_lock(&port->pnodes_lock); switch (event) { case NETDEV_UP: @@ -762,16 +831,26 @@ static int ipvlan_device_event(struct notifier_block *unused, ipvlan_migrate_l3s_hook(oldnet, newnet); break; } - case NETDEV_UNREGISTER: + case NETDEV_UNREGISTER: { + struct net *net = dev_net(dev); + if (dev->reg_state != NETREG_UNREGISTERING) break; - list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) - ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev, - &lst_kill); + list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) { + ipvlan->dying = true; + +#if IS_ENABLED(CONFIG_IPVTAP) + if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops) + __ipvtap_dellink_ptr(net, ipvlan->dev, &lst_kill); + else +#endif + __ipvlan_link_delete(net, ipvlan->dev, &lst_kill); + } + unregister_netdevice_many(&lst_kill); break; - + } case NETDEV_FEAT_CHANGE: list_for_each_entry(ipvlan, &port->ipvlans, pnode) { netif_inherit_tso_max(ipvlan->dev, dev); @@ -792,8 +871,10 @@ static int ipvlan_device_event(struct notifier_block *unused, err = netif_pre_changeaddr_notify(ipvlan->dev, prechaddr_info->dev_addr, extack); - if (err) - return notifier_from_errno(err); + if (err) { + ret = notifier_from_errno(err); + break; + } } break; @@ -806,7 +887,8 @@ static int ipvlan_device_event(struct notifier_block *unused, case NETDEV_PRE_TYPE_CHANGE: /* Forbid underlying device to change its type. */ - return NOTIFY_BAD; + ret = NOTIFY_BAD; + break; case NETDEV_NOTIFY_PEERS: case NETDEV_BONDING_FAILOVER: @@ -814,7 +896,12 @@ static int ipvlan_device_event(struct notifier_block *unused, list_for_each_entry(ipvlan, &port->ipvlans, pnode) call_netdevice_notifiers(event, ipvlan->dev); } - return NOTIFY_DONE; + + mutex_unlock(&port->pnodes_lock); + + ipvlan_port_put(port); + + return ret; } /* the caller must held the addrs lock */ diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c index 2d6bbddd1edd..66c949d94261 100644 --- a/drivers/net/ipvlan/ipvtap.c +++ b/drivers/net/ipvlan/ipvtap.c @@ -109,14 +109,26 @@ static int ipvtap_newlink(struct net_device *dev, return err; } +static void __ipvtap_dellink(struct net *net, struct net_device *dev, + struct list_head *head) +{ + struct ipvtap_dev *vlantap = netdev_priv(dev); + + netdev_rx_handler_unregister(dev); + tap_del_queues(&vlantap->tap); + __ipvlan_link_delete(net, dev, head); +} + static void ipvtap_dellink(struct net_device *dev, struct list_head *head) { - struct ipvtap_dev *vlan = netdev_priv(dev); + struct ipvtap_dev *vlantap = netdev_priv(dev); + struct ipvl_port *port = vlantap->vlan.port; - netdev_rx_handler_unregister(dev); - tap_del_queues(&vlan->tap); - ipvlan_link_delete(dev, head); + mutex_lock(&port->pnodes_lock); + if (!vlantap->vlan.dying) + __ipvtap_dellink(dev_net(dev), dev, head); + mutex_unlock(&port->pnodes_lock); } static void ipvtap_setup(struct net_device *dev) @@ -198,6 +210,8 @@ static int __init ipvtap_init(void) { int err; + __ipvtap_dellink_ptr = __ipvtap_dellink; + err = tap_create_cdev(&ipvtap_cdev, &ipvtap_major, "ipvtap", THIS_MODULE); if (err) @@ -224,6 +238,8 @@ static int __init ipvtap_init(void) out2: tap_destroy_cdev(ipvtap_major, &ipvtap_cdev); out1: + __ipvtap_dellink_ptr = NULL; + return err; } module_init(ipvtap_init); @@ -234,6 +250,7 @@ static void __exit ipvtap_exit(void) unregister_netdevice_notifier(&ipvtap_notifier_block); class_unregister(&ipvtap_class); tap_destroy_cdev(ipvtap_major, &ipvtap_cdev); + __ipvtap_dellink_ptr = NULL; } module_exit(ipvtap_exit); MODULE_ALIAS_RTNL_LINK("ipvtap"); diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index ee0e2eb7dbc6..6f9f3aceffaa 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -2636,7 +2636,7 @@ static int macsec_update_offload(struct net_device *dev, vlan_drop_rx_ctag_filter_info(dev); vlan_drop_rx_stag_filter_info(dev); } - macsec->offload = offload; + WRITE_ONCE(macsec->offload, offload); /* Add VLAN filters when enabling offload. */ if (prev_offload == MACSEC_OFFLOAD_OFF) { ret = vlan_get_rx_ctag_filter_info(dev); @@ -2666,7 +2666,7 @@ static int macsec_update_offload(struct net_device *dev, return 0; rollback_offload: - macsec->offload = prev_offload; + WRITE_ONCE(macsec->offload, prev_offload); macsec_offload(ops->mdo_del_secy, &ctx); return ret; @@ -3878,52 +3878,53 @@ static int macsec_changelink_common(struct net_device *dev, if (data[IFLA_MACSEC_ENCODING_SA]) { struct macsec_tx_sa *tx_sa; + u8 encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sc->encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sa = rtnl_dereference(tx_sc->sa[tx_sc->encoding_sa]); + WRITE_ONCE(tx_sc->encoding_sa, encoding_sa); + tx_sa = rtnl_dereference(tx_sc->sa[encoding_sa]); secy->operational = tx_sa && tx_sa->active; } if (data[IFLA_MACSEC_ENCRYPT]) - tx_sc->encrypt = !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT]); + WRITE_ONCE(tx_sc->encrypt, !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT])); if (data[IFLA_MACSEC_PROTECT]) - secy->protect_frames = !!nla_get_u8(data[IFLA_MACSEC_PROTECT]); + WRITE_ONCE(secy->protect_frames, !!nla_get_u8(data[IFLA_MACSEC_PROTECT])); if (data[IFLA_MACSEC_INC_SCI]) - tx_sc->send_sci = !!nla_get_u8(data[IFLA_MACSEC_INC_SCI]); + WRITE_ONCE(tx_sc->send_sci, !!nla_get_u8(data[IFLA_MACSEC_INC_SCI])); if (data[IFLA_MACSEC_ES]) - tx_sc->end_station = !!nla_get_u8(data[IFLA_MACSEC_ES]); + WRITE_ONCE(tx_sc->end_station, !!nla_get_u8(data[IFLA_MACSEC_ES])); if (data[IFLA_MACSEC_SCB]) - tx_sc->scb = !!nla_get_u8(data[IFLA_MACSEC_SCB]); + WRITE_ONCE(tx_sc->scb, !!nla_get_u8(data[IFLA_MACSEC_SCB])); if (data[IFLA_MACSEC_REPLAY_PROTECT]) - secy->replay_protect = !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT]); + WRITE_ONCE(secy->replay_protect, !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT])); if (data[IFLA_MACSEC_VALIDATION]) - secy->validate_frames = nla_get_u8(data[IFLA_MACSEC_VALIDATION]); + WRITE_ONCE(secy->validate_frames, nla_get_u8(data[IFLA_MACSEC_VALIDATION])); if (data[IFLA_MACSEC_CIPHER_SUITE]) { switch (nla_get_u64(data[IFLA_MACSEC_CIPHER_SUITE])) { case MACSEC_CIPHER_ID_GCM_AES_128: case MACSEC_DEFAULT_CIPHER_ID: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_128: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; default: return -EINVAL; @@ -3931,13 +3932,14 @@ static int macsec_changelink_common(struct net_device *dev, } if (data[IFLA_MACSEC_WINDOW]) { - secy->replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); + u32 replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); /* IEEE 802.1AEbw-2013 10.7.8 - maximum replay window * for XPN cipher suites */ if (secy->xpn && - secy->replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) + replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) return -EINVAL; + WRITE_ONCE(secy->replay_window, replay_window); } return 0; @@ -4385,21 +4387,21 @@ static size_t macsec_get_size(const struct net_device *dev) static int macsec_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macsec_tx_sc *tx_sc; - struct macsec_dev *macsec; - struct macsec_secy *secy; + const struct macsec_tx_sc *tx_sc; + const struct macsec_dev *macsec; + const struct macsec_secy *secy; u64 csid; macsec = macsec_priv(dev); secy = &macsec->secy; tx_sc = &secy->tx_sc; - switch (secy->key_len) { + switch (READ_ONCE(secy->key_len)) { case MACSEC_GCM_AES_128_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; break; case MACSEC_GCM_AES_256_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; break; default: goto nla_put_failure; @@ -4410,20 +4412,20 @@ static int macsec_fill_info(struct sk_buff *skb, nla_put_u8(skb, IFLA_MACSEC_ICV_LEN, secy->icv_len) || nla_put_u64_64bit(skb, IFLA_MACSEC_CIPHER_SUITE, csid, IFLA_MACSEC_PAD) || - nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, tx_sc->encoding_sa) || - nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, tx_sc->encrypt) || - nla_put_u8(skb, IFLA_MACSEC_PROTECT, secy->protect_frames) || - nla_put_u8(skb, IFLA_MACSEC_INC_SCI, tx_sc->send_sci) || - nla_put_u8(skb, IFLA_MACSEC_ES, tx_sc->end_station) || - nla_put_u8(skb, IFLA_MACSEC_SCB, tx_sc->scb) || - nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, secy->replay_protect) || - nla_put_u8(skb, IFLA_MACSEC_VALIDATION, secy->validate_frames) || - nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, macsec->offload) || + nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, READ_ONCE(tx_sc->encoding_sa)) || + nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, READ_ONCE(tx_sc->encrypt)) || + nla_put_u8(skb, IFLA_MACSEC_PROTECT, READ_ONCE(secy->protect_frames)) || + nla_put_u8(skb, IFLA_MACSEC_INC_SCI, READ_ONCE(tx_sc->send_sci)) || + nla_put_u8(skb, IFLA_MACSEC_ES, READ_ONCE(tx_sc->end_station)) || + nla_put_u8(skb, IFLA_MACSEC_SCB, READ_ONCE(tx_sc->scb)) || + nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, READ_ONCE(secy->replay_protect)) || + nla_put_u8(skb, IFLA_MACSEC_VALIDATION, READ_ONCE(secy->validate_frames)) || + nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, READ_ONCE(macsec->offload)) || 0) goto nla_put_failure; - if (secy->replay_protect) { - if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, secy->replay_window)) + if (READ_ONCE(secy->replay_protect)) { + if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, READ_ONCE(secy->replay_window))) goto nla_put_failure; } diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index ff2f4bb651b7..afad90b9222a 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -171,7 +171,7 @@ static int macvlan_hash_add_source(struct macvlan_dev *vlan, RCU_INIT_POINTER(entry->vlan, vlan); h = &port->vlan_source_hash[macvlan_eth_hash(addr)]; hlist_add_head_rcu(&entry->hlist, h); - vlan->macaddr_count++; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count + 1); return 0; } @@ -277,7 +277,7 @@ static void macvlan_broadcast(struct sk_buff *skb, return; hash_for_each_rcu(port->vlan_hash, i, vlan, hlist) { - if (vlan->dev == src || !(vlan->mode & mode)) + if (vlan->dev == src || !(READ_ONCE(vlan->mode) & mode)) continue; hash = mc_hash(vlan, eth->h_dest); @@ -306,7 +306,7 @@ static void macvlan_multicast_rx(const struct macvlan_port *port, MACVLAN_MODE_VEPA | MACVLAN_MODE_PASSTHRU| MACVLAN_MODE_BRIDGE); - else if (src->mode == MACVLAN_MODE_VEPA) + else if (READ_ONCE(src->mode) == MACVLAN_MODE_VEPA) /* flood to everyone except source */ macvlan_broadcast(skb, port, src->dev, MACVLAN_MODE_VEPA | @@ -402,7 +402,7 @@ static void macvlan_flush_sources(struct macvlan_port *port, if (rcu_access_pointer(entry->vlan) == vlan) macvlan_hash_del_source(entry); - vlan->macaddr_count = 0; + WRITE_ONCE(vlan->macaddr_count, 0); } static void macvlan_forward_source_one(struct sk_buff *skb, @@ -447,7 +447,7 @@ static bool macvlan_forward_source(struct sk_buff *skb, if (!vlan) continue; - if (vlan->flags & MACVLAN_FLAG_NODST) + if (READ_ONCE(vlan->flags) & MACVLAN_FLAG_NODST) consume = true; macvlan_forward_source_one(skb, vlan); } @@ -487,14 +487,18 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) return RX_HANDLER_CONSUMED; } src = macvlan_hash_lookup(port, eth->h_source); - if (src && src->mode != MACVLAN_MODE_VEPA && - src->mode != MACVLAN_MODE_BRIDGE) { - /* forward to original port. */ - vlan = src; - ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: - __netif_rx(skb); - handle_res = RX_HANDLER_CONSUMED; - goto out; + if (src) { + enum macvlan_mode mode = READ_ONCE(src->mode); + + if (mode != MACVLAN_MODE_VEPA && + mode != MACVLAN_MODE_BRIDGE) { + /* forward to original port. */ + vlan = src; + ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: + __netif_rx(skb); + handle_res = RX_HANDLER_CONSUMED; + goto out; + } } hash = mc_hash(NULL, eth->h_dest); @@ -515,7 +519,7 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) struct macvlan_dev, list); else vlan = macvlan_hash_lookup(port, eth->h_dest); - if (!vlan || vlan->mode == MACVLAN_MODE_SOURCE) + if (!vlan || READ_ONCE(vlan->mode) == MACVLAN_MODE_SOURCE) return RX_HANDLER_PASS; dev = vlan->dev; @@ -548,7 +552,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) const struct macvlan_port *port = vlan->port; const struct macvlan_dev *dest; - if (vlan->mode == MACVLAN_MODE_BRIDGE) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_BRIDGE) { const struct ethhdr *eth = skb_eth_hdr(skb); /* send to other bridge ports directly */ @@ -559,7 +563,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) } dest = macvlan_hash_lookup(port, eth->h_dest); - if (dest && dest->mode == MACVLAN_MODE_BRIDGE) { + if (dest && READ_ONCE(dest->mode) == MACVLAN_MODE_BRIDGE) { /* send to lowerdev first for its network taps */ dev_forward_skb(vlan->lowerdev, skb); @@ -777,7 +781,7 @@ static int macvlan_set_mac_address(struct net_device *dev, void *p) if (ether_addr_equal(dev->dev_addr, addr->__data)) return 0; - if (vlan->mode == MACVLAN_MODE_PASSTHRU) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_PASSTHRU) { macvlan_set_addr_change(vlan->port); return dev_set_mac_address(vlan->lowerdev, addr, NULL); } @@ -870,7 +874,7 @@ static void update_port_bc_cutoff(struct macvlan_dev *vlan, int cutoff) if (vlan->port->bc_cutoff == cutoff) return; - vlan->port->bc_cutoff = cutoff; + WRITE_ONCE(vlan->port->bc_cutoff, cutoff); macvlan_recompute_bc_filter(vlan); } @@ -1337,6 +1341,15 @@ static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[], if (!data) return 0; + if (data[IFLA_MACVLAN_BC_QUEUE_LEN] && + nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN]) > + MACVLAN_DEFAULT_BC_QUEUE_LEN && + !capable(CAP_NET_ADMIN)) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_MACVLAN_BC_QUEUE_LEN], + "bc_queue_len above the default requires CAP_NET_ADMIN in the initial user namespace"); + return -EPERM; + } + if (data[IFLA_MACVLAN_FLAGS] && nla_get_u16(data[IFLA_MACVLAN_FLAGS]) & ~(MACVLAN_FLAG_NOPROMISC | MACVLAN_FLAG_NODST)) @@ -1425,7 +1438,7 @@ static int macvlan_changelink_sources(struct macvlan_dev *vlan, u32 mode, entry = macvlan_hash_lookup_source(vlan, addr); if (entry) { macvlan_hash_del_source(entry); - vlan->macaddr_count--; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count - 1); } } else if (mode == MACVLAN_MACADDR_FLUSH) { macvlan_flush_sources(vlan->port, vlan); @@ -1481,8 +1494,14 @@ int macvlan_common_newlink(struct net_device *dev, /* When creating macvlans or macvtaps on top of other macvlans - use * the real device as the lowerdev. */ - if (netif_is_macvlan(lowerdev)) + if (netif_is_macvlan(lowerdev)) { lowerdev = macvlan_dev_real_dev(lowerdev); + if (!rtnl_dev_link_net_capable(dev, dev_net(lowerdev))) { + NL_SET_ERR_MSG(extack, + "Creating a macvlan on a lower device in another network namespace requires CAP_NET_ADMIN in that namespace"); + return -EPERM; + } + } if (!tb[IFLA_MTU]) dev->mtu = lowerdev->mtu; @@ -1621,6 +1640,14 @@ static int macvlan_changelink(struct net_device *dev, enum macvlan_macaddr_mode macmode; int ret; + if (data && + (data[IFLA_MACVLAN_BC_QUEUE_LEN] || data[IFLA_MACVLAN_BC_CUTOFF]) && + !rtnl_dev_link_net_capable(dev, dev_net(vlan->lowerdev))) { + NL_SET_ERR_MSG(extack, + "Changing shared macvlan port settings requires CAP_NET_ADMIN in the lower device network namespace"); + return -EPERM; + } + /* Validate mode, but don't set yet: setting flags may fail. */ if (data && data[IFLA_MACVLAN_MODE]) { set_mode = true; @@ -1647,11 +1674,12 @@ static int macvlan_changelink(struct net_device *dev, if (err < 0) return err; } - vlan->flags = flags; + WRITE_ONCE(vlan->flags, flags); } if (data && data[IFLA_MACVLAN_BC_QUEUE_LEN]) { - vlan->bc_queue_len_req = nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN]); + WRITE_ONCE(vlan->bc_queue_len_req, + nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN])); update_port_bc_queue_len(vlan->port); } @@ -1660,7 +1688,7 @@ static int macvlan_changelink(struct net_device *dev, vlan, nla_get_s32(data[IFLA_MACVLAN_BC_CUTOFF])); if (set_mode) - vlan->mode = mode; + WRITE_ONCE(vlan->mode, mode); if (data && data[IFLA_MACVLAN_MACADDR_MODE]) { if (vlan->mode != MACVLAN_MODE_SOURCE) return -EINVAL; @@ -1674,10 +1702,12 @@ static int macvlan_changelink(struct net_device *dev, static size_t macvlan_get_size_mac(const struct macvlan_dev *vlan) { - if (vlan->macaddr_count == 0) + unsigned int macaddr_count = READ_ONCE(vlan->macaddr_count); + + if (!macaddr_count) return 0; return nla_total_size(0) /* IFLA_MACVLAN_MACADDR_DATA */ - + vlan->macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); + + macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); } static size_t macvlan_get_size(const struct net_device *dev) @@ -1700,53 +1730,75 @@ static int macvlan_fill_info_macaddr(struct sk_buff *skb, const int i) { struct hlist_head *h = &vlan->port->vlan_source_hash[i]; - struct macvlan_source_entry *entry; + const struct macvlan_source_entry *entry; + int cnt = 0; - hlist_for_each_entry_rcu(entry, h, hlist, lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(entry, h, hlist) { if (rcu_access_pointer(entry->vlan) != vlan) continue; if (nla_put(skb, IFLA_MACVLAN_MACADDR, ETH_ALEN, entry->addr)) - return 1; + return -EMSGSIZE; + cnt++; } - return 0; + return cnt; } static int macvlan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macvlan_dev *vlan = netdev_priv(dev); + const struct macvlan_dev *vlan = netdev_priv(dev); struct macvlan_port *port = vlan->port; - int i; - struct nlattr *nest; + unsigned int macaddr_count = 0; + struct nlattr *nest, *attr; + int bc_cutoff, cnt, i; - if (nla_put_u32(skb, IFLA_MACVLAN_MODE, vlan->mode)) + rcu_read_lock(); + if (nla_put_u32(skb, IFLA_MACVLAN_MODE, READ_ONCE(vlan->mode))) goto nla_put_failure; - if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, vlan->flags)) + + if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, READ_ONCE(vlan->flags))) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_MACVLAN_MACADDR_COUNT, vlan->macaddr_count)) + + attr = nla_reserve(skb, IFLA_MACVLAN_MACADDR_COUNT, sizeof(u32)); + if (!attr) goto nla_put_failure; - if (vlan->macaddr_count > 0) { + + if (READ_ONCE(vlan->macaddr_count) > 0) { nest = nla_nest_start_noflag(skb, IFLA_MACVLAN_MACADDR_DATA); if (nest == NULL) goto nla_put_failure; for (i = 0; i < MACVLAN_HASH_SIZE; i++) { - if (macvlan_fill_info_macaddr(skb, vlan, i)) + cnt = macvlan_fill_info_macaddr(skb, vlan, i); + if (cnt < 0) goto nla_put_failure; + macaddr_count += cnt; } - nla_nest_end(skb, nest); + if (!macaddr_count) + nla_nest_cancel(skb, nest); + else if (nla_nest_end_safe(skb, nest) < 0) + goto nla_put_failure; } - if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req)) + *(u32 *)nla_data(attr) = macaddr_count; + + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, + READ_ONCE(vlan->bc_queue_len_req))) goto nla_put_failure; + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED, READ_ONCE(port->bc_queue_len_used))) goto nla_put_failure; - if (port->bc_cutoff != 1 && - nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff)) + + bc_cutoff = READ_ONCE(port->bc_cutoff); + if (bc_cutoff != 1 && + nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, bc_cutoff)) goto nla_put_failure; + + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } @@ -1881,7 +1933,9 @@ static int __init macvlan_init_module(void) { int err; - register_netdevice_notifier(&macvlan_notifier_block); + err = register_netdevice_notifier(&macvlan_notifier_block); + if (err) + return err; err = macvlan_link_register(&macvlan_link_ops); if (err < 0) diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig index cf325ab0b1ef..c40ac9c665b7 100644 --- a/drivers/net/mctp/Kconfig +++ b/drivers/net/mctp/Kconfig @@ -47,9 +47,25 @@ config MCTP_TRANSPORT_I3C A MCTP protocol network device is created for each I3C bus having a "mctp-controller" devicetree property. +config MCTP_TRANSPORT_USBLIB + tristate "MCTP over USB common library" + depends on USB + help + Common protocol handling functions for MCTP-over-USB transport + implementations, suitable for use in either host- or gadget-side + transport driver + + This will be automatically enabled by the transport driver. + +config MCTP_TRANSPORT_USBLIB_TEST + bool "MCTP usblib tests" if !KUNIT_ALL_TESTS + depends on MCTP_TRANSPORT_USBLIB=y && KUNIT=y + default KUNIT_ALL_TESTS + config MCTP_TRANSPORT_USB tristate "MCTP USB transport" depends on USB + select MCTP_TRANSPORT_USBLIB help Provides a driver to access MCTP devices over USB transport, defined by DMTF specification DSP0283. diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile index c36006849a1e..c870b62d3f1c 100644 --- a/drivers/net/mctp/Makefile +++ b/drivers/net/mctp/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +obj-$(CONFIG_MCTP_TRANSPORT_USBLIB) += mctp-usblib.o diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index fade65f2f269..542a570c76cc 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -3,9 +3,9 @@ * mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver. * * DSP0283 is available at: - * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf * - * Copyright (C) 2024-2025 Code Construct Pty Ltd + * Copyright (C) 2024-2026 Code Construct Pty Ltd */ #include @@ -22,95 +22,115 @@ struct mctp_usb { struct usb_device *usbdev; struct usb_interface *intf; + bool span; struct net_device *netdev; u8 ep_in; u8 ep_out; - struct urb *tx_urb; + struct mctp_usblib_rx rx; struct urb *rx_urb; + int in_err_count; + int in_err_orig; + bool clear_halt; /* enforces atomic access to rx_stopped and requeuing the retry work */ spinlock_t rx_lock; bool rx_stopped; struct delayed_work rx_retry_work; + + struct mctp_usblib_tx tx; + struct usb_anchor tx_anchor; + /* serialises tx_qmem updates to netdev queue states */ + spinlock_t tx_qmem_lock; + int tx_qmem; }; +enum { + MCTP_USB_SUBCLASS_BASE = 0x00, + MCTP_USB_SUBCLASS_SPAN = 0x02, +}; + +/* We use a total-size limit for outstanding URBs, as the transfer counts + * may vary a lot between spanning- and non-spanning modes. In spanning mode, + * this will allow for a couple of max-sized transfers to be in flight. In + * non-spanning mode, 32. + * + * We want to avoid disabling the tx queue if possible; doing so will end up + * requeueing to gso_skb, and we only dequeue from that one skb at a time, + * so can no longer perform transfer packing. + */ +static const unsigned int TX_QMEM_MAX = 16384; + static void mctp_usb_out_complete(struct urb *urb) { - struct sk_buff *skb = urb->context; - struct net_device *netdev = skb->dev; - int status; + struct mctp_usblib_tx_ctx *tx_ctx = urb->context; + struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + unsigned int len = urb->transfer_buffer_length; + struct net_device *netdev = mctp_usb->netdev; + unsigned long flags; - status = urb->status; + mctp_usblib_tx_send_complete(tx_ctx, netdev, urb->status == 0); - switch (status) { - case -ENOENT: - case -ECONNRESET: - case -ESHUTDOWN: - case -EPROTO: - dev_dstats_tx_dropped(netdev); - break; - case 0: - dev_dstats_tx_add(netdev, skb->len); + usb_free_urb(urb); + + spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags); + mctp_usb->tx_qmem -= len; + if (mctp_usb->tx_qmem < TX_QMEM_MAX && netif_running(netdev)) netif_wake_queue(netdev); - consume_skb(skb); - return; - default: - netdev_dbg(netdev, "unexpected tx urb status: %d\n", status); - dev_dstats_tx_dropped(netdev); + spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags); +} + +static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, + void *data, size_t len) +{ + struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + unsigned long flags; + struct urb *urb; + int rc; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mctp_usb->usbdev, + usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), + data, len, mctp_usb_out_complete, tx_ctx); + + if (mctp_usb->span) + urb->transfer_flags |= URB_ZERO_PACKET; + + usb_anchor_urb(urb, &mctp_usb->tx_anchor); + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc) { + netdev_dbg(mctp_usb->netdev, "TX urb submit failed, %d\n", rc); + usb_unanchor_urb(urb); + usb_free_urb(urb); + } else { + spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags); + mctp_usb->tx_qmem += len; + if (mctp_usb->tx_qmem >= TX_QMEM_MAX) + netif_stop_queue(mctp_usb->netdev); + spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags); } - kfree_skb(skb); + return rc; } +static const struct mctp_usblib_tx_ops tx_ops = { + .send = mctp_usb_tx_send, +}; + static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct mctp_usb *mctp_usb = netdev_priv(dev); - struct mctp_usb_hdr *hdr; - unsigned int plen; - struct urb *urb; - int rc; + bool more = netdev_xmit_more(); - plen = skb->len; + mctp_usblib_tx_push(dev, &mctp_usb->tx, skb, more); - if (plen + sizeof(*hdr) > MCTP_USB_XFER_SIZE) - goto err_drop; - - rc = skb_cow_head(skb, sizeof(*hdr)); - if (rc) - goto err_drop; - - hdr = skb_push(skb, sizeof(*hdr)); - if (!hdr) - goto err_drop; - - hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); - hdr->rsvd = 0; - hdr->len = plen + sizeof(*hdr); - - urb = mctp_usb->tx_urb; - - usb_fill_bulk_urb(urb, mctp_usb->usbdev, - usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), - skb->data, skb->len, - mctp_usb_out_complete, skb); - - /* Stops TX queue first to prevent race condition with URB complete */ - netif_stop_queue(dev); - rc = usb_submit_urb(urb, GFP_ATOMIC); - if (rc) { - netif_wake_queue(dev); - goto err_drop; - } - - return NETDEV_TX_OK; - -err_drop: - dev_dstats_tx_dropped(dev); - kfree_skb(skb); return NETDEV_TX_OK; } @@ -125,24 +145,23 @@ static const unsigned long RX_RETRY_DELAY = HZ / 4; static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) { unsigned long flags; - struct sk_buff *skb; + size_t len; + void *buf; int rc; - skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_XFER_SIZE, gfp); - if (!skb) { - rc = -ENOMEM; + rc = mctp_usblib_rx_prepare(mctp_usb->netdev, &mctp_usb->rx, + &buf, &len, gfp); + if (rc) goto err_retry; - } usb_fill_bulk_urb(mctp_usb->rx_urb, mctp_usb->usbdev, usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in), - skb->data, MCTP_USB_XFER_SIZE, - mctp_usb_in_complete, skb); + buf, len, mctp_usb_in_complete, mctp_usb); rc = usb_submit_urb(mctp_usb->rx_urb, gfp); if (rc) { netdev_dbg(mctp_usb->netdev, "rx urb submit failure: %d\n", rc); - kfree_skb(skb); + mctp_usblib_rx_cancel(&mctp_usb->rx); if (rc == -ENOMEM) goto err_retry; } @@ -157,14 +176,34 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) return 0; } +static const unsigned int rx_err_max = 10; + +/* Returns -1 if we have hit excessive errors, zero otherwise. */ +static int mctp_usb_in_urb_err(struct mctp_usb *mctp_usb, int status, + bool stalled) +{ + mctp_usblib_rx_cancel(&mctp_usb->rx); + + if (!mctp_usb->in_err_count++) + mctp_usb->in_err_orig = status; + + if (mctp_usb->in_err_count >= rx_err_max) { + netdev_err(mctp_usb->netdev, + "excessive errors from%s IN EP, first: %d\n", + stalled ? " (stalled)" : "", + mctp_usb->in_err_orig); + return -1; + } + + return 0; +} + static void mctp_usb_in_complete(struct urb *urb) { - struct sk_buff *skb = urb->context; - struct net_device *netdev = skb->dev; - struct mctp_usb *mctp_usb = netdev_priv(netdev); - struct mctp_skb_cb *cb; - unsigned int len; - int status; + struct mctp_usb *mctp_usb = urb->context; + struct net_device *netdev = mctp_usb->netdev; + unsigned long flags; + int rc, status; status = urb->status; @@ -172,80 +211,43 @@ static void mctp_usb_in_complete(struct urb *urb) case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: - case -EPROTO: - kfree_skb(skb); + /* device shutdown, don't resubmit */ + mctp_usblib_rx_cancel(&mctp_usb->rx); return; - case 0: - break; + + case -EPIPE: + /* endpoint stall: clear halt, which will cause a resubmit */ + rc = mctp_usb_in_urb_err(mctp_usb, status, true); + if (rc) + return; + + mctp_usb->clear_halt = true; + spin_lock_irqsave(&mctp_usb->rx_lock, flags); + if (!mctp_usb->rx_stopped) + schedule_delayed_work(&mctp_usb->rx_retry_work, + RX_RETRY_DELAY); + spin_unlock_irqrestore(&mctp_usb->rx_lock, flags); + return; + default: netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); - kfree_skb(skb); - return; + fallthrough; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -EOVERFLOW: + /* possibly transient; record first failure, resubmit */ + rc = mctp_usb_in_urb_err(mctp_usb, status, false); + if (rc) + return; + break; + + case 0: + mctp_usblib_rx_complete(netdev, &mctp_usb->rx, urb->actual_length); + mctp_usb->in_err_count = 0; + break; } - len = urb->actual_length; - __skb_put(skb, len); - - while (skb) { - struct sk_buff *skb2 = NULL; - struct mctp_usb_hdr *hdr; - u8 pkt_len; /* length of MCTP packet, no USB header */ - - skb_reset_mac_header(skb); - hdr = skb_pull_data(skb, sizeof(*hdr)); - if (!hdr) - break; - - if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { - netdev_dbg(netdev, "rx: invalid id %04x\n", - be16_to_cpu(hdr->id)); - break; - } - - if (hdr->len < - sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { - netdev_dbg(netdev, "rx: short packet (hdr) %d\n", - hdr->len); - break; - } - - /* we know we have at least sizeof(struct mctp_usb_hdr) here */ - pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); - if (pkt_len > skb->len) { - netdev_dbg(netdev, - "rx: short packet (xfer) %d, actual %d\n", - hdr->len, skb->len); - break; - } - - if (pkt_len < skb->len) { - /* more packets may follow - clone to a new - * skb to use on the next iteration - */ - skb2 = skb_clone(skb, GFP_ATOMIC); - if (skb2) { - if (!skb_pull(skb2, pkt_len)) { - kfree_skb(skb2); - skb2 = NULL; - } - } - skb_trim(skb, pkt_len); - } - - dev_dstats_rx_add(netdev, skb->len); - - skb->protocol = htons(ETH_P_MCTP); - skb_reset_network_header(skb); - cb = __mctp_cb(skb); - cb->halen = 0; - netif_rx(skb); - - skb = skb2; - } - - if (skb) - kfree_skb(skb); - mctp_usb_rx_queue(mctp_usb, GFP_ATOMIC); } @@ -253,6 +255,30 @@ static void mctp_usb_rx_retry_work(struct work_struct *work) { struct mctp_usb *mctp_usb = container_of(work, struct mctp_usb, rx_retry_work.work); + unsigned long flags; + int rc; + + /* We are only called when rx completions are suspended */ + if (mctp_usb->clear_halt) { + int pipe = usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in); + + rc = usb_clear_halt(mctp_usb->usbdev, pipe); + if (rc) { + netdev_err(mctp_usb->netdev, + "can't clear IN EP halt: %d\n", rc); + + if (++mctp_usb->in_err_count >= rx_err_max) + return; + + spin_lock_irqsave(&mctp_usb->rx_lock, flags); + if (!mctp_usb->rx_stopped) + schedule_delayed_work(&mctp_usb->rx_retry_work, + RX_RETRY_DELAY); + spin_unlock_irqrestore(&mctp_usb->rx_lock, flags); + return; + } + mctp_usb->clear_halt = false; + } mctp_usb_rx_queue(mctp_usb, GFP_KERNEL); } @@ -262,6 +288,8 @@ static int mctp_usb_open(struct net_device *dev) struct mctp_usb *mctp_usb = netdev_priv(dev); WRITE_ONCE(mctp_usb->rx_stopped, false); + mctp_usb->clear_halt = false; + mctp_usb->in_err_count = 0; netif_start_queue(dev); @@ -284,7 +312,11 @@ static int mctp_usb_stop(struct net_device *dev) flush_delayed_work(&mctp_usb->rx_retry_work); usb_kill_urb(mctp_usb->rx_urb); - usb_kill_urb(mctp_usb->tx_urb); + + usb_kill_anchored_urbs(&mctp_usb->tx_anchor); + + mctp_usblib_tx_cancel(&mctp_usb->tx, dev, SKB_DROP_REASON_DEV_READY); + mctp_usblib_rx_cancel(&mctp_usb->rx); return 0; } @@ -301,7 +333,7 @@ static void mctp_usb_netdev_setup(struct net_device *dev) dev->mtu = MCTP_USB_MTU_MIN; dev->min_mtu = MCTP_USB_MTU_MIN; - dev->max_mtu = MCTP_USB_MTU_MAX; + dev->max_mtu = MCTP_USB_1_0_MTU_MAX; dev->hard_header_len = sizeof(struct mctp_usb_hdr); dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; @@ -317,6 +349,7 @@ static int mctp_usb_probe(struct usb_interface *intf, struct usb_host_interface *iface_desc; struct net_device *netdev; struct mctp_usb *dev; + bool span; int rc; /* only one alternate */ @@ -328,6 +361,8 @@ static int mctp_usb_probe(struct usb_interface *intf, return rc; } + span = iface_desc->desc.bInterfaceSubClass == MCTP_USB_SUBCLASS_SPAN; + netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM, mctp_usb_netdev_setup); if (!netdev) @@ -335,33 +370,46 @@ static int mctp_usb_probe(struct usb_interface *intf, SET_NETDEV_DEV(netdev, &intf->dev); dev = netdev_priv(netdev); + dev->span = span; dev->netdev = netdev; dev->usbdev = interface_to_usbdev(intf); dev->intf = intf; spin_lock_init(&dev->rx_lock); + if (dev->span) + netdev->max_mtu = MCTP_USB_1_1_MTU_MAX; + spin_lock_init(&dev->tx_qmem_lock); usb_set_intfdata(intf, dev); + rc = mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize), + dev->span); + if (rc) + goto err_free_netdev; + mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, dev->span); + init_usb_anchor(&dev->tx_anchor); + dev->ep_in = ep_in->bEndpointAddress; dev->ep_out = ep_out->bEndpointAddress; - dev->tx_urb = usb_alloc_urb(0, GFP_KERNEL); dev->rx_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->tx_urb || !dev->rx_urb) { + if (!dev->rx_urb) { rc = -ENOMEM; - goto err_free_urbs; + goto err_fini_rxtx; } INIT_DELAYED_WORK(&dev->rx_retry_work, mctp_usb_rx_retry_work); rc = mctp_register_netdev(netdev, NULL, MCTP_PHYS_BINDING_USB); if (rc) - goto err_free_urbs; + goto err_free_urb; return 0; -err_free_urbs: - usb_free_urb(dev->tx_urb); +err_free_urb: usb_free_urb(dev->rx_urb); +err_fini_rxtx: + mctp_usblib_tx_fini(&dev->tx); + mctp_usblib_rx_fini(&dev->rx); +err_free_netdev: free_netdev(netdev); return rc; } @@ -371,13 +419,15 @@ static void mctp_usb_disconnect(struct usb_interface *intf) struct mctp_usb *dev = usb_get_intfdata(intf); mctp_unregister_netdev(dev->netdev); - usb_free_urb(dev->tx_urb); + mctp_usblib_rx_fini(&dev->rx); + mctp_usblib_tx_fini(&dev->tx); usb_free_urb(dev->rx_urb); free_netdev(dev->netdev); } static const struct usb_device_id mctp_usb_devices[] = { - { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) }, + { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_BASE, 0x1) }, + { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_SPAN, 0x1) }, { 0 }, }; diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c new file mode 100644 index 000000000000..9df401a914ff --- /dev/null +++ b/drivers/net/mctp/mctp-usblib-test.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library, + * unit test definitions. + * + * Copyright (C) 2026 Code Construct Pty Ltd + */ + +#include +#include +#include +#include +#include +#include +#include + +struct mctp_usblib_test_dev { + struct net_device *ndev; + struct mctp_dev *mdev; + struct sk_buff_head rx_pkts; +}; + +struct mctp_usblib_test_ctx { + struct mctp_usblib_test_dev *dev; + struct mctp_route rt; +}; + +static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb, + struct net_device *ndev) +{ + /* we don't track any TXed packets at present */ + kfree_skb(skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops mctp_test_netdev_ops = { + .ndo_start_xmit = mctp_usblib_dev_tx, +}; + +static const u16 ep_maxpacket = 512; +static const mctp_eid_t local_eid = 8; + +static void mctp_usblib_dev_setup(struct net_device *ndev) +{ + ndev->type = ARPHRD_MCTP; + ndev->mtu = 8192; + ndev->flags = IFF_NOARP; + ndev->netdev_ops = &mctp_test_netdev_ops; + ndev->needs_free_netdev = true; + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; +} + +static void mctp_usblib_test_dev_action(void *data) +{ + struct mctp_usblib_test_dev *dev = data; + + skb_queue_purge(&dev->rx_pkts); + if (dev->mdev) + mctp_dev_put(dev->mdev); + unregister_netdev(dev->ndev); +} + +static struct mctp_usblib_test_dev * +mctp_usblib_test_create_dev(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct net_device *ndev; + int rc; + + ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM, + mctp_usblib_dev_setup); + if (!ndev) + return NULL; + + dev = netdev_priv(ndev); + dev->ndev = ndev; + skb_queue_head_init(&dev->rx_pkts); + + rc = register_netdev(ndev); + if (rc) { + free_netdev(ndev); + return NULL; + } + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev); + if (rc) + return NULL; + + rcu_read_lock(); + dev->mdev = __mctp_dev_get(ndev); + if (dev->mdev) + dev->mdev->net = mctp_default_net(dev_net(ndev)); + rcu_read_unlock(); + + if (!dev->mdev) + return NULL; + + rtnl_lock(); + rc = dev_open(ndev, NULL); + rtnl_unlock(); + if (rc) + return NULL; + + return dev; +} + +static int mctp_usblib_test_dst_output(struct mctp_dst *dst, + struct sk_buff *skb) +{ + struct mctp_usblib_test_dev *dev = netdev_priv(skb->dev); + + skb_queue_tail(&dev->rx_pkts, skb); + + return 0; +} + +static void mctp_usblib_test_fini_action(void *data) +{ + struct mctp_usblib_test_ctx *ctx = data; + + /* The device will have been destroyed, so ->rt will be unlinked. + * Just ensure that the refcount is as expected. + */ + KUNIT_EXPECT_TRUE(current->kunit_test, + refcount_dec_and_test(&ctx->rt.refs)); + + kfree(ctx); +} + +static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test) +{ + struct mctp_usblib_test_ctx *ctx; + struct mctp_route *rt; + int rc; + + ctx = kzalloc_obj(*ctx); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + INIT_LIST_HEAD(&ctx->rt.list); + rt = &ctx->rt; + refcount_set(&rt->refs, 1); + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx); + KUNIT_ASSERT_EQ(test, rc, 0); + + ctx->dev = mctp_usblib_test_create_dev(test); + KUNIT_ASSERT_NOT_NULL(test, ctx->dev); + + rt->min = local_eid; + rt->max = local_eid; + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->type = RTN_LOCAL; + rt->dev = ctx->dev->mdev; + rt->output = mctp_usblib_test_dst_output; + + rtnl_lock(); + list_add_rcu(&ctx->rt.list, &init_net.mctp.routes); + refcount_inc(&rt->refs); + rtnl_unlock(); + + return ctx; +} + +/* Init a MCTP-over-USB packet within a buffer. @len is the length of the + * buffer to write, @payload_len is the reported size of the MCTP-over-USB + * packet. + */ +static void mctp_usblib_test_init_pkt(void *data, size_t len, + size_t payload_len) +{ + struct { + struct mctp_usb_hdr usb; + struct mctp_hdr mctp; + } hdr; + + hdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID); + hdr.usb.len = cpu_to_be16(payload_len); + hdr.mctp.ver = 1; + hdr.mctp.dest = local_eid; + hdr.mctp.src = 0; + hdr.mctp.flags_seq_tag = 0; + + memcpy(data, &hdr, min(len, sizeof(hdr))); + if (len > sizeof(hdr)) + memset(data + sizeof(hdr), 0, len - sizeof(hdr)); +} + +static void action_rx_fini(void *data) +{ + struct mctp_usblib_rx *rx = data; + + mctp_usblib_rx_fini(rx); + kfree(rx); +} + +static struct mctp_usblib_rx * +mctp_usblib_test_rx_init(struct kunit *test, bool span) +{ + struct mctp_usblib_rx *rx; + int rc; + + rx = kzalloc_obj(*rx); + if (rx) { + rc = kunit_add_action_or_reset(test, action_rx_fini, rx); + KUNIT_ASSERT_EQ(test, rc, 0); + } + KUNIT_ASSERT_NOT_NULL(test, rx); + + rc = mctp_usblib_rx_init(rx, ep_maxpacket, span); + KUNIT_ASSERT_EQ(test, rc, 0); + + return rx; +} + +/* Wrappers for usblib's rx_complete callback, which is intended to be called + * from atomic context + */ +static int mctp_usblib_test_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len) +{ + int rc; + + local_bh_disable(); + rc = mctp_usblib_rx_complete(netdev, rx, len); + local_bh_enable(); + + return rc; +} + +/* Single packet, starting on a transfer boundary, contained entirely within + * the transfer + */ +static void mctp_usblib_test_rx_single(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + struct sk_buff *skb; + size_t len; + void *buf; + int rc; + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + /* we should always have a maxpacket of transfer available */ + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + mctp_usblib_test_init_pkt(buf, 8, 8); + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, 8); + KUNIT_ASSERT_EQ(test, rc, 0); + + skb = __skb_dequeue(&dev->rx_pkts); + KUNIT_EXPECT_NOT_NULL(test, skb); + if (skb) + KUNIT_EXPECT_EQ(test, skb->len, 4); + kfree_skb(skb); +} + +struct mctp_usblib_test_pkt_span { + const char *name; + size_t n_pkts; + size_t pkts[6]; + size_t n_xfers; + size_t xfers[6]; +}; + +static void +mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +static void +mctp_usblib_test_pkt_span_validate(struct kunit *test, + const struct mctp_usblib_test_pkt_span *span, + size_t *len) +{ + size_t pkt_len = 0, xfer_len = 0; + unsigned int i; + + for (i = 0; i < span->n_pkts; i++) { + KUNIT_ASSERT_GE_MSG(test, span->pkts[i], 8, + "pkt[%u] len too small (%zu) for %s", + i, span->pkts[i], span->name); + pkt_len += span->pkts[i]; + } + + for (i = 0; i < span->n_xfers; i++) + xfer_len += span->xfers[i]; + + KUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len, + "invalid pkt_len (%zu) != xfer_len (%zu) for %s", + pkt_len, xfer_len, span->name); + + *len = pkt_len; +} + +static void mctp_usblib_test_rx_pkt_span(struct kunit *test) +{ + const struct mctp_usblib_test_pkt_span *pkt_span = test->param_value; + size_t len, xfer_len, off, xfer_off; + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + unsigned int i; + u8 *pktbuf; + void *buf; + int rc; + + mctp_usblib_test_pkt_span_validate(test, pkt_span, &len); + pktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pktbuf); + + /* lay out packets */ + for (off = 0, i = 0; i < pkt_span->n_pkts; i++) { + len = pkt_span->pkts[i]; + mctp_usblib_test_init_pkt(pktbuf + off, len, len); + off += len; + } + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + /* feed transfers */ + for (off = 0, xfer_off = 0, i = 0; i < pkt_span->n_xfers;) { + xfer_len = pkt_span->xfers[i] - xfer_off; + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + len = min(len, xfer_len); + memcpy(buf, pktbuf + off, len); + + if (len == xfer_len) { + /* whole/end xfer, proceed to next */ + xfer_off = 0; + i++; + } else { + /* partial */ + xfer_off += len; + } + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, len); + KUNIT_ASSERT_EQ(test, rc, 0); + off += len; + } + + /* check received packets */ + KUNIT_EXPECT_EQ(test, dev->rx_pkts.qlen, pkt_span->n_pkts); + for (i = 0; ; i++) { + struct sk_buff *skb = __skb_dequeue(&dev->rx_pkts); + + if (!skb) + break; + + if (i < pkt_span->n_pkts) + KUNIT_EXPECT_EQ(test, skb->len, pkt_span->pkts[i] - 4); + + kfree_skb(skb); + } +} + +static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = { + /* One packet completely within a transfer */ + { "1p1x-complete", 1, { 8 }, 1, { 8 } }, + /* Two small packets combined within one transfer */ + { "2p1x-combined", 2, { 8, 8 }, 1, { 16 } }, + /* A packet split over two transfers, at the MCTP payload */ + { "1p2x-split-payload", 1, { 16 }, 2, { 8, 8 } }, + /* A packet split over two transfers, at the USB transport header */ + { "1p2x-split-usbhdr", 1, { 16 }, 2, { 2, 14 } }, + /* A packet split over two transfers, at the MCTP header */ + { "1p2x-split-mctphdr", 1, { 16 }, 2, { 6, 10 } }, + /* Single packet split over 3 transfers, middle entirely continuation */ + { "1p3x-split", 1, { 12 }, 3, { 4, 4, 4 } }, + /* Max-sized single transfer */ + { "1p1x-large", 1, { 8191 }, 1, { 8191 } }, + /* Two large packets, split at the worst-case for allocation, with a + * single byte continuing the span + */ + { "2p2x-large-split", 2, { 8190, 8190 }, 2, { 8191, 8189 } }, +}; + +KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans, + mctp_usblib_test_pkt_span_to_desc); + +static struct kunit_case mctp_usblib_test_cases[] = { + KUNIT_CASE(mctp_usblib_test_rx_single), + KUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span, + mctp_usblib_test_rx_pkt_span_gen_params), + {} +}; + +static struct kunit_suite mctp_usblib_test_suite = { + .name = "mctp-usblib", + .test_cases = mctp_usblib_test_cases, +}; + +kunit_test_suite(mctp_usblib_test_suite); diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c new file mode 100644 index 000000000000..31997f989026 --- /dev/null +++ b/drivers/net/mctp/mctp-usblib.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library + * + * DSP0283 is available at: + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf + * + * Copyright (C) 2024-2026 Code Construct Pty Ltd + */ + +#include +#include +#include +#include +#include +#include + +int mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span) +{ + if (!ep_pktlen) + return -EINVAL; + + if (ep_pktlen & ~USB_ENDPOINT_MAXP_MASK) + return -EINVAL; + + memset(rx, 0, sizeof(*rx)); + rx->span = span; + rx->ep_pktlen = ep_pktlen; + + return 0; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_init); + +void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx) +{ + kfree_skb(rx->skb); +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_fini); + +/* + * Prepare a transfer buffer for future completion; *bufp and *lenp will + * be populated on success. + */ +int mctp_usblib_rx_prepare(struct net_device *netdev, + struct mctp_usblib_rx *rx, + void **bufp, size_t *lenp, gfp_t gfp) +{ + struct sk_buff *skb = rx->skb; + unsigned int len = 0; + + if (skb && skb->len >= MCTP_USB_1_1_PKTLEN_MAX) { + /* something must have gone terribly wrong. clear and restart */ + mctp_usblib_rx_cancel(rx); + skb = NULL; + } + + len = rx->span ? roundup(MCTP_USB_1_1_PKTLEN_MAX, rx->ep_pktlen) + : MCTP_USB_1_0_XFER_SIZE; + + if (!skb) { + skb = __netdev_alloc_skb(netdev, len, gfp); + if (!skb) + return -ENOMEM; + + } else if (skb->cloned || skb_tailroom(skb) < rx->ep_pktlen) { + /* We always need to realloc if ->cloned, as we cannot + * resubmit the (now-shared) skb buffer for possible DMA. + * + * Otherwise (if we have an un-cloned SKB): just ensure we + * have sufficient space to prevent babble. Since we allocated + * for max size in the last prepare (and have not consumed any + * of that space for a prior MCTP packet, because !cloned), we + * have sufficient data to finish the current MCTP packet. + */ + struct sk_buff *skb2; + + skb2 = skb_copy_expand(skb, 0, len, gfp); + if (!skb2) + return -ENOMEM; + dev_kfree_skb_any(skb); + skb = skb2; + } + + rx->skb = skb; + + /* Spanning mode allows ZLPs, so we don't require exactly one + * transfer packet. If we have extra tailroom, may as well use it, + * and we have ensured that the tailroom >= ep_pktlen. + */ + if (rx->span) + len = rounddown(skb_tailroom(skb), rx->ep_pktlen); + + *bufp = skb_tail_pointer(skb); + *lenp = len; + + return 0; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_prepare); + +static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb) +{ + struct pcpu_dstats *dstats = this_cpu_ptr(netdev->dstats); + struct mctp_skb_cb *cb; + unsigned long flags; + + skb_reset_mac_header(skb); + skb_pull(skb, sizeof(struct mctp_usb_hdr)); + + /* we're called from an URB completion handler, and cannot assume local + * irqs are always disabled + */ + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->rx_packets); + u64_stats_add(&dstats->rx_bytes, skb->len); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + + skb->protocol = htons(ETH_P_MCTP); + skb_reset_network_header(skb); + cb = __mctp_cb(skb); + cb->halen = 0; + netif_rx(skb); +} + +static void mctp_usblib_rx_stats_single_drop(struct net_device *dev) +{ + struct pcpu_dstats *dstats = this_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->rx_drops); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); +} + +/* + * Receive a USB completion of @len bytes of incoming data. We will then split + * this into packets and netif_rx() each. Intended to be called in atomic + * contexts - ie., URB completion. + * + * Assumes @netdev uses dstats. + */ +int mctp_usblib_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len) +{ + struct sk_buff *skb = rx->skb; + int rc = 0; + + __skb_put(skb, len); + + for (;;) { + struct mctp_usb_hdr *hdr; + struct sk_buff *skb2; + /* length of MCTP packet, including USB header */ + u16 pkt_len; + + /* no header yet, resubmit for the rest of the packet */ + if (skb->len < sizeof(*hdr)) { + if (!rx->span) { + netdev_dbg(netdev, + "rx: tiny xfer (%d) in non-span mode", + skb->len); + rc = -ENOMSG; + goto err_reset; + } + break; + } + + hdr = (struct mctp_usb_hdr *)skb->data; + + if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { + /* By resetting here, will start the next IN transfer + * at the beginning of the new skb. This will mean + * we re-sync when we next see a spanned packet aligned + * with the start of a transfer. + * + * In non-spanning mode, this just means we'll drop + * the current transfer only + */ + netdev_dbg(netdev, "rx: invalid id %04x\n", + be16_to_cpu(hdr->id)); + rc = -EPROTO; + goto err_reset; + } + + pkt_len = be16_to_cpu(hdr->len); + /* v1.1, with span enabled, has a 13-bit length */ + pkt_len &= rx->span ? + MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX; + if (pkt_len < sizeof(*hdr) + sizeof(struct mctp_hdr)) { + netdev_dbg(netdev, "rx: invalid len %d\n", pkt_len); + rc = -EPROTO; + goto err_reset; + } + + /* span continues to the next transfer, resubmit */ + if (pkt_len > skb->len) { + if (!rx->span) { + netdev_dbg(netdev, + "rx: short xfer (%d vs %d) in non-span mode", + pkt_len, skb->len); + rc = -EPROTO; + goto err_reset; + } + break; + } + + /* we have (exactly) a complete packet, RX it directly */ + if (pkt_len == skb->len) { + mctp_usblib_rx(netdev, skb); + rx->skb = NULL; + break; + } + + /* more packets follow - RX a clone so that we can continue + * processing the current SKB, which may be the start of a + * span. + */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2) { + skb_trim(skb2, pkt_len); + mctp_usblib_rx(netdev, skb2); + } else { + mctp_usblib_rx_stats_single_drop(netdev); + } + skb_pull(skb, pkt_len); + } + + return 0; + +err_reset: + dev_kfree_skb_any(rx->skb); + rx->skb = NULL; + return rc; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete); + +/* + * Cancel a rx context; subsequent prepare/complete calls will not be a + * continuation of any data already received. + */ +void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx) +{ + dev_kfree_skb_any(rx->skb); + rx->skb = NULL; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel); + +/* transmit context: encapsulates one transfer */ +struct mctp_usblib_tx_ctx { + struct mctp_usblib_tx *tx; + struct sk_buff_head skbs; + unsigned int buf_len, len; + enum mctp_usblib_tx_buf_type { + TX_SINGLE, + TX_FLAT, + } buf_type; + u8 buf[] ____cacheline_aligned; +}; + +void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, + const struct mctp_usblib_tx_ops *ops, + void *priv, bool span) +{ + memset(tx, 0, sizeof(*tx)); + tx->ops = *ops; + tx->priv = priv; + tx->span = span; + spin_lock_init(&tx->lock); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_init); + +static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx) +{ + return ctx->buf_type == TX_SINGLE ? 0 : ctx->buf_len - ctx->len; +} + +static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx) +{ + /* Use the baseline length (ie, BTU) as an approximate + * "reasonably-sized" packet we could expect. If there is + * insufficient capacity for that, then send. + */ + const size_t pkt_len = MCTP_USB_BTU + sizeof(struct mctp_usb_hdr); + + return mctp_usblib_tx_avail(ctx) < pkt_len; +} + +/* + * Returns zero on success, non-zero on failure - indicating that the new skb + * could not be appended. So, errors reported here to the TX path will result + * in the TX being transmitted. + */ +static int mctp_usblib_tx_append(struct mctp_usblib_tx_ctx *ctx, + struct sk_buff *skb) +{ + if (ctx->buf_type == TX_SINGLE) + return -EINVAL; + + if (mctp_usblib_tx_avail(ctx) < skb->len) + return -ENOBUFS; + + __skb_queue_tail(&ctx->skbs, skb); + + ctx->len += skb->len; + + return 0; +} + +static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx) +{ + void *buf; + + /* If we have a qlen of 1, we only ended up packing a single skb, + * despite allocating for multiple. Skip the copy and send directly + * from the skb data. + */ + if (ctx->buf_type == TX_SINGLE || ctx->skbs.qlen == 1) { + buf = ctx->skbs.next->data; + + } else if (ctx->buf_type == TX_FLAT) { + struct sk_buff *skb; + size_t pos = 0; + + skb_queue_walk(&ctx->skbs, skb) { + skb_copy_bits(skb, 0, ctx->buf + pos, skb->len); + pos += skb->len; + } + + buf = ctx->buf; + } else { + return -EINVAL; + } + + return ctx->tx->ops.send(ctx, buf, ctx->len); +} + +static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx, + enum skb_drop_reason reason) +{ + struct sk_buff *skb; + + if (!ctx) + return; + + while ((skb = __skb_dequeue(&ctx->skbs)) != NULL) + dev_kfree_skb_any_reason(skb, reason); + kfree(ctx); +} + +void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx) +{ + return tx_ctx->tx->priv; +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_ctx_priv); + +/* caller must ensure the tx & completion path is quiesced */ +void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx) +{ + mctp_usblib_tx_ctx_free(tx->cur_ctx, SKB_DROP_REASON_NOT_SPECIFIED); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini); + +/* Max size of a spanned TX. Since we allocate a separate span buffer, limit + * the tx-time allocations to 4k. Larger packets will be sent as single + * transfers. + */ +static const unsigned int TX_SPAN_MAX = 4096 - sizeof(struct mctp_usblib_tx_ctx); + +static struct mctp_usblib_tx_ctx * +mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb, + bool single) +{ + enum mctp_usblib_tx_buf_type type; + struct mctp_usblib_tx_ctx *ctx; + size_t sz = 0; + + if (single || skb->len > TX_SPAN_MAX) { + type = TX_SINGLE; + } else { + type = TX_FLAT; + sz = tx->span ? TX_SPAN_MAX : MCTP_USB_1_0_XFER_SIZE; + } + + ctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC); + if (!ctx) + return NULL; + + ctx->tx = tx; + ctx->buf_type = type; + ctx->buf_len = sz; + ctx->len = skb->len; + skb_queue_head_init(&ctx->skbs); + __skb_queue_tail(&ctx->skbs, skb); + + return ctx; +} + +static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx, + struct net_device *dev, + bool ok) +{ + struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + if (ok) { + /* Only include the network-layer data in tx stats; we know + * that there is a 4-byte header pushed to all skbs in + * tx_skb_prepare() + */ + u64 n = ctx->skbs.qlen; + s64 len = ctx->len - (n * sizeof(struct mctp_usb_hdr)); + + u64_stats_add(&dstats->tx_packets, n); + u64_stats_add(&dstats->tx_bytes, len); + } else { + u64_stats_add(&dstats->tx_drops, ctx->skbs.qlen); + } + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + put_cpu_ptr(dev->dstats); +} + +static void mctp_usblib_tx_stats_single_drop(struct net_device *dev) +{ + struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->tx_drops); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + put_cpu_ptr(dev->dstats); +} + +/* + * Completion for the ->send() op. This will update netdev stats and + * free the tx context. + * + * Likely called from (atomic) URB completion context. + */ +void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx, + struct net_device *dev, bool ok) +{ + enum skb_drop_reason reason = + ok ? SKB_CONSUMED : SKB_DROP_REASON_NOT_SPECIFIED; + + mctp_usblib_tx_stats_update(tx_ctx, dev, ok); + mctp_usblib_tx_ctx_free(tx_ctx, reason); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete); + +/* Prepare a skb for push() + * + * On error, populates @reason. + */ +static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span, + enum skb_drop_reason *reason) +{ + unsigned long plen, max_len; + struct mctp_usb_hdr *hdr; + int rc; + + max_len = span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX; + + plen = skb->len; + if (plen + sizeof(*hdr) > max_len) { + *reason = SKB_DROP_REASON_PKT_TOO_BIG; + return -EMSGSIZE; + } + + rc = skb_cow_head(skb, sizeof(*hdr)); + if (rc) { + *reason = SKB_DROP_REASON_NOMEM; + return rc; + } + + hdr = skb_push(skb, sizeof(*hdr)); + if (!hdr) { + *reason = SKB_DROP_REASON_NOMEM; + return -ENOMEM; + } + + hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); + hdr->len = cpu_to_be16(plen + sizeof(*hdr)); + + return 0; +} + +/* + * Push a new skb to the transfer. May result in zero or more calls to + * ops->send(). + * + * Takes ownership of @skb, including on error. + */ +int mctp_usblib_tx_push(struct net_device *dev, + struct mctp_usblib_tx *tx, + struct sk_buff *skb, bool more) +{ + struct mctp_usblib_tx_ctx *ctx, *send_ctx = NULL; + enum skb_drop_reason reason; + const int max_tries = 3; + unsigned long flags; + int try = 1, rc; + + rc = mctp_usblib_tx_skb_prepare(skb, tx->span, &reason); + if (rc) { + mctp_usblib_tx_stats_single_drop(dev); + kfree_skb_reason(skb, reason); + /* we may still need to proceed, in case an existing ctx + * is now sendable (ie.: !more). + */ + skb = NULL; + } + + reason = SKB_DROP_REASON_NOT_SPECIFIED; +retry: + /* Try and queue to the current context. We exit this critical section + * with a few bits of state: + * - send_ctx: indicating a prior context that needs to be sent + * - skb: indicating that a skb still needs to be queued/sent + */ + spin_lock_irqsave(&tx->lock, flags); + ctx = tx->cur_ctx; + if (ctx) { + if (skb) { + rc = mctp_usblib_tx_append(ctx, skb); + if (rc) { + /* can't append to the pending tx - detach for + * sending, and we'll create a new tx below. + */ + swap(tx->cur_ctx, send_ctx); + } else { + /* we have queued */ + skb = NULL; + if (!more || mctp_usblib_tx_should_send(ctx)) + swap(tx->cur_ctx, send_ctx); + } + } else if (!more) { + swap(tx->cur_ctx, send_ctx); + } + } + spin_unlock_irqrestore(&tx->lock, flags); + + if (send_ctx) { + rc = mctp_usblib_tx_send(send_ctx); + if (rc) { + mctp_usblib_tx_stats_update(send_ctx, dev, false); + mctp_usblib_tx_ctx_free(send_ctx, reason); + } + send_ctx = NULL; + } + + /* we have either queued, or the prepare failed; nothing more to do */ + if (!skb) + return 0; + + ctx = mctp_usblib_tx_ctx_create(tx, skb, !more); + if (!ctx) { + netdev_dbg(dev, "TX context create failed\n"); + mctp_usblib_tx_stats_single_drop(dev); + kfree_skb(skb); + return -ENOMEM; + } + + /* if we're ready to send now, no need to enqueue */ + if (!more || mctp_usblib_tx_should_send(ctx)) { + rc = mctp_usblib_tx_send(ctx); + if (rc) { + mctp_usblib_tx_stats_update(ctx, dev, false); + mctp_usblib_tx_ctx_free(ctx, reason); + } + return 0; + } + + spin_lock_irqsave(&tx->lock, flags); + if (!tx->cur_ctx) { + tx->cur_ctx = ctx; + ctx = NULL; + } + spin_unlock_irqrestore(&tx->lock, flags); + + /* we may have lost the race with a concurrent tx; shouldn't happen, as + * ndo_start_xmit should be serialised over one queue, but try again + * from the top, as we may be able to queue the skb to that context. + */ + if (ctx) { + /* unlink the new (sole) skb, we don't want it freed with ctx */ + __skb_queue_head_init(&ctx->skbs); + mctp_usblib_tx_ctx_free(ctx, reason); + if (++try > max_tries) { + kfree_skb(skb); + mctp_usblib_tx_stats_single_drop(dev); + return -EBUSY; + } + goto retry; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_push); + +/* Cancel a tx: any un-sent context is released. */ +void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev, + enum skb_drop_reason reason) +{ + struct mctp_usblib_tx_ctx *ctx = NULL; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + swap(tx->cur_ctx, ctx); + spin_unlock_irqrestore(&tx->lock, flags); + + if (!ctx) + return; + + mctp_usblib_tx_stats_update(ctx, dev, false); + mctp_usblib_tx_ctx_free(ctx, reason); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeremy Kerr "); +MODULE_DESCRIPTION("MCTP USB transport library"); + +#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST) +#include "mctp-usblib-test.c" +#endif diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index e57121019153..a05229838cb4 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -3,7 +3,8 @@ # MDIO Layer Configuration # -if PHYLIB +menu "MDIO controller drivers" + depends on PHYLIB config FWNODE_MDIO def_tristate (ACPI || OF) || COMPILE_TEST @@ -198,7 +199,7 @@ config MDIO_THUNDER ThunderX SoCs when the MDIO bus device appears as a PCI device. -comment "MDIO Multiplexers" +menu "MDIO Multiplexers" config MDIO_BUS_MUX tristate @@ -288,5 +289,5 @@ config MDIO_BUS_MUX_MMIOREG Currently, only 8/16/32 bits registers are supported. - -endif +endmenu +endmenu diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 862001d09aa8..b358e5c36735 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -37,8 +37,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -46,6 +48,7 @@ #include #include #include +#include MODULE_AUTHOR("Matt Mackall "); MODULE_DESCRIPTION("Console driver for network interfaces"); @@ -61,6 +64,19 @@ MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_USERDATA_ITEMS 256 #define MAX_PRINT_CHUNK 1000 +/* + * Sizing for the per-target fallback skb pool consulted by find_skb() + * when its GFP_ATOMIC allocation fails so messages still get out under + * memory pressure. + */ +#define MAX_UDP_CHUNK 1460 +#define MAX_SKBS 32 +#define MAX_SKB_SIZE \ + (sizeof(struct ethhdr) + \ + sizeof(struct iphdr) + \ + sizeof(struct udphdr) + \ + MAX_UDP_CHUNK) + static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@/[tgt-macaddr]"); @@ -135,13 +151,27 @@ enum target_state { STATE_DEACTIVATED, }; +/** + * struct netcons_userdata - Formatted userdata payload of a target. + * @rcu: Used to free the payload after a grace period. + * @length: Length of @data, excluding the NUL terminator. + * @data: Formatted " key=value\n" entries, NUL terminated. + * + * Immutable once published, so the transmit path never observes @data and + * @length disagreeing. + */ +struct netcons_userdata { + struct rcu_head rcu; + size_t length; + char data[]; +}; + /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. * @userdata_group: Links to the userdata configfs hierarchy - * @userdata: Cached, formatted string of append - * @userdata_length: String length of userdata. + * @userdata: Cached, formatted userdata payload. RCU protected. * @sysdata: Cached, formatted string of append * @sysdata_fields: Sysdata features enabled. * @msgcounter: Message sent counter. @@ -162,22 +192,27 @@ enum target_state { * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) - * local_port (read-write) - * remote_port (read-write) - * local_ip (read-write) - * remote_ip (read-write) * local_mac (read-only) - * remote_mac (read-write) + * @local_ip: Source IP address of the target (read-write). + * @remote_ip: Destination IP address of the target (read-write). + * @ipv6: Whether the target addresses are IPv6 (read-write). + * @local_port: Source UDP port of the target (read-write). + * @remote_port: Destination UDP port of the target (read-write). + * @remote_mac: Destination ethernet address of the target (read-write). * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target + * @skb_pool: Per-target fallback skb pool consulted by find_skb() when + * its GFP_ATOMIC allocation fails. Lifetime brackets a + * successful netpoll_setup() / netpoll_cleanup() pair on @np. + * @refill_wq: Work item that asynchronously tops @skb_pool back up to + * MAX_SKBS after find_skb() drains an entry. */ struct netconsole_target { struct list_head list; #ifdef CONFIG_NETCONSOLE_DYNAMIC struct config_group group; struct config_group userdata_group; - char *userdata; - size_t userdata_length; + struct netcons_userdata __rcu *userdata; char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; /* bit-wise with sysdata_feature bits */ @@ -190,11 +225,17 @@ struct netconsole_target { bool extended; bool release; struct netpoll np; + union inet_addr local_ip, remote_ip; + bool ipv6; + u16 local_port, remote_port; + u8 remote_mac[ETH_ALEN]; /* protected by target_list_lock; +1 gives scnprintf() room for its * NUL terminator so a full MAX_PRINT_CHUNK payload is not truncated */ char buf[MAX_PRINT_CHUNK + 1]; struct work_struct resume_wq; + struct sk_buff_head skb_pool; + struct work_struct refill_wq; }; #ifdef CONFIG_NETCONSOLE_DYNAMIC @@ -292,11 +333,258 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } +static void refill_skbs(struct netconsole_target *nt) +{ + struct sk_buff_head *skb_pool = &nt->skb_pool; + struct sk_buff *skb; + + while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { + skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); + if (!skb) + break; + + skb_queue_tail(skb_pool, skb); + } +} + +static void refill_skbs_work_handler(struct work_struct *work) +{ + struct netconsole_target *nt = + container_of(work, struct netconsole_target, refill_wq); + + refill_skbs(nt); +} + +/* Seed the per-target skb pool that find_skb() falls back to. The queue + * head and refill work are set up once in alloc_and_init(); this only + * (re)fills the pool. Pair with netconsole_skb_pool_flush(). + */ +static void netconsole_skb_pool_init(struct netconsole_target *nt) +{ + refill_skbs(nt); +} + +static void netconsole_skb_pool_flush(struct netconsole_target *nt) +{ + cancel_work_sync(&nt->refill_wq); + skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); +} + +static void netcons_wait_carrier(struct netpoll *np, struct net_device *ndev) +{ + unsigned long atmost; + + atmost = jiffies + netpoll_get_carrier_timeout() * HZ; + while (!netif_carrier_ok(ndev)) { + if (time_after(jiffies, atmost)) { + np_notice(np, "timeout waiting for carrier\n"); + break; + } + msleep(1); + } +} + +/* + * Returns a pointer to a string representation of the identifier used + * to select the egress interface for the given netpoll instance. buf + * is used to format np->dev_mac when np->dev_name is empty; bufsz must + * be at least MAC_ADDR_STR_LEN + 1 to fit the formatted MAC address + * and its NUL terminator. + */ +static char *netcons_egress_dev(struct netpoll *np, char *buf, size_t bufsz) +{ + if (np->dev_name[0]) + return np->dev_name; + + snprintf(buf, bufsz, "%pM", np->dev_mac); + return buf; +} + +/* + * Populate the target's local_ip with the IPv6 address from ndev. + */ +static int netcons_take_ipv6(struct netconsole_target *nt, + struct net_device *ndev) +{ + char buf[MAC_ADDR_STR_LEN + 1]; + struct netpoll *np = &nt->np; + int err = -EDESTADDRREQ; + struct inet6_dev *idev; + + if (!IS_ENABLED(CONFIG_IPV6)) { + np_err(np, "IPv6 is not supported %s, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + return -EINVAL; + } + + idev = __in6_dev_get(ndev); + if (idev) { + struct inet6_ifaddr *ifp; + + read_lock_bh(&idev->lock); + list_for_each_entry(ifp, &idev->addr_list, if_list) { + if (!!(ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) != + !!(ipv6_addr_type(&nt->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) + continue; + /* Got the IP, let's return */ + nt->local_ip.in6 = ifp->addr; + err = 0; + break; + } + read_unlock_bh(&idev->lock); + } + if (err) { + np_err(np, "no IPv6 address for %s, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + return err; + } + + np_info(np, "local IPv6 %pI6c\n", &nt->local_ip.in6); + return 0; +} + +/* + * Populate the target's local_ip with the IPv4 address from ndev. + */ +static int netcons_take_ipv4(struct netconsole_target *nt, + struct net_device *ndev) +{ + char buf[MAC_ADDR_STR_LEN + 1]; + struct netpoll *np = &nt->np; + const struct in_ifaddr *ifa; + struct in_device *in_dev; + + in_dev = __in_dev_get_rtnl(ndev); + if (!in_dev) { + np_err(np, "no IP address for %s, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + return -EDESTADDRREQ; + } + + ifa = rtnl_dereference(in_dev->ifa_list); + if (!ifa) { + np_err(np, "no IP address for %s, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + return -EDESTADDRREQ; + } + + nt->local_ip.ip = ifa->ifa_local; + np_info(np, "local IP %pI4\n", &nt->local_ip.ip); + + return 0; +} + +/* + * Test whether the caller left nt->local_ip unset, so that + * netcons_netpoll_setup() should auto-populate it from the egress device. + * + * nt->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), + * so an IPv6 address whose first 4 bytes are zero (e.g. ::1, ::2, + * IPv4-mapped ::ffff:a.b.c.d) must not be tested via the IPv4 arm — + * doing so would misclassify a caller-supplied address as unset and + * silently overwrite it with whatever address the device exposes. + */ +static bool netcons_local_ip_unset(const struct netconsole_target *nt) +{ + if (nt->ipv6) + return ipv6_addr_any(&nt->local_ip.in6); + return !nt->local_ip.ip; +} + +static int netcons_netpoll_setup(struct netconsole_target *nt) +{ + struct net *net = current->nsproxy->net_ns; + char buf[MAC_ADDR_STR_LEN + 1]; + struct net_device *ndev = NULL; + struct netpoll *np = &nt->np; + bool ip_overwritten = false; + int err; + + rtnl_lock(); + if (np->dev_name[0]) + ndev = __dev_get_by_name(net, np->dev_name); + else if (is_valid_ether_addr(np->dev_mac)) + ndev = dev_getbyhwaddr(net, ARPHRD_ETHER, np->dev_mac); + + if (!ndev) { + np_err(np, "%s doesn't exist, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + err = -ENODEV; + goto unlock; + } + netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL); + + if (netdev_master_upper_dev_get(ndev)) { + np_err(np, "%s is a slave device, aborting\n", + netcons_egress_dev(np, buf, sizeof(buf))); + err = -EBUSY; + goto put; + } + + if (!netif_running(ndev)) { + np_info(np, "device %s not up yet, forcing it\n", + netcons_egress_dev(np, buf, sizeof(buf))); + + err = dev_open(ndev, NULL); + if (err) { + np_err(np, "failed to open %s\n", ndev->name); + goto put; + } + + rtnl_unlock(); + netcons_wait_carrier(np, ndev); + rtnl_lock(); + } + + if (netcons_local_ip_unset(nt)) { + if (!nt->ipv6) { + err = netcons_take_ipv4(nt, ndev); + if (err) + goto put; + } else { + err = netcons_take_ipv6(nt, ndev); + if (err) + goto put; + } + ip_overwritten = true; + } + + err = __netpoll_setup(np, ndev); + if (err) + goto put; + rtnl_unlock(); + + /* Make sure all NAPI polls which started before dev->npinfo + * was visible have exited before we start calling NAPI poll. + * NAPI skips locking if dev->npinfo is NULL. + */ + synchronize_rcu(); + + return 0; + +put: + DEBUG_NET_WARN_ON_ONCE(np->dev); + if (ip_overwritten) + memset(&nt->local_ip, 0, sizeof(nt->local_ip)); + netdev_put(ndev, &np->dev_tracker); +unlock: + rtnl_unlock(); + return err; +} + /* Attempts to resume logging to a deactivated target. */ static void resume_target(struct netconsole_target *nt) { - if (netpoll_setup(&nt->np)) { + /* Initialise the skb pool before netpoll_setup() makes nt->np.dev + * visible to target_list walkers (e.g. netconsole_netdev_event), + * which otherwise may move the target to the cleanup list and + * call netconsole_skb_pool_flush() on uninitialised state. + */ + netconsole_skb_pool_init(nt); + + if (netcons_netpoll_setup(nt)) { /* netpoll fails setup once, do not try again. */ + netconsole_skb_pool_flush(nt); nt->state = STATE_DISABLED; return; } @@ -358,6 +646,7 @@ static void process_resume_target(struct work_struct *work) rtnl_lock(); if (nt->state == STATE_ENABLED && nt->np.dev && nt->np.dev->reg_state != NETREG_REGISTERED) { + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); nt->state = STATE_DISABLED; } @@ -393,11 +682,14 @@ static struct netconsole_target *alloc_and_init(void) nt->np.name = "netconsole"; strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); - nt->np.local_port = 6665; - nt->np.remote_port = 6666; - eth_broadcast_addr(nt->np.remote_mac); + nt->local_port = 6665; + nt->remote_port = 6666; + eth_broadcast_addr(nt->remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); + /* Set up the skb pool primitives once; enabling only refills it. */ + skb_queue_head_init(&nt->skb_pool); + INIT_WORK(&nt->refill_wq, refill_skbs_work_handler); return nt; } @@ -417,6 +709,7 @@ static void netconsole_process_cleanups_core(void) list_for_each_entry_safe(nt, tmp, &target_cleanup_list, list) { /* all entries in the cleanup_list needs to be disabled */ WARN_ON_ONCE(nt->state == STATE_ENABLED); + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); /* moved the cleaned target to target_list. Need to hold both * locks @@ -429,21 +722,23 @@ static void netconsole_process_cleanups_core(void) mutex_unlock(&target_cleanup_list_lock); } -static void netconsole_print_banner(struct netpoll *np) +static void netconsole_print_banner(struct netconsole_target *nt) { - np_info(np, "local port %d\n", np->local_port); - if (np->ipv6) - np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); + struct netpoll *np = &nt->np; + + np_info(np, "local port %d\n", nt->local_port); + if (nt->ipv6) + np_info(np, "local IPv6 address %pI6c\n", &nt->local_ip.in6); else - np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); + np_info(np, "local IPv4 address %pI4\n", &nt->local_ip.ip); np_info(np, "interface name '%s'\n", np->dev_name); np_info(np, "local ethernet address '%pM'\n", np->dev_mac); - np_info(np, "remote port %d\n", np->remote_port); - if (np->ipv6) - np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); + np_info(np, "remote port %d\n", nt->remote_port); + if (nt->ipv6) + np_info(np, "remote IPv6 address %pI6c\n", &nt->remote_ip.in6); else - np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); - np_info(np, "remote ethernet address %pM\n", np->remote_mac); + np_info(np, "remote IPv4 address %pI4\n", &nt->remote_ip.ip); + np_info(np, "remote ethernet address %pM\n", nt->remote_mac); } /* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is @@ -561,32 +856,32 @@ static ssize_t dev_name_show(struct config_item *item, char *buf) static ssize_t local_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.local_port); + return sysfs_emit(buf, "%d\n", to_target(item)->local_port); } static ssize_t remote_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.remote_port); + return sysfs_emit(buf, "%d\n", to_target(item)->remote_port); } static ssize_t local_ip_show(struct config_item *item, char *buf) { struct netconsole_target *nt = to_target(item); - if (nt->np.ipv6) - return sysfs_emit(buf, "%pI6c\n", &nt->np.local_ip.in6); + if (nt->ipv6) + return sysfs_emit(buf, "%pI6c\n", &nt->local_ip.in6); else - return sysfs_emit(buf, "%pI4\n", &nt->np.local_ip); + return sysfs_emit(buf, "%pI4\n", &nt->local_ip); } static ssize_t remote_ip_show(struct config_item *item, char *buf) { struct netconsole_target *nt = to_target(item); - if (nt->np.ipv6) - return sysfs_emit(buf, "%pI6c\n", &nt->np.remote_ip.in6); + if (nt->ipv6) + return sysfs_emit(buf, "%pI6c\n", &nt->remote_ip.in6); else - return sysfs_emit(buf, "%pI4\n", &nt->np.remote_ip); + return sysfs_emit(buf, "%pI4\n", &nt->remote_ip); } static ssize_t local_mac_show(struct config_item *item, char *buf) @@ -599,7 +894,7 @@ static ssize_t local_mac_show(struct config_item *item, char *buf) static ssize_t remote_mac_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%pM\n", to_target(item)->np.remote_mac); + return sysfs_emit(buf, "%pM\n", to_target(item)->remote_mac); } static ssize_t transmit_errors_show(struct config_item *item, char *buf) @@ -756,11 +1051,21 @@ static ssize_t enabled_store(struct config_item *item, * Skip netconsole_parser_cmdline() -- all the attributes are * already configured via configfs. Just print them out. */ - netconsole_print_banner(&nt->np); + netconsole_print_banner(nt); - ret = netpoll_setup(&nt->np); - if (ret) + /* Initialise the skb pool before netpoll_setup() so the pool + * is valid as soon as nt->np.dev becomes visible to + * target_list walkers (netconsole_netdev_event), which would + * otherwise call netconsole_skb_pool_flush() on uninitialised + * state. + */ + netconsole_skb_pool_init(nt); + + ret = netcons_netpoll_setup(nt); + if (ret) { + netconsole_skb_pool_flush(nt); goto out_unlock; + } nt->state = STATE_ENABLED; pr_info("network logging started\n"); @@ -885,7 +1190,7 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.local_port); + ret = kstrtou16(buf, 10, &nt->local_port); if (ret < 0) goto out_unlock; ret = count; @@ -907,7 +1212,7 @@ static ssize_t remote_port_store(struct config_item *item, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.remote_port); + ret = kstrtou16(buf, 10, &nt->remote_port); if (ret < 0) goto out_unlock; ret = count; @@ -930,10 +1235,10 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - ipv6 = netpoll_parse_ip_addr(buf, &nt->np.local_ip); + ipv6 = netpoll_parse_ip_addr(buf, &nt->local_ip); if (ipv6 == -1) goto out_unlock; - nt->np.ipv6 = !!ipv6; + nt->ipv6 = !!ipv6; ret = count; out_unlock: @@ -955,10 +1260,10 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - ipv6 = netpoll_parse_ip_addr(buf, &nt->np.remote_ip); + ipv6 = netpoll_parse_ip_addr(buf, &nt->remote_ip); if (ipv6 == -1) goto out_unlock; - nt->np.ipv6 = !!ipv6; + nt->ipv6 = !!ipv6; ret = count; out_unlock: @@ -994,7 +1299,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, goto out_unlock; if (buf[MAC_ADDR_STR_LEN] && buf[MAC_ADDR_STR_LEN] != '\n') goto out_unlock; - memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); + memcpy(nt->remote_mac, remote_mac, ETH_ALEN); ret = count; out_unlock: @@ -1059,12 +1364,11 @@ static int calc_userdata_len(struct netconsole_target *nt) static int update_userdata(struct netconsole_target *nt) { + struct netcons_userdata *new = NULL; + struct netcons_userdata *old; struct userdatum *udm_item; struct config_item *item; struct list_head *entry; - char *old_buf = NULL; - char *new_buf = NULL; - unsigned long flags; int offset = 0; int len; @@ -1076,8 +1380,8 @@ static int update_userdata(struct netconsole_target *nt) /* Allocate new buffer */ if (len) { - new_buf = kmalloc(len + 1, GFP_KERNEL); - if (!new_buf) + new = kmalloc_flex(*new, data, len + 1); + if (!new) return -ENOMEM; } @@ -1087,22 +1391,21 @@ static int update_userdata(struct netconsole_target *nt) udm_item = to_userdatum(item); /* Skip userdata with no value set */ if (udm_item->value[0]) { - offset += scnprintf(&new_buf[offset], len + 1 - offset, + offset += scnprintf(&new->data[offset], + len + 1 - offset, " %s=%s\n", item->ci_name, udm_item->value); } } WARN_ON_ONCE(offset != len); + if (new) + new->length = offset; - /* Switch to new buffer and free old buffer */ - spin_lock_irqsave(&target_list_lock, flags); - old_buf = nt->userdata; - nt->userdata = new_buf; - nt->userdata_length = offset; - spin_unlock_irqrestore(&target_list_lock, flags); - - kfree(old_buf); + /* Writers are serialized by dynamic_netconsole_mutex. */ + old = rcu_replace_pointer(nt->userdata, new, + lockdep_is_held(&dynamic_netconsole_mutex)); + kfree_rcu(old, rcu); return 0; } @@ -1392,7 +1695,7 @@ static void netconsole_target_release(struct config_item *item) { struct netconsole_target *nt = to_target(item); - kfree(nt->userdata); + kfree(rcu_access_pointer(nt->userdata)); kfree(nt); } @@ -1481,15 +1784,15 @@ static void drop_netconsole_target(struct config_group *group, mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); - /* A STATE_DEACTIVATED target may have been moved to - * target_cleanup_list by netconsole_netdev_event() but not yet - * processed by netconsole_process_cleanups_core(). Unlinking it below - * hides it from the cleanup worker, so this path has to clean it up - * itself. Record that the target still owns a netpoll before the - * state is downgraded. + /* A target moved to target_cleanup_list by netconsole_netdev_event() + * but not yet processed still owns a netpoll; unlinking it below hides + * it from the cleanup worker, so this path must tear it down itself. + * This covers NETDEV_UNREGISTER (STATE_DEACTIVATED) and + * NETDEV_RELEASE / NETDEV_JOIN (STATE_DISABLED); key off nt->np.dev, + * which stays set until the netpoll is cleaned up. */ needs_cleanup = nt->state == STATE_ENABLED || - nt->state == STATE_DEACTIVATED; + nt->state == STATE_DEACTIVATED || nt->np.dev; /* Disable deactivated target to prevent races between resume attempt * and target removal. */ @@ -1514,8 +1817,10 @@ static void drop_netconsole_target(struct config_group *group, * netpoll_cleanup() is idempotent (it skips when np->dev is NULL), so * it is safe even if the cleanup worker already tore the netpoll down. */ - if (needs_cleanup) + if (needs_cleanup) { + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); + } config_item_put(&nt->group.cg_item); } @@ -1706,7 +2011,7 @@ static struct notifier_block netconsole_netdev_notifier = { * pool locks and is therefore not NMI-safe. Skip the refill when called * from NMI context; the next non-NMI caller will top the pool back up. */ -static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) +static struct sk_buff *netcons_skb_pop(struct netconsole_target *nt, int len) { struct sk_buff *skb; @@ -1718,28 +2023,30 @@ static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) if (!in_nmi()) net_warn_ratelimited("netconsole: dropping message, requested skb len %d exceeds pool buffer size %zu on %s\n", len, (size_t)MAX_SKB_SIZE, - np->dev->name); + nt->np.dev->name); return NULL; } - skb = skb_dequeue(&np->skb_pool); + skb = skb_dequeue(&nt->skb_pool); if (!in_nmi()) - schedule_work(&np->refill_wq); + schedule_work(&nt->refill_wq); return skb; } -static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) +static struct sk_buff *find_skb(struct netconsole_target *nt, int len, + int reserve) { + struct netpoll *np = &nt->np; int count = 0; struct sk_buff *skb; netpoll_zap_completion_queue(); repeat: - skb = alloc_skb(len, GFP_ATOMIC); + skb = alloc_skb(len, GFP_ATOMIC | __GFP_NOWARN); if (!skb) - skb = netcons_skb_pop(np, len); + skb = netcons_skb_pop(nt, len); if (!skb) { if (++count < 10) { @@ -1754,8 +2061,8 @@ static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) return skb; } -static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, - int len) +static void netpoll_udp_checksum(struct netconsole_target *nt, + struct sk_buff *skb, int len) { struct udphdr *udph; int udp_len; @@ -1765,21 +2072,21 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, /* check needs to be set, since it will be consumed in csum_partial */ udph->check = 0; - if (np->ipv6) - udph->check = csum_ipv6_magic(&np->local_ip.in6, - &np->remote_ip.in6, + if (nt->ipv6) + udph->check = csum_ipv6_magic(&nt->local_ip.in6, + &nt->remote_ip.in6, udp_len, IPPROTO_UDP, csum_partial(udph, udp_len, 0)); else - udph->check = csum_tcpudp_magic(np->local_ip.ip, - np->remote_ip.ip, + udph->check = csum_tcpudp_magic(nt->local_ip.ip, + nt->remote_ip.ip, udp_len, IPPROTO_UDP, csum_partial(udph, udp_len, 0)); if (udph->check == 0) udph->check = CSUM_MANGLED_0; } -static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) +static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) { struct udphdr *udph; int udp_len; @@ -1790,28 +2097,30 @@ static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) skb_reset_transport_header(skb); udph = udp_hdr(skb); - udph->source = htons(np->local_port); - udph->dest = htons(np->remote_port); - udph->len = htons(udp_len); + udph->source = htons(nt->local_port); + udph->dest = htons(nt->remote_port); + udp_set_len_short(udph, udp_len); - netpoll_udp_checksum(np, skb, len); + netpoll_udp_checksum(nt, skb, len); } -static void push_eth(struct netpoll *np, struct sk_buff *skb) +static void push_eth(struct netconsole_target *nt, struct sk_buff *skb) { + struct netpoll *np = &nt->np; struct ethhdr *eth; eth = skb_push(skb, ETH_HLEN); skb_reset_mac_header(skb); ether_addr_copy(eth->h_source, np->dev->dev_addr); - ether_addr_copy(eth->h_dest, np->remote_mac); - if (np->ipv6) + ether_addr_copy(eth->h_dest, nt->remote_mac); + if (nt->ipv6) eth->h_proto = htons(ETH_P_IPV6); else eth->h_proto = htons(ETH_P_IP); } -static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) +static void push_ipv4(struct netconsole_target *nt, struct sk_buff *skb, + int len) { static atomic_t ip_ident; struct iphdr *iph; @@ -1832,13 +2141,14 @@ static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) iph->ttl = 64; iph->protocol = IPPROTO_UDP; iph->check = 0; - put_unaligned(np->local_ip.ip, &iph->saddr); - put_unaligned(np->remote_ip.ip, &iph->daddr); + put_unaligned(nt->local_ip.ip, &iph->saddr); + put_unaligned(nt->remote_ip.ip, &iph->daddr); iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); skb->protocol = htons(ETH_P_IP); } -static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) +static void push_ipv6(struct netconsole_target *nt, struct sk_buff *skb, + int len) { struct ipv6hdr *ip6h; @@ -1855,14 +2165,16 @@ static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) ip6h->payload_len = htons(sizeof(struct udphdr) + len); ip6h->nexthdr = IPPROTO_UDP; ip6h->hop_limit = 32; - ip6h->saddr = np->local_ip.in6; - ip6h->daddr = np->remote_ip.in6; + ip6h->saddr = nt->local_ip.in6; + ip6h->daddr = nt->remote_ip.in6; skb->protocol = htons(ETH_P_IPV6); } -static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) +static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, + int len) { + struct netpoll *np = &nt->np; int total_len, ip_len, udp_len; struct sk_buff *skb; @@ -1870,14 +2182,14 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) WARN_ON_ONCE(!irqs_disabled()); udp_len = len + sizeof(struct udphdr); - if (np->ipv6) + if (nt->ipv6) ip_len = udp_len + sizeof(struct ipv6hdr); else ip_len = udp_len + sizeof(struct iphdr); total_len = ip_len + LL_RESERVED_SPACE(np->dev); - skb = find_skb(np, total_len + np->dev->needed_tailroom, + skb = find_skb(nt, total_len + np->dev->needed_tailroom, total_len - len); if (!skb) return -ENOMEM; @@ -1885,12 +2197,12 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) skb_copy_to_linear_data(skb, msg, len); skb_put(skb, len); - push_udp(np, skb, len); - if (np->ipv6) - push_ipv6(np, skb, len); + push_udp(nt, skb, len); + if (nt->ipv6) + push_ipv6(nt, skb, len); else - push_ipv4(np, skb, len); - push_eth(np, skb); + push_ipv4(nt, skb, len); + push_eth(nt, skb); skb->dev = np->dev; return (int)netpoll_send_skb(np, skb); @@ -1908,7 +2220,7 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) */ static void send_udp(struct netconsole_target *nt, const char *msg, int len) { - int result = netpoll_send_udp(&nt->np, msg, len); + int result = netpoll_send_udp(nt, msg, len); if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) { if (result == NET_XMIT_DROP) { @@ -1926,14 +2238,13 @@ static void send_udp(struct netconsole_target *nt, const char *msg, int len) static void send_msg_no_fragmentation(struct netconsole_target *nt, const char *msg, int msg_len, - int release_len) + int release_len, + const struct netcons_userdata *userdata) { - const char *userdata = NULL; const char *sysdata = NULL; const char *release; #ifdef CONFIG_NETCONSOLE_DYNAMIC - userdata = nt->userdata; sysdata = nt->sysdata; #endif @@ -1950,7 +2261,7 @@ static void send_msg_no_fragmentation(struct netconsole_target *nt, if (userdata) msg_len += scnprintf(&nt->buf[msg_len], sizeof(nt->buf) - msg_len, "%s", - userdata); + userdata->data); if (sysdata) msg_len += scnprintf(&nt->buf[msg_len], @@ -1970,7 +2281,8 @@ static void append_release(char *buf) static void send_fragmented_body(struct netconsole_target *nt, const char *msgbody_ptr, int header_len, - int msgbody_len, int sysdata_len) + int msgbody_len, int sysdata_len, + const struct netcons_userdata *userdata) { const char *userdata_ptr = NULL; const char *sysdata_ptr = NULL; @@ -1981,12 +2293,12 @@ static void send_fragmented_body(struct netconsole_target *nt, int userdata_len = 0; #ifdef CONFIG_NETCONSOLE_DYNAMIC - userdata_ptr = nt->userdata; sysdata_ptr = nt->sysdata; - userdata_len = nt->userdata_length; #endif - if (WARN_ON_ONCE(!userdata_ptr && userdata_len != 0)) - return; + if (userdata) { + userdata_ptr = userdata->data; + userdata_len = userdata->length; + } if (WARN_ON_ONCE(!sysdata_ptr && sysdata_len != 0)) return; @@ -2063,7 +2375,8 @@ static void send_msg_fragmented(struct netconsole_target *nt, const char *msg, int msg_len, int release_len, - int sysdata_len) + int sysdata_len, + const struct netcons_userdata *userdata) { int header_len, msgbody_len; const char *msgbody; @@ -2092,7 +2405,7 @@ static void send_msg_fragmented(struct netconsole_target *nt, * will be replaced */ send_fragmented_body(nt, msgbody, header_len, msgbody_len, - sysdata_len); + sysdata_len, userdata); } /** @@ -2107,25 +2420,33 @@ static void send_msg_fragmented(struct netconsole_target *nt, static void send_ext_msg_udp(struct netconsole_target *nt, struct nbcon_write_context *wctxt) { + const struct netcons_userdata *userdata = NULL; int userdata_len = 0; int release_len = 0; int sysdata_len = 0; int len; + /* Keeps the payload picked below alive until the last send_udp(). */ + rcu_read_lock(); + #ifdef CONFIG_NETCONSOLE_DYNAMIC sysdata_len = prepare_sysdata(nt, wctxt); - userdata_len = nt->userdata_length; + userdata = rcu_dereference(nt->userdata); + if (userdata) + userdata_len = userdata->length; #endif if (nt->release) release_len = strlen(init_utsname()->release) + 1; len = wctxt->len + release_len + sysdata_len + userdata_len; if (len <= MAX_PRINT_CHUNK) - return send_msg_no_fragmentation(nt, wctxt->outbuf, - wctxt->len, release_len); + send_msg_no_fragmentation(nt, wctxt->outbuf, wctxt->len, + release_len, userdata); + else + send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len, + sysdata_len, userdata); - return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len, - sysdata_len); + rcu_read_unlock(); } static void send_msg_udp(struct netconsole_target *nt, const char *msg, @@ -2203,8 +2524,9 @@ __releases(&target_list_lock) spin_unlock_irqrestore(&target_list_lock, flags); } -static int netconsole_parser_cmdline(struct netpoll *np, char *opt) +static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) { + struct netpoll *np = &nt->np; bool ipversion_set = false; char *cur = opt; char *delim; @@ -2215,7 +2537,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (!delim) goto parse_failed; *delim = 0; - if (kstrtou16(cur, 10, &np->local_port)) + if (kstrtou16(cur, 10, &nt->local_port)) goto parse_failed; cur = delim; } @@ -2227,11 +2549,11 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (!delim) goto parse_failed; *delim = 0; - ipv6 = netpoll_parse_ip_addr(cur, &np->local_ip); + ipv6 = netpoll_parse_ip_addr(cur, &nt->local_ip); if (ipv6 < 0) goto parse_failed; else - np->ipv6 = (bool)ipv6; + nt->ipv6 = (bool)ipv6; cur = delim; } cur++; @@ -2262,7 +2584,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) *delim = 0; if (*cur == ' ' || *cur == '\t') np_info(np, "warning: whitespace is not allowed\n"); - if (kstrtou16(cur, 10, &np->remote_port)) + if (kstrtou16(cur, 10, &nt->remote_port)) goto parse_failed; cur = delim; } @@ -2273,22 +2595,22 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (!delim) goto parse_failed; *delim = 0; - ipv6 = netpoll_parse_ip_addr(cur, &np->remote_ip); + ipv6 = netpoll_parse_ip_addr(cur, &nt->remote_ip); if (ipv6 < 0) goto parse_failed; - else if (ipversion_set && np->ipv6 != (bool)ipv6) + else if (ipversion_set && nt->ipv6 != (bool)ipv6) goto parse_failed; else - np->ipv6 = (bool)ipv6; + nt->ipv6 = (bool)ipv6; cur = delim + 1; if (*cur != 0) { /* MAC address */ - if (!mac_pton(cur, np->remote_mac)) + if (!mac_pton(cur, nt->remote_mac)) goto parse_failed; } - netconsole_print_banner(np); + netconsole_print_banner(nt); return 0; @@ -2326,14 +2648,22 @@ static struct netconsole_target *alloc_param_target(char *target_config, } /* Parse parameters and setup netpoll */ - err = netconsole_parser_cmdline(&nt->np, target_config); + err = netconsole_parser_cmdline(nt, target_config); if (err) goto fail; - err = netpoll_setup(&nt->np); + /* Initialise the skb pool before netpoll_setup() so the pool is + * valid as soon as nt->np.dev becomes visible. The target is not + * yet on target_list, so a netdev event cannot reach it here, but + * mirror the configfs path for symmetry. + */ + netconsole_skb_pool_init(nt); + + err = netcons_netpoll_setup(nt); if (err) { pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); + netconsole_skb_pool_flush(nt); if (!IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) /* only fail if dynamic reconfiguration is set, * otherwise, keep the target in the list, but disabled. @@ -2355,9 +2685,11 @@ static struct netconsole_target *alloc_param_target(char *target_config, static void free_param_target(struct netconsole_target *nt) { cancel_work_sync(&nt->resume_wq); + if (nt->state == STATE_ENABLED) + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); #ifdef CONFIG_NETCONSOLE_DYNAMIC - kfree(nt->userdata); + kfree(rcu_access_pointer(nt->userdata)); #endif kfree(nt); } diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c index 41483e371f05..5c55c308487b 100644 --- a/drivers/net/netdevsim/bus.c +++ b/drivers/net/netdevsim/bus.c @@ -443,8 +443,6 @@ static const struct bus_type nsim_bus = { .num_vf = nsim_num_vf, }; -#define NSIM_BUS_DEV_MAX_VFS 4 - static struct nsim_bus_dev * nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queues) { @@ -464,7 +462,6 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queu nsim_bus_dev->port_count = port_count; nsim_bus_dev->num_queues = num_queues; nsim_bus_dev->initial_net = current->nsproxy->net_ns; - nsim_bus_dev->max_vfs = NSIM_BUS_DEV_MAX_VFS; /* Disallow using nsim_bus_dev */ smp_store_release(&nsim_bus_dev->init, false); diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index aed9ad5f1b43..feb88ccbbced 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -225,78 +225,6 @@ static const struct file_operations nsim_dev_trap_fa_cookie_fops = { .owner = THIS_MODULE, }; -static ssize_t nsim_bus_dev_max_vfs_read(struct file *file, char __user *data, - size_t count, loff_t *ppos) -{ - struct nsim_dev *nsim_dev = file->private_data; - char buf[11]; - ssize_t len; - - len = scnprintf(buf, sizeof(buf), "%u\n", - READ_ONCE(nsim_dev->nsim_bus_dev->max_vfs)); - - return simple_read_from_buffer(data, count, ppos, buf, len); -} - -static ssize_t nsim_bus_dev_max_vfs_write(struct file *file, - const char __user *data, - size_t count, loff_t *ppos) -{ - struct nsim_vf_config *vfconfigs; - struct nsim_dev *nsim_dev; - char buf[10]; - ssize_t ret; - u32 val; - - if (*ppos != 0) - return 0; - - if (count >= sizeof(buf)) - return -ENOSPC; - - ret = copy_from_user(buf, data, count); - if (ret) - return -EFAULT; - buf[count] = '\0'; - - ret = kstrtouint(buf, 10, &val); - if (ret) - return -EINVAL; - - /* max_vfs limited by the maximum number of provided port indexes */ - if (val > NSIM_DEV_VF_PORT_INDEX_MAX - NSIM_DEV_VF_PORT_INDEX_BASE) - return -ERANGE; - - vfconfigs = kzalloc_objs(struct nsim_vf_config, val, - GFP_KERNEL | __GFP_NOWARN); - if (!vfconfigs) - return -ENOMEM; - - nsim_dev = file->private_data; - devl_lock(priv_to_devlink(nsim_dev)); - /* Reject if VFs are configured */ - if (nsim_dev_get_vfs(nsim_dev)) { - ret = -EBUSY; - } else { - swap(nsim_dev->vfconfigs, vfconfigs); - WRITE_ONCE(nsim_dev->nsim_bus_dev->max_vfs, val); - *ppos += count; - ret = count; - } - devl_unlock(priv_to_devlink(nsim_dev)); - - kfree(vfconfigs); - return ret; -} - -static const struct file_operations nsim_dev_max_vfs_fops = { - .open = simple_open, - .read = nsim_bus_dev_max_vfs_read, - .write = nsim_bus_dev_max_vfs_write, - .llseek = generic_file_llseek, - .owner = THIS_MODULE, -}; - static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev) { char dev_ddir_name[sizeof(DRV_NAME) + 10]; @@ -343,9 +271,6 @@ static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev) debugfs_create_bool("fail_trap_policer_counter_get", 0600, nsim_dev->ddir, &nsim_dev->fail_trap_policer_counter_get); - /* caution, dev_max_vfs write takes devlink lock */ - debugfs_create_file("max_vfs", 0600, nsim_dev->ddir, - nsim_dev, &nsim_dev_max_vfs_fops); nsim_dev->nodes_ddir = debugfs_create_dir("rate_nodes", nsim_dev->ddir); if (IS_ERR(nsim_dev->nodes_ddir)) { @@ -845,7 +770,7 @@ static struct sk_buff *nsim_dev_trap_skb_build(void) udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len); get_random_bytes(&udph->source, sizeof(u16)); get_random_bytes(&udph->dest, sizeof(u16)); - udph->len = htons(sizeof(struct udphdr) + data_len); + udp_set_len_short(udph, sizeof(struct udphdr) + data_len); return skb; } @@ -1673,7 +1598,7 @@ int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev) dev_set_drvdata(&nsim_bus_dev->dev, nsim_dev); nsim_dev->vfconfigs = kzalloc_objs(struct nsim_vf_config, - nsim_bus_dev->max_vfs, + NSIM_BUS_DEV_MAX_VFS, GFP_KERNEL | __GFP_NOWARN); if (!nsim_dev->vfconfigs) { err = -ENOMEM; @@ -1872,7 +1797,7 @@ int nsim_drv_configure_vfs(struct nsim_bus_dev *nsim_bus_dev, ret = -EBUSY; goto exit_unlock; } - if (nsim_bus_dev->max_vfs < num_vfs) { + if (num_vfs > NSIM_BUS_DEV_MAX_VFS) { ret = -ENOMEM; goto exit_unlock; } diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 64f77f93d937..55aec41237b9 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -292,7 +292,6 @@ enum nsim_dev_port_type { }; #define NSIM_DEV_VF_PORT_INDEX_BASE 128 -#define NSIM_DEV_VF_PORT_INDEX_MAX UINT_MAX struct nsim_dev_port { struct list_head list; @@ -472,6 +471,7 @@ nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext) {} int nsim_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data); +#define NSIM_BUS_DEV_MAX_VFS 4 struct nsim_bus_dev { struct device dev; struct list_head list; @@ -480,7 +480,6 @@ struct nsim_bus_dev { struct net *initial_net; /* Purpose of this is to carry net pointer * during the probe time only. */ - unsigned int max_vfs; unsigned int num_vfs; bool init; }; diff --git a/drivers/net/netdevsim/psample.c b/drivers/net/netdevsim/psample.c index 717d157c3ae2..1e71c3da4def 100644 --- a/drivers/net/netdevsim/psample.c +++ b/drivers/net/netdevsim/psample.c @@ -73,7 +73,7 @@ static struct sk_buff *nsim_dev_psample_skb_build(void) udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len); get_random_bytes(&udph->source, sizeof(u16)); get_random_bytes(&udph->dest, sizeof(u16)); - udph->len = htons(sizeof(struct udphdr) + data_len); + udp_set_len_short(udph, sizeof(struct udphdr) + data_len); return skb; } diff --git a/drivers/net/netdevsim/psp.c b/drivers/net/netdevsim/psp.c index 59c990fdc79e..6b3532b5e360 100644 --- a/drivers/net/netdevsim/psp.c +++ b/drivers/net/netdevsim/psp.c @@ -84,6 +84,7 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns, struct iphdr *iph; struct udphdr *uh; __wsum csum; + int udplen; /* Do not decapsulate. Receive the skb with the udp and psp * headers still there as if this is a normal udp packet. @@ -91,19 +92,20 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns, * provide a valid checksum here, so the skb isn't dropped. */ uh = udp_hdr(skb); + udplen = udp_get_len(skb, uh, skb_transport_offset(skb)); csum = skb_checksum(skb, skb_transport_offset(skb), - ntohs(uh->len), 0); + udplen, 0); switch (skb->protocol) { case htons(ETH_P_IP): iph = ip_hdr(skb); - uh->check = udp_v4_check(ntohs(uh->len), iph->saddr, + uh->check = udp_v4_check(udplen, iph->saddr, iph->daddr, csum); break; #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): ip6h = ipv6_hdr(skb); - uh->check = udp_v6_check(ntohs(uh->len), &ip6h->saddr, + uh->check = udp_v6_check(udplen, &ip6h->saddr, &ip6h->daddr, csum); break; #endif diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynxi.c index a753bd88cbc2..7290fc3e5d18 100644 --- a/drivers/net/pcs/pcs-mtk-lynxi.c +++ b/drivers/net/pcs/pcs-mtk-lynxi.c @@ -113,8 +113,11 @@ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs, unsigned int bm, adv; /* Read the BMSR and LPA */ - regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm); - regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv); + if (regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm) || + regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv)) { + state->link = false; + return; + } phylink_mii_c22_pcs_decode_state(state, neg_mode, FIELD_GET(SGMII_BMSR, bm), diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index d8e4d60f5834..e1cca779d2ec 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -29,6 +29,7 @@ static unsigned int pfcp_net_id __read_mostly; struct pfcp_net { struct list_head pfcp_dev_list; + struct mutex lock; }; static void @@ -209,7 +210,10 @@ static int pfcp_newlink(struct net_device *dev, } pn = net_generic(link_net, pfcp_net_id); + + mutex_lock(&pn->lock); list_add(&pfcp->list, &pn->pfcp_dev_list); + mutex_unlock(&pn->lock); netdev_dbg(dev, "registered new PFCP interface\n"); @@ -223,12 +227,26 @@ static int pfcp_newlink(struct net_device *dev, return err; } -static void pfcp_dellink(struct net_device *dev, struct list_head *head) +static void __pfcp_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct pfcp_dev *pfcp = netdev_priv(dev); - list_del(&pfcp->list); - unregister_netdevice_queue(dev, head); + list_del_init(&pfcp->list); + unregister_netdevice_queue_net(net, dev, head); +} + +static void pfcp_dellink(struct net_device *dev, struct list_head *head) +{ + struct pfcp_dev *pfcp = netdev_priv(dev); + struct pfcp_net *pn; + + pn = net_generic(pfcp->net, pfcp_net_id); + + mutex_lock(&pn->lock); + if (!list_empty(&pfcp->list)) + __pfcp_dellink(dev_net(dev), dev, head); + mutex_unlock(&pn->lock); } static struct rtnl_link_ops pfcp_link_ops __read_mostly = { @@ -244,6 +262,8 @@ static int __net_init pfcp_net_init(struct net *net) struct pfcp_net *pn = net_generic(net, pfcp_net_id); INIT_LIST_HEAD(&pn->pfcp_dev_list); + mutex_init(&pn->lock); + return 0; } @@ -253,8 +273,12 @@ static void __net_exit pfcp_net_exit_rtnl(struct net *net, struct pfcp_net *pn = net_generic(net, pfcp_net_id); struct pfcp_dev *pfcp, *pfcp_next; + mutex_lock(&pn->lock); + list_for_each_entry_safe(pfcp, pfcp_next, &pn->pfcp_dev_list, list) - pfcp_dellink(pfcp->dev, dev_to_kill); + __pfcp_dellink(net, pfcp->dev, dev_to_kill); + + mutex_unlock(&pn->lock); } static struct pernet_operations pfcp_net_ops = { diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 099f25dceabb..b4ef927fd4a6 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -136,6 +136,12 @@ config ADIN1100_PHY Currently supports the: - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY +config ADIN1140_PHY + tristate "Analog Devices ADIN1140 10BASE-T1S PHY" + help + Adds support for the Analog Devices, Inc. ADIN1140's internal + 10BASE-T1S PHY. + config AMCC_QT2025_PHY tristate "AMCC QT2025 PHY" depends on RUST_PHYLIB_ABSTRACTIONS @@ -232,6 +238,15 @@ config CORTINA_PHY help Currently supports the CS4340 phy. +config DAP8211R_PHY + tristate "DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY" + depends on OF + help + Support for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + This PHY is designed for enterprise and industrial networking + applications, supporting 10/100/1000 Mbps operation. Supports + RGMII interface with configurable TX/RX clock delays. + config DAVICOM_PHY tristate "Davicom PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index de660ae94945..25c4a3c2429f 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -29,6 +29,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +obj-$(CONFIG_ADIN1140_PHY) += adin1140-phy.o obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o @@ -52,6 +53,7 @@ obj-$(CONFIG_BCM_NET_PHYPTP) += bcm-phy-ptp.o obj-$(CONFIG_BROADCOM_PHY) += broadcom.o obj-$(CONFIG_CICADA_PHY) += cicada.o obj-$(CONFIG_CORTINA_PHY) += cortina.o +obj-$(CONFIG_DAP8211R_PHY) += dap8211r.o obj-$(CONFIG_DAVICOM_PHY) += davicom.o obj-$(CONFIG_DP83640_PHY) += dp83640.o obj-$(CONFIG_DP83822_PHY) += dp83822.o diff --git a/drivers/net/phy/adin1140-phy.c b/drivers/net/phy/adin1140-phy.c new file mode 100644 index 000000000000..d35da4ad680d --- /dev/null +++ b/drivers/net/phy/adin1140-phy.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include + +#define ADIN1140_PHY_ID 0x0283be00 + +#define ADIN1140_PCS_CTRL 0x08f3 +#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14) + +static int adin1140_config_aneg(struct phy_device *phydev) +{ + /* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled. + * However, on the ADIN1140, that field is non-standard, being used + * to control the reset status of the PHY (thus it needs to remain set). + */ + return 0; +} + +static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL, + ADIN1140_PCS_CTRL_LOOPBACK, + enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0); +} + +static int adin1140_read_status(struct phy_device *phydev) +{ + phydev->link = 1; + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_10; + phydev->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static struct phy_driver adin1140_driver[] = { + { + PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID), + .name = "ADIN1140_PHY", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .read_status = adin1140_read_status, + .config_aneg = adin1140_config_aneg, + .set_loopback = adin1140_loopback, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = genphy_c45_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, +}; +module_phy_driver(adin1140_driver); + +static const struct mdio_device_id __maybe_unused adin1140_tbl[] = { + { PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, adin1140_tbl); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/dap8211r.c b/drivers/net/phy/dap8211r.c new file mode 100644 index 000000000000..0bf770f8ebbb --- /dev/null +++ b/drivers/net/phy/dap8211r.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + * + * Specifications: + * - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T + * - IEEE 802.3az-2010 Energy Efficient Ethernet + * - IEEE 1588 SyncE support + * - RGMII + * + * Author: Artem Shimko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAP8211R_PHY_ID 0x0008011B +#define DAP8211R_PHY_ID_MASK 0xFFFFFFFF + +#define DAP8211R_EXT_ADD 0x1E +#define DAP8211R_EXT_DATA 0x1F + +#define DAP8211R_PHY_CON 0xA001 +#define DAP8211R_PHY_SW_RST BIT(15) + +#define DAP8211R_RGMII_CON 0xA003 +#define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0) +#define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10) + +#define DAP8211R_RGMII_CONFIG_MASK (DAP8211R_RGMII_RX_DEL_MASK | DAP8211R_RGMII_TX_DEL_MASK) + +/* + * Hardware reset RX RGMII default delay from the datasheet: 0 * 150ps == 0.00ns + * Used when rx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_TXID to set rx delay. + */ +#define DAP8211R_INITIAL_RX_DEL_VAL 0 + +/* + * Hardware reset TX RGMII default delay from the datasheet: 1 * 150ps == 0.15ns + * Used when tx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_RXID to set tx delay. + */ +#define DAP8211R_INITIAL_TX_DEL_VAL 1 + +/* + * Default RGMII delay: 13 * 150 == 1.95ns + * Used when rx-internal-delay-ps or tx-internal-delay-ps are not specified in DT + * for RGMII ID modes to set tx or rx delay. + */ +#define DAP8211R_DEFAULT_DEL_SEL 0xD + +static const int dap8211r_internal_delay[] = {0, 150, 300, 450, 600, 750, 900, + 1050, 1200, 1350, 1500, 1650, 1800, + 1950, 2100, 2250}; + +#define DAP8211R_DELAY_SIZE ARRAY_SIZE(dap8211r_internal_delay) + +/** + * dap8211r_read_ext() - Read extended register + * @phydev: PHY device structure + * @reg: Extended register address + * + * Reads a PHY extended register using the indirect access method. + * The caller must hold the MDIO bus lock. + * + * Return: Register value on success, or negative error code + */ +static int dap8211r_read_ext(struct phy_device *phydev, u16 reg) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_read(phydev, DAP8211R_EXT_DATA); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_modify_ext() - Modify extended register bits + * @phydev: PHY device structure + * @reg: Extended register address + * @mask: Bit mask of bits to clear + * @set: Bit mask of bits to set + * + * Modifies a PHY extended register using the indirect access method. + * New value = (old value & ~mask) | set. + * The caller must hold the MDIO bus lock. + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_config_init() - Initialize PHY + * @phydev: PHY device structure + * + * Configures the PHY during initialization: + * - RGMII delays based on interface mode + * - Software reset to apply settings (low active, self clear) + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_config_init(struct phy_device *phydev) +{ + u16 set = 0; + int ret, val; + s32 rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + s32 tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + + if (!phy_interface_is_rgmii(phydev)) + return 0; + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID) + rx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, true); + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID) + tx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, false); + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + fallthrough; + case PHY_INTERFACE_MODE_RGMII_TXID: + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + default: + phydev_err(phydev, "Unsupported interface: %d\n", + phydev->interface); + return -EINVAL; + } + + set |= FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK, rx_internal_delay); + set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK, tx_internal_delay); + + ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0); + if (ret) + return ret; + + /* Wait for reset self-clear (from low active to high) */ + ret = read_poll_timeout(dap8211r_read_ext, val, + (val & DAP8211R_PHY_SW_RST), + 20, 200, false, phydev, DAP8211R_PHY_CON); + if (ret) + return ret; + if (val < 0) + return val; + + ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, DAP8211R_RGMII_CONFIG_MASK, set); + if (ret) + return ret; + + return 0; +} + +static struct phy_driver dap8211r_driver[] = { + { + PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID), + .name = "DAP8211R Gigabit Ethernet", + .soft_reset = genphy_soft_reset, + .config_init = dap8211r_config_init, + .read_status = genphy_read_status, + .set_loopback = genphy_loopback, + .config_aneg = genphy_config_aneg, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; +module_phy_driver(dap8211r_driver); + +MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("Artem Shimko "); +MODULE_LICENSE("GPL"); + +static const struct mdio_device_id __maybe_unused dap8211r_tb[] = { + { DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(mdio, dap8211r_tb); + diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c index 98472abdd392..6867e7c6f3b7 100644 --- a/drivers/net/phy/dp83640.c +++ b/drivers/net/phy/dp83640.c @@ -21,6 +21,7 @@ #include #include "dp83640_reg.h" +#include "phylib.h" #define DP83640_PHY_ID 0x20005ce1 #define PAGESEL 0x13 @@ -128,10 +129,6 @@ struct dp83640_private { }; struct dp83640_clock { - /* keeps the instance in the 'phyter_clocks' list */ - struct list_head list; - /* we create one clock instance per MII bus */ - struct mii_bus *bus; /* protects extended registers from concurrent access */ struct mutex extreg_lock; /* remembers which page was last selected */ @@ -146,6 +143,8 @@ struct dp83640_clock { struct list_head phylist; /* reference to our PTP hardware clock */ struct ptp_clock *ptp_clock; + /* protected by the PTP core pin configuration lock */ + struct ptp_pin_desc pin_config[DP83640_N_PINS]; }; /* globals */ @@ -206,10 +205,6 @@ static void dp83640_gpio_defaults(struct ptp_pin_desc *pd) } } -/* a list of clocks and a mutex to protect it */ -static LIST_HEAD(phyter_clocks); -static DEFINE_MUTEX(phyter_clocks_lock); - static void rx_timestamp_work(struct work_struct *work); /* extended register access functions */ @@ -953,13 +948,12 @@ static void decode_status_frame(struct dp83640_private *dp83640, } } -static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) +static void dp83640_clock_init(struct dp83640_clock *clock) { - INIT_LIST_HEAD(&clock->list); - clock->bus = bus; mutex_init(&clock->extreg_lock); mutex_init(&clock->clock_lock); INIT_LIST_HEAD(&clock->phylist); + clock->caps.pin_config = clock->pin_config; clock->caps.owner = THIS_MODULE; sprintf(clock->caps.name, "dp83640 timer"); clock->caps.max_adj = 1953124; @@ -977,14 +971,8 @@ static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) clock->caps.settime64 = ptp_dp83640_settime; clock->caps.enable = ptp_dp83640_enable; clock->caps.verify = ptp_dp83640_verify; - /* - * Convert the module param defaults into a dynamic pin configuration. - */ + /* Initialize the runtime pin configuration from gpio_tab. */ dp83640_gpio_defaults(clock->caps.pin_config); - /* - * Get a reference to this bus instance. - */ - get_device(&bus->dev); } static int choose_this_phy(struct dp83640_clock *clock, @@ -999,58 +987,6 @@ static int choose_this_phy(struct dp83640_clock *clock, return 0; } -static struct dp83640_clock *dp83640_clock_get(struct dp83640_clock *clock) -{ - if (clock) - mutex_lock(&clock->clock_lock); - return clock; -} - -/* - * Look up and lock a clock by bus instance. - * If there is no clock for this bus, then create it first. - */ -static struct dp83640_clock *dp83640_clock_get_bus(struct mii_bus *bus) -{ - struct dp83640_clock *clock = NULL, *tmp; - struct list_head *this; - - mutex_lock(&phyter_clocks_lock); - - list_for_each(this, &phyter_clocks) { - tmp = list_entry(this, struct dp83640_clock, list); - if (tmp->bus == bus) { - clock = tmp; - break; - } - } - if (clock) - goto out; - - clock = kzalloc_obj(struct dp83640_clock); - if (!clock) - goto out; - - clock->caps.pin_config = kzalloc_objs(struct ptp_pin_desc, - DP83640_N_PINS); - if (!clock->caps.pin_config) { - kfree(clock); - clock = NULL; - goto out; - } - dp83640_clock_init(clock, bus); - list_add_tail(&clock->list, &phyter_clocks); -out: - mutex_unlock(&phyter_clocks_lock); - - return dp83640_clock_get(clock); -} - -static void dp83640_clock_put(struct dp83640_clock *clock) -{ - mutex_unlock(&clock->clock_lock); -} - static int dp83640_soft_reset(struct phy_device *phydev) { int ret; @@ -1400,20 +1336,31 @@ static int dp83640_ts_info(struct mii_timestamper *mii_ts, static int dp83640_probe(struct phy_device *phydev) { - struct dp83640_clock *clock; struct dp83640_private *dp83640; - int err = -ENOMEM, i; + struct dp83640_clock *clock; + int err, i; if (phydev->mdio.addr == BROADCAST_ADDR) return 0; - clock = dp83640_clock_get_bus(phydev->mdio.bus); - if (!clock) - goto no_clock; + err = phy_package_join(phydev, BROADCAST_ADDR, sizeof(*clock)); + if (err) + return err; + + clock = phy_package_get_priv(phydev); + /* Ensure other PHY probes wait for shared clock initialization. */ + phy_package_lock(phydev); + if (phy_package_probe_once(phydev)) + dp83640_clock_init(clock); + phy_package_unlock(phydev); + + mutex_lock(&clock->clock_lock); dp83640 = kzalloc_obj(struct dp83640_private); - if (!dp83640) + if (!dp83640) { + err = -ENOMEM; goto no_memory; + } dp83640->phydev = phydev; dp83640->mii_ts.rxtstamp = dp83640_rxtstamp; @@ -1450,15 +1397,20 @@ static int dp83640_probe(struct phy_device *phydev) } else list_add_tail(&dp83640->list, &clock->phylist); - dp83640_clock_put(clock); + mutex_unlock(&clock->clock_lock); + return 0; no_register: clock->chosen = NULL; + clock->ptp_clock = NULL; + phydev->default_timestamp = false; + phydev->mii_ts = NULL; + phydev->priv = NULL; kfree(dp83640); no_memory: - dp83640_clock_put(clock); -no_clock: + mutex_unlock(&clock->clock_lock); + phy_package_leave(phydev); return err; } @@ -1467,7 +1419,6 @@ static void dp83640_remove(struct phy_device *phydev) struct dp83640_clock *clock; struct list_head *this, *next; struct dp83640_private *tmp, *dp83640 = phydev->priv; - bool remove_clock = false; if (phydev->mdio.addr == BROADCAST_ADDR) return; @@ -1480,7 +1431,8 @@ static void dp83640_remove(struct phy_device *phydev) skb_queue_purge(&dp83640->rx_queue); skb_queue_purge(&dp83640->tx_queue); - clock = dp83640_clock_get(dp83640->clock); + clock = dp83640->clock; + mutex_lock(&clock->clock_lock); if (dp83640 == clock->chosen) { ptp_clock_unregister(clock->ptp_clock); @@ -1495,23 +1447,10 @@ static void dp83640_remove(struct phy_device *phydev) } } - if (!clock->chosen && list_empty(&clock->phylist)) - remove_clock = true; - - dp83640_clock_put(clock); + mutex_unlock(&clock->clock_lock); kfree(dp83640); - if (remove_clock) { - mutex_lock(&phyter_clocks_lock); - list_del(&clock->list); - mutex_unlock(&phyter_clocks_lock); - - mutex_destroy(&clock->extreg_lock); - mutex_destroy(&clock->clock_lock); - put_device(&clock->bus->dev); - kfree(clock->caps.pin_config); - kfree(clock); - } + phy_package_leave(phydev); } static struct phy_driver dp83640_driver[] = { diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index 56080d3d2d25..06151f207134 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -8,7 +8,6 @@ #include #include -#include #include #include #include diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig index bb7dc876271e..3b9cf82c0cb2 100644 --- a/drivers/net/phy/mediatek/Kconfig +++ b/drivers/net/phy/mediatek/Kconfig @@ -21,11 +21,20 @@ config MEDIATEK_GE_PHY common operations with MediaTek SoC built-in Gigabit Ethernet PHYs. +config MEDIATEK_FE_SOC_PHY + tristate "MediaTek SoC Fast Ethernet PHYs" + depends on SOC_MT7620 || COMPILE_TEST + help + Support for MediaTek MT7628 built-in Fast Ethernet PHYs. + This driver only sets an initialization bit required for the PHY + to establish 100 Mbps links. All other PHY operations are handled + by the kernel's generic PHY code. + config MEDIATEK_GE_SOC_PHY - tristate "MediaTek SoC Ethernet PHYs" - depends on ARM64 || COMPILE_TEST + tristate "MediaTek SoC Gigabit Ethernet PHYs" + depends on ARM64 || ECONET || COMPILE_TEST depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \ - COMPILE_TEST + ECONET || COMPILE_TEST select MTK_NET_PHYLIB select PHY_PACKAGE help @@ -36,5 +45,9 @@ config MEDIATEK_GE_SOC_PHY present in the SoCs efuse and will dynamically calibrate VCM (common-mode voltage) during startup. + Also include support for the built-in Gigabit Ethernet PHYs of + the Airoha AN7581 and AN7583 SoCs and of the EcoNet EN7528 SoC, + which need no efuse calibration data. + config MTK_NET_PHYLIB tristate diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile index ac57ecc799fc..6f9cacf7f906 100644 --- a/drivers/net/phy/mediatek/Makefile +++ b/drivers/net/phy/mediatek/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_MEDIATEK_2P5GE_PHY) += mtk-2p5ge.o +obj-$(CONFIG_MEDIATEK_FE_SOC_PHY) += mtk-fe-soc.o obj-$(CONFIG_MEDIATEK_GE_PHY) += mtk-ge.o obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mtk-ge-soc.o obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o diff --git a/drivers/net/phy/mediatek/mtk-fe-soc.c b/drivers/net/phy/mediatek/mtk-fe-soc.c new file mode 100644 index 000000000000..0b0d6c1fefbd --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-fe-soc.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT7628 Embedded Switch internal Fast Ethernet PHYs + */ +#include +#include + +#define MTK_FPHY_ID_MT7628 0x03a29410 +#define MTK_EXT_PAGE_ACCESS 0x1f + +static int mt7628_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MTK_EXT_PAGE_ACCESS); +} + +static int mt7628_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page); +} + +static int mt7628_phy_config_init(struct phy_device *phydev) +{ + /* + * This undocumented bit is required for the PHYs to be able to + * establish 100mbps links. + */ + return phy_modify_paged(phydev, 0x8000, 30, BIT(13), BIT(13)); +} + +static struct phy_driver mtk_soc_fe_phy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628), + .name = "MediaTek MT7628 PHY", + .config_init = mt7628_phy_config_init, + .read_page = mt7628_phy_read_page, + .write_page = mt7628_phy_write_page, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; + +module_phy_driver(mtk_soc_fe_phy_driver); +static const struct mdio_device_id __maybe_unused mtk_soc_fe_phy_tbl[] = { + { PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek SoC Fast Ethernet PHY driver"); +MODULE_AUTHOR("Joris Vaisvila "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_soc_fe_phy_tbl); diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c index 9a54949644d5..0041300d4f10 100644 --- a/drivers/net/phy/mediatek/mtk-ge-soc.c +++ b/drivers/net/phy/mediatek/mtk-ge-soc.c @@ -16,6 +16,7 @@ #define MTK_GPHY_ID_MT7981 0x03a29461 #define MTK_GPHY_ID_MT7988 0x03a29481 +#define MTK_GPHY_ID_EN7528 0x03a29491 #define MTK_GPHY_ID_AN7581 0x03a294c1 #define MTK_GPHY_ID_AN7583 0xc0ff0420 @@ -320,6 +321,14 @@ /* Registers on MDIO_MMD_VEND2 */ #define MTK_PHY_LED1_DEFAULT_POLARITIES BIT(1) +/* LED basic control register, part of the same LED block as the LED0/LED1 + * control registers above. The air_en8811h driver describes the same + * register as AIR_PHY_LED_BCR. + */ +#define MTK_PHY_LED_BCR 0x21 +#define MTK_PHY_LED_BCR_CLK_EN BIT(3) +#define MTK_PHY_LED_BCR_EXT_CTRL BIT(15) + #define MTK_PHY_RG_BG_RASEL 0x115 #define MTK_PHY_RG_BG_RASEL_MASK GENMASK(2, 0) @@ -1470,6 +1479,19 @@ static int an7583_phy_config_init(struct phy_device *phydev) return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN); } +static int en7528_phy_config_init(struct phy_device *phydev) +{ + /* The LED controller of the EN7528 powers up with its external + * control disabled, leaving the LED pins dark regardless of what is + * programmed into the LED control registers. Hand the pins over to + * the LED control registers the same way the air_en8811h driver + * does; the mode field of this register is already set out of reset. + */ + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED_BCR, + MTK_PHY_LED_BCR_CLK_EN | + MTK_PHY_LED_BCR_EXT_CTRL); +} + static struct phy_driver mtk_socphy_driver[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981), @@ -1505,6 +1527,18 @@ static struct phy_driver mtk_socphy_driver[] = { .led_hw_control_set = mt798x_phy_led_hw_control_set, .led_hw_control_get = mt798x_phy_led_hw_control_get, }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_EN7528), + .name = "EcoNet EN7528 PHY", + .config_init = en7528_phy_config_init, + .probe = an7581_phy_probe, + .led_blink_set = mt798x_phy_led_blink_set, + .led_brightness_set = mt798x_phy_led_brightness_set, + .led_hw_is_supported = mt798x_phy_led_hw_is_supported, + .led_hw_control_set = mt798x_phy_led_hw_control_set, + .led_hw_control_get = mt798x_phy_led_hw_control_get, + .led_polarity_set = an7581_phy_led_polarity_set, + }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581), .name = "Airoha AN7581 PHY", @@ -1537,6 +1571,7 @@ module_phy_driver(mtk_socphy_driver); static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_EN7528) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583) }, { } diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 55df5efcfc86..ae830781824b 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1161,6 +1161,65 @@ static int ksz9031_set_loopback(struct phy_device *phydev, bool enable, 5000, 500000, true); } +/* KSZ9131-specific sequence to enable loopback, registers are undocumented + * in the datasheet but mentionned in the local loopback mode configuration + * steps. + * + * Without taking these steps, the PHY appears to disable its RXC while in + * loopback mode, which may be needed by some MACs such as stmmac. + */ +static int ksz9131_loopback_enable(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x15, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x16, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x18, 0xeeee); + if (ret) + return ret; + + return phy_write_mmd(phydev, 0x1c, 0x1b, 0xeeee); +} + +/* KSZ9131 datasheet doesn't state how to deal with the MMD 0x1c registers + * when disabling loopback. + * + * Set them back to their measured initial state when disabling loopback, and + * ignore errors while doing so. + */ +static void ksz9131_loopback_disable(struct phy_device *phydev) +{ + phy_write_mmd(phydev, 0x1c, 0x15, 0x6eff); + phy_write_mmd(phydev, 0x1c, 0x16, 0xe6ff); + phy_write_mmd(phydev, 0x1c, 0x18, 0x43ff); + phy_write_mmd(phydev, 0x1c, 0x1b, 0x07ff); +} + +static int ksz9131_set_loopback(struct phy_device *phydev, bool enable, + int speed) +{ + int ret; + + if (enable) { + ret = ksz9131_loopback_enable(phydev); + if (ret) + return ret; + } + + ret = ksz9031_set_loopback(phydev, enable, speed); + + if (ret || !enable) + ksz9131_loopback_disable(phydev); + + return ret; +} + static int ksz9031_of_load_skew_values(struct phy_device *phydev, const struct device_node *of_node, u16 reg, size_t field_sz, @@ -6993,6 +7052,7 @@ static struct phy_driver ksphy_driver[] = { .cable_test_start = ksz9x31_cable_test_start, .cable_test_get_status = ksz9x31_cable_test_get_status, .get_features = ksz9477_get_features, + .set_loopback = ksz9131_set_loopback, }, { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8873MLL), .name = "Micrel KSZ8873MLL Switch", diff --git a/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c index 62b36a318100..3292b2235c8f 100644 --- a/drivers/net/phy/microchip_t1.c +++ b/drivers/net/phy/microchip_t1.c @@ -1028,11 +1028,7 @@ static int lan87xx_read_status(struct phy_device *phydev) if (rc < 0) return rc; - rc = genphy_read_status_fixed(phydev); - if (rc < 0) - return rc; - - return rc; + return genphy_read_status_fixed(phydev); } static int lan87xx_config_aneg(struct phy_device *phydev) diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index e601d56b2507..73c23d311d72 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev) return 0; } -/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and - * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes - * it uses C22 protocol and always uses C22 registers indirect access to access - * C45 registers. This is because, we don't have a clean separation between - * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45 - * registers direct access can't be used which can save multiple SPI bus access. - * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call - * .read_c45/.write_c45 in the OPEN Alliance framework - * drivers/net/ethernet/oa_tc6.c - */ -static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum, - u16 regnum) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_read(bus, addr, devnum, regnum); -} - -static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum, - u16 regnum, u16 val) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_write(bus, addr, devnum, regnum, val); -} - static struct phy_driver microchip_t1s_driver[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), @@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = { .features = PHY_BASIC_T1S_P2MP_FEATURES, .config_init = lan865x_revb_config_init, .read_status = lan86xx_read_status, - .read_mmd = lan865x_phy_read_mmd, - .write_mmd = lan865x_phy_write_mmd, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = lan86xx_plca_set_cfg, .get_plca_status = genphy_c45_plca_get_status, diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 5071605a1a11..c5a2cda8d31b 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -6,6 +6,7 @@ * Author: Frank */ +#include #include #include #include @@ -1064,6 +1065,7 @@ static int yt8521_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct yt8521_priv *priv; + struct clk *clk; int chip_config; u16 mask, val; u32 freq; @@ -1075,6 +1077,11 @@ static int yt8521_probe(struct phy_device *phydev) phydev->priv = priv; + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + chip_config = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG); if (chip_config < 0) return chip_config; @@ -1180,9 +1187,15 @@ static int yt8521_probe(struct phy_device *phydev) static int yt8531_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; + struct clk *clk; u16 mask, val; u32 freq; + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + if (device_property_read_u32(dev, "motorcomm,clk-out-frequency-hz", &freq)) freq = YTPHY_DTS_OUTPUT_CLK_DIS; diff --git a/drivers/net/phy/nxp-tja11xx.c b/drivers/net/phy/nxp-tja11xx.c index 3c38a8ddae2f..079d912a1357 100644 --- a/drivers/net/phy/nxp-tja11xx.c +++ b/drivers/net/phy/nxp-tja11xx.c @@ -620,6 +620,7 @@ static int tja1102_p0_probe(struct phy_device *phydev) return -ENOMEM; priv->phydev = phydev; + phydev->priv = priv; INIT_WORK(&priv->phy_register_work, tja1102_p1_register); ret = tja11xx_hwmon_register(phydev, priv); @@ -631,6 +632,13 @@ static int tja1102_p0_probe(struct phy_device *phydev) return 0; } +static void tja1102_p0_remove(struct phy_device *phydev) +{ + struct tja11xx_priv *priv = phydev->priv; + + cancel_work_sync(&priv->phy_register_work); +} + static int tja1102_match_phy_device(struct phy_device *phydev, bool port0) { int ret; @@ -849,6 +857,7 @@ static struct phy_driver tja11xx_driver[] = { .features = PHY_BASIC_T1_FEATURES, .flags = PHY_POLL_CABLE_TEST, .probe = tja1102_p0_probe, + .remove = tja1102_p0_remove, .soft_reset = tja11xx_soft_reset, .config_aneg = tja11xx_config_aneg, .config_init = tja11xx_config_init, diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 126951741428..870920311f9a 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -384,6 +384,119 @@ int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart) } EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg); +/** + * genphy_c45_pma_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register + * @phydev: target phy_device struct + * + * Return: 0 on success, negative errno on failure. + */ +int genphy_c45_pma_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, + MDIO_CTRL1, val, + !(val & MDIO_CTRL1_RESET), + 5000, 600000, true); +} +EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset); + +/** + * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs + * @phydev: target phy_device struct + * + * Description: Configure the forced or preferred Master/Slave role + * 10GBASE-T control register (MMD 7, Register 0x0020) according to + * IEEE 802.3 standards. + * + * Return: negative errno code on failure, 0 if Master/Slave didn't change, + * or 1 if Master/Slave modes changed. + */ +static int genphy_c45_an_setup_master_slave(struct phy_device *phydev) +{ + u16 ctl = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + break; + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_UNSUPPORTED: + return 0; + default: + phydev_warn(phydev, "Unsupported Master/Slave mode\n"); + return -EOPNOTSUPP; + } + + return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_MS_ENABLE | + MDIO_AN_10GBT_CTRL_MS_VALUE | + MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl); +} + +/** + * genphy_c45_read_master_slave - read master/slave status + * @phydev: target phy_device struct + * + * Description: Read the Master/Slave configuration and status + * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021). + * + * Return: 0 on success, or a negative error code on failure. + */ +static int genphy_c45_read_master_slave(struct phy_device *phydev) +{ + int val; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) { + if (val & MDIO_AN_10GBT_CTRL_MS_VALUE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE; + } else { + if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED; + } + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_STAT_MS_FAULT) { + phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; + } else if (phydev->link) { + if (val & MDIO_AN_10GBT_STAT_MS_RES) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + } else { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + } + + return 0; +} + /** * genphy_c45_aneg_done - return auto-negotiation complete status * @phydev: target phy_device struct @@ -1192,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev) ret = genphy_c45_baset1_read_status(phydev); if (ret < 0) return ret; + } else { + ret = genphy_c45_read_master_slave(phydev); + if (ret < 0) + return ret; } phy_resolve_aneg_linkmode(phydev); @@ -1225,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev) if (ret > 0) changed = true; + if (!genphy_c45_baset1_able(phydev)) { + ret = genphy_c45_an_setup_master_slave(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + } + return genphy_c45_check_and_restart_aneg(phydev, changed); } EXPORT_SYMBOL_GPL(genphy_c45_config_aneg); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 0615228459ef..94b2e85e00a3 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -2770,6 +2770,31 @@ int genphy_read_abilities(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_read_abilities); +/* Some PHYs support direct C45 register access but not C22 indirect + * MMD access (registers 13 and 14). When discovered via C22, phylib + * routes MMD access through the indirect path, which won't work on + * these devices. These helpers bypass indirect access and use the bus + * C45 accessors directly. + */ +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_read(bus, addr, devnum, regnum); +} +EXPORT_SYMBOL(genphy_read_mmd_c45); + +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_write(bus, addr, devnum, regnum, val); +} +EXPORT_SYMBOL(genphy_write_mmd_c45); + /* This is used for the phy device which doesn't support the MMD extended * register access, but it does have side effect when we are trying to access * the MMD register via indirect method. diff --git a/drivers/net/phy/phy_package.c b/drivers/net/phy/phy_package.c index 16ae8d1c1f89..735806c5bea8 100644 --- a/drivers/net/phy/phy_package.c +++ b/drivers/net/phy/phy_package.c @@ -52,6 +52,29 @@ void *phy_package_get_priv(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(phy_package_get_priv); +/** + * phy_package_lock - acquire the PHY package lock + * @phydev: PHY device that has joined the package + * + * Use this to serialize access to package-private data. Release the lock + * with phy_package_unlock(). + */ +void phy_package_lock(struct phy_device *phydev) +{ + mutex_lock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_lock); + +/** + * phy_package_unlock - release the PHY package lock + * @phydev: PHY device that has joined the package + */ +void phy_package_unlock(struct phy_device *phydev) +{ + mutex_unlock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_unlock); + static int phy_package_address(struct phy_device *phydev, unsigned int addr_offset) { diff --git a/drivers/net/phy/phylib.h b/drivers/net/phy/phylib.h index 0fba245f9745..c6e26ac6b28f 100644 --- a/drivers/net/phy/phylib.h +++ b/drivers/net/phy/phylib.h @@ -12,6 +12,8 @@ struct mii_bus; struct device_node *phy_package_get_node(struct phy_device *phydev); void *phy_package_get_priv(struct phy_device *phydev); +void phy_package_lock(struct phy_device *phydev); +void phy_package_unlock(struct phy_device *phydev); int __phy_package_read(struct phy_device *phydev, unsigned int addr_offset, u32 regnum); int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset, diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 18d2ead97aa5..5b8e956902fb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -153,8 +153,7 @@ static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces); * phylink_set_port_modes() - set the port type modes in the ethtool mask * @mask: ethtool link mode mask * - * Sets all the port type modes in the ethtool mask. MAC drivers should - * use this in their 'validate' callback. + * Sets all the port type modes in the ethtool mask. */ void phylink_set_port_modes(unsigned long *mask) { @@ -1040,6 +1039,7 @@ static enum inband_type phylink_get_inband_type(phy_interface_t interface) { switch (interface) { case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_QUSGMII: case PHY_INTERFACE_MODE_USXGMII: @@ -1966,9 +1966,7 @@ EXPORT_SYMBOL_GPL(phylink_destroy); */ bool phylink_expects_phy(struct phylink *pl) { - if (pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && - phy_interface_mode_is_8023z(pl->link_interface))) + if (pl->cfg_link_an_mode == MLO_AN_FIXED) return false; return true; } @@ -2096,9 +2094,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); @@ -2207,9 +2205,7 @@ static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, { u32 flags = 0; - if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && - phy_interface_mode_is_8023z(interface) && !pl->sfp_bus))) + if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED)) return -EINVAL; if (pl->phydev) @@ -3932,9 +3928,9 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); @@ -4183,6 +4179,7 @@ void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state, break; case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) phylink_decode_sgmii_word(state, lpa); @@ -4263,6 +4260,7 @@ int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface, adv |= ADVERTISE_1000XPSE_ASYM; return adv; case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: return 0x0001; default: diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index ba4dc07752b6..6872dbf77856 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -537,7 +537,7 @@ static void at803x_link_change_notify(struct phy_device *phydev) * in the FIFO. In such cases, the FIFO enters an error mode it * cannot recover from by software. */ - if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) { + if (phydev->state == PHY_NOLINK && phy_device_has_reset(phydev)) { struct at803x_context context; at803x_context_save(phydev, &context); diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 0d2321bd18c8..177b62a7b2d1 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -8,7 +8,9 @@ * Copyright (c) 2004 Freescale Semiconductor, Inc. */ #include +#include #include +#include #include #include #include @@ -141,6 +143,10 @@ #define RTL8211F_PHYSICAL_ADDR_WORD1 17 #define RTL8211F_PHYSICAL_ADDR_WORD2 18 +#define RTL8261X_EXT_ADDR_REG 0xa436 +#define RTL8261X_EXT_DATA_REG 0xa438 +#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d + #define RTL822X_VND1_SERDES_OPTION 0x697a #define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0) #define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0 @@ -251,6 +257,70 @@ #define RTL_8221B_VM_CG 0x001cc84a #define RTL_8251B 0x001cc862 #define RTL_8261C 0x001cc890 +#define RTL_8261C_CG 0x001cc898 + +#define RTL8261C_CE_MODEL 0x00 +#define RTL8261D_MODEL 0x81 +#define RTL8261X_INT_AUTONEG_ERROR BIT(0) +#define RTL8261X_INT_PAGE_RECV BIT(2) +#define RTL8261X_INT_AUTONEG_DONE BIT(3) +#define RTL8261X_INT_LINK_CHG BIT(4) +#define RTL8261X_INT_PHY_REG_ACCESS BIT(5) +#define RTL8261X_INT_PME BIT(7) +#define RTL8261X_INT_ALDPS_CHG BIT(9) +#define RTL8261X_INT_JABBER BIT(10) + +#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_JABBER) + +#define RTL8261X_INT_MASK_ALL (RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_PAGE_RECV | \ + RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_PHY_REG_ACCESS | \ + RTL8261X_INT_PME | \ + RTL8261X_INT_ALDPS_CHG | \ + RTL8261X_INT_JABBER) + +#define FW_MAIN_MAGIC 0x52544C38 +#define FW_SUB_MAGIC_8261C 0x32363143 +#define RTL8261X_POLL_TIMEOUT_MS 100 +#define RTL8261X_MAX_MMD_DEV 31 + +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin" +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME); + +enum rtl8261x_fw_op { + OP_WRITE = 0x00, /* Write */ + OP_POLL = 0x02, /* Polling */ +}; + +struct rtl8261x_fw_header { + __le32 main_magic; /* Main magic number */ + __le32 sub_magic; /* Sub magic number */ + __le16 version_major; /* Major version */ + __le16 version_minor; /* Minor version */ + __le16 num_entries; /* Number of entries */ + __le16 reserved; /* Reserved */ + __le32 crc32; /* CRC32 checksum */ +}; + +struct rtl8261x_fw_entry { + __u8 type; /* Operation type (OP_*) */ + __u8 dev; /* MMD device */ + __le16 addr; /* Register address */ + __u8 msb; /* MSB bit position */ + __u8 lsb; /* LSB bit position */ + __le16 value; /* Value to write/compare */ + __le16 timeout_ms; /* Poll timeout in milliseconds */ + __u8 poll_set; /* Poll until equal (1) or not equal (0) */ + __u8 reserved; /* Reserved */ +}; + +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -270,6 +340,11 @@ struct rtl821x_priv { u16 iner; }; +struct rtl8261x_priv { + const char *fw_name; + bool fw_loaded; +}; + static int rtl821x_read_page(struct phy_device *phydev) { return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -310,6 +385,329 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, return phy_restore_page(phydev, oldpage, ret); } +static int rtl8261x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct rtl8261x_priv *priv; + int sub_phy_id, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG, + RTL_8261X_SUB_PHY_ID_ADDR); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG); + if (ret < 0) + return ret; + + sub_phy_id = (ret >> 8) & 0xff; + + switch (sub_phy_id) { + case RTL8261C_CE_MODEL: + priv->fw_name = RTL8261C_CE_FW_NAME; + phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + case RTL8261D_MODEL: + phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + default: + phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id); + return -ENODEV; + } + + return 0; +} + +static int rtl8261x_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret) + return ret; + /* + * Supplement Multi-Gig speeds that may not be automatically detected + * RTL8261X supports 2.5G/5G in addition to standard 10G + */ + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported); + + return 0; +} + +static int rtl8261x_read_status(struct phy_device *phydev) +{ + int ret, val = 0; + + if (phydev->autoneg == AUTONEG_ENABLE) { + ret = genphy_c45_aneg_done(phydev); + if (ret < 0) + return ret; + + if (ret) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_STAT1000)); + if (val < 0) + return val; + } + } + + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw) +{ + const struct rtl8261x_fw_header *hdr; + u32 main_magic, sub_magic; + u32 calc_crc, file_crc; + size_t data_len; + u16 num_entries; + + if (fw->size < FW_HEADER_SIZE) { + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size); + return -EINVAL; + } + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + main_magic = le32_to_cpu(hdr->main_magic); + if (main_magic != FW_MAIN_MAGIC) { + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic); + return -EINVAL; + } + + sub_magic = le32_to_cpu(hdr->sub_magic); + if (sub_magic != FW_SUB_MAGIC_8261C) { + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic); + return -EINVAL; + } + + num_entries = le16_to_cpu(hdr->num_entries); + data_len = num_entries * FW_ENTRY_SIZE; + + if (fw->size != sizeof(*hdr) + data_len) { + phydev_err(phydev, "Firmware size mismatch\n"); + return -EINVAL; + } + + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0; + file_crc = le32_to_cpu(hdr->crc32); + + if (calc_crc != file_crc) { + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n", + calc_crc, file_crc); + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_execute_entry(struct phy_device *phydev, + const struct rtl8261x_fw_entry *entry) +{ + u16 addr, value, timeout_ms; + u8 dev, msb, lsb, poll_set; + u32 bits, expect_val; + int ret, val; + + dev = entry->dev; + addr = le16_to_cpu(entry->addr); + msb = entry->msb; + lsb = entry->lsb; + value = le16_to_cpu(entry->value); + timeout_ms = le16_to_cpu(entry->timeout_ms); + poll_set = entry->poll_set; + + if (timeout_ms == 0) + timeout_ms = RTL8261X_POLL_TIMEOUT_MS; + + if (dev > RTL8261X_MAX_MMD_DEV) { + phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev); + return -EINVAL; + } + + if (msb > 15 || lsb > msb) { + phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb); + return -EINVAL; + } + + switch (entry->type) { + case OP_WRITE: + ret = phy_modify_mmd(phydev, dev, addr, + GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb)); + if (ret) + return ret; + break; + + case OP_POLL: + bits = GENMASK(msb, lsb); + expect_val = (value << lsb) & bits; + + if (poll_set) + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) == expect_val, + 1000, timeout_ms * 1000, false); + else + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) != expect_val, + 1000, timeout_ms * 1000, false); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_load(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + const struct rtl8261x_fw_entry *entry; + const struct rtl8261x_fw_header *hdr; + const struct firmware *fw; + int ret, i; + + if (!priv->fw_name) + return 0; + + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev); + if (ret) { + phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret); + return ret; + } + + ret = rtl8261x_verify_firmware(phydev, fw); + if (ret) + goto release_fw; + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE); + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) { + ret = rtl8261x_fw_execute_entry(phydev, entry); + if (ret) { + phydev_err(phydev, "Entry %d failed: %d\n", i, ret); + goto release_fw; + } + } + + priv->fw_loaded = true; + +release_fw: + release_firmware(fw); + return ret; +} + +static int rtl8261x_config_intr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, + RTL8261X_INT_MASK_DEFAULT); + if (ret < 0) + return ret; + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + } + + return 0; +} + +static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev) +{ + int irq_status; + + irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (!(irq_status & RTL8261X_INT_MASK_ALL)) + return IRQ_NONE; + + if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE | + RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER)) + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int rtl8261x_config_aneg(struct phy_device *phydev) +{ + u16 adv_1g = 0; + int ret; + + ret = genphy_c45_config_aneg(phydev); + if (ret < 0) + return ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->advertising)) + adv_1g = ADVERTISE_1000FULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->advertising)) + adv_1g |= ADVERTISE_1000HALF; + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_CTRL1000), + ADVERTISE_1000FULL | ADVERTISE_1000HALF, + adv_1g); + if (ret < 0) + return ret; + if (ret > 0) + return genphy_c45_restart_aneg(phydev); + + return 0; +} + +static int rtl8261x_config_init(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + + /* The firmware parameters are preserved across IEEE soft resets and + * suspend/resume cycles. Reloading is only necessary after a power + * cycle or hard reset. + */ + if (priv->fw_name && !priv->fw_loaded) + return rtl8261x_fw_load(phydev); + + return 0; +} + static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -3002,6 +3400,19 @@ static struct phy_driver realtek_drvs[] = { .resume = genphy_resume, .read_mmd = genphy_read_mmd_unsupported, .write_mmd = genphy_write_mmd_unsupported, + }, { + PHY_ID_MATCH_EXACT(RTL_8261C_CG), + .name = "Realtek RTL8261C/D 10Gbps PHY", + .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, + .get_features = rtl8261x_get_features, + .config_aneg = rtl8261x_config_aneg, + .read_status = rtl8261x_read_status, + .config_intr = rtl8261x_config_intr, + .handle_interrupt = rtl8261x_handle_interrupt, + .soft_reset = genphy_c45_pma_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, }, }; diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index f520206734da..2ec91466acdf 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -516,6 +516,15 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id, { .vendor = _v, .part = _p, .support = _s, .fixup = _f, } #define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL) #define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f) +/* Like SFP_QUIRK_F, but matches the part as a prefix; the vendor name + * is still matched exactly. Use for modules whose EEPROM vendor PN + * field reads back with garbage past the legitimate characters instead + * of the SFF-8472-mandated space padding, so sfp_strlen can't trim the + * field down to the legitimate length. + */ +#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \ + { .vendor = _v, .part = _p, .support = NULL, .fixup = _f, \ + .part_prefix_match = true } static const struct sfp_quirk sfp_quirks[] = { // Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly @@ -548,6 +557,13 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex, sfp_fixup_ignore_tx_fault), + // Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with + // ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup; + // mask both signals and extend T_START_UP via the potron fixup. The + // PN is the product name (XGS-SFP-ONT-MAC-I) truncated at the 16-byte + // field width, so the field is fully occupied and matches exactly. + SFP_QUIRK_F("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron), + SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp), SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron), @@ -594,6 +610,9 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_F("YV", "SFP+ONU-XGSPON", sfp_fixup_potron), + // HORACO HC-10GE-113C uses Rollball protocol to talk to the PHY. + SFP_QUIRK_F("OEM", "HC-10GE-113C", sfp_fixup_rollball), + // OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault), @@ -605,6 +624,14 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex), SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc), + + // OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and + // LOS indicators and slow startup, just like potron. On cold + // power-up the EEPROM vendor PN field reads back with non-printable + // garbage past the legitimate string instead of space padding, so + // match the part as a prefix. + SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron), + SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball), @@ -626,13 +653,16 @@ static size_t sfp_strlen(const char *str, size_t maxlen) return size; } -static bool sfp_match(const char *qs, const char *str, size_t len) +static bool sfp_match(const char *qs, const char *str, size_t len, bool prefix) { + size_t qs_len; + if (!qs) return true; - if (strlen(qs) != len) + qs_len = strlen(qs); + if (prefix ? qs_len > len : qs_len != len) return false; - return !strncmp(qs, str, len); + return !strncmp(qs, str, qs_len); } static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id) @@ -645,8 +675,9 @@ static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id) ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn)); for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++) - if (sfp_match(q->vendor, id->base.vendor_name, vs) && - sfp_match(q->part, id->base.vendor_pn, ps)) + if (sfp_match(q->vendor, id->base.vendor_name, vs, false) && + sfp_match(q->part, id->base.vendor_pn, ps, + q->part_prefix_match)) return q; return NULL; @@ -1916,7 +1947,11 @@ static void sfp_hwmon_remove(struct sfp *sfp) if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) { hwmon_device_unregister(sfp->hwmon_dev); sfp->hwmon_dev = NULL; + } + + if (!IS_ERR_OR_NULL(sfp->hwmon_name)) { kfree(sfp->hwmon_name); + sfp->hwmon_name = NULL; } } diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h index 879dff7afe6a..19fe29d844a1 100644 --- a/drivers/net/phy/sfp.h +++ b/drivers/net/phy/sfp.h @@ -12,6 +12,7 @@ struct sfp_quirk { void (*support)(const struct sfp_eeprom_id *id, struct sfp_module_caps *caps); void (*fixup)(struct sfp *sfp); + bool part_prefix_match; }; struct sfp_socket_ops { diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index 93a7b0f6c4e7..583426d06381 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -49,8 +49,6 @@ struct asyncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; int tpkt_pos; diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index cacc4c3a37d2..1a610a18893b 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -140,7 +139,6 @@ struct ppp { void *rc_state; /* its internal state 98 */ unsigned long last_xmit; /* jiffies when last pkt sent 9c */ unsigned long last_recv; /* jiffies when last pkt rcvd a0 */ - struct net_device *dev; /* network interface device a4 */ int closing; /* is device closing down? a8 */ #ifdef CONFIG_PPP_MULTILINK int nxchan; /* next channel to send something on */ @@ -176,7 +174,7 @@ struct channel { struct ppp_file file; /* stuff for read/write/poll */ struct list_head list; /* link in all/new_channels list */ struct ppp_channel *chan; /* public channel data structure */ - struct rw_semaphore chan_sem; /* protects `chan' during chan ioctl */ + struct mutex chan_sem; /* protects `chan' during chan ioctl */ spinlock_t downl; /* protects `chan', file.xq dequeue */ struct ppp __rcu *ppp; /* ppp unit we're connected to */ struct net *chan_net; /* the net channel belongs to */ @@ -413,7 +411,7 @@ static int ppp_release(struct inode *unused, struct file *file) ppp = PF_TO_PPP(pf); rtnl_lock(); if (file == ppp->owner) - unregister_netdevice(ppp->dev); + unregister_netdevice(netdev_from_priv(ppp)); rtnl_unlock(); ppp_release_interface(ppp); break; @@ -558,7 +556,7 @@ static __poll_t ppp_poll(struct file *file, poll_table *wait) return 0; poll_wait(file, &pf->rwait, wait); mask = EPOLLOUT | EPOLLWRNORM; - if (skb_peek(&pf->rq)) + if (!skb_queue_empty_lockless(&pf->rq)) mask |= EPOLLIN | EPOLLRDNORM; if (pf->dead) mask |= EPOLLHUP; @@ -788,12 +786,12 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; default: - down_read(&pch->chan_sem); + mutex_lock(&pch->chan_sem); chan = pch->chan; err = -ENOTTY; if (chan && chan->ops->ioctl) err = chan->ops->ioctl(chan, cmd, arg); - up_read(&pch->chan_sem); + mutex_unlock(&pch->chan_sem); } goto out; } @@ -922,7 +920,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } else { WRITE_ONCE(ppp->npmode[i], npi.mode); /* we may be able to transmit more packets now (??) */ - netif_wake_queue(ppp->dev); + netif_wake_queue(netdev_from_priv(ppp)); } err = 0; break; @@ -1148,7 +1146,7 @@ static __net_exit void ppp_exit_rtnl_net(struct net *net, int id; idr_for_each_entry(&pn->units_idr, ppp, id) - ppp_nl_dellink(ppp->dev, dev_to_kill); + ppp_nl_dellink(netdev_from_priv(ppp), dev_to_kill); } static __net_exit void ppp_exit_net(struct net *net) @@ -1171,6 +1169,7 @@ static struct pernet_operations ppp_net_ops = { static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) { + struct net_device *dev = netdev_from_priv(ppp); struct ppp_net *pn = ppp_pernet(ppp->ppp_net); int ret; @@ -1182,8 +1181,8 @@ static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) goto err; if (!ifname_is_set) { while (1) { - snprintf(ppp->dev->name, IFNAMSIZ, "ppp%i", ret); - if (!netdev_name_in_use(ppp->ppp_net, ppp->dev->name)) + snprintf(dev->name, IFNAMSIZ, "ppp%i", ret); + if (!netdev_name_in_use(ppp->ppp_net, dev->name)) break; unit_put(&pn->units_idr, ret); ret = unit_get(&pn->units_idr, ppp, ret + 1); @@ -1211,11 +1210,11 @@ static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) ppp->file.index = ret; if (!ifname_is_set) - snprintf(ppp->dev->name, IFNAMSIZ, "ppp%i", ppp->file.index); + snprintf(dev->name, IFNAMSIZ, "ppp%i", ppp->file.index); mutex_unlock(&pn->all_ppp_mutex); - ret = register_netdevice(ppp->dev); + ret = register_netdevice(dev); if (ret < 0) goto err_unit; @@ -1240,7 +1239,6 @@ static int ppp_dev_configure(struct net *src_net, struct net_device *dev, int err; int cpu; - ppp->dev = dev; ppp->ppp_net = src_net; ppp->mru = PPP_MRU; ppp->owner = conf->file; @@ -1659,7 +1657,7 @@ static void ppp_xmit_flush(struct ppp *ppp) /* If there's no work left to do, tell the core net code that we can * accept some more. */ - netif_wake_queue(ppp->dev); + netif_wake_queue(netdev_from_priv(ppp)); } static void __ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) @@ -1675,7 +1673,7 @@ static void __ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) if (likely(skb_queue_empty(&ppp->file.xq))) { if (unlikely(!ppp_push(ppp, skb))) { skb_queue_tail(&ppp->file.xq, skb); - netif_stop_queue(ppp->dev); + netif_stop_queue(netdev_from_priv(ppp)); } goto out; } @@ -1713,17 +1711,18 @@ static void ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) kfree_skb(skb); if (net_ratelimit()) - netdev_err(ppp->dev, "recursion detected\n"); + netdev_err(netdev_from_priv(ppp), "recursion detected\n"); } static inline struct sk_buff * pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *new_skb; int len; - int new_skb_size = ppp->dev->mtu + - ppp->xcomp->comp_extra + ppp->dev->hard_header_len; - int compressor_skb_size = ppp->dev->mtu + + int new_skb_size = dev->mtu + + ppp->xcomp->comp_extra + dev->hard_header_len; + int compressor_skb_size = dev->mtu + ppp->xcomp->comp_extra + PPP_HDRLEN; if (skb_linearize(skb)) @@ -1732,12 +1731,11 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) new_skb = alloc_skb(new_skb_size, GFP_ATOMIC); if (!new_skb) { if (net_ratelimit()) - netdev_err(ppp->dev, "PPP: no memory (comp pkt)\n"); + netdev_err(dev, "PPP: no memory (comp pkt)\n"); return NULL; } - if (ppp->dev->hard_header_len > PPP_HDRLEN) - skb_reserve(new_skb, - ppp->dev->hard_header_len - PPP_HDRLEN); + if (dev->hard_header_len > PPP_HDRLEN) + skb_reserve(new_skb, dev->hard_header_len - PPP_HDRLEN); /* compressor still expects A/C bytes in hdr */ len = ppp->xcomp->compress(ppp->xc_state, skb->data - 2, @@ -1762,7 +1760,7 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) * the same number. */ if (net_ratelimit()) - netdev_err(ppp->dev, "ppp: compressor dropped pkt\n"); + netdev_err(dev, "ppp: compressor dropped pkt\n"); consume_skb(new_skb); new_skb = NULL; } @@ -1778,13 +1776,14 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) static int ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *skb = *pskb; int proto = PPP_PROTO(skb); struct sk_buff *new_skb; int len; unsigned char *cp; - skb->dev = ppp->dev; + skb->dev = dev; if (proto < 0x8000) { #ifdef CONFIG_PPP_FILTER @@ -1795,7 +1794,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: outbound frame " "not passed\n"); kfree_skb(skb); @@ -1812,7 +1811,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) #endif /* CONFIG_PPP_FILTER */ } - dev_sw_netstats_tx_add(ppp->dev, 1, skb->len - PPP_PROTO_LEN); + dev_sw_netstats_tx_add(dev, 1, skb->len - PPP_PROTO_LEN); switch (proto) { case PPP_IP: @@ -1823,13 +1822,13 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) goto drop; /* try to do VJ TCP header compression */ - new_skb = alloc_skb(skb->len + ppp->dev->hard_header_len - 2, + new_skb = alloc_skb(skb->len + dev->hard_header_len - 2, GFP_ATOMIC); if (!new_skb) { - netdev_err(ppp->dev, "PPP: no memory (VJ comp pkt)\n"); + netdev_err(dev, "PPP: no memory (VJ comp pkt)\n"); goto drop; } - skb_reserve(new_skb, ppp->dev->hard_header_len - 2); + skb_reserve(new_skb, dev->hard_header_len - 2); cp = skb->data + 2; len = slhc_compress(ppp->vj, cp, skb->len - 2, new_skb->data + 2, &cp, @@ -1865,7 +1864,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) proto != PPP_LCP && proto != PPP_CCP) { if (!(ppp->flags & SC_CCP_UP) && (ppp->flags & SC_MUST_COMP)) { if (net_ratelimit()) - netdev_err(ppp->dev, + netdev_err(dev, "ppp: compression required but " "down - pkt dropped.\n"); goto drop; @@ -1893,7 +1892,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) drop: kfree_skb(skb); - DEV_STATS_INC(ppp->dev, tx_errors); + DEV_STATS_INC(dev, tx_errors); return 1; } @@ -1963,6 +1962,7 @@ MODULE_PARM_DESC(mp_protocol_compress, */ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); int len, totlen; int i, bits, hdrlen, mtu; int flen; @@ -2159,8 +2159,8 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) spin_unlock(&pch->downl); err_linearize: if (READ_ONCE(ppp->debug) & 1) - netdev_err(ppp->dev, "PPP: no memory (fragment)\n"); - DEV_STATS_INC(ppp->dev, tx_errors); + netdev_err(dev, "PPP: no memory (fragment)\n"); + DEV_STATS_INC(dev, tx_errors); ++ppp->nxseq; return 1; /* abandon the frame */ } @@ -2333,7 +2333,7 @@ ppp_input(struct ppp_channel *chan, struct sk_buff *skb) if (!ppp_decompress_proto(skb)) { kfree_skb(skb); if (ppp) { - DEV_STATS_INC(ppp->dev, rx_length_errors); + DEV_STATS_INC(netdev_from_priv(ppp), rx_length_errors); ppp_receive_error(ppp); } goto done; @@ -2395,7 +2395,7 @@ ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) static void ppp_receive_error(struct ppp *ppp) { - DEV_STATS_INC(ppp->dev, rx_errors); + DEV_STATS_INC(netdev_from_priv(ppp), rx_errors); if (ppp->vj) slhc_toss(ppp->vj); } @@ -2403,6 +2403,7 @@ ppp_receive_error(struct ppp *ppp) static void ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *ns; int proto, len, npi; @@ -2432,8 +2433,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) /* copy to a new sk_buff with more tailroom */ ns = dev_alloc_skb(skb->len + 128); if (!ns) { - netdev_err(ppp->dev, "PPP: no memory " - "(VJ decomp)\n"); + netdev_err(dev, "PPP: no memory (VJ decomp)\n"); goto err; } skb_reserve(ns, 2); @@ -2446,7 +2446,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) len = slhc_uncompress(ppp->vj, skb->data + 2, skb->len - 2); if (len <= 0) { - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: VJ decompression error\n"); goto err; } @@ -2469,7 +2469,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) goto err; if (slhc_remember(ppp->vj, skb->data + 2, skb->len - 2) <= 0) { - netdev_err(ppp->dev, "PPP: VJ uncompressed error\n"); + netdev_err(dev, "PPP: VJ uncompressed error\n"); goto err; } proto = PPP_IP; @@ -2480,7 +2480,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) break; } - dev_sw_netstats_rx_add(ppp->dev, skb->len - PPP_PROTO_LEN); + dev_sw_netstats_rx_add(dev, skb->len - PPP_PROTO_LEN); npi = proto_to_npindex(proto); if (npi < 0) { @@ -2507,7 +2507,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: inbound frame " "not passed\n"); kfree_skb(skb); @@ -2521,17 +2521,17 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) #endif /* CONFIG_PPP_FILTER */ WRITE_ONCE(ppp->last_recv, jiffies); - if ((ppp->dev->flags & IFF_UP) == 0 || + if ((dev->flags & IFF_UP) == 0 || READ_ONCE(ppp->npmode[npi]) != NPMODE_PASS) { kfree_skb(skb); } else { /* chop off protocol */ skb_pull_rcsum(skb, 2); - skb->dev = ppp->dev; + skb->dev = dev; skb->protocol = htons(npindex_to_ethertype[npi]); skb_reset_mac_header(skb); skb_scrub_packet(skb, !net_eq(ppp->ppp_net, - dev_net(ppp->dev))); + dev_net(dev))); netif_rx(skb); } } @@ -2569,8 +2569,8 @@ ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb) ns = dev_alloc_skb(obuff_size); if (!ns) { - netdev_err(ppp->dev, "ppp_decompress_frame: " - "no memory\n"); + netdev_err(netdev_from_priv(ppp), + "ppp_decompress_frame: no memory\n"); goto err; } /* the decompressor still expects the A/C bytes in the hdr */ @@ -2618,6 +2618,7 @@ ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb) static void ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) { + struct net_device *dev = netdev_from_priv(ppp); u32 mask, seq; struct channel *ch; int mphdrlen = (ppp->flags & SC_MP_SHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN; @@ -2662,7 +2663,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) */ if (seq_before(seq, ppp->nextseq)) { kfree_skb(skb); - DEV_STATS_INC(ppp->dev, rx_dropped); + DEV_STATS_INC(dev, rx_dropped); ppp_receive_error(ppp); return; } @@ -2698,7 +2699,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) if (pskb_may_pull(skb, 2)) ppp_receive_nonmp_frame(ppp, skb); else { - DEV_STATS_INC(ppp->dev, rx_length_errors); + DEV_STATS_INC(dev, rx_length_errors); kfree_skb(skb); ppp_receive_error(ppp); } @@ -2740,6 +2741,7 @@ ppp_mp_insert(struct ppp *ppp, struct sk_buff *skb) static struct sk_buff * ppp_mp_reconstruct(struct ppp *ppp) { + struct net_device *dev = netdev_from_priv(ppp); u32 seq = ppp->nextseq; u32 minseq = ppp->minseq; struct sk_buff_head *list = &ppp->mrq; @@ -2756,8 +2758,7 @@ ppp_mp_reconstruct(struct ppp *ppp) again: if (seq_before(PPP_MP_CB(p)->sequence, seq)) { /* this can't happen, anyway ignore the skb */ - netdev_err(ppp->dev, "ppp_mp_reconstruct bad " - "seq %u < %u\n", + netdev_err(dev, "ppp_mp_reconstruct bad seq %u < %u\n", PPP_MP_CB(p)->sequence, seq); __skb_unlink(p, list); kfree_skb(p); @@ -2776,7 +2777,7 @@ ppp_mp_reconstruct(struct ppp *ppp) minseq + 1: PPP_MP_CB(p)->sequence; if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "lost frag %u..%u\n", oldseq, seq-1); @@ -2804,8 +2805,8 @@ ppp_mp_reconstruct(struct ppp *ppp) if (lost == 0 && (PPP_MP_CB(p)->BEbits & E) && (PPP_MP_CB(head)->BEbits & B)) { if (len > ppp->mrru + 2) { - DEV_STATS_INC(ppp->dev, rx_length_errors); - netdev_printk(KERN_DEBUG, ppp->dev, + DEV_STATS_INC(dev, rx_length_errors); + netdev_printk(KERN_DEBUG, dev, "PPP: reconstructed packet" " is too long (%d)\n", len); } else { @@ -2825,7 +2826,7 @@ ppp_mp_reconstruct(struct ppp *ppp) skb_queue_reverse_walk_from_safe(list, p, tmp2) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); __skb_unlink(p, list); @@ -2847,7 +2848,7 @@ ppp_mp_reconstruct(struct ppp *ppp) if (p == head) break; if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); __skb_unlink(p, list); @@ -2855,11 +2856,11 @@ ppp_mp_reconstruct(struct ppp *ppp) } if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, " missed pkts %u..%u\n", ppp->nextseq, PPP_MP_CB(head)->sequence-1); - DEV_STATS_INC(ppp->dev, rx_dropped); + DEV_STATS_INC(dev, rx_dropped); ppp_receive_error(ppp); } @@ -2922,7 +2923,7 @@ int ppp_register_net_channel(struct net *net, struct ppp_channel *chan) #ifdef CONFIG_PPP_MULTILINK pch->lastseq = -1; #endif /* CONFIG_PPP_MULTILINK */ - init_rwsem(&pch->chan_sem); + mutex_init(&pch->chan_sem); spin_lock_init(&pch->downl); spin_lock_init(&pch->upl); @@ -2978,8 +2979,8 @@ char *ppp_dev_name(struct ppp_channel *chan) if (pch) { ppp = rcu_dereference(pch->ppp); - if (ppp && ppp->dev) - name = ppp->dev->name; + if (ppp) + name = netdev_from_priv(ppp)->name; } return name; } @@ -3005,11 +3006,11 @@ ppp_unregister_channel(struct ppp_channel *chan) * the channel's start_xmit or ioctl routine before we proceed. */ ppp_disconnect_channel(pch); - down_write(&pch->chan_sem); + mutex_lock(&pch->chan_sem); spin_lock_bh(&pch->downl); pch->chan = NULL; spin_unlock_bh(&pch->downl); - up_write(&pch->chan_sem); + mutex_unlock(&pch->chan_sem); pn = ppp_pernet(pch->chan_net); spin_lock_bh(&pn->all_channels_lock); @@ -3310,12 +3311,13 @@ find_compressor(int type) static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st) { + struct net_device *dev = netdev_from_priv(ppp); struct slcompress *vj = ppp->vj; int cpu; memset(st, 0, sizeof(*st)); for_each_possible_cpu(cpu) { - struct pcpu_sw_netstats *p = per_cpu_ptr(ppp->dev->tstats, cpu); + struct pcpu_sw_netstats *p = per_cpu_ptr(dev->tstats, cpu); u64 rx_packets, rx_bytes, tx_packets, tx_bytes; rx_packets = u64_stats_read(&p->rx_packets); @@ -3328,8 +3330,8 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st) st->p.ppp_opackets += tx_packets; st->p.ppp_obytes += tx_bytes; } - st->p.ppp_ierrors = DEV_STATS_READ(ppp->dev, rx_errors); - st->p.ppp_oerrors = DEV_STATS_READ(ppp->dev, tx_errors); + st->p.ppp_ierrors = DEV_STATS_READ(dev, rx_errors); + st->p.ppp_oerrors = DEV_STATS_READ(dev, tx_errors); if (!vj) return; st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed; @@ -3409,6 +3411,8 @@ init_ppp_file(struct ppp_file *pf, int kind) */ static void ppp_release_interface(struct ppp *ppp) { + struct net_device *dev = netdev_from_priv(ppp); + if (!refcount_dec_and_test(&ppp->file.refcnt)) return; @@ -3416,7 +3420,7 @@ static void ppp_release_interface(struct ppp *ppp) if (!ppp->file.dead || ppp->n_channels) { /* "can't happen" */ - netdev_err(ppp->dev, "ppp: destroying ppp struct %p " + netdev_err(dev, "ppp: destroying ppp struct %p " "but dead=%d n_channels=%d !\n", ppp, ppp->file.dead, ppp->n_channels); return; @@ -3446,7 +3450,7 @@ static void ppp_release_interface(struct ppp *ppp) free_percpu(ppp->xmit_recursion); - free_netdev(ppp->dev); + free_netdev(dev); } /* @@ -3493,6 +3497,7 @@ ppp_find_channel(struct ppp_net *pn, int unit) static int ppp_connect_channel(struct channel *pch, int unit) { + struct net_device *dev; struct ppp *ppp; struct ppp_net *pn; int ret = -ENXIO; @@ -3504,6 +3509,7 @@ ppp_connect_channel(struct channel *pch, int unit) ppp = ppp_find_unit(pn, unit); if (!ppp) goto out; + dev = netdev_from_priv(ppp); spin_lock(&pch->upl); ret = -EINVAL; if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) || @@ -3520,15 +3526,15 @@ ppp_connect_channel(struct channel *pch, int unit) goto outl; } if (pch->chan->direct_xmit) - ppp->dev->priv_flags |= IFF_NO_QUEUE; + dev->priv_flags |= IFF_NO_QUEUE; else - ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + dev->priv_flags &= ~IFF_NO_QUEUE; spin_unlock_bh(&pch->downl); if (pch->file.hdrlen > ppp->file.hdrlen) ppp->file.hdrlen = pch->file.hdrlen; hdrlen = pch->file.hdrlen + 2; /* for protocol bytes */ - if (hdrlen > ppp->dev->hard_header_len) - ppp->dev->hard_header_len = hdrlen; + if (hdrlen > dev->hard_header_len) + dev->hard_header_len = hdrlen; list_add_tail_rcu(&pch->clist, &ppp->channels); ++ppp->n_channels; rcu_assign_pointer(pch->ppp, ppp); @@ -3600,6 +3606,7 @@ static void ppp_release_channel(struct channel *pch) pr_err("ppp: destroying undead channel %p !\n", pch); return; } + mutex_destroy(&pch->chan_sem); call_rcu(&pch->rcu, ppp_release_channel_free); } diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index b7f243b416f8..0b1bd1635c39 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -59,8 +59,6 @@ struct syncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; unsigned long last_xmit; diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index 6874a1a8edaf..bf7414b46a26 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -85,8 +85,6 @@ #define PPPOE_HASH_SIZE (1 << PPPOE_HASH_BITS) #define PPPOE_HASH_MASK (PPPOE_HASH_SIZE - 1) -static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb); - static const struct proto_ops pppoe_ops; static const struct ppp_channel_ops pppoe_chan_ops; @@ -839,11 +837,13 @@ static int pppoe_sendmsg(struct socket *sock, struct msghdr *m, /************************************************************************ * - * xmit function for internal use. + * xmit function called by generic PPP driver + * sends PPP frame over PPPoE socket * ***********************************************************************/ -static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb) +static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb) { + struct sock *sk = chan->private; struct pppox_sock *po = pppox_sk(sk); struct net_device *dev = po->pppoe_dev; struct pppoe_hdr *ph; @@ -893,18 +893,6 @@ static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb) return 1; } -/************************************************************************ - * - * xmit function called by generic PPP driver - * sends PPP frame over PPPoE socket - * - ***********************************************************************/ -static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb) -{ - struct sock *sk = chan->private; - return __pppoe_xmit(sk, skb); -} - static int pppoe_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path, const struct ppp_channel *chan) diff --git a/drivers/net/ppp/pppox.c b/drivers/net/ppp/pppox.c index 5861a2f6ce3e..a6f72c813bef 100644 --- a/drivers/net/ppp/pppox.c +++ b/drivers/net/ppp/pppox.c @@ -74,7 +74,9 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) switch (cmd) { case PPPIOCGCHAN: { + struct sk_buff *skb; int index; + rc = -ENOTCONN; if (!(sk->sk_state & PPPOX_CONNECTED)) break; @@ -85,7 +87,22 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) break; rc = 0; + /* PPPIOCGCHAN historically marks the userspace handoff to + * generic PPP; pppd then attaches the returned channel to + * /dev/ppp. + */ sk->sk_state |= PPPOX_BOUND; + /* Let lockless receive paths finish queueing against the old + * state. + */ + synchronize_net(); + /* Drain packets queued before the handoff because a bound + * socket is no longer readable. + */ + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + skb_orphan(skb); + ppp_input(&po->chan, skb); + } break; } default: diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c index cc8c102122d8..a797a0606f6b 100644 --- a/drivers/net/ppp/pptp.c +++ b/drivers/net/ppp/pptp.c @@ -278,11 +278,8 @@ static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb) __u8 *payload; struct pptp_gre_header *header; - if (!(sk->sk_state & PPPOX_CONNECTED)) { - if (sock_queue_rcv_skb(sk, skb)) - goto drop; - return NET_RX_SUCCESS; - } + if (!(sk->sk_state & PPPOX_CONNECTED)) + goto drop; header = (struct pptp_gre_header *)(skb->data); headersize = sizeof(*header); @@ -539,7 +536,6 @@ static void pptp_sock_destruct(struct sock *sk) del_chan(pppox_sk(sk)); pppox_unbind_sock(sk); } - skb_queue_purge(&sk->sk_receive_queue); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); } diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig index 7ef29657ee5d..a0f2ae668c67 100644 --- a/drivers/net/pse-pd/Kconfig +++ b/drivers/net/pse-pd/Kconfig @@ -13,6 +13,34 @@ menuconfig PSE_CONTROLLER if PSE_CONTROLLER +config PSE_REALTEK_MCU + tristate + help + Shared core for the Realtek PSE MCU driver. This is selected + automatically by the transport options below. + +config PSE_REALTEK_MCU_I2C + tristate "Realtek PSE MCU driver (I2C transport)" + depends on I2C + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via I2C/SMBus. The MCU exposes a message-based protocol; the actual + PSE silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-i2c. + +config PSE_REALTEK_MCU_UART + tristate "Realtek PSE MCU driver (UART transport)" + depends on SERIAL_DEV_BUS + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via UART. The MCU exposes a message-based protocol; the actual PSE + silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-uart. + config PSE_REGULATOR tristate "Regulator based PSE controller" help diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile index cc78f7ea7f5f..9cca5900fe34 100644 --- a/drivers/net/pse-pd/Makefile +++ b/drivers/net/pse-pd/Makefile @@ -3,6 +3,9 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o +obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o +obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o +obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o obj-$(CONFIG_PSE_PD692X0) += pd692x0.o obj-$(CONFIG_PSE_SI3474) += si3474.o diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c new file mode 100644 index 000000000000..e64f13ea6e4e --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the microcontroller (MCU) fronting PSE silicon on various + * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame + * management protocol; this driver covers two generations of the + * protocol via a per-dialect opcode table and response parsers. + * + * Many PoE switch designs put a dedicated microcontroller in front of the + * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or + * UART, and the MCU in turn manages the PSE chips on the board. The MCU + * speaks a small message-based protocol. The PSE chips themselves are not + * accessed directly; everything goes through MCU commands. + * + * This driver targets that architecture for the Realtek-family protocol. + * Two generations are supported: Gen1 being used on older switches where + * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with + * Realtek PSE silicon. The two share frame format and a sum-mod-256 + * checksum but diverge on opcode numbers and on a few response layouts; + * this is handled by the per-dialect opcode table and parser hooks. + * + * Out of scope: PSE chips that are interfaced directly from the host + * without a management MCU, MCU designs that speak an unrelated protocol + * family, and "dumb PSE" modes where no host control is wired up at all. + * + * This core module implements the protocol, decoding/encoding of MCU + * responses, and the pse_controller_ops integration. Transport modules + * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv + * callbacks. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_DEVICE_ID_RTL8238B 0x0138 +#define RTPSE_MCU_DEVICE_ID_RTL8239 0x0039 +#define RTPSE_MCU_DEVICE_ID_RTL8239C 0x0139 +#define RTPSE_MCU_DEVICE_ID_BCM59111 0xe111 +#define RTPSE_MCU_DEVICE_ID_BCM59121 0xe121 + +#define RTPSE_MCU_PORT_STS_DISABLED 0x00 +#define RTPSE_MCU_PORT_STS_SEARCHING 0x01 +#define RTPSE_MCU_PORT_STS_DELIVERING 0x02 +#define RTPSE_MCU_PORT_STS_TEST 0x03 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_FAULT 0x04 +#define RTPSE_MCU_PORT_STS_OTHER_FAULT 0x05 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_REQUESTING 0x06 + +/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */ +#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER 0x02 + +#define RTPSE_MCU_MAX_PORTS 48 +#define RTPSE_MCU_PORT_MAX_PRIORITY 3 + +/* Bounded resends when the MCU replies NOT_READY (busy). */ +#define RTPSE_MCU_NOT_READY_RETRIES 3 + +/* Nominal PSE rail; 802.3at/bt operating range. */ +#define RTPSE_MCU_PSE_VOLTAGE_UV 54000000 + +enum rtpse_mcu_cmd { + RTPSE_MCU_CMD_SET_GLOBAL_STATE, + RTPSE_MCU_CMD_GET_SYSTEM_INFO, + RTPSE_MCU_CMD_GET_EXT_CONFIG, + + RTPSE_MCU_CMD_PORT_ENABLE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + RTPSE_MCU_CMD_PORT_SET_PRIORITY, + RTPSE_MCU_CMD_PORT_GET_STATUS, + RTPSE_MCU_CMD_PORT_GET_POWER_STATS, + RTPSE_MCU_CMD_PORT_GET_CONFIG, + RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG, + + RTPSE_MCU_NUM_CMDS, +}; + +struct rtpse_mcu_opcode { + u8 op; + bool valid; +}; + +/* Shorthand for the designated-initializer entries in dialect opcode tables. */ +#define RTPSE_MCU_OP(opc) { .op = (opc), .valid = true } + +/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */ + +struct rtpse_mcu_info { + u8 max_ports; + bool system_enable; + u16 device_id; + u8 mcu_type; +}; + +struct rtpse_mcu_ext_config { + u8 num_of_pses; +}; + +struct rtpse_mcu_port_status { + u8 sts1; + u8 sts2; + u8 sts3; +}; + +struct rtpse_mcu_port_measurement { + u16 voltage_raw; /* 64.45mV/LSB */ + u16 current_raw; /* 1mA/LSB */ + u16 temperature_raw; /* T(mC) = 1250 * (220 - raw) */ + u16 power_raw; /* 100mW/LSB */ +}; + +struct rtpse_mcu_port_config { + bool enable; +}; + +struct rtpse_mcu_port_ext_config { + u8 max_power; + u8 priority; +}; + +struct rtpse_mcu_dialect { + struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS]; + + /* + * Response parsers for the fields that differ between dialects; each + * dialect supplies its own. Other responses share one layout and are + * decoded directly - a dialect that diverges there must add a hook, + * as a mismatched layout cannot be detected (the checksum still passes). + */ + void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info); + int (*parse_port_class)(const struct rtpse_mcu_port_status *status); + const char *(*mcu_type_str)(unsigned int mcu_type); +}; + +struct rtpse_mcu_chip_info { + const char *name; + u32 max_mW_per_port; + enum rtpse_mcu_cmd pw_set_cmd; /* command used by set_pw_limit */ + u32 pw_set_lsb_mW; /* LSB of pw_set_cmd value, in mW */ + u32 pw_read_lsb_mW; /* LSB of ext_config.max_power read-back, in mW */ +}; + +static const struct rtpse_mcu_chip_info rtl8238b_info = { + .max_mW_per_port = 30000, + .name = "RTL8238B", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info rtl8239_info = { + .max_mW_per_port = 90000, + .name = "RTL8239", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info rtl8239c_info = { + .max_mW_per_port = 90000, + .name = "RTL8239C", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info bcm59111_info = { + .max_mW_per_port = 30000, + .name = "BCM59111", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info bcm59121_info = { + /* + * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at + * and the Gen1 dialect has only the 8-bit/0.2W set command (<=51W); + * cap at the 30W the hardware actually offers. + */ + .max_mW_per_port = 30000, + .name = "BCM59121", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +/* Helpers and basic functions */ + +static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev); +} + +static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode) +{ + memset(msg, 0xff, sizeof(*msg)); + msg->opcode = opcode; +} + +static u8 rtpse_mcu_checksum(const u8 *buf, size_t len) +{ + u8 sum = 0; + + while (len--) + sum += *buf++; + return sum; +} + +static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + unsigned int tries; + int ret; + + for (tries = 0; ; tries++) { + scoped_guard(mutex, &pse->mutex) { + /* Rolling seq_num (skip 0) so a stale/all-zero reply can't match. */ + if (++pse->seq == 0) + pse->seq = 1; + req->seq_num = pse->seq; + req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1); + + ret = pse->transport->send(pse, req); + if (ret) + return ret; + + /* Pace the base reply delay; the transport waits its own way. */ + msleep(RTPSE_MCU_RESPONSE_MS); + + memset(resp, 0, sizeof(*resp)); + ret = pse->transport->recv(pse, req, resp); + if (ret) + return ret; + } + + /* NOT_READY: MCU busy, wants the command resent; bounded retry. */ + if (resp->opcode != RTPSE_MCU_OPCODE_NOT_READY || + tries >= RTPSE_MCU_NOT_READY_RETRIES) + break; + msleep(RTPSE_MCU_RESPONSE_MS); + } + + /* Explicit MCU error opcodes (Gen1); map to a meaningful errno. */ + switch (resp->opcode) { + case RTPSE_MCU_OPCODE_INCOMPLETE: + return -EBADE; + case RTPSE_MCU_OPCODE_BAD_CSUM: + return -EBADMSG; + case RTPSE_MCU_OPCODE_NOT_READY: + return -EAGAIN; + } + + if (resp->opcode != req->opcode || + resp->seq_num != req->seq_num || + resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1)) + return -EBADMSG; + + return 0; +} + +static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_msg req; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + + ret = rtpse_mcu_do_xfer(pse, &req, resp); + if (ret) + return ret; + + if (resp->payload[0] != port) + return -EIO; + + return 0; +} + +static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg) +{ + struct rtpse_mcu_msg req, resp; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + req.payload[1] = arg; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + if (resp.payload[0] != port || resp.payload[1] != 0) + return -EIO; + + return 0; +} + +/* Global operations */ + +static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + pse->dialect->parse_system_info(resp.payload, info); + return 0; +} + +static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + config->num_of_pses = resp.payload[6]; + + return 0; +} + +static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + req.payload[0] = enable ? 0x1 : 0x0; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + return (resp.payload[0] == 0x0) ? 0 : -EIO; +} + +/* Port operations */ + +static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_status *status) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + status->sts1 = resp.payload[1]; + status->sts2 = resp.payload[2]; + status->sts3 = resp.payload[3]; + + return 0; +} + +static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_measurement *measurement) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]); + measurement->current_raw = get_unaligned_be16(&resp.payload[3]); + measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]); + measurement->power_raw = get_unaligned_be16(&resp.payload[7]); + + return 0; +} + +static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->enable = (resp.payload[1] == 1); + + return 0; +} + +static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_ext_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->max_power = resp.payload[3]; + config->priority = resp.payload[4]; + + return 0; +} + +static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable) +{ + const struct rtpse_mcu_opcode *opc; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0); +} + +/* PSE controller ops */ + +static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_config config; + int ret; + + ret = rtpse_mcu_port_get_config(pse, id, &config); + if (ret) + return ret; + + admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED : + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + return 0; +} + +static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + break; + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_REQUESTING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; + break; + case RTPSE_MCU_PORT_STS_DELIVERING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + break; + case RTPSE_MCU_PORT_STS_TEST: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST; + break; + case RTPSE_MCU_PORT_STS_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; + break; + case RTPSE_MCU_PORT_STS_OTHER_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT; + break; + default: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + /* + * As per datasheet, the classification result is only valid when in + * one of those operational modes, otherwise not. + */ + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_DELIVERING: + case RTPSE_MCU_PORT_STS_REQUESTING: + return pse->dialect->parse_port_class(&status); + default: + /* + * No class to report, return 0 instead. This is indistinguishable + * from a real class-0 PD but userspace disambiguates via the + * power status. + */ + return 0; + } +} + +static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 100mW per LSB */ + return measurement.power_raw * 100U; +} + +static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + u32 uV; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 64.45mV per LSB */ + uV = measurement.voltage_raw * 64450U; + + /* + * Idle ports measure 0V, which the core rejects when turning a power + * limit into a current limit. Fall back to the nominal rail so a limit + * can be set before a PD is attached. + */ + if (!uV) + return RTPSE_MCU_PSE_VOLTAGE_UV; + + return min_t(u32, uV, INT_MAX); +} + +static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true); +} + +static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false); +} + +static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* + * The MCU's raw max_power byte can scale above the chip's rated cap; + * clamp to the same bound set_pw_limit() and the advertised range use. + */ + return min_t(u32, config.max_power * pse->chip->pw_read_lsb_mW, + pse->chip->max_mW_per_port); +} + +static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW) +{ + const struct rtpse_mcu_opcode *type_opc, *val_opc; + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_chip_info *chip = pse->chip; + u8 prg_val; + int ret; + + if (max_mW < 0 || max_mW > chip->max_mW_per_port) + return -ERANGE; + + type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE]; + val_opc = &pse->dialect->opcode[chip->pw_set_cmd]; + /* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */ + if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW) + return -EOPNOTSUPP; + + /* + * Round up so a sub-LSB request maps to one LSB, not silently to 0; + * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot. + */ + prg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip->pw_set_lsb_mW), U8_MAX); + + /* + * Program the value before switching to user-defined mode. The two + * commands aren't atomic, but this order never leaves a stale cap: a + * failure keeps the previous cap, or (already user mode) the requested. + */ + ret = rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val); + if (ret) + return ret; + + return rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER); +} + +static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *out) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct ethtool_c33_pse_pw_limit_range *range; + + range = kzalloc_obj(*range); + if (!range) + return -ENOMEM; + + range[0].min = 0; + range[0].max = pse->chip->max_mW_per_port; + + out->c33_pw_limit_ranges = range; + return 1; +} + +static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* Clamp to the advertised max; set_prio() and pis_prio_max use the same bound. */ + return min_t(u8, config.priority, RTPSE_MCU_PORT_MAX_PRIORITY); +} + +static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_opcode *opc; + + if (prio > RTPSE_MCU_PORT_MAX_PRIORITY) + return -ERANGE; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, id, opc->op, prio); +} + +static const struct pse_controller_ops rtpse_mcu_ops = { + .pi_get_admin_state = rtpse_mcu_port_get_admin_state, + .pi_get_pw_status = rtpse_mcu_port_get_pw_status, + .pi_get_pw_class = rtpse_mcu_port_get_pw_class, + .pi_get_actual_pw = rtpse_mcu_port_get_actual_pw, + .pi_enable = rtpse_mcu_port_enable, + .pi_disable = rtpse_mcu_port_disable, + .pi_get_voltage = rtpse_mcu_port_get_voltage, + .pi_get_pw_limit = rtpse_mcu_port_get_pw_limit, + .pi_set_pw_limit = rtpse_mcu_port_set_pw_limit, + .pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges, + .pi_get_prio = rtpse_mcu_port_get_prio, + .pi_set_prio = rtpse_mcu_port_set_prio, +}; + +static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_ext_config ext_config; + unsigned long deadline; + int ret; + + /* + * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address + * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a + * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a + * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail + * immediately. + */ + deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS); + do { + ret = rtpse_mcu_get_info(pse, info); + if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO && + ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE) + break; + msleep(RTPSE_MCU_BOOT_RETRY_MS); + } while (time_before(jiffies, deadline)); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU info\n"); + + switch (info->device_id) { + case RTPSE_MCU_DEVICE_ID_RTL8238B: + pse->chip = &rtl8238b_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239: + pse->chip = &rtl8239_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239C: + pse->chip = &rtl8239c_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59111: + pse->chip = &bcm59111_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59121: + pse->chip = &bcm59121_info; + break; + default: + return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n", + info->device_id); + } + + if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS) + return dev_err_probe(pse->dev, -EINVAL, + "MCU reports invalid port count %u\n", info->max_ports); + + ret = rtpse_mcu_get_ext_config(pse, &ext_config); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n"); + + dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n", + pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name, + info->device_id, info->max_ports, ext_config.num_of_pses); + return 0; +} + +static void rtpse_mcu_global_disable(void *data) +{ + struct rtpse_mcu_ctrl *pse = data; + + rtpse_mcu_set_global_state(pse, false); +} + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse) +{ + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_info info; + struct gpio_desc *gpiod; + int ret; + + BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE); + + ret = devm_mutex_init(pse->dev, &pse->mutex); + if (ret) + return ret; + + match = device_get_match_data(pse->dev); + if (!match) + return dev_err_probe(pse->dev, -ENODEV, "missing match data\n"); + pse->dialect = match->dialect; + + /* + * Catch a dialect that forgot to set one of the required hooks at + * probe time, rather than NULL-deref'ing later from a fast path. + */ + if (!pse->dialect || + !pse->dialect->parse_system_info || + !pse->dialect->parse_port_class || + !pse->dialect->mcu_type_str) + return dev_err_probe(pse->dev, -EINVAL, + "dialect for chip is incomplete\n"); + + /* + * Release the MCU from reset before the first transaction; the + * boot-retry loop in discover() waits for it to answer. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get reset gpio\n"); + + ret = rtpse_mcu_discover(pse, &info); + if (ret) + return ret; + + /* + * Some boards gate all ports through a hardware line; deassert it only + * after the MCU is confirmed, so a discover failure never ungates the + * ports. It is then left to the MCU - not re-gated on unbind or a later + * probe error - so a driver reload doesn't black out PoE. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get disable-ports gpio\n"); + + if (!info.system_enable) { + ret = rtpse_mcu_set_global_state(pse, true); + /* Dialects without a global-state concept (e.g. Gen1) return + * -EOPNOTSUPP; treat that as "no separate enable required". + */ + if (ret && ret != -EOPNOTSUPP) + return dev_err_probe(pse->dev, ret, + "failed to enable PSE system\n"); + if (!ret) { + ret = devm_add_action_or_reset(pse->dev, + rtpse_mcu_global_disable, pse); + if (ret) + return ret; + } + } + + /* + * Depending on the MCU firmware configuration (which might be different + * for every board), it isn't known whether the PoE subsystem is active or + * inactive by default. At this stage, the PSE chips might already deliver + * power to PDs without any explicit enable. + */ + + /* pcdev.owner is set by the transport, so the registered controller + * pins the transport module that owns the live device, not the core. + */ + pse->pcdev.ops = &rtpse_mcu_ops; + pse->pcdev.dev = pse->dev; + pse->pcdev.types = ETHTOOL_PSE_C33; + pse->pcdev.nr_lines = info.max_ports; + pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY; + pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC; + + return devm_pse_controller_register(pse->dev, &pse->pcdev); +} +EXPORT_SYMBOL_GPL(rtpse_mcu_register); + +static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + info->system_enable = (payload[2] == 0x1); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Class lives in the upper nibble of sts2. */ + return FIELD_GET(GENMASK(7, 4), status->sts2); +} + +static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "GigaDevice GD32F310"; + case 0x01: return "GigaDevice GD32F230"; + case 0x02: return "GigaDevice GD32F303"; + case 0x03: return "GigaDevice GD32F103"; + case 0x04: return "GigaDevice GD32E103"; + case 0x10: return "Nuvoton M0516"; + case 0x11: return "Nuvoton M0564"; + case 0x12: return "Nuvoton NUC029"; + default: return "unknown"; + } +} + +static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + /* Gen1 has no explicit system_enable byte; the closest analog is the + * "remote enable" bit in the system-status flags at payload[7]. + */ + info->system_enable = !!(payload[7] & BIT(2)); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Gen1 puts the detected class in payload[3] (== sts3) directly. + * Mask to the low nibble; class is 0..8 and any high bits would be + * noise. + */ + return status->sts3 & 0x0f; +} + +static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "ST Micro ST32F100"; + case 0x01: return "Nuvoton M05xx LAN"; + case 0x02: return "ST Micro STF030C8"; + case 0x03: return "Nuvoton M058SAN"; + case 0x04: return "Nuvoton NUC122"; + default: return "unknown"; + } +} + +/* Map each logical command the core issues to its per-dialect opcode. */ +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = { + .parse_system_info = rtpse_mcu_gen2_parse_system_info, + .parse_port_class = rtpse_mcu_gen2_parse_port_class, + .mcu_type_str = rtpse_mcu_gen2_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_SET_GLOBAL_STATE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x13), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x42), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x44), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x48), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x49), + }, +}; + +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = { + .parse_system_info = rtpse_mcu_gen1_parse_system_info, + .parse_port_class = rtpse_mcu_gen1_parse_port_class, + .mcu_type_str = rtpse_mcu_gen1_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x16), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x1a), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x21), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x30), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x25), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x26), + }, +}; + +const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = { + .dialect = &rtpse_mcu_dialect_gen1, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data); + +const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = { + .dialect = &rtpse_mcu_dialect_gen2, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data); + +/* Same dialect as gen2, but the MCU expects raw-I2C framing. */ +const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = { + .dialect = &rtpse_mcu_dialect_gen2, + .native_i2c = true, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (core)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c new file mode 100644 index 000000000000..b6769ac1d470 --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +/* + * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so + * the response is normally ready on the very first read. For commands the + * MCU produces more slowly, keep polling at the typical response cadence + * up to the worst-case ceiling. + */ +#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS +#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS) + +static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + + /* Send opcode as SMBus command byte; remaining 11 bytes as block data */ + return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1, + (const u8 *)req + 1); +} + +static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + /* MCU needs 0x00 as command byte for read */ + ret = i2c_smbus_read_i2c_block_data(client, 0x00, + RTPSE_MCU_MSG_SIZE, + (u8 *)resp); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = { + .send = rtpse_mcu_i2c_smbus_send, + .recv = rtpse_mcu_i2c_smbus_recv, +}; + +static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int ret; + + ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO; +} + +static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = { + .send = rtpse_mcu_i2c_native_send, + .recv = rtpse_mcu_i2c_native_recv, +}; + +static int rtpse_mcu_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_ctrl *pse; + bool use_native; + + match = device_get_match_data(dev); + if (!match) + return dev_err_probe(dev, -ENODEV, "missing match data\n"); + + /* The framing (raw I2C vs SMBus) is carried by the match data. */ + use_native = match->native_i2c; + if (use_native) { + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EOPNOTSUPP, + "plain-I2C MCU protocol requires I2C-capable adapter\n"); + } else { + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return dev_err_probe(dev, -EOPNOTSUPP, + "SMBus MCU protocol requires SMBus I2C-block support\n"); + } + + pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL); + if (!pse) + return -ENOMEM; + + pse->dev = dev; + pse->pcdev.owner = THIS_MODULE; + pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops; + + return rtpse_mcu_register(pse); +} + +static const struct of_device_id rtpse_mcu_i2c_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data }, + { .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match); + +static struct i2c_driver rtpse_mcu_i2c_driver = { + .driver = { + .name = "realtek-pse-mcu-i2c", + .of_match_table = rtpse_mcu_i2c_of_match, + }, + .probe = rtpse_mcu_i2c_probe, +}; +module_i2c_driver(rtpse_mcu_i2c_driver); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c new file mode 100644 index 000000000000..9baa17d8d31f --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_UART_BAUD_DEFAULT 19200 +#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100) +#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS) + +struct rtpse_mcu_uart { + struct rtpse_mcu_ctrl pse; + struct serdev_device *serdev; + struct completion rx_done; + spinlock_t rx_lock; /* protects rx_buf and rx_len */ + size_t rx_len; + u8 rx_buf[RTPSE_MCU_MSG_SIZE]; +}; + +#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse) + +/* + * No framing is done here: a glitched frame costs one transaction, then + * the next _send re-frames from rx_len 0. Resync works by returning count + * (not take), dropping any overflow so serdev keeps no leftover to bleed + * into the next frame. + */ +static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev, + const u8 *buf, size_t count) +{ + struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev); + size_t take; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len); + if (take) { + memcpy(ctx->rx_buf + ctx->rx_len, buf, take); + ctx->rx_len += take; + if (ctx->rx_len == sizeof(ctx->rx_buf)) + complete(&ctx->rx_done); + } + } + + /* consume all to avoid desync/misalignment */ + return count; +} + +static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = { + .receive_buf = rtpse_mcu_uart_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + int written; + + /* clear any leftover rx state before transmitting */ + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + reinit_completion(&ctx->rx_done); + ctx->rx_len = 0; + } + + written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req), + RTPSE_MCU_UART_TX_TIMEOUT); + if (written < 0) + return written; + if (written != sizeof(*req)) + return -EIO; + + return 0; +} + +static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse, + const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + + if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT)) + return -ETIMEDOUT; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + if (ctx->rx_len != sizeof(*resp)) + return -EIO; + + memcpy(resp, ctx->rx_buf, sizeof(*resp)); + } + return 0; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = { + .send = rtpse_mcu_uart_send, + .recv = rtpse_mcu_uart_recv, +}; + +static int rtpse_mcu_uart_probe(struct serdev_device *serdev) +{ + u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT; + struct device *dev = &serdev->dev; + struct rtpse_mcu_uart *ctx; + unsigned int baud; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->serdev = serdev; + ctx->pse.dev = dev; + ctx->pse.pcdev.owner = THIS_MODULE; + ctx->pse.transport = &rtpse_mcu_uart_transport_ops; + init_completion(&ctx->rx_done); + spin_lock_init(&ctx->rx_lock); + + serdev_device_set_drvdata(serdev, ctx); + serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "failed to open serdev\n"); + + fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed); + + baud = serdev_device_set_baudrate(serdev, speed); + if (baud != speed) + dev_warn(dev, "could not set baudrate %u, controller uses %u\n", + speed, baud); + + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + dev_warn(dev, "could not set parity to none: %d\n", ret); + + return rtpse_mcu_register(&ctx->pse); +} + +static const struct of_device_id rtpse_mcu_uart_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match); + +static struct serdev_device_driver rtpse_mcu_uart_driver = { + .driver = { + .name = "realtek-pse-mcu-uart", + .of_match_table = rtpse_mcu_uart_of_match, + }, + .probe = rtpse_mcu_uart_probe, +}; +module_serdev_device_driver(rtpse_mcu_uart_driver); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h new file mode 100644 index 000000000000..d75e91669b8f --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _REALTEK_PSE_MCU_H +#define _REALTEK_PSE_MCU_H + +#include +#include +#include + +/* + * Time the MCU itself needs between accepting a request and having a + * response ready. These are properties of the MCU firmware, not of the + * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS + * and both transports size their per-transaction recv ceiling from + * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as + * needing up to ~1s to produce a reply. + */ +#define RTPSE_MCU_RESPONSE_MS 25 +#define RTPSE_MCU_RESPONSE_MAX_MS 1000 + +/* + * Total time to keep retrying the first MCU read at probe, and the pause + * between attempts. Right after reset-gpios is deasserted the MCU may not + * answer on the bus yet; give it a bounded window to come up before + * declaring the probe failed. + */ +#define RTPSE_MCU_BOOT_TIMEOUT_MS 3000 +#define RTPSE_MCU_BOOT_RETRY_MS 100 + +#define RTPSE_MCU_MSG_SIZE 12 + +struct rtpse_mcu_msg { + u8 opcode; + u8 seq_num; + u8 payload[9]; + u8 checksum; +} __packed; + +/* + * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them). + * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient. + */ +#define RTPSE_MCU_OPCODE_INCOMPLETE 0xfd /* -EBADE */ +#define RTPSE_MCU_OPCODE_BAD_CSUM 0xfe /* -EBADMSG */ +#define RTPSE_MCU_OPCODE_NOT_READY 0xff /* -EAGAIN */ + +/* + * A polling transport can stop here: the reply to this request (opcode and + * seq_num) or a terminal error. The seq_num rejects a stale normal reply; the + * terminal errors match unconditionally, as a request the MCU couldn't parse + * carries no seq_num to correlate against. + */ +static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req, + const struct rtpse_mcu_msg *resp) +{ + return (resp->opcode == req->opcode && resp->seq_num == req->seq_num) || + resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE || + resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM; +} + +/* Opaque to transports; defined in realtek-pse-mcu-core.c. */ +struct rtpse_mcu_dialect; +struct rtpse_mcu_chip_info; +struct rtpse_mcu_ctrl; + +/* Per-compatible match data (the of_match .data). */ +struct rtpse_mcu_match_data { + const struct rtpse_mcu_dialect *dialect; + bool native_i2c; /* raw-I2C framing (vs SMBus); I2C transport only */ +}; + +struct rtpse_mcu_transport_ops { + int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req); + int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp); +}; + +struct rtpse_mcu_ctrl { + struct device *dev; + struct pse_controller_dev pcdev; + struct mutex mutex; /* serializes MCU request/response transactions */ + const struct rtpse_mcu_dialect *dialect; + const struct rtpse_mcu_chip_info *chip; + const struct rtpse_mcu_transport_ops *transport; + u8 seq; /* rolling request seq_num, echoed by the MCU */ +}; + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse); + +extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data; + +#endif diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 49d6389da067..8a3bbd623412 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -1520,11 +1520,7 @@ static int tps23881_i2c_probe(struct i2c_client *client) "failed to register PSE controller\n"); } - ret = tps23881_setup_irq(priv, client->irq); - if (ret) - return ret; - - return ret; + return tps23881_setup_irq(priv, client->irq); } static const struct i2c_device_id tps23881_id[] = { diff --git a/drivers/net/tap.c b/drivers/net/tap.c index 9b5af08cdd47..ff67d99deb39 100644 --- a/drivers/net/tap.c +++ b/drivers/net/tap.c @@ -883,7 +883,7 @@ static int set_offload(struct tap_queue *q, unsigned long arg) /* TODO: for now USO4 and USO6 should work simultaneously */ if ((arg & (TUN_F_USO4 | TUN_F_USO6)) == (TUN_F_USO4 | TUN_F_USO6)) - features |= NETIF_F_GSO_UDP_L4; + feature_mask |= NETIF_F_GSO_UDP_L4; } /* tun/tap driver inverts the usage for TSO offloads, where @@ -894,8 +894,7 @@ static int set_offload(struct tap_queue *q, unsigned long arg) * When user space turns off TSO, we turn off GSO/LRO so that * user-space will not receive TSO frames. */ - if (feature_mask & (NETIF_F_TSO | NETIF_F_TSO6) || - (feature_mask & (TUN_F_USO4 | TUN_F_USO6)) == (TUN_F_USO4 | TUN_F_USO6)) + if (feature_mask & (NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4)) features |= RX_OFFLOADS; else features &= ~RX_OFFLOADS; diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index feaa75fbf8fc..beffbe450612 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -3229,7 +3229,9 @@ static int __init team_module_init(void) { int err; - register_netdevice_notifier(&team_notifier_block); + err = register_netdevice_notifier(&team_notifier_block); + if (err) + return err; err = rtnl_link_register(&team_link_ops); if (err) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 98893732bc6e..2a1728621887 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -626,6 +626,14 @@ static int tbnet_alloc_tx_buffers(struct tbnet *net) return 0; } +static void tbnet_connect_failed(struct tbnet *net) +{ + /* Leave login_received set: only the peer can make it true again. */ + mutex_lock(&net->connection_lock); + net->login_sent = false; + mutex_unlock(&net->connection_lock); +} + static void tbnet_connected_work(struct work_struct *work) { struct tbnet *net = container_of(work, typeof(*net), connected_work); @@ -647,6 +655,9 @@ static void tbnet_connected_work(struct work_struct *work) ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path); if (ret != net->remote_transmit_path) { netdev_err(net->dev, "failed to allocate Rx HopID\n"); + if (ret >= 0) + tb_xdomain_release_in_hopid(net->xd, ret); + tbnet_connect_failed(net); return; } @@ -691,6 +702,7 @@ static void tbnet_connected_work(struct work_struct *work) tb_ring_stop(net->rx_ring.ring); tb_ring_stop(net->tx_ring.ring); tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path); + tbnet_connect_failed(net); } static void tbnet_login_work(struct work_struct *work) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index fed9dfdfcc3b..5a302709a68a 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev, #define TUN_FASYNC IFF_ATTACH_QUEUE #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \ - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS) + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \ + IFF_BACKPRESSURE) #define GOODCOPY_LEN 128 @@ -145,6 +146,8 @@ struct tun_file { struct list_head next; struct tun_struct *detached; struct ptr_ring tx_ring; + /* Protected by tx_ring.consumer_lock */ + int cons_cnt; struct xdp_rxq_info xdp_rxq; }; @@ -530,7 +533,7 @@ static void tun_disable_queue(struct tun_struct *tun, struct tun_file *tfile) { tfile->detached = tun; list_add_tail(&tfile->next, &tun->disabled); - ++tun->numdisabled; + WRITE_ONCE(tun->numdisabled, tun->numdisabled + 1); } static struct tun_struct *tun_enable_queue(struct tun_file *tfile) @@ -539,7 +542,7 @@ static struct tun_struct *tun_enable_queue(struct tun_file *tfile) tfile->detached = NULL; list_del_init(&tfile->next); - --tun->numdisabled; + WRITE_ONCE(tun->numdisabled, tun->numdisabled - 1); return tun; } @@ -585,15 +588,20 @@ static void __tun_detach(struct tun_file *tfile, bool clean) u16 index = tfile->queue_index; BUG_ON(index >= tun->numqueues); + spin_lock(&tfile->tx_ring.consumer_lock); rcu_assign_pointer(tun->tfiles[index], tun->tfiles[tun->numqueues - 1]); + spin_unlock(&tfile->tx_ring.consumer_lock); ntfile = rtnl_dereference(tun->tfiles[index]); + spin_lock(&ntfile->tx_ring.consumer_lock); ntfile->queue_index = index; ntfile->xdp_rxq.queue_index = index; + ntfile->cons_cnt = 0; + spin_unlock(&ntfile->tx_ring.consumer_lock); rcu_assign_pointer(tun->tfiles[tun->numqueues - 1], NULL); - --tun->numqueues; + WRITE_ONCE(tun->numqueues, tun->numqueues - 1); if (clean) { RCU_INIT_POINTER(tfile->tun, NULL); sock_put(&tfile->sk); @@ -606,6 +614,14 @@ static void __tun_detach(struct tun_file *tfile, bool clean) tun_flow_delete_by_queue(tun, tun->numqueues + 1); /* Drop read queue */ tun_queue_purge(tfile); + spin_lock_bh(&ntfile->tx_ring.consumer_lock); + spin_lock(&ntfile->tx_ring.producer_lock); + ntfile->cons_cnt = 0; + if (netif_running(tun->dev) && + __ptr_ring_empty(&ntfile->tx_ring)) + netif_wake_subqueue(tun->dev, index); + spin_unlock(&ntfile->tx_ring.producer_lock); + spin_unlock_bh(&ntfile->tx_ring.consumer_lock); tun_set_real_num_queues(tun); } else if (tfile->detached && clean) { tun = tun_enable_queue(tfile); @@ -656,7 +672,7 @@ static void tun_detach_all(struct net_device *dev) tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN; tfile->socket.sk->sk_data_ready(tfile->socket.sk); RCU_INIT_POINTER(tfile->tun, NULL); - --tun->numqueues; + WRITE_ONCE(tun->numqueues, tun->numqueues - 1); } list_for_each_entry(tfile, &tun->disabled, next) { tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN; @@ -687,6 +703,25 @@ static void tun_detach_all(struct net_device *dev) module_put(THIS_MODULE); } +static void tun_force_wake_queue(struct tun_struct *tun, + struct tun_file *tfile) +{ + /* Ensure that the producer can not stop the + * queue concurrently by taking locks. + */ + spin_lock_bh(&tfile->tx_ring.consumer_lock); + spin_lock(&tfile->tx_ring.producer_lock); + tfile->cons_cnt = 0; + /* Tested under the locks that tun_net_close() takes, so this can not + * undo its stop. tun_net_open() wakes the queues of a device that + * comes back up. + */ + if (netif_running(tun->dev)) + netif_wake_subqueue(tun->dev, tfile->queue_index); + spin_unlock(&tfile->tx_ring.producer_lock); + spin_unlock_bh(&tfile->tx_ring.consumer_lock); +} + static int tun_attach(struct tun_struct *tun, struct file *file, bool skip_filter, bool napi, bool napi_frags, bool publish_tun) @@ -730,8 +765,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file, goto out; } + spin_lock(&tfile->tx_ring.consumer_lock); tfile->queue_index = tun->numqueues; + spin_unlock(&tfile->tx_ring.consumer_lock); tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN; + tun_force_wake_queue(tun, tfile); if (tfile->detached) { /* Re-attach detached tfile, updating XDP queue_index */ @@ -776,7 +814,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, if (publish_tun) rcu_assign_pointer(tfile->tun, tun); rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile); - tun->numqueues++; + WRITE_ONCE(tun->numqueues, tun->numqueues + 1); tun_set_real_num_queues(tun); out: return err; @@ -964,6 +1002,24 @@ static int tun_net_open(struct net_device *dev) /* Net device close. */ static int tun_net_close(struct net_device *dev) { + struct tun_struct *tun = netdev_priv(dev); + struct tun_file *tfile; + int i; + + /* netif_running() is already false: take both ring locks to keep the + * wake sites out, so the stop below is the last write to + * __QUEUE_STATE_DRV_XOFF. + */ + for (i = 0; i < tun->numqueues; i++) { + tfile = rtnl_dereference(tun->tfiles[i]); + + spin_lock_bh(&tfile->tx_ring.consumer_lock); + spin_lock(&tfile->tx_ring.producer_lock); + tfile->cons_cnt = 0; + spin_unlock(&tfile->tx_ring.producer_lock); + spin_unlock_bh(&tfile->tx_ring.consumer_lock); + } + netif_tx_stop_all_queues(dev); return 0; } @@ -1008,6 +1064,7 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *queue; struct tun_file *tfile; int len = skb->len; + int ret; rcu_read_lock(); tfile = rcu_dereference(tun->tfiles[txq]); @@ -1062,13 +1119,35 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) nf_reset_ct(skb); - if (ptr_ring_produce(&tfile->tx_ring, skb)) { + queue = netdev_get_tx_queue(dev, txq); + + spin_lock(&tfile->tx_ring.producer_lock); + ret = __ptr_ring_produce(&tfile->tx_ring, skb); + /* Do not touch the queue state of a device that is going down. */ + if ((tun->flags & IFF_BACKPRESSURE) && netif_running(dev) && + !qdisc_txq_has_no_queue(queue) && + __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) { + netif_tx_stop_queue(queue); + /* Paired with smp_mb() in __tun_wake_queue() */ + smp_mb__after_atomic(); + if (!__ptr_ring_check_produce(&tfile->tx_ring)) + netif_tx_wake_queue(queue); + } + spin_unlock(&tfile->tx_ring.producer_lock); + + if (ret) { + /* This should be a rare case if IFF_BACKPRESSURE is enabled and + * a qdisc is present, but can happen due to lltx. + * Since skb_tx_timestamp(), skb_orphan(), + * run_ebpf_filter() and pskb_trim() could have tinkered + * with the SKB, returning NETDEV_TX_BUSY is unsafe and + * we must drop instead. + */ drop_reason = SKB_DROP_REASON_FULL_RING; goto drop; } /* dev->lltx requires to do our own update of trans_start */ - queue = netdev_get_tx_queue(dev, txq); txq_trans_cond_update(queue); /* Notify and wake up reader process */ @@ -1107,11 +1186,16 @@ static netdev_features_t tun_net_fix_features(struct net_device *dev, static void tun_set_headroom(struct net_device *dev, int new_hr) { struct tun_struct *tun = netdev_priv(dev); + size_t max_headroom; - if (new_hr < NET_SKB_PAD) - new_hr = NET_SKB_PAD; + max_headroom = min_t(size_t, SKB_MAX_HEAD(0), U16_MAX - 1); - tun->align = new_hr; + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + max_headroom -= ETH_HLEN + NET_IP_ALIGN; + else + max_headroom -= 1; + + tun->align = clamp_t(int, new_hr, NET_SKB_PAD, max_headroom); } static void @@ -1822,7 +1906,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { - u8 ip_version = skb->len ? (skb->data[0] >> 4) : 0; + u8 ip_version; + + if (!pskb_may_pull(skb, 1)) { + err = -EINVAL; + goto drop; + } + ip_version = skb->data[0] >> 4; switch (ip_version) { case 4: @@ -1842,7 +1932,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, skb->dev = tun->dev; break; case IFF_TAP: - if (frags && !pskb_may_pull(skb, ETH_HLEN)) { + if (!pskb_may_pull(skb, ETH_HLEN)) { err = -ENOMEM; drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; @@ -2116,13 +2206,61 @@ static ssize_t tun_put_user(struct tun_struct *tun, return total; } -static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err) +/* Callers must hold ring.consumer_lock */ +static void __tun_wake_queue(struct tun_struct *tun, + struct tun_file *tfile, int consumed) +{ + u16 queue_index = tfile->queue_index; + struct netdev_queue *txq; + + lockdep_assert_held(&tfile->tx_ring.consumer_lock); + + if (!(tun->flags & IFF_BACKPRESSURE)) + return; + + /* A stop from tun_net_close() is not backpressure, leave it alone. */ + if (unlikely(!netif_running(tun->dev))) + return; + + /* Only the current owner of the slot may wake its subqueue. */ + if (unlikely(rcu_access_pointer(tun->tfiles[queue_index]) != tfile)) + return; + + txq = netdev_get_tx_queue(tun->dev, queue_index); + + /* Paired with smp_mb__after_atomic() in tun_net_xmit() */ + smp_mb(); + if (netif_tx_queue_stopped(txq)) { + tfile->cons_cnt += consumed; + if (tfile->cons_cnt >= tfile->tx_ring.size / 2 || + __ptr_ring_empty(&tfile->tx_ring)) { + netif_tx_wake_queue(txq); + tfile->cons_cnt = 0; + } + } +} + +static void *tun_ring_consume(struct tun_struct *tun, struct tun_file *tfile) +{ + void *ptr; + + spin_lock(&tfile->tx_ring.consumer_lock); + ptr = __ptr_ring_consume(&tfile->tx_ring); + if (ptr) + __tun_wake_queue(tun, tfile, 1); + + spin_unlock(&tfile->tx_ring.consumer_lock); + return ptr; +} + +static void *tun_ring_recv(struct tun_struct *tun, struct tun_file *tfile, + int noblock, int *err) { DECLARE_WAITQUEUE(wait, current); void *ptr = NULL; int error = 0; - ptr = ptr_ring_consume(&tfile->tx_ring); + ptr = tun_ring_consume(tun, tfile); if (ptr) goto out; if (noblock) { @@ -2134,7 +2272,7 @@ static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err) while (1) { set_current_state(TASK_INTERRUPTIBLE); - ptr = ptr_ring_consume(&tfile->tx_ring); + ptr = tun_ring_consume(tun, tfile); if (ptr) break; if (signal_pending(current)) { @@ -2171,7 +2309,7 @@ static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile, if (!ptr) { /* Read frames from ring */ - ptr = tun_ring_recv(tfile, noblock, &err); + ptr = tun_ring_recv(tun, tfile, noblock, &err); if (!ptr) return err; } @@ -2306,32 +2444,36 @@ static size_t tun_get_size(const struct net_device *dev) static int tun_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct tun_struct *tun = netdev_priv(dev); + const struct tun_struct *tun = netdev_priv(dev); + unsigned int flags = READ_ONCE(tun->flags); + kuid_t owner = READ_ONCE(tun->owner); + kgid_t group = READ_ONCE(tun->group); - if (nla_put_u8(skb, IFLA_TUN_TYPE, tun->flags & TUN_TYPE_MASK)) + if (nla_put_u8(skb, IFLA_TUN_TYPE, flags & TUN_TYPE_MASK)) goto nla_put_failure; - if (uid_valid(tun->owner) && + if (uid_valid(owner) && nla_put_u32(skb, IFLA_TUN_OWNER, - from_kuid_munged(current_user_ns(), tun->owner))) + from_kuid_munged(current_user_ns(), owner))) goto nla_put_failure; - if (gid_valid(tun->group) && + if (gid_valid(group) && nla_put_u32(skb, IFLA_TUN_GROUP, - from_kgid_munged(current_user_ns(), tun->group))) + from_kgid_munged(current_user_ns(), group))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_PI, !(tun->flags & IFF_NO_PI))) + if (nla_put_u8(skb, IFLA_TUN_PI, !(flags & IFF_NO_PI))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(tun->flags & IFF_VNET_HDR))) + if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(flags & IFF_VNET_HDR))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(tun->flags & IFF_PERSIST))) + if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(flags & IFF_PERSIST))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_TUN_MULTI_QUEUE, - !!(tun->flags & IFF_MULTI_QUEUE))) + !!(flags & IFF_MULTI_QUEUE))) goto nla_put_failure; - if (tun->flags & IFF_MULTI_QUEUE) { - if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, tun->numqueues)) + if (flags & IFF_MULTI_QUEUE) { + if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, + READ_ONCE(tun->numqueues))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_TUN_NUM_DISABLED_QUEUES, - tun->numdisabled)) + READ_ONCE(tun->numdisabled))) goto nla_put_failure; } @@ -2699,8 +2841,9 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) { struct tun_struct *tun; struct tun_file *tfile = file->private_data; + struct tun_file *ntfile; struct net_device *dev; - int err; + int err, i; if (tfile->detached) return -EINVAL; @@ -2750,8 +2893,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) return 0; } - tun->flags = (tun->flags & ~TUN_FEATURES) | - (ifr->ifr_flags & TUN_FEATURES); + WRITE_ONCE(tun->flags, (tun->flags & ~TUN_FEATURES) | + (ifr->ifr_flags & TUN_FEATURES)); netdev_state_change(dev); } else { @@ -2829,8 +2972,10 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) /* Make sure persistent devices do not get stuck in * xoff state. */ - if (netif_running(tun->dev)) - netif_tx_wake_all_queues(tun->dev); + for (i = 0; i < tun->numqueues; i++) { + ntfile = rtnl_dereference(tun->tfiles[i]); + tun_force_wake_queue(tun, ntfile); + } strscpy(ifr->ifr_name, tun->dev->name); return 0; @@ -3149,13 +3294,13 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, /* Disable/Enable persist mode. Keep an extra reference to the * module to prevent the module being unprobed. */ - if (arg && !(tun->flags & IFF_PERSIST)) { - tun->flags |= IFF_PERSIST; + if (arg && !(READ_ONCE(tun->flags) & IFF_PERSIST)) { + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) | IFF_PERSIST); __module_get(THIS_MODULE); do_notify = true; } - if (!arg && (tun->flags & IFF_PERSIST)) { - tun->flags &= ~IFF_PERSIST; + if (!arg && (READ_ONCE(tun->flags) & IFF_PERSIST)) { + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) & ~IFF_PERSIST); module_put(THIS_MODULE); do_notify = true; } @@ -3171,10 +3316,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; break; } - tun->owner = owner; + WRITE_ONCE(tun->owner, owner); do_notify = true; netif_info(tun, drv, tun->dev, "owner set to %u\n", - from_kuid(&init_user_ns, tun->owner)); + from_kuid(&init_user_ns, owner)); break; case TUNSETGROUP: @@ -3184,10 +3329,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; break; } - tun->group = group; + WRITE_ONCE(tun->group, group); do_notify = true; netif_info(tun, drv, tun->dev, "group set to %u\n", - from_kgid(&init_user_ns, tun->group)); + from_kgid(&init_user_ns, group)); break; case TUNSETLINK: @@ -3626,6 +3771,13 @@ static int tun_queue_resize(struct tun_struct *tun) dev->tx_queue_len, GFP_KERNEL, tun_ptr_free); + if (!ret) { + for (i = 0; i < tun->numqueues; i++) { + tfile = rtnl_dereference(tun->tfiles[i]); + tun_force_wake_queue(tun, tfile); + } + } + kfree(rings); return ret; } @@ -3734,6 +3886,31 @@ struct ptr_ring *tun_get_tx_ring(struct file *file) } EXPORT_SYMBOL_GPL(tun_get_tx_ring); +/* Callers must hold ring.consumer_lock */ +void tun_wake_queue(struct file *file, int consumed) +{ + struct tun_file *tfile; + struct tun_struct *tun; + + if (file->f_op != &tun_fops) + return; + + tfile = file->private_data; + if (!tfile) + return; + + lockdep_assert_held(&tfile->tx_ring.consumer_lock); + + rcu_read_lock(); + + tun = rcu_dereference(tfile->tun); + if (tun) + __tun_wake_queue(tun, tfile, consumed); + + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(tun_wake_queue); + module_init(tun_init); module_exit(tun_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h index fa5cab9d3e55..f4c652b1fa44 100644 --- a/drivers/net/tun_vnet.h +++ b/drivers/net/tun_vnet.h @@ -40,9 +40,9 @@ static inline long tun_set_vnet_be(unsigned int *flags, int __user *argp) return -EFAULT; if (be) - *flags |= TUN_VNET_BE; + WRITE_ONCE(*flags, *flags | TUN_VNET_BE); else - *flags &= ~TUN_VNET_BE; + WRITE_ONCE(*flags, *flags & ~TUN_VNET_BE); return 0; } @@ -93,9 +93,9 @@ static inline long tun_vnet_ioctl(int *vnet_hdr_sz, unsigned int *flags, if (get_user(s, sp)) return -EFAULT; if (s) - *flags |= TUN_VNET_LE; + WRITE_ONCE(*flags, *flags | TUN_VNET_LE); else - *flags &= ~TUN_VNET_LE; + WRITE_ONCE(*flags, *flags & ~TUN_VNET_LE); return 0; case TUNGETVNETBE: diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index a0a5740590b9..b5e4b195b69a 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -538,6 +538,16 @@ static const struct driver_info cdc_info = { .manage_power = usbnet_manage_power, }; +static const struct driver_info ami_bmc_info = { + .description = "AMI BMC USB Ethernet", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_LINK_INTR, + .bind = usbnet_cdc_bind, + .unbind = usbnet_cdc_unbind, + .status = usbnet_cdc_status, + .set_rx_mode = usbnet_cdc_update_filter, + .manage_power = usbnet_manage_power, +}; + static const struct driver_info zte_cdc_info = { .description = "ZTE CDC Ethernet Device", .flags = FLAG_ETHER | FLAG_POINTTOPOINT, @@ -946,6 +956,12 @@ static const struct usb_device_id products[] = { USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), .driver_info = (unsigned long)&wwan_info, +}, { + /* AMI BMC gadget */ + USB_DEVICE_AND_INTERFACE_INFO(0x046b, 0xffb0, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&ami_bmc_info, }, { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), diff --git a/drivers/net/usb/lg-vl600.c b/drivers/net/usb/lg-vl600.c index c4ad2d9f6f4f..d6b2e3e3d950 100644 --- a/drivers/net/usb/lg-vl600.c +++ b/drivers/net/usb/lg-vl600.c @@ -172,7 +172,7 @@ static int vl600_rx_fixup(struct usbnet *dev, struct sk_buff *skb) * the h_proto field is in the same place so we just leave it * alone and fill in the remaining fields. */ - ethhdr = (struct ethhdr *) skb->data; + ethhdr = (struct ethhdr *)buf->data; if (be16_to_cpup(ðhdr->h_proto) == ETH_P_ARP && buf->len > 0x26) { /* Copy the addresses from packet contents */ diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 94cdb61dca83..8178a8758cd3 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1360,6 +1360,7 @@ static const struct usb_device_id products[] = { {QMI_FIXED_INTF(0x1bbb, 0x0203, 2)}, /* Alcatel L800MA */ {QMI_FIXED_INTF(0x2357, 0x0201, 4)}, /* TP-LINK HSUPA Modem MA180 */ {QMI_FIXED_INTF(0x2357, 0x9000, 4)}, /* TP-LINK MA260 */ + {QMI_QUIRK_SET_DTR(0x1bc7, 0x0991, 0)}, /* Telit FE990D50 */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1031, 3)}, /* Telit LE910C1-EUX */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1034, 2)}, /* Telit LE910C4-WWX */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1037, 4)}, /* Telit LE910C4-WWX */ diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c index 5e39d05a2d7b..37d4865f5c5e 100644 --- a/drivers/net/usb/rndis_host.c +++ b/drivers/net/usb/rndis_host.c @@ -14,6 +14,7 @@ #include #include #include +#include /* @@ -506,6 +507,7 @@ int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb) struct rndis_data_hdr *hdr = (void *)skb->data; struct sk_buff *skb2; u32 msg_type, msg_len, data_offset, data_len; + u32 overflow_check; msg_type = le32_to_cpu(hdr->msg_type); msg_len = le32_to_cpu(hdr->msg_len); @@ -514,7 +516,9 @@ int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* don't choke if we see oob, per-packet data, etc */ if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len - || (data_offset + data_len + 8) > msg_len)) { + || (data_offset + data_len + 8) > msg_len + || check_add_overflow(data_offset, data_len, &overflow_check) + || check_add_overflow(overflow_check, 8, &overflow_check))) { dev->net->stats.rx_frame_errors++; netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n", le32_to_cpu(hdr->msg_type), diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c index c880c95c41a5..d51e43170e03 100644 --- a/drivers/net/usb/rtl8150.c +++ b/drivers/net/usb/rtl8150.c @@ -732,7 +732,9 @@ static void set_carrier(struct net_device *netdev) rtl8150_t *dev = netdev_priv(netdev); short tmp; - get_registers(dev, CSCR, 2, &tmp); + if (get_registers(dev, CSCR, 2, &tmp)) + return; + if (tmp & CSCR_LINK_STATUS) netif_carrier_on(netdev); else diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 6ab84c837a33..6ed3ee81153f 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -77,6 +77,7 @@ struct veth_priv { struct bpf_prog *_xdp_prog; struct veth_rq *rq; unsigned int requested_headroom; + netdevice_tracker peer_tracker; }; struct veth_xdp_tx_bq { @@ -1907,15 +1908,17 @@ static int veth_newlink(struct net_device *dev, priv = netdev_priv(dev); rcu_assign_pointer(priv->peer, peer); + netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL); err = veth_init_queues(dev, tb); if (err) goto err_queues; priv = netdev_priv(peer); rcu_assign_pointer(priv->peer, dev); + netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL); err = veth_init_queues(peer, tb); if (err) - goto err_queues; + goto err_peer_queues; veth_disable_gro(dev); /* update XDP supported features */ @@ -1924,7 +1927,11 @@ static int veth_newlink(struct net_device *dev, return 0; +err_peer_queues: + netdev_put(dev, &priv->peer_tracker); + priv = netdev_priv(dev); err_queues: + netdev_put(peer, &priv->peer_tracker); unregister_netdevice(dev); err_register_dev: /* nothing to do */ @@ -1939,24 +1946,25 @@ static int veth_newlink(struct net_device *dev, static void veth_dellink(struct net_device *dev, struct list_head *head) { - struct veth_priv *priv; + netdevice_tracker *peer_tracker; struct net_device *peer; + struct veth_priv *priv; priv = netdev_priv(dev); - peer = rtnl_dereference(priv->peer); + peer_tracker = &priv->peer_tracker; + peer = unrcu_pointer(xchg(&priv->peer, NULL)); + if (!peer) + return; - /* Note : dellink() is called from default_device_exit_batch(), - * before a rcu_synchronize() point. The devices are guaranteed - * not being freed before one RCU grace period. - */ - RCU_INIT_POINTER(priv->peer, NULL); unregister_netdevice_queue(dev, head); - if (peer) { - priv = netdev_priv(peer); - RCU_INIT_POINTER(priv->peer, NULL); - unregister_netdevice_queue(peer, head); - } + priv = netdev_priv(peer); + dev = unrcu_pointer(xchg(&priv->peer, NULL)); + if (dev) + unregister_netdevice_queue_net(dev_net(dev), peer, head); + + netdev_put(peer, peer_tracker); + netdev_put(dev, &priv->peer_tracker); } static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = { diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 3e2a5876c6c8..e34c52d059d3 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3444,17 +3444,31 @@ static void virtnet_rx_resume_all(struct virtnet_info *vi) static int virtnet_rx_resize(struct virtnet_info *vi, struct receive_queue *rq, u32 ring_num) { + unsigned int old_ring_num = virtqueue_get_vring_size(rq->vq); + struct xdp_buff **tmp_xsk_buffs = NULL; int err, qindex; qindex = rq - vi->rq; + if (rq->xsk_pool && ring_num > old_ring_num) { + tmp_xsk_buffs = kvzalloc_objs(*tmp_xsk_buffs, ring_num); + if (!tmp_xsk_buffs) + return -ENOMEM; + } + virtnet_rx_pause(vi, rq); err = virtqueue_resize(rq->vq, ring_num, virtnet_rq_unmap_free_buf, NULL); + + /* virtqueue_resize may have changed the size even if err != 0 */ + if (tmp_xsk_buffs && virtqueue_get_vring_size(rq->vq) > old_ring_num) + swap(rq->xsk_buffs, tmp_xsk_buffs); + if (err) netdev_err(vi->dev, "resize rx fail: rx queue index: %d err: %d\n", qindex, err); virtnet_rx_resume(vi, rq, true); + kvfree(tmp_xsk_buffs); return err; } diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index 46209917ae4d..a0557a3a7026 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -1932,7 +1932,9 @@ static int __init vrf_init_module(void) { int rc; - register_netdevice_notifier(&vrf_notifier_block); + rc = register_netdevice_notifier(&vrf_notifier_block); + if (rc < 0) + return rc; rc = register_pernet_subsys(&vrf_net_ops); if (rc < 0) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 824144bb7774..ac88d1c85bea 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3058,18 +3058,19 @@ vxlan_fdb_flush_match_remotes(struct vxlan_fdb *f, struct vxlan_dev *vxlan, const struct vxlan_fdb_flush_desc *desc, bool *p_destroy_fdb) { - bool remotes_flushed = false; struct vxlan_rdst *rd, *tmp; list_for_each_entry_safe(rd, tmp, &f->remotes, list) { if (!vxlan_fdb_flush_remote_matches(desc, rd)) continue; - vxlan_fdb_dst_destroy(vxlan, f, rd, true); - remotes_flushed = true; - } + if (list_is_singular(&f->remotes)) { + *p_destroy_fdb = true; + return; + } - *p_destroy_fdb = remotes_flushed && list_empty(&f->remotes); + vxlan_fdb_dst_destroy(vxlan, f, rd, true); + } } /* Purge the forwarding table */ @@ -3377,6 +3378,8 @@ static void vxlan_setup(struct net_device *dev) dev->mangleid_features = NETIF_F_GSO_PARTIAL; netif_keep_dst(dev); + netif_set_tso_max_size(dev, GSO_MAX_SIZE); + dev->priv_flags |= IFF_NO_QUEUE; dev->change_proto_down = true; dev->lltx = true; @@ -3964,9 +3967,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, return 0; } -static int __vxlan_dev_create(struct net *net, struct net_device *dev, - struct vxlan_config *conf, - struct netlink_ext_ack *extack) +static int vxlan_dev_create(struct net *net, struct net_device *dev, + struct vxlan_config *conf, + struct netlink_ext_ack *extack) { struct vxlan_net *vn = net_generic(net, vxlan_net_id); struct vxlan_dev *vxlan = netdev_priv(dev); @@ -4414,7 +4417,7 @@ static int vxlan_newlink(struct net_device *dev, if (err) return err; - return __vxlan_dev_create(link_net, dev, &conf, extack); + return vxlan_dev_create(link_net, dev, &conf, extack); } static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], @@ -4698,40 +4701,6 @@ static struct rtnl_link_ops vxlan_link_ops __read_mostly = { .get_link_net = vxlan_get_link_net, }; -struct net_device *vxlan_dev_create(struct net *net, const char *name, - u8 name_assign_type, - struct vxlan_config *conf) -{ - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - int err; - - memset(&tb, 0, sizeof(tb)); - - dev = rtnl_create_link(net, name, name_assign_type, - &vxlan_link_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - err = __vxlan_dev_create(net, dev, conf, NULL); - if (err < 0) { - free_netdev(dev); - return ERR_PTR(err); - } - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) { - LIST_HEAD(list_kill); - - vxlan_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); - } - - return dev; -} -EXPORT_SYMBOL_GPL(vxlan_dev_create); - static void vxlan_handle_lowerdev_unregister(struct vxlan_net *vn, struct net_device *dev) { diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index 9a9038ae90c1..d71e1925ecfd 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -1428,14 +1428,17 @@ static void vxlan_mdb_flush(struct vxlan_dev *vxlan, struct vxlan_mdb_entry *mdb_entry; struct hlist_node *tmp; - /* The removal of an entry cannot trigger the removal of another entry - * since entries are always added to the head of the list. - */ hlist_for_each_entry_safe(mdb_entry, tmp, &vxlan->mdb_list, mdb_node) { if (desc->src_vni && desc->src_vni != mdb_entry->key.vni) continue; vxlan_mdb_remotes_flush(vxlan, mdb_entry, desc); + /* The flush can remove the (S, G) entries created for the + * source list of this entry, including the one saved by + * hlist_for_each_entry_safe(), so re-read it while this entry + * is still linked. + */ + tmp = mdb_entry->mdb_node.next; /* Entry will only be removed if its remotes list is empty. */ vxlan_mdb_entry_put(vxlan, mdb_entry); } diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 3e76f4e21094..dd94085e0886 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -462,10 +462,8 @@ static int vxlan_vnifilter_dump(struct sk_buff *skb, struct netlink_callback *cb static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX + 1] = { [VXLAN_VNIFILTER_ENTRY_START] = { .type = NLA_U32 }, [VXLAN_VNIFILTER_ENTRY_END] = { .type = NLA_U32 }, - [VXLAN_VNIFILTER_ENTRY_GROUP] = { .type = NLA_BINARY, - .len = sizeof_field(struct iphdr, daddr) }, - [VXLAN_VNIFILTER_ENTRY_GROUP6] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [VXLAN_VNIFILTER_ENTRY_GROUP] = NLA_POLICY_EXACT_LEN(sizeof_field(struct iphdr, daddr)), + [VXLAN_VNIFILTER_ENTRY_GROUP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), }; static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = { diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c index 9861c99ea56c..c3630a82913b 100644 --- a/drivers/net/wan/lapbether.c +++ b/drivers/net/wan/lapbether.c @@ -497,9 +497,15 @@ static const char banner[] __initconst = static int __init lapbeth_init_driver(void) { + int err; + dev_add_pack(&lapbeth_packet_type); - register_netdevice_notifier(&lapbeth_dev_notifier); + err = register_netdevice_notifier(&lapbeth_dev_notifier); + if (err) { + dev_remove_pack(&lapbeth_packet_type); + return err; + } printk(banner); diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c index 065c00c12cc1..c9873f07e06c 100644 --- a/drivers/net/wan/wanxl.c +++ b/drivers/net/wan/wanxl.c @@ -37,7 +37,6 @@ static const char *version = "wanXL serial card driver version: 0.48"; #define PLX_CTL_RESET 0x40000000 /* adapter reset */ #undef DEBUG_PKT -#undef DEBUG_PCI /* MAILBOX #1 - PUTS COMMANDS */ #define MBX1_CMD_ABORTJ 0x85000000 /* Abort and Jump */ @@ -88,22 +87,6 @@ static inline port_status_t *get_status(struct port *port) return &port->card->status->port_status[port->node]; } -#ifdef DEBUG_PCI -static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr, - size_t size, int direction) -{ - dma_addr_t addr = dma_map_single(&pdev->dev, ptr, size, direction); - - if (addr + size > 0x100000000LL) - pr_crit("%s: pci_map_single() returned memory at 0x%llx!\n", - pci_name(pdev), (unsigned long long)addr); - return addr; -} - -#undef pci_map_single -#define pci_map_single pci_map_single_debug -#endif - /* Cable and/or personality module change interrupt service */ static inline void wanxl_cable_intr(struct port *port) { diff --git a/drivers/net/wireguard/receive.c b/drivers/net/wireguard/receive.c index eb8851113654..824bbefce61c 100644 --- a/drivers/net/wireguard/receive.c +++ b/drivers/net/wireguard/receive.c @@ -62,7 +62,7 @@ static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg) * to have UDP fields. */ return -EINVAL; - data_len = ntohs(udp->len); + data_len = udp_get_len_short(udp); if (unlikely(data_len < sizeof(struct udphdr) || data_len > skb->len - data_offset)) /* UDP packet is reporting too small of a size or lying about diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index c6599594dc99..f4f969fdbc02 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -27,6 +27,8 @@ source "drivers/net/wireless/intersil/Kconfig" source "drivers/net/wireless/marvell/Kconfig" source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/microchip/Kconfig" +source "drivers/net/wireless/morsemicro/Kconfig" +source "drivers/net/wireless/nxp/Kconfig" source "drivers/net/wireless/purelifi/Kconfig" source "drivers/net/wireless/ralink/Kconfig" source "drivers/net/wireless/realtek/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index e1c4141c6004..fa3100ff3bf7 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/ obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/ +obj-$(CONFIG_WLAN_VENDOR_MORSEMICRO) += morsemicro/ +obj-$(CONFIG_WLAN_VENDOR_NXP) += nxp/ obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/ obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/ obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/ diff --git a/drivers/net/wireless/ath/ath10k/ahb.c b/drivers/net/wireless/ath/ath10k/ahb.c index eb8b35b6224d..7456f885d2b5 100644 --- a/drivers/net/wireless/ath/ath10k/ahb.c +++ b/drivers/net/wireless/ath/ath10k/ahb.c @@ -87,24 +87,24 @@ static int ath10k_ahb_clock_init(struct ath10k *ar) dev = &ar_ahb->pdev->dev; ar_ahb->cmd_clk = devm_clk_get(dev, "wifi_wcss_cmd"); - if (IS_ERR_OR_NULL(ar_ahb->cmd_clk)) { + if (IS_ERR(ar_ahb->cmd_clk)) { ath10k_err(ar, "failed to get cmd clk: %ld\n", PTR_ERR(ar_ahb->cmd_clk)); - return ar_ahb->cmd_clk ? PTR_ERR(ar_ahb->cmd_clk) : -ENODEV; + return PTR_ERR(ar_ahb->cmd_clk); } ar_ahb->ref_clk = devm_clk_get(dev, "wifi_wcss_ref"); - if (IS_ERR_OR_NULL(ar_ahb->ref_clk)) { + if (IS_ERR(ar_ahb->ref_clk)) { ath10k_err(ar, "failed to get ref clk: %ld\n", PTR_ERR(ar_ahb->ref_clk)); - return ar_ahb->ref_clk ? PTR_ERR(ar_ahb->ref_clk) : -ENODEV; + return PTR_ERR(ar_ahb->ref_clk); } ar_ahb->rtc_clk = devm_clk_get(dev, "wifi_wcss_rtc"); - if (IS_ERR_OR_NULL(ar_ahb->rtc_clk)) { + if (IS_ERR(ar_ahb->rtc_clk)) { ath10k_err(ar, "failed to get rtc clk: %ld\n", PTR_ERR(ar_ahb->rtc_clk)); - return ar_ahb->rtc_clk ? PTR_ERR(ar_ahb->rtc_clk) : -ENODEV; + return PTR_ERR(ar_ahb->rtc_clk); } return 0; diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c index b3f1b7186721..ab2d373b4750 100644 --- a/drivers/net/wireless/ath/ath10k/htt_rx.c +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c @@ -2345,10 +2345,8 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt) if (ret < 0) { ath10k_warn(ar, "rx ring became corrupted: %d\n", ret); __skb_queue_purge(&amsdu); - /* FIXME: It's probably a good idea to reboot the - * device instead of leaving it inoperable. - */ htt->rx_confused = true; + ath10k_core_start_recovery(ar); return ret; } @@ -3313,6 +3311,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) if (ret < 0) { ath10k_warn(ar, "failed to pop paddr list: %d\n", ret); htt->rx_confused = true; + ath10k_core_start_recovery(ar); return -EIO; } @@ -3346,6 +3345,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) ath10k_warn(ar, "failed to extract amsdu: %d\n", ret); htt->rx_confused = true; __skb_queue_purge(&list); + ath10k_core_start_recovery(ar); return -EIO; } } diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index 310650227578..33c98927e8fe 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -1475,11 +1476,15 @@ static void ath10k_msa_dump_memory(struct ath10k *ar, hdr->length = cpu_to_le32(ar->msa.mem_size); if (current_region->len < ar->msa.mem_size) { - memcpy(buf, ar->msa.vaddr, current_region->len); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + current_region->len); ath10k_warn(ar, "msa dump length is less than msa size %x, %x\n", current_region->len, ar->msa.mem_size); } else { - memcpy(buf, ar->msa.vaddr, ar->msa.mem_size); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + ar->msa.mem_size); } } diff --git a/drivers/net/wireless/ath/ath10k/testmode.c b/drivers/net/wireless/ath/ath10k/testmode.c index d3bd385694d6..282ae6e20c8e 100644 --- a/drivers/net/wireless/ath/ath10k/testmode.c +++ b/drivers/net/wireless/ath/ath10k/testmode.c @@ -156,6 +156,14 @@ static void ath10k_tm_event_segmented(struct ath10k *ar, u32 cmd_id, struct sk_b cfg80211_testmode_event(nl_skb, GFP_ATOMIC); } +static bool ath10k_tm_is_utf_event(u32 cmd_id) +{ + return cmd_id == WMI_10X_PDEV_UTF_EVENTID || + cmd_id == WMI_10_2_PDEV_UTF_EVENTID || + cmd_id == WMI_10_4_PDEV_UTF_EVENTID || + cmd_id == WMI_TLV_PDEV_UTF_EVENTID; +} + /* Returns true if callee consumes the skb and the skb should be discarded. * Returns false if skb is not used. Does not sleep. */ @@ -182,6 +190,12 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb) */ consumed = true; + if (!ath10k_tm_is_utf_event(cmd_id)) { + ath10k_dbg(ar, ATH10K_DBG_TESTMODE, + "testmode drop non-utf event cmd_id %u\n", cmd_id); + goto out; + } + if (ar->testmode.expected_seq != ATH10K_FTM_SEG_NONE) ath10k_tm_event_segmented(ar, cmd_id, skb); else diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 1e1dea485760..ec5bf5c9fd79 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -431,36 +431,44 @@ static void ath11k_ahb_init_qmi_ce_config(struct ath11k_base *ab) ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id; } -static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq_grp(struct ath11k_base *ab, + struct ath11k_ext_irq_grp *irq_grp) { - int i, j; + int j; - for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; + for (j = 0; j < irq_grp->num_irq; j++) + free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - for (j = 0; j < irq_grp->num_irq; j++) - free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - - netif_napi_del(&irq_grp->napi); - free_netdev(irq_grp->napi_ndev); - } + netif_napi_del(&irq_grp->napi); + free_netdev(irq_grp->napi_ndev); } -static void ath11k_ahb_free_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) { - int irq_idx; int i; - if (ab->hw_params.hybrid_bus_type) - return ath11k_pcic_free_irq(ab); + for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) + ath11k_ahb_free_ext_irq_grp(ab, &ab->ext_irq_grp[i]); +} - for (i = 0; i < ab->hw_params.ce_count; i++) { +static void ath11k_ahb_free_ce_irqs(struct ath11k_base *ab, int max_idx) +{ + int irq_idx, i; + + for (i = 0; i < max_idx; i++) { if (ath11k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) continue; irq_idx = ATH11K_IRQ_CE0_OFFSET + i; free_irq(ab->irq_num[irq_idx], &ab->ce.ce_pipe[i]); } +} +static void ath11k_ahb_free_irq(struct ath11k_base *ab) +{ + if (ab->hw_params.hybrid_bus_type) + return ath11k_pcic_free_irq(ab); + + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); ath11k_ahb_free_ext_irq(ab); } @@ -524,20 +532,25 @@ static irqreturn_t ath11k_ahb_ext_interrupt_handler(int irq, void *arg) static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) { struct ath11k_hw_params *hw = &ab->hw_params; + struct ath11k_ext_irq_grp *irq_grp; int i, j; int irq; int ret; for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; + irq_grp = &ab->ext_irq_grp[i]; + irq_grp->ab = ab; irq_grp->grp_id = i; irq_grp->napi_ndev = alloc_netdev_dummy(0); - if (!irq_grp->napi_ndev) - return -ENOMEM; + if (!irq_grp->napi_ndev) { + ret = -ENOMEM; + irq_grp->num_irq = 0; + goto err_request_irq; + } netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath11k_ahb_ext_grp_napi_poll); @@ -585,14 +598,11 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) } } } - irq_grp->num_irq = num_irq; - - for (j = 0; j < irq_grp->num_irq; j++) { + for (j = 0; j < num_irq; j++) { int irq_idx = irq_grp->irqs[j]; irq = platform_get_irq_byname(ab->pdev, irq_name[irq_idx]); - ab->irq_num[irq_idx] = irq; irq_set_status_flags(irq, IRQ_NOAUTOEN | IRQ_DISABLE_UNLAZY); ret = request_irq(irq, ath11k_ahb_ext_interrupt_handler, IRQF_TRIGGER_RISING, @@ -600,11 +610,24 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) if (ret) { ath11k_err(ab, "failed request_irq for %d\n", irq); + irq_grp->num_irq = j; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + goto err_request_irq; } + ab->irq_num[irq_idx] = irq; } + + irq_grp->num_irq = num_irq; } return 0; + +err_request_irq: + for (i--; i >= 0; i--) { + irq_grp = &ab->ext_irq_grp[i]; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + } + return ret; } static int ath11k_ahb_config_irq(struct ath11k_base *ab) @@ -629,16 +652,24 @@ static int ath11k_ahb_config_irq(struct ath11k_base *ab) ret = request_irq(irq, ath11k_ahb_ce_interrupt_handler, IRQF_TRIGGER_RISING, irq_name[irq_idx], ce_pipe); - if (ret) + if (ret) { + ath11k_err(ab, "failed request_irq for %d\n", irq); + ath11k_ahb_free_ce_irqs(ab, i); return ret; + } ab->irq_num[irq_idx] = irq; } /* Configure external interrupts */ ret = ath11k_ahb_config_ext_irq(ab); + if (ret) { + ath11k_err(ab, "failed to configure ext irq: %d\n", ret); + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); + return ret; + } - return ret; + return 0; } static int ath11k_ahb_map_service_to_pipe(struct ath11k_base *ab, u16 service_id, diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 8dacc878c006..8039124e7832 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1049,9 +1049,11 @@ static const struct __ath11k_core_usecase_firmware_table { const char *compatible; const char *firmware_name; } ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, - { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,purwa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,qcs6490-rb3gen2", "nfa765"}, { /* Sentinel */ } }; diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 8e2abc7b8383..33425707c084 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2334,10 +2334,10 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, case RX_MSDU_START_PKT_TYPE_11N: rx_status->encoding = RX_ENC_HT; if (rate_mcs > ATH11K_HT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HT_MCS_MAX); + rate_mcs = ATH11K_HT_MCS_MAX; } rx_status->rate_idx = rate_mcs + (8 * (nss - 1)); if (sgi) @@ -2346,13 +2346,13 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, break; case RX_MSDU_START_PKT_TYPE_11AC: rx_status->encoding = RX_ENC_VHT; - rx_status->rate_idx = rate_mcs; if (rate_mcs > ATH11K_VHT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in VHT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received VHT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_VHT_MCS_MAX); + rate_mcs = ATH11K_VHT_MCS_MAX; } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; if (sgi) rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; @@ -2362,14 +2362,14 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, rx_status->enc_flags |= RX_ENC_FLAG_LDPC; break; case RX_MSDU_START_PKT_TYPE_11AX: - rx_status->rate_idx = rate_mcs; - if (rate_mcs > ATH11K_HE_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HE mode %d\n", - rate_mcs); - break; - } rx_status->encoding = RX_ENC_HE; + if (rate_mcs > ATH11K_HE_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HE frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HE_MCS_MAX); + rate_mcs = ATH11K_HE_MCS_MAX; + } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; rx_status->he_gi = ath11k_mac_he_gi_to_nl80211_he_gi(sgi); rx_status->bw = ath11k_mac_bw_to_mac80211_bw(bw); diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index dca6e011cc40..bbca275a8289 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -159,6 +159,8 @@ static const struct wmi_tlv_policy wmi_tlv_policies[] = { .min_len = sizeof(struct ath11k_wmi_p2p_noa_info) }, [WMI_TAG_P2P_NOA_EVENT] = { .min_len = sizeof(struct wmi_p2p_noa_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct wmi_pdev_csa_switch_ev) }, }; #define PRIMAP(_hw_mode_) \ @@ -262,6 +264,13 @@ const void **ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab, return tb; } +static u32 ath11k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return FIELD_GET(WMI_TLV_LEN, tlv->header); +} + static int ath11k_wmi_cmd_send_nowait(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -2423,8 +2432,8 @@ int ath11k_wmi_send_scan_start_cmd(struct ath11k *ar, for (i = 0; i < params->num_hint_bssid; ++i) { hint_bssid->freq_flags = params->hint_bssid[i].freq_flags; - ether_addr_copy(¶ms->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + ether_addr_copy(&hint_bssid->bssid.addr[0], + ¶ms->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } @@ -4800,14 +4809,16 @@ static int ath11k_wmi_tlv_mac_phy_caps_parse(struct ath11k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kcalloc(svc_rdy_ext->tot_phy_id, - len, GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; @@ -4858,6 +4869,12 @@ static int ath11k_wmi_tlv_ext_hal_reg_caps(struct ath11k_base *soc, return ret; } + if (reg_cap.phy_id >= ARRAY_SIZE(soc->hal_reg_cap)) { + ath11k_warn(soc, "invalid reg cap phy_id %u\n", + reg_cap.phy_id); + return -EINVAL; + } + memcpy(&soc->hal_reg_cap[reg_cap.phy_id], ®_cap, sizeof(reg_cap)); } @@ -5118,6 +5135,7 @@ static int ath11k_service_ready_ext_event(struct ath11k_base *ab, return 0; err: + kfree(svc_rdy_ext.mac_phy_caps); ath11k_wmi_free_dbring_caps(ab); return ret; } @@ -8353,15 +8371,23 @@ ath11k_wmi_process_csa_switch_count_event(struct ath11k_base *ab, const struct wmi_pdev_csa_switch_ev *ev, const u32 *vdev_ids) { - int i; + u32 vdev_ids_len = ath11k_wmi_tlv_data_len(vdev_ids); + u32 num_vdevs = ev->num_vdevs; struct ath11k_vif *arvif; + int i; /* Finish CSA once the switch count becomes NULL */ if (ev->current_switch_count) return; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath11k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); - for (i = 0; i < ev->num_vdevs; i++) { + for (i = 0; i < num_vdevs; i++) { arvif = ath11k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); if (!arvif) { @@ -8895,13 +8921,15 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; + if (skb->len < sizeof(*cmd_hdr)) + goto out; + cmd_hdr = (struct wmi_cmd_hdr *)skb->data; id = FIELD_GET(WMI_CMD_HDR_CMD_ID, (cmd_hdr->cmd_id)); trace_ath11k_wmi_event(ab, id, skb->data, skb->len); - if (skb_pull(skb, sizeof(struct wmi_cmd_hdr)) == NULL) - goto out; + skb_pull(skb, sizeof(*cmd_hdr)); switch (id) { /* Process all the WMI events here */ diff --git a/drivers/net/wireless/ath/ath12k/Kconfig b/drivers/net/wireless/ath/ath12k/Kconfig index 4a2b240f967a..0d5d1c55bfc1 100644 --- a/drivers/net/wireless/ath/ath12k/Kconfig +++ b/drivers/net/wireless/ath/ath12k/Kconfig @@ -18,7 +18,7 @@ config ATH12K_AHB bool "Qualcomm ath12k AHB support" depends on ATH12K && REMOTEPROC select QCOM_MDT_LOADER - select QCOM_SCM + select QCOM_PAS help Enable support for Ath12k AHB bus chipsets, example IPQ5332. diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 30733a244454..0fc55c9169e1 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -5,19 +5,19 @@ */ #include -#include +#include #include #include #include #include #include #include +#include #include "ahb.h" #include "debug.h" #include "hif.h" #define ATH12K_IRQ_CE0_OFFSET 4 -#define ATH12K_MAX_UPDS 1 #define ATH12K_UPD_IRQ_WRD_LEN 18 static struct ath12k_ahb_driver *ath12k_ahb_family_drivers[ATH12K_DEVICE_FAMILY_MAX]; @@ -25,6 +25,22 @@ static const char ath12k_userpd_irq[][9] = {"spawn", "ready", "stop-ack"}; +/* + * Multi-UserPD Architecture: + * + * One Q6 RootPD (managed by separate rproc driver) supports multiple + * ath12k UserPDs. Each UserPD represents a WiFi radio instance. + * + * Lifecycle: + * - RootPD boots when first UserPD probes + * - All UserPDs share RootPD's SSR notifier + * + * Locking: + * - ath12k_rproc_info_lock: Protects g_rproc_info allocation/free + */ +static struct ath12k_ahb_rproc_info *g_rproc_info; +static DEFINE_MUTEX(ath12k_rproc_info_lock); + static const char *irq_name[ATH12K_IRQ_NUM_MAX] = { "misc-pulse1", "misc-latch", @@ -338,24 +354,25 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) char fw2_name[ATH12K_USERPD_FW_NAME_LEN]; struct device *dev = ab->dev; const struct firmware *fw, *fw2; - struct reserved_mem *rmem = NULL; unsigned long time_left; phys_addr_t mem_phys; + struct resource res; void *mem_region; size_t mem_size; u32 pasid; int ret; - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) - return -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region", + &res); + if (ret) + return ret; - mem_phys = rmem->base; - mem_size = rmem->size; + mem_phys = res.start; + mem_size = resource_size(&res); mem_region = devm_memremap(dev, mem_phys, mem_size, MEMREMAP_WC); if (IS_ERR(mem_region)) { - ath12k_err(ab, "unable to map memory region: %pa+%pa\n", - &rmem->base, &rmem->size); + ath12k_err(ab, "unable to map memory region: %pa+%zx\n", + &res.start, mem_size); return PTR_ERR(mem_region); } @@ -420,7 +437,7 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) if (ab_ahb->scm_auth_enabled) { /* Authenticate FW image using peripheral ID */ - ret = qcom_scm_pas_auth_and_reset(pasid); + ret = qcom_pas_auth_and_reset(pasid); if (ret) { ath12k_err(ab, "failed to boot the remote processor %d\n", ret); goto err_fw2; @@ -485,10 +502,10 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend) pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) | ATH12K_AHB_UPD_SWID; /* Release the firmware */ - ret = qcom_scm_pas_shutdown(pasid); + ret = qcom_pas_shutdown(pasid); if (ret) - ath12k_err(ab, "scm pas shutdown failed for userPD%d\n", - ab_ahb->userpd_id); + ath12k_err(ab, "PAS shutdown failed for userPD%d: %d\n", + ab_ahb->userpd_id, ret); } } @@ -703,7 +720,7 @@ static int ath12k_ahb_map_service_to_pipe(struct ath12k_base *ab, u16 service_id return 0; } -static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { +const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .start = ath12k_ahb_start, .stop = ath12k_ahb_stop, .read32 = ath12k_ahb_read32, @@ -714,6 +731,7 @@ static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .power_up = ath12k_ahb_power_up, .power_down = ath12k_ahb_power_down, }; +EXPORT_SYMBOL(ath12k_ahb_hif_ops); static irqreturn_t ath12k_userpd_irq_handler(int irq, void *data) { @@ -784,44 +802,85 @@ static int ath12k_ahb_config_rproc_irq(struct ath12k_base *ab) static int ath12k_ahb_root_pd_state_notifier(struct notifier_block *nb, const unsigned long event, void *data) { - struct ath12k_ahb *ab_ahb = container_of(nb, struct ath12k_ahb, root_pd_nb); - struct ath12k_base *ab = ab_ahb->ab; + struct ath12k_ahb_rproc_info *rproc_info = + container_of(nb, struct ath12k_ahb_rproc_info, root_pd_nb); if (event == ATH12K_RPROC_AFTER_POWERUP) { - ath12k_dbg(ab, ATH12K_DBG_AHB, "Root PD is UP\n"); - complete(&ab_ahb->rootpd_ready); + ath12k_generic_dbg(ATH12K_DBG_AHB, "Root PD is UP\n"); + complete(&rproc_info->rootpd_ready); } return 0; } -static int ath12k_ahb_register_rproc_notifier(struct ath12k_base *ab) +static int ath12k_ahb_register_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + int ret; - ab_ahb->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; - init_completion(&ab_ahb->rootpd_ready); + lockdep_assert_held(&ath12k_rproc_info_lock); - ab_ahb->root_pd_notifier = qcom_register_ssr_notifier(ab_ahb->tgt_rproc->name, - &ab_ahb->root_pd_nb); - if (IS_ERR(ab_ahb->root_pd_notifier)) - return PTR_ERR(ab_ahb->root_pd_notifier); + if (g_rproc_info->root_pd_notifier) + return 0; + + g_rproc_info->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; + + g_rproc_info->root_pd_notifier = + qcom_register_ssr_notifier(g_rproc_info->tgt_rproc->name, + &g_rproc_info->root_pd_nb); + if (IS_ERR(g_rproc_info->root_pd_notifier)) { + ret = PTR_ERR(g_rproc_info->root_pd_notifier); + g_rproc_info->root_pd_notifier = NULL; + return ret; + } return 0; } -static void ath12k_ahb_unregister_rproc_notifier(struct ath12k_base *ab) +static void ath12k_ahb_unregister_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + lockdep_assert_held(&ath12k_rproc_info_lock); - if (!ab_ahb->root_pd_notifier) { - ath12k_err(ab, "Rproc notifier not registered\n"); + if (!g_rproc_info->root_pd_notifier) return; - } - qcom_unregister_ssr_notifier(ab_ahb->root_pd_notifier, - &ab_ahb->root_pd_nb); - ab_ahb->root_pd_notifier = NULL; + qcom_unregister_ssr_notifier(g_rproc_info->root_pd_notifier, + &g_rproc_info->root_pd_nb); + g_rproc_info->root_pd_notifier = NULL; +} + +static void ath12k_ahb_cleanup_userpd(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info) + return; + + rproc_info->userpd[ab_ahb->userpd_id - 1] = NULL; + rproc_info->num_userpd--; + ab_ahb->rproc_info = NULL; +} + +static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + rproc_info = kzalloc_obj(*rproc_info, GFP_KERNEL); + if (!rproc_info) + return NULL; + + rproc_info->rootpd_booted_by_driver = false; + rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + rproc_info->num_userpd = 1; + init_completion(&rproc_info->rootpd_ready); + ab_ahb->rproc_info = rproc_info; + + return rproc_info; } static int ath12k_ahb_get_rproc(struct ath12k_base *ab) @@ -830,37 +889,69 @@ static int ath12k_ahb_get_rproc(struct ath12k_base *ab) struct device *dev = ab->dev; struct device_node *np; struct rproc *prproc; + int ret; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (ab_ahb->userpd_id > ATH12K_MAX_DEVICES) + return -ENOSPC; + + if (g_rproc_info) { + if (g_rproc_info->num_userpd >= ATH12K_MAX_DEVICES) { + ath12k_err(ab, "Max UserPD limit reached\n"); + return -ENOSPC; + } + + g_rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + g_rproc_info->num_userpd++; + ab_ahb->rproc_info = g_rproc_info; + return 0; + } + + g_rproc_info = ath12k_ahb_rproc_info_alloc(ab); + if (!g_rproc_info) + return -ENOMEM; np = of_parse_phandle(dev->of_node, "qcom,rproc", 0); if (!np) { ath12k_err(ab, "failed to get q6_rproc handle\n"); - return -ENOENT; + ret = -ENOENT; + goto err_free_rproc_info; } prproc = rproc_get_by_phandle(np->phandle); of_node_put(np); - if (!prproc) - return dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, - "failed to get rproc\n"); - - ab_ahb->tgt_rproc = prproc; + if (!prproc) { + ret = dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, + "failed to get rproc\n"); + goto err_free_rproc_info; + } + g_rproc_info->tgt_rproc = prproc; return 0; + +err_free_rproc_info: + ab_ahb->rproc_info = NULL; + kfree(g_rproc_info); + g_rproc_info = NULL; + return ret; } static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); unsigned long time_left; int ret; - ret = rproc_boot(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + reinit_completion(&g_rproc_info->rootpd_ready); + + ret = rproc_boot(g_rproc_info->tgt_rproc); if (ret < 0) { ath12k_err(ab, "RootPD boot failed\n"); return ret; } - time_left = wait_for_completion_timeout(&ab_ahb->rootpd_ready, + time_left = wait_for_completion_timeout(&g_rproc_info->rootpd_ready, ATH12K_ROOTPD_READY_TIMEOUT); if (!time_left) { ath12k_err(ab, "RootPD ready wait timed out\n"); @@ -872,44 +963,74 @@ static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) static int ath12k_ahb_configure_rproc(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); int ret; - ret = ath12k_ahb_get_rproc(ab); - if (ret < 0) - return ret; + mutex_lock(&ath12k_rproc_info_lock); - ret = ath12k_ahb_register_rproc_notifier(ab); + ret = ath12k_ahb_get_rproc(ab); + if (ret < 0) { + mutex_unlock(&ath12k_rproc_info_lock); + return ret; + } + + ret = ath12k_ahb_register_rproc_notifier(); if (ret < 0) { ret = dev_err_probe(&ab->pdev->dev, ret, "failed to register rproc notifier\n"); - goto err_put_rproc; + goto err_cleanup_userpd; } - if (ab_ahb->tgt_rproc->state != RPROC_RUNNING) { + if (g_rproc_info->tgt_rproc->state != RPROC_RUNNING) { ret = ath12k_ahb_boot_root_pd(ab); if (ret < 0) { ath12k_err(ab, "failed to boot the remote processor Q6\n"); goto err_unreg_notifier; } + g_rproc_info->rootpd_booted_by_driver = true; } - return ath12k_ahb_config_rproc_irq(ab); + mutex_unlock(&ath12k_rproc_info_lock); + return 0; err_unreg_notifier: - ath12k_ahb_unregister_rproc_notifier(ab); + ath12k_ahb_unregister_rproc_notifier(); -err_put_rproc: - rproc_put(ab_ahb->tgt_rproc); +err_cleanup_userpd: + ath12k_ahb_cleanup_userpd(ab); + + if (g_rproc_info && !g_rproc_info->num_userpd) { + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } + + mutex_unlock(&ath12k_rproc_info_lock); return ret; } static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab) { struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; - ath12k_ahb_unregister_rproc_notifier(ab); - rproc_put(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info || !g_rproc_info) + return; + + ath12k_ahb_cleanup_userpd(ab); + + if (!g_rproc_info->num_userpd) { + ath12k_ahb_unregister_rproc_notifier(); + + if (g_rproc_info->rootpd_booted_by_driver && + g_rproc_info->tgt_rproc->state == RPROC_RUNNING) + rproc_shutdown(g_rproc_info->tgt_rproc); + + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } } static int ath12k_ahb_resource_init(struct ath12k_base *ab) @@ -1037,7 +1158,6 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb = ath12k_ab_to_ahb(ab); ab_ahb->ab = ab; - ab->hif.ops = &ath12k_ahb_hif_ops; ab->pdev = pdev; platform_set_drvdata(pdev, ab); @@ -1093,6 +1213,10 @@ static int ath12k_ahb_probe(struct platform_device *pdev) if (ret) goto err_ce_free; + ret = ath12k_ahb_config_rproc_irq(ab); + if (ret) + goto err_rproc_deconfigure; + ret = ath12k_ahb_config_irq(ab); if (ret) { ath12k_err(ab, "failed to configure irq: %d\n", ret); @@ -1120,7 +1244,9 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb->device_family_ops->arch_deinit(ab); err_rproc_deconfigure: + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); err_ce_free: ath12k_ce_free_pipes(ab); @@ -1162,7 +1288,9 @@ static void ath12k_ahb_free_resources(struct ath12k_base *ab) ath12k_hal_srng_deinit(ab); ath12k_ce_free_pipes(ab); ath12k_ahb_resource_deinit(ab); + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); ab_ahb->device_family_ops->arch_deinit(ab); ath12k_core_free(ab); platform_set_drvdata(pdev, NULL); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index 0fa15daaa3e6..cdb58b07338f 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -27,9 +27,27 @@ #define ATH12K_USERPD_SPAWN_TIMEOUT (5 * HZ) #define ATH12K_USERPD_READY_TIMEOUT (10 * HZ) #define ATH12K_USERPD_STOP_TIMEOUT (5 * HZ) -#define ATH12K_USERPD_ID_MASK GENMASK(9, 8) +#define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 +enum ath12k_ahb_userpd_id { + ATH12K_AHB_USERPD_ID_0 = 1, + ATH12K_AHB_USERPD_ID_1, + ATH12K_AHB_USERPD_ID_2, +}; + +struct ath12k_ahb_userpd_map { + phys_addr_t io_start; + const char *node_name; + u32 upd_id; +}; + +struct ath12k_ahb_desc { + enum ath12k_hw_rev hw_rev; + bool auth_enabled; + const struct ath12k_hif_ops *ops; +}; + enum ath12k_ahb_smp2p_msg_id { ATH12K_AHB_POWER_SAVE_ENTER = 1, ATH12K_AHB_POWER_SAVE_EXIT, @@ -43,6 +61,7 @@ enum ath12k_ahb_userpd_irq { }; struct ath12k_base; +extern const struct ath12k_hif_ops ath12k_ahb_hif_ops; struct ath12k_ahb_device_family_ops { int (*probe)(struct platform_device *pdev); @@ -50,13 +69,19 @@ struct ath12k_ahb_device_family_ops { void (*arch_deinit)(struct ath12k_base *ab); }; -struct ath12k_ahb { - struct ath12k_base *ab; +struct ath12k_ahb_rproc_info { struct rproc *tgt_rproc; - struct clk *xo_clk; - struct completion rootpd_ready; struct notifier_block root_pd_nb; void *root_pd_notifier; + struct completion rootpd_ready; + u8 num_userpd; + bool rootpd_booted_by_driver; + struct ath12k_ahb *userpd[ATH12K_MAX_DEVICES]; +}; + +struct ath12k_ahb { + struct ath12k_base *ab; + struct clk *xo_clk; struct qcom_smem_state *spawn_state; struct qcom_smem_state *stop_state; struct completion userpd_spawned; @@ -69,6 +94,7 @@ struct ath12k_ahb { const struct ath12k_ahb_ops *ahb_ops; const struct ath12k_ahb_device_family_ops *device_family_ops; bool scm_auth_enabled; + struct ath12k_ahb_rproc_info *rproc_info; }; struct ath12k_ahb_driver { diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index e87165e4f4b3..262a2045309b 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -23,6 +23,7 @@ #include "wow.h" #include "dp_cmn.h" #include "peer.h" +#include "qmi.h" unsigned int ath12k_debug_mask; module_param_named(debug_mask, ath12k_debug_mask, uint, 0644); @@ -49,9 +50,10 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .dp_params = { .tx_comp_ring_size = 32768, .rxdma_monitor_buf_ring_size = 4096, - .rxdma_monitor_dst_ring_size = 8092, + .rxdma_monitor_dst_ring_size = 8192, .num_pool_tx_desc = 32768, .rx_desc_count = 12288, + .rx_release_ring_size = 16384, }, }, [ATH12K_QMI_MEMORY_MODE_LOW_512_M] = { @@ -65,6 +67,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .rxdma_monitor_dst_ring_size = 512, .num_pool_tx_desc = 16384, .rx_desc_count = 6144, + .rx_release_ring_size = 8192, }, }, }; @@ -637,31 +640,6 @@ u32 ath12k_core_get_max_peers_per_radio(struct ath12k_base *ab) } EXPORT_SYMBOL(ath12k_core_get_max_peers_per_radio); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index) -{ - struct device *dev = ab->dev; - struct reserved_mem *rmem; - struct device_node *node; - - node = of_parse_phandle(dev->of_node, "memory-region", index); - if (!node) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "failed to parse memory-region for index %d\n", index); - return NULL; - } - - rmem = of_reserved_mem_lookup(node); - of_node_put(node); - if (!rmem) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "unable to get memory-region for index %d\n", index); - return NULL; - } - - return rmem; -} - static inline void ath12k_core_to_group_ref_get(struct ath12k_base *ab) { @@ -708,8 +686,10 @@ static void ath12k_core_stop(struct ath12k_base *ab) ath12k_core_to_group_ref_put(ab); - if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) + if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) { + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_qmi_firmware_stop(ab); + } ath12k_acpi_stop(ab); @@ -817,7 +797,7 @@ static int ath12k_core_soc_create(struct ath12k_base *ab) int ret; if (ath12k_ftm_mode) { - ab->fw_mode = ATH12K_FIRMWARE_MODE_FTM; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_FTM; ath12k_info(ab, "Booting in ftm mode\n"); } @@ -1211,7 +1191,7 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag) } static int ath12k_core_start_firmware(struct ath12k_base *ab, - enum ath12k_firmware_mode mode) + enum ath12k_qmi_firmware_mode mode) { int ret; @@ -1371,6 +1351,7 @@ int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab) goto exit; err_deinit: + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_dp_cmn_device_deinit(ath12k_ab_to_dp(ab)); mutex_unlock(&ab->core_lock); mutex_unlock(&ag->mutex); @@ -1524,7 +1505,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) complete_all(&ar->scan.completed); complete(&ar->scan.on_channel); complete(&ar->peer_assoc_done); - complete(&ar->peer_delete_done); + ath12k_peer_delete_wait_flush(ar); complete(&ar->install_key_done); complete(&ar->vdev_setup_done); complete(&ar->vdev_delete_done); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 30726e580833..a98fc6e0699d 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -161,14 +161,6 @@ enum ath12k_hw_rev { ATH12K_HW_IPQ5424_HW10, }; -enum ath12k_firmware_mode { - /* the default mode, standard 802.11 functionality */ - ATH12K_FIRMWARE_MODE_NORMAL, - - /* factory tests etc */ - ATH12K_FIRMWARE_MODE_FTM, -}; - #define ATH12K_IRQ_NUM_MAX 57 #define ATH12K_EXT_IRQ_NUM_MAX 16 #define ATH12K_MAX_TCL_RING_NUM 3 @@ -666,7 +658,8 @@ struct ath12k { /* protects the radio specific data like debug stats, ppdu_stats_info stats, * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info, - * channel context data, survey info, test mode data, regd_channel_update_queue. + * channel context data, test mode data, regd_channel_update_queue, + * peer_delete_waits. */ spinlock_t data_lock; @@ -688,7 +681,7 @@ struct ath12k { u8 radio_idx; struct completion peer_assoc_done; - struct completion peer_delete_done; + struct list_head peer_delete_waits; int install_key_status; struct completion install_key_done; @@ -722,7 +715,6 @@ struct ath12k { * avoid reporting garbage data. */ bool ch_info_can_report_survey; - struct survey_info survey[ATH12K_NUM_CHANS]; struct completion bss_survey_done; struct work_struct regd_update_work; @@ -792,6 +784,11 @@ struct ath12k_hw { */ struct mutex hw_mutex; enum ath12k_hw_state state; + + /* protects survey[] shared across radios of this hw. */ + spinlock_t survey_lock; + struct survey_info survey[ATH12K_NUM_CHANS]; + bool regd_updated; bool use_6ghz_regd; bool host_alloc_ml_id; @@ -937,6 +934,7 @@ struct ath12k_dp_profile_params { u32 rxdma_monitor_dst_ring_size; u32 num_pool_tx_desc; u32 rx_desc_count; + u32 rx_release_ring_size; }; struct ath12k_mem_profile_based_param { @@ -1145,7 +1143,7 @@ struct ath12k_base { struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; - enum ath12k_firmware_mode fw_mode; + enum ath12k_qmi_firmware_mode fw_mode; struct ath12k_ftm_event_obj ftm_event_obj; bool hw_group_ref; @@ -1297,8 +1295,6 @@ void ath12k_fw_stats_init(struct ath12k *ar); void ath12k_fw_stats_bcn_free(struct list_head *head); void ath12k_fw_stats_free(struct ath12k_fw_stats *stats); void ath12k_fw_stats_reset(struct ath12k *ar); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index); enum ath12k_qmi_mem_mode ath12k_core_get_memory_mode(struct ath12k_base *ab); static inline const char *ath12k_scan_state_str(enum ath12k_scan_state state) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index d17d4a8f1cb7..d54995b7adb2 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1031,6 +1031,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, struct ath12k_device_dp_stats *device_stats = &dp->device_stats; int len = 0, i, j, ret; struct ath12k *ar; + u32 center_freq; const int size = 4096; static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = { [HAL_REO_ENTR_RING_RXDMA_ECODE_OVERFLOW_ERR] = "Overflow", @@ -1082,6 +1083,9 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, if (!buf) return -ENOMEM; + len += scnprintf(buf + len, size - len, + "DEVICE DP STATS (timestamp: %lldms):\n\n", + ktime_to_ms(ktime_get())); len += scnprintf(buf + len, size - len, "DEVICE RX STATS:\n\n"); len += scnprintf(buf + len, size - len, "err ring pkts: %u\n", device_stats->err_ring_pkts); @@ -1161,6 +1165,12 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < ab->num_radios; i++) { ar = ath12k_mac_get_ar_by_pdev_id(ab, DP_SW2HW_MACID(i)); if (ar) { + spin_lock_bh(&ar->data_lock); + center_freq = ar->rx_channel ? ar->rx_channel->center_freq : 0; + spin_unlock_bh(&ar->data_lock); + len += scnprintf(buf + len, size - len, + "\nradio%d center_freq: %u\n", + i, center_freq); len += scnprintf(buf + len, size - len, "\nradio%d tx_pending: %u\n", i, atomic_read(&ar->dp.num_tx_pending)); @@ -1173,7 +1183,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < DP_REO_DST_RING_MAX; i++) { len += scnprintf(buf + len, size - len, "Ring%d:", i + 1); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, device_stats->reo_rx[i][j]); @@ -1190,7 +1200,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < HAL_WBM_REL_SRC_MODULE_MAX; i++) { len += scnprintf(buf + len, size - len, "%s:", wbm_rel_src[i]); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index af5f11fc1d84..f9b37d75956d 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -487,7 +487,7 @@ static int ath12k_dp_srng_common_setup(struct ath12k_base *ab) ret = ath12k_dp_srng_setup(ab, &dp->rx_rel_ring, HAL_WBM2SW_RELEASE, HAL_WBM2SW_REL_ERR_RING_NUM, 0, - DP_RX_RELEASE_RING_SIZE); + DP_RX_RELEASE_RING_SIZE(ab)); if (ret) { ath12k_warn(ab, "failed to set up rx_rel ring :%d\n", ret); goto err; @@ -1097,7 +1097,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) return; if (dp->reoq_lut.vaddr_unaligned) { - ath12k_hal_write_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->reoq_lut.size, dp->reoq_lut.vaddr_unaligned, dp->reoq_lut.paddr_unaligned); @@ -1105,7 +1104,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) } if (dp->ml_reoq_lut.vaddr_unaligned) { - ath12k_hal_write_ml_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->ml_reoq_lut.size, dp->ml_reoq_lut.vaddr_unaligned, dp->ml_reoq_lut.paddr_unaligned); @@ -1568,6 +1566,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab) ath12k_dp_rx_free(ab); fail_cmn_reoq_cleanup: + ath12k_dp_reoq_lut_addr_reset(dp); ath12k_dp_reoq_lut_cleanup(ab); fail_cmn_srng_cleanup: @@ -1627,3 +1626,14 @@ void ath12k_dp_cmn_hw_group_assign(struct ath12k_dp *dp, dp->device_id = ab->device_id; dp_hw_grp->dp[dp->device_id] = dp; } + +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp) +{ + struct ath12k_base *ab = dp->ab; + + if (dp->reoq_lut.vaddr_unaligned) + ath12k_hal_write_reoq_lut_addr(ab, 0); + + if (dp->ml_reoq_lut.vaddr_unaligned) + ath12k_hal_write_ml_reoq_lut_addr(ab, 0); +} diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index f8cfc7bb29dd..bef0f2ba0560 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -200,12 +200,13 @@ struct ath12k_pdev_dp { #define DP_REO_DST_RING_MAX 8 #define DP_REO_DST_RING_SIZE 2048 #define DP_REO_REINJECT_RING_SIZE 32 -#define DP_RX_RELEASE_RING_SIZE 1024 +#define DP_RX_RELEASE_RING_SIZE(ab) \ + ((ab)->profile_param->dp_params.rx_release_ring_size) #define DP_REO_EXCEPTION_RING_SIZE 128 #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 #define DP_RXDMA_BUF_RING_SIZE 4096 -#define DP_RX_MAC_BUF_RING_SIZE 2048 +#define DP_RX_MAC_BUF_RING_SIZE 4096 #define DP_RXDMA_REFILL_RING_SIZE 2048 #define DP_RXDMA_ERR_DST_RING_SIZE 1024 #define DP_RXDMA_MON_STATUS_RING_SIZE 1024 @@ -538,7 +539,7 @@ struct ath12k_dp { /* Lock for protection of peers and rhead_peer_addr */ spinlock_t dp_lock; - struct ath12k_dp_arch_ops *ops; + const struct ath12k_dp_arch_ops *ops; /* Linked list of struct ath12k_dp_link_peer */ struct list_head peers; @@ -701,4 +702,5 @@ struct ath12k_rx_desc_info *ath12k_dp_get_rx_desc(struct ath12k_dp *dp, u32 cookie); struct ath12k_tx_desc_info *ath12k_dp_get_tx_desc(struct ath12k_dp *dp, u32 desc_id); +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp); #endif diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index 44c5cff75f16..7d5be77b081f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -493,12 +493,8 @@ EXPORT_SYMBOL(ath12k_dp_mon_update_radiotap); void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap) + struct ieee80211_rx_status *status) { - struct ath12k_dp *dp = dp_pdev->dp; - struct ath12k_base *ab = dp->ab; static const struct ieee80211_radiotap_he known = { .data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN | IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN), @@ -506,14 +502,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, }; struct ieee80211_rx_status *rx_status; struct ieee80211_radiotap_he *he = NULL; - struct ieee80211_sta *pubsta = NULL; - struct ath12k_dp_link_peer *peer; - struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); - struct hal_rx_desc_data rx_info; - bool is_mcbc = rxcb->is_mcbc; - bool is_eapol_tkip = rxcb->is_eapol; - struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data; - u8 addr[ETH_ALEN] = {}; status->link_valid = 0; @@ -524,64 +512,10 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, status->flag |= RX_FLAG_RADIOTAP_HE; } - ath12k_dp_extract_rx_desc_data(dp->hal, &rx_info, rx_desc, rx_desc); - - rcu_read_lock(); - spin_lock_bh(&dp->dp_lock); - peer = ath12k_dp_rx_h_find_link_peer(dp_pdev, msdu, &rx_info); - if (peer && peer->sta) { - pubsta = peer->sta; - memcpy(addr, peer->addr, ETH_ALEN); - if (pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->link_id; - } - } - - spin_unlock_bh(&dp->dp_lock); - rcu_read_unlock(); - - ath12k_dbg(ab, ATH12K_DBG_DATA, - "rx skb %p len %u peer %pM %u %s %s%s%s%s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", - msdu, - msdu->len, - addr, - rxcb->tid, - (is_mcbc) ? "mcast" : "ucast", - (status->encoding == RX_ENC_LEGACY) ? "legacy" : "", - (status->encoding == RX_ENC_HT) ? "ht" : "", - (status->encoding == RX_ENC_VHT) ? "vht" : "", - (status->encoding == RX_ENC_HE) ? "he" : "", - (status->bw == RATE_INFO_BW_40) ? "40" : "", - (status->bw == RATE_INFO_BW_80) ? "80" : "", - (status->bw == RATE_INFO_BW_160) ? "160" : "", - (status->bw == RATE_INFO_BW_320) ? "320" : "", - status->enc_flags & RX_ENC_FLAG_SHORT_GI ? "sgi " : "", - status->rate_idx, - status->nss, - status->freq, - status->band, status->flag, - !!(status->flag & RX_FLAG_FAILED_FCS_CRC), - !!(status->flag & RX_FLAG_MMIC_ERROR), - !!(status->flag & RX_FLAG_AMSDU_MORE)); - - ath12k_dbg_dump(ab, ATH12K_DBG_DP_RX, NULL, "dp rx msdu: ", - msdu->data, msdu->len); rx_status = IEEE80211_SKB_RXCB(msdu); *rx_status = *status; - /* TODO: trace rx packet */ - - /* PN for multicast packets are not validate in HW, - * so skip 802.3 rx path - * Also, fast_rx expects the STA to be authorized, hence - * eapol packets are sent in slow path. - */ - if (decap == DP_RX_DECAP_TYPE_ETHERNET2_DIX && !is_eapol_tkip && - !(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED)) - rx_status->flag |= RX_FLAG_8023; - - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); + ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), NULL, msdu, napi); } EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu); diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.h b/drivers/net/wireless/ath/ath12k/dp_mon.h index 167028d27513..162cdcaa57a7 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.h +++ b/drivers/net/wireless/ath/ath12k/dp_mon.h @@ -112,9 +112,7 @@ void ath12k_dp_mon_update_radiotap(struct ath12k_pdev_dp *dp_pdev, void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap); + struct ieee80211_rx_status *status); struct sk_buff * ath12k_dp_mon_rx_merg_msdus(struct ath12k_pdev_dp *dp_pdev, struct dp_mon_mpdu *mon_mpdu, diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.c b/drivers/net/wireless/ath/ath12k/dp_tx.c index c10da6195c9c..9644f9ef2c74 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/dp_tx.c @@ -82,6 +82,52 @@ enum hal_encrypt_type ath12k_dp_tx_get_encrypt_type(u32 cipher) } EXPORT_SYMBOL(ath12k_dp_tx_get_encrypt_type); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_IV_LEN; + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_HDR_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_IV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_iv_len); + +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_MIC_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_ICV_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_ICV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_icv_len); + void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, struct ath12k_tx_desc_info *tx_desc, u8 pool_id) diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.h b/drivers/net/wireless/ath/ath12k/dp_tx.h index 7cef20540179..1af79af2ada2 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.h +++ b/drivers/net/wireless/ath/ath12k/dp_tx.h @@ -19,6 +19,8 @@ enum hal_tcl_encap_type ath12k_dp_tx_get_encap_type(struct ath12k_base *ab, struct sk_buff *skb); void ath12k_dp_tx_encap_nwifi(struct sk_buff *skb); u8 ath12k_dp_tx_get_tid(struct sk_buff *skb); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type); +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type); void *ath12k_dp_metadata_align_skb(struct sk_buff *skb, u8 tail_len); int ath12k_dp_tx_align_payload(struct ath12k_dp *dp, struct sk_buff **pskb); void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index a164563fff28..c0c3d2f047ef 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -828,8 +828,8 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len) { struct hal_tlv_64_hdr *tlv64 = tlv; - tlv64->tl = le64_encode_bits(tag, HAL_TLV_HDR_TAG) | - le64_encode_bits(len, HAL_TLV_HDR_LEN); + tlv64->tl = le64_encode_bits(tag, HAL_TLV_64_HDR_TAG) | + le64_encode_bits(len, HAL_TLV_64_HDR_LEN); return tlv64->value; } @@ -846,26 +846,44 @@ void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len) } EXPORT_SYMBOL(ath12k_hal_encode_tlv32_hdr); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_64_hdr *tlv64 = tlv; - u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_SRNG_TLV_HDR_TAG); - *desc = tlv64->value; + if (tag) + *tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); + if (len) + *len = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_LEN); + if (usrid) + *usrid = le64_get_bits(tlv64->tl, HAL_TLV_64_USR_ID); - return tag; + return tlv64->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv64_hdr); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_hdr *tlv32 = tlv; - u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_SRNG_TLV_HDR_TAG); - *desc = tlv32->value; + if (tag) + *tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); + if (len) + *len = le32_get_bits(tlv32->tl, HAL_TLV_HDR_LEN); + if (usrid) + *usrid = le32_get_bits(tlv32->tl, HAL_TLV_USR_ID); - return tag; + return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); + +u32 ath12k_hal_get_tlv64_hdr_align(void) +{ + return HAL_TLV_64_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv64_hdr_align); + +u32 ath12k_hal_get_tlv32_hdr_align(void) +{ + return HAL_TLV_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv32_hdr_align); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 21c551d8b248..3a874db7968e 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1024,7 +1024,7 @@ enum hal_wbm_rel_desc_type { /* Interrupt mitigation - timer threshold in us */ #define HAL_SRNG_INT_TIMER_THRESHOLD_TX 1000 -#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 500 +#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 200 #define HAL_SRNG_INT_TIMER_THRESHOLD_OTHER 256 enum hal_srng_mac_type { @@ -1441,10 +1441,12 @@ struct hal_ops { u8 *rbm, u32 *msdu_cnt); void *(*reo_cmd_enc_tlv_hdr)(void *tlv, u64 tag, u64 len); u16 (*reo_status_dec_tlv_hdr)(void *tlv, void **desc); + void *(*mon_rx_status_dec_tlv_hdr)(void *tlv, u16 *tag, u16 *len, u16 *usrid); + u32 (*get_tlv_hdr_align)(void); }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_TLV_HDR_LEN GENMASK(25, 10) +#define HAL_TLV_HDR_LEN GENMASK(21, 10) #define HAL_TLV_USR_ID GENMASK(31, 26) #define HAL_TLV_ALIGN 4 @@ -1464,9 +1466,6 @@ struct hal_tlv_64_hdr { u8 value[]; } __packed; -#define HAL_SRNG_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_SRNG_TLV_HDR_LEN GENMASK(25, 10) - dma_addr_t ath12k_hal_srng_get_tp_addr(struct ath12k_base *ab, struct hal_srng *srng); dma_addr_t ath12k_hal_srng_get_hp_addr(struct ath12k_base *ab, @@ -1556,6 +1555,8 @@ void ath12k_hal_rx_reo_ent_buf_paddr_get(struct ath12k_hal *hal, void *rx_desc, u8 *rbm, u32 *msdu_cnt); void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc); +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +u32 ath12k_hal_get_tlv64_hdr_align(void); +u32 ath12k_hal_get_tlv32_hdr_align(void); #endif diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 8d2fa0bfb96c..011bb22e21c7 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -100,7 +100,6 @@ struct ieee80211_rx_status; #define ATH12K_REGDB_FILE_NAME "regdb.bin" #define ATH12K_PCIE_MAX_PAYLOAD_SIZE 128 -#define ATH12K_IPQ5332_USERPD_ID 1 enum ath12k_hw_rate_cck { ATH12K_HW_RATE_CCK_LP_11M = 0, @@ -196,6 +195,7 @@ struct ath12k_hw_params { bool supports_shadow_regs:1; bool supports_aspm:1; bool current_cc_support:1; + bool supports_cong_ctrl_max_msdus:1; u32 num_tcl_banks; u32 max_tx_ring; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index a0928890671a..99bf5cf79d10 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -859,7 +859,7 @@ struct ath12k *ath12k_mac_get_ar_by_pdev_id(struct ath12k_base *ab, u32 pdev_id) return NULL; for (i = 0; i < ab->num_radios; i++) { - if (ab->fw_mode == ATH12K_FIRMWARE_MODE_FTM) + if (ab->fw_mode == ATH12K_QMI_FIRMWARE_MODE_FTM) pdev = &ab->pdevs[i]; else pdev = rcu_dereference(ab->pdevs_active[i]); @@ -4047,6 +4047,17 @@ static void ath12k_bss_assoc(struct ath12k *ar, ath12k_warn(ar->ab, "failed to set vdev %i OBSS PD parameters: %d\n", arvif->vdev_id, ret); + if (ar->ab->hw_params->supports_sta_ps && + ahvif->vdev_type == WMI_VDEV_TYPE_STA && + ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) { + ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, + WMI_VDEV_PARAM_DTIM_POLICY, + WMI_DTIM_POLICY_STICK); + if (ret) + ath12k_warn(ar->ab, "failed to set vdev %d stick DTIM policy: %d\n", + arvif->vdev_id, ret); + } + if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ar->ab->wmi_ab.svc_map) && ahvif->vdev_type == WMI_VDEV_TYPE_STA && ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) @@ -9774,6 +9785,19 @@ static int ath12k_mac_start(struct ath12k *ar) goto err; } + if (ab->hw_params->supports_cong_ctrl_max_msdus) { + ret = ath12k_wmi_pdev_set_param(ar, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS, + ATH12K_NUM_POOL_TX_DESC(ab), + pdev->pdev_id); + if (ret) { + ath12k_err(ab, + "failed to set congestion control MAX MSDUS: %d\n", + ret); + goto err; + } + } + __ath12k_set_antenna(ar, ar->cfg_tx_chainmask, ar->cfg_rx_chainmask); /* TODO: Do we need to enable ANI? */ @@ -10169,16 +10193,16 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) if (vif->type != NL80211_IFTYPE_STATION && vif->type != NL80211_IFTYPE_AP) vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED | - IEEE80211_OFFLOAD_DECAP_ENABLED); + IEEE80211_OFFLOAD_DECAP_ENABLED | + IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); - if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET; - vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_4ADDR; - } else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) { + else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_RAW; - } else { + else ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_NATIVE_WIFI; - } ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param_id, ahvif->dp_vif.tx_encap_type); @@ -10188,6 +10212,10 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) vif->offload_flags &= ~IEEE80211_OFFLOAD_ENCAP_ENABLED; } + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) + vif->offload_flags |= (IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); + param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE; if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED) param_value = ATH12K_HW_TXRX_ETHERNET; @@ -10616,22 +10644,8 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif) err_peer_del: if (ahvif->vdev_type == WMI_VDEV_TYPE_AP) { - reinit_completion(&ar->peer_delete_done); - - ret = ath12k_wmi_send_peer_delete_cmd(ar, arvif->bssid, - arvif->vdev_id); - if (ret) { - ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", - arvif->vdev_id, arvif->bssid); - goto err_dp_peer_del; - } - - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, - arvif->bssid); - if (ret) - goto err_dp_peer_del; - - ar->num_peers--; + /* ignore return value: propagate the original error */ + ath12k_peer_delete(ar, arvif->vdev_id, arvif->bssid); } err_dp_peer_del: @@ -11305,6 +11319,8 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, ml_arg->assoc_link = arvif->is_sta_assoc_link; + ml_arg->ieee_link_id = arvif->link_id; + partner_info = ml_arg->partner_info; links = ahvif->links_map; @@ -11328,6 +11344,7 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, partner_info->vdev_id = arvif_p->vdev_id; partner_info->hw_link_id = arvif_p->ar->pdev->hw_link_id; + partner_info->ieee_link_id = arvif_p->link_id; ether_addr_copy(partner_info->addr, link_conf->addr); ml_arg->num_partner_links++; partner_info++; @@ -13633,52 +13650,54 @@ ath12k_mac_update_bss_chan_survey(struct ath12k *ar, int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) { + struct ath12k_hw *ah = hw->priv; struct ath12k *ar; struct ieee80211_supported_band *sband; - struct survey_info *ar_survey; + struct survey_info *ah_survey; + int sband_idx = idx; lockdep_assert_wiphy(hw->wiphy); - if (idx >= ATH12K_NUM_CHANS) + if (sband_idx >= ATH12K_NUM_CHANS) return -ENOENT; sband = hw->wiphy->bands[NL80211_BAND_2GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_5GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_6GHZ]; - if (!sband || idx >= sband->n_channels) + if (!sband || sband_idx >= sband->n_channels) return -ENOENT; - ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[idx]); + ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[sband_idx]); if (!ar) { - if (sband->channels[idx].flags & IEEE80211_CHAN_DISABLED) { + if (sband->channels[sband_idx].flags & IEEE80211_CHAN_DISABLED) { memset(survey, 0, sizeof(*survey)); return 0; } return -ENOENT; } - ar_survey = &ar->survey[idx]; + ah_survey = &ah->survey[idx]; - ath12k_mac_update_bss_chan_survey(ar, &sband->channels[idx]); + ath12k_mac_update_bss_chan_survey(ar, &sband->channels[sband_idx]); - spin_lock_bh(&ar->data_lock); - memcpy(survey, ar_survey, sizeof(*survey)); - spin_unlock_bh(&ar->data_lock); + scoped_guard(spinlock_bh, &ah->survey_lock) { + memcpy(survey, ah_survey, sizeof(*survey)); + } - survey->channel = &sband->channels[idx]; + survey->channel = &sband->channels[sband_idx]; if (ar->rx_channel == survey->channel) survey->filled |= SURVEY_INFO_IN_USE; @@ -14923,12 +14942,6 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION; - /* MLO is not yet supported so disable Wireless Extensions for now - * to make sure ath12k users don't use it. This flag can be removed - * once WIPHY_FLAG_SUPPORTS_MLO is enabled. - */ - wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT; - /* Copy over MLO related capabilities received from * WMI_SERVICE_READY_EXT2_EVENT if single_chip_mlo_supp is set. */ @@ -15106,11 +15119,11 @@ static void ath12k_mac_setup(struct ath12k *ar) spin_lock_init(&ar->dp.ppdu_list_lock); INIT_LIST_HEAD(&ar->arvifs); INIT_LIST_HEAD(&ar->dp.ppdu_stats_info); + INIT_LIST_HEAD(&ar->peer_delete_waits); init_completion(&ar->vdev_setup_done); init_completion(&ar->vdev_delete_done); init_completion(&ar->peer_assoc_done); - init_completion(&ar->peer_delete_done); init_completion(&ar->install_key_done); init_completion(&ar->bss_survey_done); init_completion(&ar->scan.started); @@ -15360,6 +15373,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, mutex_init(&ah->hw_mutex); init_completion(&ah->peer_ml_id_done); + spin_lock_init(&ah->survey_lock); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list); diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index fee4129ea405..6441927b5382 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "mhi.h" #include "debug.h" #include "hal.h" +#include "qmi.h" #define ATH12K_PCI_BAR_NUM 0 #define ATH12K_PCI_DMA_MASK 36 @@ -541,6 +543,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) int i, j, n, ret, num_vectors = 0; u32 user_base_data = 0, base_vector = 0, base_idx; struct ath12k_ext_irq_grp *irq_grp; + bool threaded_napi = false; + int irq; base_idx = ATH12K_PCI_IRQ_CE0_OFFSET + CE_COUNT_MAX; ret = ath12k_pci_get_user_msi_assignment(ab, "DP", @@ -550,6 +554,10 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) if (ret < 0) return ret; + irq = ath12k_pci_get_msi_irq(ab->dev, base_vector); + if (irq >= 0) + threaded_napi = !irq_can_set_affinity(irq); + for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) { irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; @@ -564,6 +572,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath12k_pci_ext_grp_napi_poll); + if (threaded_napi) + netif_threaded_enable(irq_grp->napi_ndev); if (ab->hw_params->ring_mask->tx[i] || ab->hw_params->ring_mask->rx[i] || @@ -582,7 +592,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) for (j = 0; j < irq_grp->num_irq; j++) { int irq_idx = irq_grp->irqs[j]; int vector = (i % num_vectors) + base_vector; - int irq = ath12k_pci_get_msi_irq(ab->dev, vector); + + irq = ath12k_pci_get_msi_irq(ab->dev, vector); ab->irq_num[irq_idx] = irq; @@ -1545,7 +1556,7 @@ static int ath12k_pci_probe(struct pci_dev *pdev, ab_pci->ab = ab; ab_pci->pdev = pdev; ab->hif.ops = &ath12k_pci_hif_ops; - ab->fw_mode = ATH12K_FIRMWARE_MODE_NORMAL; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_NORMAL; pci_set_drvdata(pdev, ab); spin_lock_init(&ab_pci->window_lock); diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index ed0524ddff80..80edebf0e364 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -9,6 +9,55 @@ #include "debug.h" #include "debugfs.h" +static void ath12k_peer_delete_wait_register(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait, + u32 vdev_id, const u8 *addr) +{ + wait->vdev_id = vdev_id; + ether_addr_copy(wait->addr, addr); + init_completion(&wait->done); + + spin_lock_bh(&ar->data_lock); + list_add(&wait->list, &ar->peer_delete_waits); + spin_unlock_bh(&ar->data_lock); +} + +static void ath12k_peer_delete_wait_unregister(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) +{ + spin_lock_bh(&ar->data_lock); + list_del(&wait->list); + spin_unlock_bh(&ar->data_lock); +} + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr) +{ + struct ath12k_peer_delete_wait *wait; + + guard(spinlock_bh)(&ar->data_lock); + + list_for_each_entry(wait, &ar->peer_delete_waits, list) { + if (wait->vdev_id == vdev_id && + ether_addr_equal(wait->addr, addr)) { + complete(&wait->done); + return; + } + } + + ath12k_warn(ar->ab, "failed to find link peer with vdev id %u addr %pM\n", + vdev_id, addr); +} + +void ath12k_peer_delete_wait_flush(struct ath12k *ar) +{ + struct ath12k_peer_delete_wait *wait; + + spin_lock_bh(&ar->data_lock); + list_for_each_entry(wait, &ar->peer_delete_waits, list) + complete(&wait->done); + spin_unlock_bh(&ar->data_lock); +} + static int ath12k_wait_for_dp_link_peer_common(struct ath12k_base *ab, int vdev_id, const u8 *addr, bool expect_mapped) { @@ -62,20 +111,19 @@ static int ath12k_wait_for_peer_deleted(struct ath12k *ar, int vdev_id, const u8 return ath12k_wait_for_dp_link_peer_common(ar->ab, vdev_id, addr, false); } -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr) +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) { - int ret; unsigned long time_left; + int ret; - ret = ath12k_wait_for_peer_deleted(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_deleted(ar, wait->vdev_id, wait->addr); if (ret) { - ath12k_warn(ar->ab, "failed wait for peer deleted"); + ath12k_warn(ar->ab, "failed wait for peer deleted\n"); return ret; } - time_left = wait_for_completion_timeout(&ar->peer_delete_done, - 3 * HZ); + time_left = wait_for_completion_timeout(&wait->done, 3 * HZ); if (time_left == 0) { ath12k_warn(ar->ab, "Timeout in receiving peer delete response\n"); return -ETIMEDOUT; @@ -91,8 +139,6 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); - reinit_completion(&ar->peer_delete_done); - ret = ath12k_wmi_send_peer_delete_cmd(ar, addr, vdev_id); if (ret) { ath12k_warn(ab, @@ -106,6 +152,7 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) { + struct ath12k_peer_delete_wait wait; int ret; lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); @@ -114,17 +161,25 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) &(ath12k_ar_to_ah(ar)->dp_hw), vdev_id, addr, ar->hw_link_id); + /* + * Register the stack waiter before sending so the resp_event for + * this peer cannot arrive while no waiter is queued. + */ + ath12k_peer_delete_wait_register(ar, &wait, vdev_id, addr); + ret = ath12k_peer_delete_send(ar, vdev_id, addr); if (ret) - return ret; + goto out; - ret = ath12k_wait_for_peer_delete_done(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); if (ret) - return ret; + goto out; ar->num_peers--; - return 0; +out: + ath12k_peer_delete_wait_unregister(ar, &wait); + return ret; } static int ath12k_wait_for_peer_created(struct ath12k *ar, int vdev_id, const u8 *addr) @@ -184,22 +239,26 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arg->vdev_id, arg->peer_addr); if (!peer) { + struct ath12k_peer_delete_wait wait; + spin_unlock_bh(&dp->dp_lock); ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n", arg->peer_addr, arg->vdev_id); - reinit_completion(&ar->peer_delete_done); + ath12k_peer_delete_wait_register(ar, &wait, arg->vdev_id, + arg->peer_addr); ret = ath12k_wmi_send_peer_delete_cmd(ar, arg->peer_addr, arg->vdev_id); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", arg->vdev_id, arg->peer_addr); + ath12k_peer_delete_wait_unregister(ar, &wait); return ret; } - ret = ath12k_wait_for_peer_delete_done(ar, arg->vdev_id, - arg->peer_addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); + ath12k_peer_delete_wait_unregister(ar, &wait); if (ret) return ret; @@ -308,13 +367,14 @@ void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) { + DECLARE_BITMAP(registered, IEEE80211_MLD_MAX_NUM_LINKS); struct ieee80211_sta *sta = ath12k_ahsta_to_sta(ahsta); struct ath12k_hw *ah = ahvif->ah; struct ath12k_link_vif *arvif; struct ath12k_link_sta *arsta; + int ret, err_ret = 0; unsigned long links; struct ath12k *ar; - int ret, err_ret = 0; u8 link_id; lockdep_assert_wiphy(ah->hw->wiphy); @@ -322,8 +382,19 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st if (!sta->mlo) return -EINVAL; - /* FW expects delete of all link peers at once before waiting for reception - * of peer unmap or delete responses + struct ath12k_peer_delete_wait *waits __free(kfree) = + kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS); + if (!waits) + return -ENOMEM; + + bitmap_zero(registered, IEEE80211_MLD_MAX_NUM_LINKS); + + /* + * Firmware expects delete of all link peers at once before waiting + * for reception of peer unmap or delete responses. Phase 1 registers + * a per-link stack waiter and sends WMI peer delete for every + * link; the resp_event handler matches each response to its + * (vdev_id, addr) waiter on ar->peer_delete_waits. */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { @@ -343,29 +414,36 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st arvif->vdev_id, arsta->addr, ar->hw_link_id); + ath12k_peer_delete_wait_register(ar, &waits[link_id], + arvif->vdev_id, arsta->addr); + ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM ret %d\n", arvif->vdev_id, arsta->addr, ret); err_ret = ret; + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); continue; } + + set_bit(link_id, registered); } - /* Ensure all link peers are deleted and unmapped */ + /* + * Phase 2: wait for unmap + delete_resp on each registered link + * and tear down the waiter. + */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + if (!test_bit(link_id, registered)) + continue; + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); - arsta = wiphy_dereference(ah->hw->wiphy, ahsta->link[link_id]); - if (!arvif || !arsta) - continue; - ar = arvif->ar; - if (!ar) - continue; - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, arsta->addr); + ret = ath12k_wait_for_peer_delete_done(ar, &waits[link_id]); + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); if (ret) { err_ret = ret; continue; diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 0f7f25b8e89c..3f4ac17b9aa6 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -9,13 +9,23 @@ #include "dp_peer.h" +struct ath12k_peer_delete_wait { + struct list_head list; + u32 vdev_id; + u8 addr[ETH_ALEN]; + struct completion done; +}; + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr); +void ath12k_peer_delete_wait_flush(struct ath12k *ar); + void ath12k_peer_cleanup(struct ath12k *ar, u32 vdev_id); int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr); int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_sta *sta, struct ath12k_wmi_peer_create_arg *arg); -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr); +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait); int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta); struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah, const u8 *addr); diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index fd762b5d7bb5..280e50a1f31d 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -13,6 +13,7 @@ #include #include #include +#include #define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02 #define HOST_CSTATE_BIT 0x04 @@ -21,45 +22,45 @@ static const struct qmi_elem_info wlfw_host_mlo_chip_info_s_v01_ei[] = { { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, chip_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, num_local_links), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, hw_link_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, valid_mlo_link_id), }, { - .data_type = QMI_EOTI, + .data_type = QMI_EOTI, .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -506,6 +507,24 @@ static const struct qmi_elem_info qmi_wlanfw_host_cap_req_msg_v01_ei[] = { .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, feature_list), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -585,23 +604,41 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = { board_id), }, { - .data_type = QMI_OPT_FLAG, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support_valid), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -1625,42 +1662,45 @@ static const struct qmi_elem_info qmi_wlanfw_m3_info_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_req_msg_v01_ei[] = { { - .data_type = QMI_UNSIGNED_8_BYTE, - .elem_len = 1, - .elem_size = sizeof(u64), - .array_type = NO_ARRAY, - .tlv_type = 0x01, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, addr), + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + addr), }, { - .data_type = QMI_UNSIGNED_4_BYTE, - .elem_len = 1, - .elem_size = sizeof(u32), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, size), + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + size), }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_resp_msg_v01_ei[] = { { - .data_type = QMI_STRUCT, - .elem_len = 1, - .elem_size = sizeof(struct qmi_response_type_v01), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, resp), - .ei_array = qmi_response_type_v01_ei, + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1772,7 +1812,8 @@ static const struct qmi_elem_info qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] = { }, { .data_type = QMI_EOTI, - .array_type = QMI_COMMON_TLV_TYPE, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1925,7 +1966,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_OPT_FLAG, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_valid), @@ -1934,7 +1975,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_DATA_LEN, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_len), @@ -1943,7 +1984,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_STRUCT, .elem_len = QMI_WLANFW_MAX_NUM_SHADOW_REG_V01, .elem_size = sizeof(struct qmi_wlanfw_shadow_reg_cfg_s_v01), - .array_type = VAR_LEN_ARRAY, + .array_type = VAR_LEN_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg), @@ -2003,15 +2044,17 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_mem_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_fw_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -2094,14 +2137,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, if (!ag->mlo_capable) { ath12k_dbg(ab, ATH12K_DBG_QMI, - "MLO is disabled hence skip QMI MLO cap"); + "MLO is disabled hence skip QMI MLO cap\n"); return 0; } if (!ab->qmi.num_radios || ab->qmi.num_radios == U8_MAX) { ag->mlo_capable = false; ath12k_dbg(ab, ATH12K_DBG_QMI, - "skip QMI MLO cap due to invalid num_radio %d\n", + "skip QMI MLO cap due to invalid num_radio %u\n", ab->qmi.num_radios); return 0; } @@ -2125,7 +2168,7 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, req->mlo_num_chips_valid = 1; req->mlo_num_chips = ag->num_devices; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %d group_id %d num_devices %d", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %u group_id %u num_devices %u\n", req->mlo_chip_id, req->mlo_group_id, req->mlo_num_chips); mutex_lock(&ag->mutex); @@ -2146,14 +2189,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, info->chip_id = partner_ab->device_id; info->num_local_links = partner_ab->qmi.num_radios; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %d num_link %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %u num_link %u\n", info->chip_id, info->num_local_links); for (j = 0; j < info->num_local_links; j++) { info->hw_link_id[j] = partner_ab->wsi_info.hw_link_id_base + j; info->valid_mlo_link_id[j] = 1; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %u\n", info->hw_link_id[j]); hw_link_id++; @@ -2248,6 +2291,11 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) if (ret < 0) goto out; + if (ab->qmi.dynamic_ddr_support) { + req.dynamic_mem_support_valid = 1; + req.dynamic_mem_support = 1; + } + ret = qmi_txn_init(&ab->qmi.handle, &txn, qmi_wlanfw_host_cap_resp_msg_v01_ei, &resp); if (ret < 0) @@ -2268,7 +2316,7 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Host capability request failed, result: %d, err: %d\n", + ath12k_warn(ab, "Host capability request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2319,11 +2367,15 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = resp.num_phy; + if (resp.dynamic_ddr_support_valid) + ab->qmi.dynamic_ddr_support = resp.dynamic_ddr_support; + ath12k_dbg(ab, ATH12K_DBG_QMI, - "phy capability resp valid %d single_chip_mlo_support %d valid %d num_phy %d valid %d board_id %d\n", + "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u dynamic_ddr_valid %u dynamic_ddr_support %u\n", resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support, resp.num_phy_valid, resp.num_phy, - resp.board_id_valid, resp.board_id); + resp.board_id_valid, resp.board_id, resp.dynamic_ddr_support_valid, + resp.dynamic_ddr_support); return; @@ -2332,7 +2384,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = ab->hw_params->def_num_link; ath12k_dbg(ab, ATH12K_DBG_QMI, - "no valid response from PHY capability, choose default num_phy %d\n", + "no valid response from PHY capability, choose default num_phy %u\n", ab->qmi.num_radios); } @@ -2393,7 +2445,7 @@ static int ath12k_qmi_fw_ind_register_send(struct ath12k_base *ab) } if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "FW Ind register request failed, result: %d, err: %d\n", + ath12k_warn(ab, "FW Ind register request failed, result: %u, err: %u\n", resp->resp.result, resp->resp.error); ret = -EINVAL; goto out; @@ -2428,7 +2480,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (!test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) && ab->qmi.target_mem_delayed) { delayed = true; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %u\n", ab->qmi.mem_seg_count); } else { delayed = false; @@ -2474,7 +2526,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (delayed && resp.resp.error == 0) goto out; - ath12k_warn(ab, "Respond mem req failed, result: %d, err: %d\n", + ath12k_warn(ab, "Respond mem req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2606,13 +2658,13 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab, if (chunk->size > ATH12K_QMI_MAX_CHUNK_SIZE) { ab->qmi.target_mem_delayed = true; ath12k_warn(ab, - "qmi dma allocation failed (%d B type %u), will try later with small size\n", + "qmi dma allocation failed (%u B type %u), will try later with small size\n", chunk->size, chunk->type); ath12k_qmi_free_target_mem_chunk(ab); return -EAGAIN; } - ath12k_warn(ab, "memory allocation failure for %u size: %d\n", + ath12k_warn(ab, "memory allocation failure for %u size: %u\n", chunk->type, chunk->size); return -ENOMEM; } @@ -2659,7 +2711,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_size += chunk->size; if (ag->mlo_mem.mlo_mem_size && mlo_size > ag->mlo_mem.mlo_mem_size) { - ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d\n", mlo_size, ag->mlo_mem.mlo_mem_size); ret = -EINVAL; goto err; @@ -2668,7 +2720,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_chunk = &ag->mlo_mem.chunk[mlo_idx]; if (mlo_chunk->paddr) { if (chunk->size != mlo_chunk->size) { - ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %u is more than allocated size %u\n", mlo_idx, chunk->size, mlo_chunk->size); ret = -EINVAL; goto err; @@ -2699,7 +2751,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) if (!ag->mlo_mem.mlo_mem_size) { ag->mlo_mem.mlo_mem_size = mlo_size; } else if (ag->mlo_mem.mlo_mem_size != mlo_size) { - ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d", + ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d\n", ag->mlo_mem.mlo_mem_size, mlo_size); ret = -EINVAL; goto err; @@ -2725,121 +2777,96 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) return ret; } +static const char *ath12k_qmi_get_mem_reg_name(int mem_type) +{ + switch (mem_type) { + case HOST_DDR_REGION_TYPE: + case BDF_MEM_REGION_TYPE: + return "q6-region"; + case M3_DUMP_REGION_TYPE: + return "m3-dump"; + case CALDB_MEM_REGION_TYPE: + return "q6-caldb"; + case MLO_GLOBAL_MEM_REGION_TYPE: + return "mlo-global-mem"; + default: + return NULL; + } +} + static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { - struct reserved_mem *rmem; - size_t avail_rmem_size; + struct device_node *np = ab->dev->of_node; + size_t avail_rmem_size, offset = 0; + struct target_mem_chunk *chunk; + struct resource res; + const char *rname; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { - switch (ab->qmi.target_mem[i].type) { - case HOST_DDR_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; - goto out; - } - - avail_rmem_size = rmem->size; - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - - ab->qmi.target_mem[idx].paddr = rmem->base; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case BDF_MEM_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; - goto out; - } - - avail_rmem_size = rmem->size - ab->hw_params->bdf_addr_offset; - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - ab->qmi.target_mem[idx].paddr = - rmem->base + ab->hw_params->bdf_addr_offset; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case CALDB_MEM_REGION_TYPE: - /* Cold boot calibration is not enabled in Ath12k. Hence, + chunk = &ab->qmi.target_mem[i]; + if (chunk->type == CALDB_MEM_REGION_TYPE) { + /* + * Cold boot calibration is not enabled in Ath12k. Hence, * assign paddr = 0. * Once cold boot calibration is enabled add support to * assign reserved memory from DT. */ ab->qmi.target_mem[idx].paddr = 0; ab->qmi.target_mem[idx].v.ioaddr = NULL; - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; idx++; - break; - case M3_DUMP_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 1); - if (!rmem) { - ret = -EINVAL; - goto out; - } + continue; + } - avail_rmem_size = rmem->size; - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, + rname = ath12k_qmi_get_mem_reg_name(chunk->type); + if (!rname) { + ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", + chunk->type); + continue; + } + + ret = of_reserved_mem_region_to_resource_byname(np, rname, &res); + if (ret) + goto out; + + avail_rmem_size = resource_size(&res); + if (chunk->type == BDF_MEM_REGION_TYPE || + chunk->type == HOST_DDR_REGION_TYPE) { + if (ab->hw_params->bdf_addr_offset > avail_rmem_size || + offset > avail_rmem_size - ab->hw_params->bdf_addr_offset) { + ath12k_err(ab, "qmi mem offset overflow: bdf_offset=%u offset=%zu size=%zu\n", + ab->hw_params->bdf_addr_offset, offset, avail_rmem_size); ret = -EINVAL; goto out; } - ab->qmi.target_mem[idx].paddr = rmem->base; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - default: - ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", - ab->qmi.target_mem[i].type); - break; + avail_rmem_size -= ab->hw_params->bdf_addr_offset + offset; + res.start += ab->hw_params->bdf_addr_offset + offset; + offset += chunk->size; } + + if (avail_rmem_size < chunk->size) { + ath12k_dbg(ab, ATH12K_DBG_QMI, + "failed to assign mem type %u req size %u avail size %zu\n", + chunk->type, chunk->size, avail_rmem_size); + ret = -EINVAL; + goto out; + } + + ab->qmi.target_mem[idx].paddr = res.start; + ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, + chunk->size); + if (!ab->qmi.target_mem[idx].v.ioaddr) { + ret = -EIO; + goto out; + } + + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; + idx++; } ab->qmi.mem_seg_count = idx; @@ -2884,7 +2911,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi targetcap req failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi targetcap req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2936,7 +2963,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) ab->qmi.target.chip_id, ab->qmi.target.chip_family, ab->qmi.target.board_id, ab->qmi.target.soc_id); - ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s", + ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s\n", ab->qmi.target.fw_version, ab->qmi.target.fw_build_timestamp, ab->qmi.target.fw_build_id); @@ -3006,7 +3033,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, if (ret < 0) goto out; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %u\n", type); ret = qmi_send_request(&ab->qmi.handle, NULL, &txn, @@ -3023,7 +3050,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi BDF download failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi BDF download failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3036,7 +3063,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, temp += req->data_len; req->seg_id++; ath12k_dbg(ab, ATH12K_DBG_QMI, - "qmi bdf download request remaining %i\n", + "qmi bdf download request remaining %u\n", remaining); } } @@ -3127,7 +3154,7 @@ int ath12k_qmi_load_bdf_qmi(struct ath12k_base *ab, release_firmware(fw_entry); return ret; default: - ath12k_warn(ab, "unknown file type for load %d", type); + ath12k_warn(ab, "unknown file type for load %d\n", type); goto out; } @@ -3239,7 +3266,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) if (ab->hw_params->fw.m3_loader == ath12k_m3_fw_loader_driver) { ret = ath12k_qmi_m3_load(ab); if (ret) { - ath12k_err(ab, "failed to load m3 firmware: %d", ret); + ath12k_err(ab, "failed to load m3 firmware: %d\n", ret); return ret; } req.addr = m3_mem->paddr; @@ -3269,7 +3296,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi M3 info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi M3 info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3364,7 +3391,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) ret = ath12k_qmi_aux_uc_load(ab); if (ret) { - ath12k_err(ab, "failed to load aux_uc firmware: %d", ret); + ath12k_err(ab, "failed to load aux_uc firmware: %d\n", ret); return ret; } @@ -3394,7 +3421,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi AUX_UC info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi AUX_UC info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3404,7 +3431,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { struct qmi_wlanfw_wlan_mode_req_msg_v01 req = {}; struct qmi_wlanfw_wlan_mode_resp_msg_v01 resp = {}; @@ -3426,24 +3453,24 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, qmi_wlanfw_wlan_mode_req_msg_v01_ei, &req); if (ret < 0) { qmi_txn_cancel(&txn); - ath12k_warn(ab, "qmi failed to send mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed to send mode request, mode: %u, err = %d\n", mode, ret); goto out; } ret = qmi_txn_wait(&txn, msecs_to_jiffies(ATH12K_QMI_WLANFW_TIMEOUT_MS)); if (ret < 0) { - if (mode == ATH12K_FIRMWARE_MODE_OFF && ret == -ENETRESET) { + if (mode == ATH12K_QMI_FIRMWARE_MODE_OFF && ret == -ENETRESET) { ath12k_warn(ab, "WLFW service is dis-connected\n"); return 0; } - ath12k_warn(ab, "qmi failed set mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed set mode request, mode: %u, err = %d\n", mode, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Mode request failed, mode: %d, result: %d err: %d\n", + ath12k_warn(ab, "Mode request failed, mode: %u, result: %u err: %u\n", mode, resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3536,7 +3563,7 @@ static int ath12k_qmi_wlanfw_wlan_cfg_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi wlan config request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi wlan config request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3580,7 +3607,7 @@ static int ath12k_qmi_wlanfw_wlan_ini_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "QMI wlan ini response failure: %d %d\n", + ath12k_warn(ab, "QMI wlan ini response failure: %u %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3596,7 +3623,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) clear_bit(ATH12K_FLAG_QMI_FW_READY_COMPLETE, &ab->dev_flags); - ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_FIRMWARE_MODE_OFF); + ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_QMI_FIRMWARE_MODE_OFF); if (ret < 0) { ath12k_warn(ab, "qmi failed to send wlan mode off\n"); return; @@ -3604,7 +3631,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { int ret; @@ -3663,7 +3690,7 @@ void ath12k_qmi_trigger_host_cap(struct ath12k_base *ab) spin_unlock(&qmi->event_lock); - ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %u\n", ab->device_id); ath12k_qmi_driver_event_post(qmi, ATH12K_QMI_EVENT_HOST_CAP, NULL); @@ -3833,7 +3860,7 @@ static void ath12k_qmi_msg_mem_request_cb(struct qmi_handle *qmi_hdl, for (i = 0; i < qmi->mem_seg_count ; i++) { ab->qmi.target_mem[i].type = msg->mem_seg[i].type; ab->qmi.target_mem[i].size = msg->mem_seg[i].size; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %u\n", msg->mem_seg[i].type, msg->mem_seg[i].size); } @@ -3954,7 +3981,7 @@ static int ath12k_qmi_event_host_cap(struct ath12k_qmi *qmi) ret = ath12k_qmi_host_cap_send(ab); if (ret < 0) { - ath12k_warn(ab, "failed to send qmi host cap for device id %d: %d\n", + ath12k_warn(ab, "failed to send qmi host cap for device id %u: %d\n", ab->device_id, ret); return ret; } @@ -4024,7 +4051,7 @@ static void ath12k_qmi_driver_event_work(struct work_struct *work) set_bit(ATH12K_FLAG_QMI_FAIL, &ab->dev_flags); break; default: - ath12k_warn(ab, "invalid event type: %d", event->type); + ath12k_warn(ab, "invalid event type: %d\n", event->type); break; } diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 2a63e214eb42..6da10f3cb597 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -13,7 +13,6 @@ #define ATH12K_HOST_VERSION_STRING "WIN" #define ATH12K_QMI_WLANFW_TIMEOUT_MS 10000 #define ATH12K_QMI_MAX_BDF_FILE_NAME_SIZE 64 -#define ATH12K_QMI_CALDB_ADDRESS 0x4BA00000 #define ATH12K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128 #define ATH12K_QMI_WLFW_SERVICE_VERS_V01 0x01 #define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02 @@ -24,9 +23,7 @@ #define ATH12K_QMI_WLANFW_MAX_TIMESTAMP_LEN_V01 32 #define ATH12K_QMI_RESP_LEN_MAX 8192 #define ATH12K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01 52 -#define ATH12K_QMI_CALDB_SIZE 0x480000 #define ATH12K_QMI_BDF_EXT_STR_LENGTH 0x20 -#define ATH12K_QMI_FW_MEM_REQ_SEGMENT_CNT 3 #define ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01 4 #define ATH12K_QMI_DEVMEM_CMEM_INDEX 0 @@ -35,7 +32,6 @@ #define QMI_WLFW_FW_READY_IND_V01 0x0038 #define QMI_WLANFW_MAX_DATA_SIZE_V01 6144 -#define ATH12K_FIRMWARE_MODE_OFF 4 #define ATH12K_BOARD_ID_DEFAULT 0xFF @@ -156,12 +152,11 @@ struct ath12k_qmi { struct m3_mem_region aux_uc_mem; unsigned int service_ins_id; struct dev_mem_info dev_mem[ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01]; + u8 dynamic_ddr_support; }; -#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261 +#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 265 #define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034 -#define QMI_WLANFW_HOST_CAP_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034 #define QMI_WLFW_MAX_NUM_GPIO_V01 32 #define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64 #define QMI_WLANFW_MAX_HOST_DDR_RANGE_SIZE_V01 3 @@ -258,7 +253,8 @@ struct qmi_wlanfw_host_cap_req_msg_v01 { struct wlfw_host_mlo_chip_info_s_v01 mlo_chip_info[QMI_WLFW_MAX_NUM_MLO_CHIPS_V01]; u8 feature_list_valid; u64 feature_list; - + u8 dynamic_mem_support_valid; + u8 dynamic_mem_support; }; struct qmi_wlanfw_host_cap_resp_msg_v01 { @@ -267,8 +263,6 @@ struct qmi_wlanfw_host_cap_resp_msg_v01 { #define QMI_WLANFW_PHY_CAP_REQ_MSG_V01_MAX_LEN 0 #define QMI_WLANFW_PHY_CAP_REQ_V01 0x0057 -#define QMI_WLANFW_PHY_CAP_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_PHY_CAP_RESP_V01 0x0057 struct qmi_wlanfw_phy_cap_req_msg_v01 { }; @@ -281,12 +275,12 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 { u32 board_id; u8 single_chip_mlo_support_valid; u8 single_chip_mlo_support; + u8 dynamic_ddr_support_valid; + u8 dynamic_ddr_support; }; #define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54 #define QMI_WLANFW_IND_REGISTER_REQ_V01 0x0020 -#define QMI_WLANFW_IND_REGISTER_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_IND_REGISTER_RESP_V01 0x0020 #define QMI_WLANFW_CLIENT_ID 0x4b4e454c struct qmi_wlanfw_ind_register_req_msg_v01 { @@ -322,12 +316,8 @@ struct qmi_wlanfw_ind_register_resp_msg_v01 { u64 fw_status; }; -#define QMI_WLANFW_REQUEST_MEM_IND_MSG_V01_MAX_LEN 1824 #define QMI_WLANFW_RESPOND_MEM_REQ_MSG_V01_MAX_LEN 888 -#define QMI_WLANFW_RESPOND_MEM_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_REQUEST_MEM_IND_V01 0x0035 #define QMI_WLANFW_RESPOND_MEM_REQ_V01 0x0036 -#define QMI_WLANFW_RESPOND_MEM_RESP_V01 0x0036 #define QMI_WLANFW_MAX_NUM_MEM_CFG_V01 2 #define QMI_WLANFW_MAX_STR_LEN_V01 16 @@ -385,9 +375,7 @@ struct qmi_wlanfw_fw_ready_ind_msg_v01 { }; #define QMI_WLANFW_CAP_REQ_MSG_V01_MAX_LEN 0 -#define QMI_WLANFW_CAP_RESP_MSG_V01_MAX_LEN 207 #define QMI_WLANFW_CAP_REQ_V01 0x0024 -#define QMI_WLANFW_CAP_RESP_V01 0x0024 enum qmi_wlanfw_pipedir_enum_v01 { QMI_WLFW_PIPEDIR_NONE_V01 = 0, @@ -500,8 +488,6 @@ struct qmi_wlanfw_cap_req_msg_v01 { }; #define QMI_WLANFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_LEN 6182 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_V01 0x0025 #define QMI_WLANFW_BDF_DOWNLOAD_REQ_V01 0x0025 /* TODO: Need to check with MCL and FW team that data can be pointer and * can be last element in structure @@ -529,8 +515,6 @@ struct qmi_wlanfw_bdf_download_resp_msg_v01 { }; #define QMI_WLANFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 -#define QMI_WLANFW_M3_INFO_RESP_V01 0x003C #define QMI_WLANFW_M3_INFO_REQ_V01 0x003C struct qmi_wlanfw_m3_info_req_msg_v01 { @@ -543,7 +527,6 @@ struct qmi_wlanfw_m3_info_resp_msg_v01 { }; #define QMI_WLANFW_AUX_UC_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_AUX_UC_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 #define QMI_WLANFW_AUX_UC_INFO_REQ_V01 0x005A struct qmi_wlanfw_aux_uc_info_req_msg_v01 { @@ -556,13 +539,9 @@ struct qmi_wlanfw_aux_uc_info_resp_msg_v01 { }; #define QMI_WLANFW_WLAN_MODE_REQ_MSG_V01_MAX_LEN 11 -#define QMI_WLANFW_WLAN_MODE_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_CFG_REQ_MSG_V01_MAX_LEN 803 -#define QMI_WLANFW_WLAN_CFG_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_MODE_REQ_V01 0x0022 -#define QMI_WLANFW_WLAN_MODE_RESP_V01 0x0022 #define QMI_WLANFW_WLAN_CFG_REQ_V01 0x0023 -#define QMI_WLANFW_WLAN_CFG_RESP_V01 0x0023 #define QMI_WLANFW_MAX_STR_LEN_V01 16 #define QMI_WLANFW_MAX_NUM_CE_V01 12 #define QMI_WLANFW_MAX_NUM_SVC_V01 24 @@ -605,9 +584,7 @@ struct qmi_wlanfw_wlan_cfg_resp_msg_v01 { }; #define ATH12K_QMI_WLANFW_WLAN_INI_REQ_V01 0x002F -#define ATH12K_QMI_WLANFW_WLAN_INI_RESP_V01 0x002F #define QMI_WLANFW_WLAN_INI_REQ_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_WLAN_INI_RESP_MSG_V01_MAX_LEN 7 struct qmi_wlanfw_wlan_ini_req_msg_v01 { /* Must be set to true if enable_fwlog is being passed */ @@ -624,6 +601,17 @@ enum ath12k_qmi_mem_mode { ATH12K_QMI_MEMORY_MODE_LOW_512_M, }; +enum ath12k_qmi_firmware_mode { + /* the default mode, standard 802.11 functionality */ + ATH12K_QMI_FIRMWARE_MODE_NORMAL, + + /* factory tests etc */ + ATH12K_QMI_FIRMWARE_MODE_FTM, + + /* firmware offline; values 2-3 reserved by firmware ABI */ + ATH12K_QMI_FIRMWARE_MODE_OFF = 4, +}; + static inline void ath12k_qmi_set_event_block(struct ath12k_qmi *qmi, bool block) { lockdep_assert_held(&qmi->event_lock); @@ -639,7 +627,7 @@ static inline bool ath12k_qmi_get_event_block(struct ath12k_qmi *qmi) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode); + enum ath12k_qmi_firmware_mode mode); void ath12k_qmi_firmware_stop(struct ath12k_base *ab); void ath12k_qmi_deinit_service(struct ath12k_base *ab); int ath12k_qmi_init_service(struct ath12k_base *ab); diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 6a8b8b2a56f9..98a6606ffd76 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,44 +15,100 @@ #include "dp.h" #include "core.h" +/* + * Node name to UserPD ID mapping + * + * The io_start field is used for additional validation when the reg + * property is present in the device tree. If io_start is 0, only + * node_name matching is performed. + * + * For platforms where not all WiFi nodes have a 'reg' property, set + * io_start to 0 for those entries. The driver will match purely by + * node name in such cases. + */ +static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { + { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, +}; + +static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { + [ATH12K_HW_IPQ5332_HW10] = { + .hw_rev = ATH12K_HW_IPQ5332_HW10, + .auth_enabled = true, + .ops = &ath12k_ahb_hif_ops, + }, + [ATH12K_HW_IPQ5424_HW10] = { + .hw_rev = ATH12K_HW_IPQ5424_HW10, + .auth_enabled = false, + .ops = &ath12k_ahb_hif_ops, + }, +}; + static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { { .compatible = "qcom,ipq5332-wifi", - .data = (void *)ATH12K_HW_IPQ5332_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5332_HW10], }, { .compatible = "qcom,ipq5424-wifi", - .data = (void *)ATH12K_HW_IPQ5424_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5424_HW10], }, { } }; MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); +/* + * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties + * @ab: ath12k base structure + * + * Returns: UserPD ID (1-based) on success, 0 on failure + * + * Resolution logic: + * 1. If reg property exist in DT, get userpd_id from io_start + * 2. If reg property is absent, get userpd_id from DT node name + * 3. Return 0 if no match found (probe will fail) + */ +static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) +{ + const struct ath12k_ahb_userpd_map *map; + struct resource *res; + size_t i; + + res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); + + for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { + map = &ath12k_wifi7_ahb_userpd_map[i]; + + if (res) { + if (map->io_start && map->io_start == res->start) + return map->upd_id; + } else if (map->node_name && + of_node_name_eq(ab->dev->of_node, map->node_name)) { + return map->upd_id; + } + } + + return 0; +} + static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { + const struct ath12k_ahb_desc *desc; struct ath12k_ahb *ab_ahb; - enum ath12k_hw_rev hw_rev; struct ath12k_base *ab; int ret; ab = platform_get_drvdata(pdev); ab_ahb = ath12k_ab_to_ahb(ab); - - hw_rev = (enum ath12k_hw_rev)(kernel_ulong_t)of_device_get_match_data(&pdev->dev); - switch (hw_rev) { - case ATH12K_HW_IPQ5332_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = true; - break; - case ATH12K_HW_IPQ5424_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = false; - break; - default: + desc = of_device_get_match_data(&pdev->dev); + if (!desc) return -EOPNOTSUPP; - } ab->target_mem_mode = ATH12K_QMI_MEMORY_MODE_DEFAULT; - ab->hw_rev = hw_rev; + ab->hw_rev = desc->hw_rev; + ab->hif.ops = desc->ops; + ab_ahb->scm_auth_enabled = desc->auth_enabled; + ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + if (!ab_ahb->userpd_id) + return -EOPNOTSUPP; ret = ath12k_wifi7_hw_init(ab); if (ret) { diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp.c b/drivers/net/wireless/ath/ath12k/wifi7/dp.c index c72f604661ce..397da016bc78 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp.c @@ -139,7 +139,7 @@ static int ath12k_wifi7_dp_service_srng(struct ath12k_dp *dp, return tot_work_done; } -static struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { +static const struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { .service_srng = ath12k_wifi7_dp_service_srng, .tx_get_vdev_bank_config = ath12k_wifi7_dp_tx_get_vdev_bank_config, .reo_cmd_send = ath12k_wifi7_dp_reo_cmd_send, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 7dd4a49d64d5..016b0c38e51e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -1565,16 +1565,17 @@ ath12k_wifi7_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon, static enum hal_rx_mon_status ath12k_wifi7_dp_mon_rx_parse_status_tlv(struct ath12k_pdev_dp *dp_pdev, struct ath12k_mon_data *pmon, - const struct hal_tlv_64_hdr *tlv) + const void *tlv) { struct hal_rx_mon_ppdu_info *ppdu_info = &pmon->mon_ppdu_info; - const void *tlv_data = tlv->value; - u32 info[7], userid; - u16 tlv_tag, tlv_len; + struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); + struct ath12k_hal *hal = &ar->ab->hal; + u16 tlv_tag, tlv_len, userid; + void *tlv_data; + u32 info[7]; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); - userid = le64_get_bits(tlv->tl, HAL_TLV_64_USR_ID); + tlv_data = hal->ops->mon_rx_status_dec_tlv_hdr((void *)tlv, &tlv_tag, + &tlv_len, &userid); if (ppdu_info->tlv_aggr.in_progress && ppdu_info->tlv_aggr.tlv_tag != tlv_tag) { ath12k_wifi7_dp_mon_parse_eht_sig_hdr(ppdu_info, @@ -2480,7 +2481,6 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, { struct sk_buff *mon_skb, *skb_next, *header; struct ieee80211_rx_status *rxs = &dp_pdev->rx_status; - u8 decap = DP_RX_DECAP_TYPE_RAW; mon_skb = ath12k_dp_mon_rx_merg_msdus(dp_pdev, mon_mpdu, ppduinfo, rxs); if (!mon_skb) @@ -2507,12 +2507,8 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, } rxs->flag |= RX_FLAG_ONLY_MONITOR; - if (!(rxs->flag & RX_FLAG_ONLY_MONITOR)) - decap = mon_mpdu->decap_format; - ath12k_dp_mon_update_radiotap(dp_pdev, ppduinfo, mon_skb, rxs); - ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, ppduinfo, - rxs, decap); + ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, rxs); mon_skb = skb_next; } while (mon_skb); rxs->flag = 0; @@ -2930,11 +2926,12 @@ static enum dp_mon_status_buf_state ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, struct dp_rxdma_mon_ring *rx_ring) { + struct ath12k_hal *hal = &ab->hal; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; struct sk_buff *skb; void *status_desc; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; int buf_id; u8 rbm; @@ -2959,8 +2956,8 @@ ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != HAL_RX_STATUS_BUFFER_DONE) + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) return DP_MON_STATUS_NO_DMA; return DP_MON_STATUS_REPLINISH; @@ -2972,41 +2969,40 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *skb) { struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); - struct hal_tlv_64_hdr *tlv; + struct ath12k_hal *hal = &ar->ab->hal; + u8 *tlv_value, *tlv = skb->data; struct ath12k_skb_rxcb *rxcb; enum hal_rx_mon_status hal_status; u16 tlv_tag, tlv_len; - u8 *ptr = skb->data; + u32 tlv_hdr_len; + + tlv_hdr_len = hal->ops->get_tlv_hdr_align(); do { - tlv = (struct hal_tlv_64_hdr *)ptr; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); + tlv_value = hal->ops->mon_rx_status_dec_tlv_hdr(tlv, &tlv_tag, + &tlv_len, NULL); /* The actual length of PPDU_END is the combined length of many PHY * TLVs that follow. Skip the TLV header and * rx_rxpcu_classification_overview that follows the header to get to * next TLV. */ - if (tlv_tag == HAL_RX_PPDU_END) tlv_len = sizeof(struct hal_rx_rxpcu_classification_overview); - else - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); hal_status = ath12k_wifi7_dp_mon_rx_parse_status_tlv(dp_pdev, pmon, tlv); if (ar->monitor_started && ar->ab->hw_params->rxdma1_enable && ath12k_wifi7_dp_mon_parse_rx_dest_tlv(dp_pdev, pmon, hal_status, - tlv->value)) + tlv_value)) return HAL_RX_MON_STATUS_PPDU_DONE; - ptr += sizeof(*tlv) + tlv_len; - ptr = PTR_ALIGN(ptr, HAL_TLV_64_ALIGN); + tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((ptr - skb->data) > skb->len) + if (unlikely(tlv - skb->data > skb->len || + skb->len - (tlv - skb->data) < tlv_hdr_len)) break; - } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || (hal_status == HAL_RX_MON_STATUS_BUF_ADDR) || (hal_status == HAL_RX_MON_STATUS_MPDU_START) || @@ -3056,15 +3052,16 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, int buf_id, srng_id, num_buffs_reaped = 0; enum dp_mon_status_buf_state reap_status; struct dp_rxdma_mon_ring *rx_ring; + struct ath12k_hal *hal = &ab->hal; struct ath12k_mon_data *pmon; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; void *rx_mon_status_desc; struct hal_srng *srng; struct ath12k_dp *dp; struct sk_buff *skb; struct ath12k *ar; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; u8 rbm; @@ -3109,14 +3106,13 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != - HAL_RX_STATUS_BUFFER_DONE) { + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, + NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) { pmon->buf_state = DP_MON_STATUS_NO_DMA; ath12k_warn(ab, - "mon status DONE not set %llx, buf_id %d\n", - le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG), - buf_id); + "mon status DONE not set %x, buf_id %d\n", + tlv_tag, buf_id); /* RxDMA status done bit might not be set even * though tp is moved by HW. */ diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index d2749de44553..587d58eeccfa 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -13,6 +13,49 @@ #include "hal.h" #include "hal_tx.h" +/* + * Convert an encrypted EAPOL frame from native-WiFi format to + * the layout expected by the firmware RAW encrypt pipeline: + * + * [802.11 hdr][IV (zeroed)][LLC/SNAP][EAPOL payload][ICV (zeroed)] + * + * mac80211 delivers the frame as [802.11 hdr][LLC/SNAP][EAPOL payload]. + * The MAC header length is read from the unmodified skb and is safe because + * ieee80211_hdrlen() only inspects the 2-byte frame_control field. + * pskb_expand_head() is used to grow both head (for the IV) and tail + * (for the ICV) in a single call and allocation. + */ +static int +ath12k_wifi7_dp_tx_encap_eapol(struct sk_buff *skb, + struct hal_tx_info *ti, + struct ath12k_skb_cb *skb_cb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + enum hal_encrypt_type enc_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + u16 mac_hdr_len = ieee80211_hdrlen(hdr->frame_control); + u8 iv_len = ath12k_dp_tx_crypto_iv_len(enc_type); + u8 icv_len = ath12k_dp_tx_crypto_icv_len(enc_type); + + if (pskb_expand_head(skb, iv_len, icv_len, GFP_ATOMIC)) + return -ENOMEM; + + if (iv_len) { + skb_push(skb, iv_len); + memmove(skb->data, skb->data + iv_len, mac_hdr_len); + memset(skb->data + mac_hdr_len, 0, iv_len); + } + + if (icv_len) + memset(skb_put(skb, icv_len), 0, icv_len); + + ti->flags0 |= u32_encode_bits(1, HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti->encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti->encrypt_type = enc_type; + + return 0; +} + static void ath12k_wifi7_hal_tx_cmd_ext_desc_setup(struct ath12k_base *ab, struct hal_tx_msdu_ext_desc *tcl_ext_cmd, @@ -91,6 +134,7 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a u32 iova_mask = dp->hw_params->iova_mask; bool is_diff_encap = false; bool is_null_frame = false; + bool eapol_encap_done = false; if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) return -ESHUTDOWN; @@ -211,9 +255,27 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a case HAL_TCL_ENCAP_TYPE_NATIVE_WIFI: is_null_frame = ieee80211_is_nullfunc(hdr->frame_control); if (ahvif->vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { - if (skb->protocol == cpu_to_be16(ETH_P_PAE) || is_null_frame) + if ((skb->protocol == cpu_to_be16(ETH_P_PAE) && + !(skb_cb->flags & ATH12K_SKB_CIPHER_SET)) || is_null_frame) is_diff_encap = true; + if (skb->protocol == cpu_to_be16(ETH_P_PAE) && + (skb_cb->flags & ATH12K_SKB_CIPHER_SET)) { + if (!eapol_encap_done) { + ret = ath12k_wifi7_dp_tx_encap_eapol(skb, &ti, + skb_cb); + if (ret) + goto fail_remove_tx_buf; + hdr = (void *)skb->data; + eapol_encap_done = true; + } else { + ti.flags0 |= u32_encode_bits(1, + HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti.encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti.encrypt_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + } + } /* Firmware expects msdu ext descriptor for nwifi/raw packets * received in ETH mode. Without this, observed tx fail for * Multicast packets in ETH mode. diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 8cebb229ebed..7cf00fda996f 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -455,7 +455,7 @@ static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcc2072(void *tlv, void **desc) struct hal_reo_get_queue_stats_status_qcc2072 *status_tlv; u16 tag; - tag = ath12k_hal_decode_tlv32_hdr(tlv, (void **)&status_tlv); + status_tlv = ath12k_hal_decode_tlv32_hdr(tlv, &tag, NULL, NULL); /* * actual desc of REO status entry starts after tlv32_padding, * see hal_reo_get_queue_stats_status_qcc2072 @@ -506,6 +506,8 @@ const struct hal_ops hal_qcc2072_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv32_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcc2072, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv32_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv32_hdr_align, }; u32 ath12k_hal_rx_desc_get_mpdu_start_offset_qcc2072(void) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 9d5180ef83b4..052b59265af8 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -950,6 +950,15 @@ void ath12k_hal_extract_rx_desc_data_qcn9274(struct hal_rx_desc_data *rx_desc_da rx_desc_data->err_bitmap = ath12k_hal_rx_h_mpdu_err_qcn9274(rx_desc); } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcn9274(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, .wbm2sw_cc_enable = HAL_WBM_SW_COOKIE_CONV_CFG_WBM2SW0_EN | @@ -1138,5 +1147,7 @@ const struct hal_ops hal_qcn9274_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h index 0d19a9cbb68c..6d69851e529d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h @@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 { __le32 res1; } __packed; +struct rx_mpdu_start_qcc2072 { + __le32 info0; + __le32 info2; + __le32 reo_queue_desc_lo; + __le32 info1; + __le32 pn[4]; + __le32 info4; + __le32 peer_meta_data; + __le16 ast_index; + __le16 sw_peer_id; + __le16 info3; + __le16 phy_ppdu_id; + __le32 info5; + __le32 info6; + __le16 frame_ctrl; + __le16 duration; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + __le16 qos_ctrl; + __le32 ht_ctrl; + __le32 info7; + __le32 res0; + __le32 res1; + __le32 res2; + __le32 info8; + __le32 res3; + __le32 res4; +} __packed; + #define QCN9274_MPDU_START_SELECT_MPDU_START_TAG BIT(0) #define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO BIT(1) #define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0 BIT(2) @@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 { struct rx_msdu_end_qcn9274 msdu_end; u8 rx_padding0[RX_BE_PADDING0_BYTES]; __le32 mpdu_start_tag; - struct rx_mpdu_start_qcn9274 mpdu_start; + struct rx_mpdu_start_qcc2072 mpdu_start; struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv; u8 msdu_payload[]; }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index efbbc1cbd3e4..61be8443e46e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -756,6 +756,15 @@ int ath12k_hal_srng_create_config_wcn7850(struct ath12k_hal *hal) return 0; } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_wcn7850(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hal_tcl_to_wbm_rbm_map ath12k_hal_tcl_to_wbm_rbm_map_wcn7850[DP_TCL_NUM_RING_MAX] = { { @@ -821,5 +830,7 @@ const struct hal_ops hal_wcn7850_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 03dedfd907fc..7b86f324b2e6 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -393,6 +393,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = false, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -485,6 +486,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -575,6 +577,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -663,6 +666,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -698,7 +702,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .ce_ie_addr = &ath12k_wifi7_ce_ie_addr_ipq5332, .ce_remap = &ath12k_wifi7_ce_remap_ipq5332, - .bdf_addr_offset = 0xC00000, + .bdf_addr_offset = 0x1A00000, .dp_primary_link_only = true, .client = { @@ -749,6 +753,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -839,6 +844,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -918,6 +924,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, struct ethhdr *eth; bool is_prb_rsp; u16 mcbc_gsn; + u8 cb_flags; u8 link_id; int ret; struct ath12k_dp *tmp_dp; @@ -1011,8 +1018,13 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, ieee80211_has_protected(hdr->frame_control)) is_dvlan = true; + /* + * Add a sta pointer check to differentiate multicast encapsulation + * offload packets, as the ATH12K_SKB_HW_80211_ENCAP flag is also set + * for such packets. + */ if (!vif->valid_links || !is_mcast || is_dvlan || - (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) || + ((skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) && sta) || test_bit(ATH12K_FLAG_RAW_MODE, &ar->ab->dev_flags)) { ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, arsta, skb, false, 0, is_mcast); if (unlikely(ret)) { @@ -1024,6 +1036,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, mcbc_gsn = atomic_inc_return(&ahvif->dp_vif.mcbc_gsn) & 0xfff; links_map = ahvif->links_map; + cb_flags = skb_cb->flags; for_each_set_bit(link_id, &links_map, IEEE80211_MLD_MAX_NUM_LINKS) { tmp_arvif = rcu_dereference(ahvif->link[link_id]); @@ -1031,21 +1044,52 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, continue; tmp_ar = tmp_arvif->ar; - tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_ar->ab->dp, + if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &tmp_ar->ab->dev_flags))) + continue; + + tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); + tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp, tmp_ar->pdev_idx); if (!tmp_dp_pdev) continue; - msdu_copied = skb_copy(skb, GFP_ATOMIC); - if (!msdu_copied) { - ath12k_err(ar->ab, - "skb copy failure link_id 0x%X vdevid 0x%X\n", - link_id, tmp_arvif->vdev_id); - continue; - } - ath12k_mlo_mcast_update_tx_link_address(vif, link_id, - msdu_copied, - info_flags); + if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) { + /* + * skb->data may be modified for the + * iova_mask devices. It is better to + * use skb_copy() for such devices to + * avoid any potential skb corruption + * related issues. + */ + if (tmp_dp->hw_params->iova_mask) { + msdu_copied = skb_copy(skb, GFP_ATOMIC); + } else { + /* + * ath12k_wifi7_dp_tx() should + * treat cloned HW-encap Ethernet + * multicast frames as read-only. + */ + msdu_copied = skb_clone(skb, GFP_ATOMIC); + } + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy/clone failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + } else { + msdu_copied = skb_copy(skb, GFP_ATOMIC); + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + + ath12k_mlo_mcast_update_tx_link_address(vif, link_id, + msdu_copied, + info_flags); + } skb_cb = ATH12K_SKB_CB(msdu_copied); skb_cb->link_id = link_id; @@ -1061,7 +1105,6 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, if (unlikely(!ahvif->dp_vif.key_cipher)) goto skip_peer_find; - tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); spin_lock_bh(&tmp_dp->dp_lock); peer = ath12k_dp_link_peer_find_by_addr(tmp_dp, tmp_arvif->bssid); @@ -1080,11 +1123,16 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, skb_cb->cipher = key->cipher; skb_cb->flags |= ATH12K_SKB_CIPHER_SET; + if (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) + goto skip_fctl_protected_check; + hdr = (struct ieee80211_hdr *)msdu_copied->data; if (!ieee80211_has_protected(hdr->frame_control)) hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); } + +skip_fctl_protected_check: spin_unlock_bh(&tmp_dp->dp_lock); skip_peer_find: diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 84a31b953db8..d5160af60e00 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -207,6 +207,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = { .min_len = sizeof(struct wmi_per_chain_rssi_stat_params) }, [WMI_TAG_OBSS_COLOR_COLLISION_EVT] = { .min_len = sizeof(struct wmi_obss_color_collision_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct ath12k_wmi_pdev_csa_event) }, }; __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len) @@ -374,6 +376,13 @@ ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb) return tb; } +static u32 ath12k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return le32_get_bits(tlv->header, WMI_TLV_LEN); +} + static int ath12k_wmi_cmd_send_nowait(struct ath12k_wmi_pdev *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -1228,10 +1237,16 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, le32_encode_bits(arg->ml.mcast_link, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, - ATH12K_WMI_FLAG_MLO_LINK_ADD); + ATH12K_WMI_FLAG_MLO_LINK_ADD) | + le32_encode_bits(arg->ml.assoc_link, + ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE) | + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %d start ml flags 0x%x\n", - arg->vdev_id, ml_params->flags); + ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id); + + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %u start link_id %u ml flags 0x%x\n", + arg->vdev_id, arg->ml.ieee_link_id, + le32_to_cpu(ml_params->flags)); ptr += sizeof(*ml_params); @@ -1244,19 +1259,23 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, partner_info = ptr; for (i = 0; i < arg->ml.num_partner_links; i++) { + struct wmi_ml_partner_info *pinfo = &arg->ml.partner_info[i]; + partner_info->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PARTNER_LINK_PARAMS, sizeof(*partner_info)); - partner_info->vdev_id = - cpu_to_le32(arg->ml.partner_info[i].vdev_id); - partner_info->hw_link_id = - cpu_to_le32(arg->ml.partner_info[i].hw_link_id); + partner_info->vdev_id = cpu_to_le32(pinfo->vdev_id); + partner_info->hw_link_id = cpu_to_le32(pinfo->hw_link_id); ether_addr_copy(partner_info->vdev_addr.addr, - arg->ml.partner_info[i].addr); + pinfo->addr); + partner_info->flags = + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER); + partner_info->ieee_link_id = cpu_to_le32(pinfo->ieee_link_id); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %d hw_link_id %d macaddr%pM\n", - partner_info->vdev_id, partner_info->hw_link_id, - partner_info->vdev_addr.addr); + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %u hw_link_id %u macaddr %pM link_id %u ml flags 0x%x\n", + pinfo->vdev_id, pinfo->hw_link_id, + pinfo->addr, pinfo->ieee_link_id, + le32_to_cpu(partner_info->flags)); partner_info++; } @@ -2629,9 +2648,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, struct wmi_tlv *tlv; void *ptr; int i, ret, len; - u32 *tmp_ptr, extraie_len_with_pad = 0; - struct ath12k_wmi_hint_short_ssid_arg *s_ssid = NULL; - struct ath12k_wmi_hint_bssid_arg *hint_bssid = NULL; + __le32 *tmp_ptr; + u32 extraie_len_with_pad = 0; + struct ath12k_wmi_hint_short_ssid_params *s_ssid = NULL; + struct ath12k_wmi_hint_bssid_params *hint_bssid = NULL; len = sizeof(*cmd); @@ -2714,9 +2734,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, tlv = ptr; tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_UINT32, len); ptr += TLV_HDR_SIZE; - tmp_ptr = (u32 *)ptr; + tmp_ptr = (__le32 *)ptr; - memcpy(tmp_ptr, arg->chan_list, arg->num_chan * 4); + for (i = 0; i < arg->num_chan; i++) + tmp_ptr[i] = cpu_to_le32(arg->chan_list[i]); ptr += len; @@ -2772,8 +2793,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, ptr += TLV_HDR_SIZE; s_ssid = ptr; for (i = 0; i < arg->num_hint_s_ssid; ++i) { - s_ssid->freq_flags = arg->hint_s_ssid[i].freq_flags; - s_ssid->short_ssid = arg->hint_s_ssid[i].short_ssid; + s_ssid->freq_flags = + cpu_to_le32(arg->hint_s_ssid[i].freq_flags); + s_ssid->short_ssid = + cpu_to_le32(arg->hint_s_ssid[i].short_ssid); s_ssid++; } ptr += len; @@ -2787,9 +2810,9 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, hint_bssid = ptr; for (i = 0; i < arg->num_hint_bssid; ++i) { hint_bssid->freq_flags = - arg->hint_bssid[i].freq_flags; - ether_addr_copy(&arg->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + cpu_to_le32(arg->hint_bssid[i].freq_flags); + ether_addr_copy(&hint_bssid->bssid.addr[0], + &arg->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } @@ -4751,14 +4774,16 @@ static int ath12k_wmi_mac_phy_caps_parse(struct ath12k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kzalloc((svc_rdy_ext->tot_phy_id) * len, - GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; @@ -5154,6 +5179,7 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, __le32 cap_info_internal) { struct ath12k_band_cap *cap_band = &pdev->cap.band[band]; + u8 *phy_cap = (u8 *)&cap_band->eht_cap_phy_info[0]; u32 support_320mhz; u8 i; @@ -5167,8 +5193,22 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++) cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]); - if (band == NL80211_BAND_6GHZ) + if (band == NL80211_BAND_6GHZ) { cap_band->eht_cap_phy_info[0] |= support_320mhz; + } else { + /* + * Firmware may report 6 GHz/320 MHz specific capabilities for + * non-6 GHz bands, so explicitly clear them. + */ + phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK; + phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK; + phy_cap[3] &= ~IEEE80211_EHT_PHY_CAP3_SOUNDING_DIM_320MHZ_MASK; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_EHT_DUP_6GHZ_SUPP; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ; + } cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]); cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]); @@ -6713,16 +6753,12 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb, return 0; } -static int freq_to_idx(struct ath12k *ar, int freq) +static int freq_to_idx(struct ieee80211_hw *hw, int freq) { struct ieee80211_supported_band *sband; - struct ieee80211_hw *hw = ath12k_ar_to_hw(ar); int band, ch, idx = 0; for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { - if (!ar->mac.sbands[band].channels) - continue; - sband = hw->wiphy->bands[band]; if (!sband) continue; @@ -7072,25 +7108,29 @@ static void ath12k_peer_delete_resp_event(struct ath12k_base *ab, struct sk_buff { struct wmi_peer_delete_resp_event peer_del_resp; struct ath12k *ar; + u32 vdev_id; if (ath12k_pull_peer_del_resp_ev(ab, skb, &peer_del_resp) != 0) { - ath12k_warn(ab, "failed to extract peer delete resp"); + ath12k_warn(ab, "failed to extract peer delete resp\n"); return; } + vdev_id = le32_to_cpu(peer_del_resp.vdev_id); + rcu_read_lock(); - ar = ath12k_mac_get_ar_by_vdev_id(ab, le32_to_cpu(peer_del_resp.vdev_id)); + ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id); if (!ar) { - ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d", - peer_del_resp.vdev_id); + ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d\n", + vdev_id); rcu_read_unlock(); return; } - complete(&ar->peer_delete_done); + ath12k_peer_delete_resp_signal(ar, vdev_id, + peer_del_resp.peer_macaddr.addr); rcu_read_unlock(); ath12k_dbg(ab, ATH12K_DBG_WMI, "peer delete resp for vdev id %d addr %pM\n", - peer_del_resp.vdev_id, peer_del_resp.peer_macaddr.addr); + vdev_id, peer_del_resp.peer_macaddr.addr); } static void ath12k_vdev_delete_resp_event(struct ath12k_base *ab, @@ -7629,6 +7669,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) { struct wmi_chan_info_event ch_info_ev = {}; struct ath12k *ar; + struct ath12k_hw *ah; struct survey_info *survey; int idx; /* HW channel counters frequency value in hertz */ @@ -7660,6 +7701,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } spin_lock_bh(&ar->data_lock); + ah = ath12k_ar_to_ah(ar); switch (ar->scan.state) { case ATH12K_SCAN_IDLE: @@ -7671,8 +7713,8 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) break; } - idx = freq_to_idx(ar, le32_to_cpu(ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "chan info: invalid frequency %d (idx %d out of bounds)\n", ch_info_ev.freq, idx); goto exit; @@ -7685,14 +7727,20 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) cc_freq_hz = (le32_to_cpu(ch_info_ev.mac_clk_mhz) * 1000); if (ch_info_ev.cmd_flags == WMI_CHAN_INFO_START_RESP) { - survey = &ar->survey[idx]; - memset(survey, 0, sizeof(*survey)); - survey->noise = le32_to_cpu(ch_info_ev.noise_floor); - survey->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY; - survey->time = div_u64(le32_to_cpu(ch_info_ev.cycle_count), cc_freq_hz); - survey->time_busy = div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), - cc_freq_hz); + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + memset(survey, 0, sizeof(*survey)); + survey->noise = le32_to_cpu(ch_info_ev.noise_floor); + survey->time = + div_u64(le32_to_cpu(ch_info_ev.cycle_count), + cc_freq_hz); + survey->time_busy = + div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), + cc_freq_hz); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + } } exit: spin_unlock_bh(&ar->data_lock); @@ -7705,6 +7753,7 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_pdev_bss_chan_info_event bss_ch_info_ev = {}; struct survey_info *survey; struct ath12k *ar; + struct ath12k_hw *ah; u32 cc_freq_hz = ab->cc_freq_hz; u64 busy, total, tx, rx, rx_bss; int idx; @@ -7745,28 +7794,31 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } - spin_lock_bh(&ar->data_lock); - idx = freq_to_idx(ar, le32_to_cpu(bss_ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + ah = ath12k_ar_to_ah(ar); + + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(bss_ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "bss chan info: invalid frequency %d (idx %d out of bounds)\n", bss_ch_info_ev.freq, idx); goto exit; } - survey = &ar->survey[idx]; + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + + survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); + survey->time = div_u64(total, cc_freq_hz); + survey->time_busy = div_u64(busy, cc_freq_hz); + survey->time_rx = div_u64(rx_bss, cc_freq_hz); + survey->time_tx = div_u64(tx, cc_freq_hz); + survey->filled |= (SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX); + } - survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); - survey->time = div_u64(total, cc_freq_hz); - survey->time_busy = div_u64(busy, cc_freq_hz); - survey->time_rx = div_u64(rx_bss, cc_freq_hz); - survey->time_tx = div_u64(tx, cc_freq_hz); - survey->filled |= (SURVEY_INFO_NOISE_DBM | - SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY | - SURVEY_INFO_TIME_RX | - SURVEY_INFO_TIME_TX); exit: - spin_unlock_bh(&ar->data_lock); complete(&ar->bss_survey_done); rcu_read_unlock(); @@ -9034,12 +9086,19 @@ ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab, const u32 *vdev_ids) { u32 current_switch_count = le32_to_cpu(ev->current_switch_count); + u32 vdev_ids_len = ath12k_wmi_tlv_data_len(vdev_ids); u32 num_vdevs = le32_to_cpu(ev->num_vdevs); struct ieee80211_bss_conf *conf; struct ath12k_link_vif *arvif; struct ath12k_vif *ahvif; int i; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath12k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); for (i = 0; i < num_vdevs; i++) { arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); @@ -9929,6 +9988,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, void *ptr = skb->data; struct ath12k *ar; u16 tlv_tag; + u16 tlv_len; u32 event_count; int ret; @@ -9944,6 +10004,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, tlv = (struct wmi_tlv *)ptr; tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG); + tlv_len = le32_get_bits(tlv->header, WMI_TLV_LEN); ptr += sizeof(*tlv); if (tlv_tag != WMI_TAG_HALPHY_CTRL_PATH_EVENT_FIXED_PARAM) { @@ -9951,6 +10012,12 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, return; } + if (tlv_len < sizeof(*fixed_param)) { + ath12k_warn(ab, "TPC stats fixed param tlv len %u too short\n", + tlv_len); + return; + } + fixed_param = (struct ath12k_wmi_pdev_tpc_stats_event_fixed_params *)ptr; rcu_read_lock(); ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(fixed_param->pdev_id) + 1); @@ -10257,12 +10324,12 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; - cmd_hdr = (struct wmi_cmd_hdr *)skb->data; - id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); - - if (!skb_pull(skb, sizeof(struct wmi_cmd_hdr))) + cmd_hdr = skb_pull_data(skb, sizeof(*cmd_hdr)); + if (!cmd_hdr) goto out; + id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); + switch (id) { /* Process all the WMI events here */ case WMI_SERVICE_READY_EVENTID: diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c452e3d57a29..b508aa759bd8 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -1083,6 +1083,7 @@ enum wmi_tlv_pdev_param { WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE, WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE, WMI_PDEV_PARAM_MESH_MCAST_ENABLE, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS = 0xa6, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe, WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6, @@ -2330,6 +2331,13 @@ enum wmi_slot_time { WMI_VDEV_SLOT_TIME_SHORT = 2, }; +enum wmi_dtim_policy { + WMI_DTIM_POLICY_IGNORE = 1, + WMI_DTIM_POLICY_NORMAL = 2, + WMI_DTIM_POLICY_STICK = 3, + WMI_DTIM_POLICY_AUTO = 4, +}; + enum wmi_preamble { WMI_VDEV_PREAMBLE_LONG = 1, WMI_VDEV_PREAMBLE_SHORT = 2, @@ -2954,10 +2962,14 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE BIT(17) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) struct wmi_vdev_start_mlo_params { __le32 tlv_header; __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_partner_link_info { @@ -2965,6 +2977,8 @@ struct wmi_partner_link_info { __le32 vdev_id; __le32 hw_link_id; struct ath12k_wmi_mac_addr_params vdev_addr; + __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_vdev_delete_cmd { @@ -3120,6 +3134,7 @@ struct wmi_ml_partner_info { bool primary_umac; bool logical_link_idx_valid; u32 logical_link_idx; + u32 ieee_link_id; }; struct wmi_ml_arg { @@ -3127,6 +3142,7 @@ struct wmi_ml_arg { bool assoc_link; bool mcast_link; bool link_add; + u32 ieee_link_id; u8 num_partner_links; struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS]; }; @@ -3549,6 +3565,16 @@ struct ath12k_wmi_hint_bssid_arg { struct ath12k_wmi_mac_addr_params bssid; }; +struct ath12k_wmi_hint_short_ssid_params { + __le32 freq_flags; + __le32 short_ssid; +}; + +struct ath12k_wmi_hint_bssid_params { + __le32 freq_flags; + struct ath12k_wmi_mac_addr_params bssid; +}; + struct ath12k_wmi_scan_req_arg { u32 scan_id; u32 scan_req_id; diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index cc0f2c45fc3a..8bbe554a9b36 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -754,6 +754,11 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *assoc_resp_ie = assoc_info + beacon_ie_len + assoc_req_len + assoc_resp_ie_offset; + if (assoc_req_len < assoc_req_ie_offset) + assoc_req_len = assoc_req_ie_offset; + if (assoc_resp_len < assoc_resp_ie_offset) + assoc_resp_len = assoc_resp_ie_offset; + assoc_req_len -= assoc_req_ie_offset; assoc_resp_len -= assoc_resp_ie_offset; @@ -3034,20 +3039,14 @@ static int ath6kl_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr) + u64 cookie, const u8 *rx_addr) { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); - u32 id; /* TODO: if already pending or ongoing remain-on-channel, * return -EBUSY */ - id = ++vif->last_roc_id; - if (id == 0) { - /* Do not use 0 as the cookie value */ - id = ++vif->last_roc_id; - } - *cookie = id; + vif->last_roc_id = cookie; return ath6kl_wmi_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx, chan->center_freq, duration); @@ -3106,6 +3105,7 @@ static int ath6kl_send_go_probe_resp(struct ath6kl_vif *vif, static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, u32 id, + u64 cookie, u32 freq, u32 wait, const u8 *buf, @@ -3138,6 +3138,7 @@ static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, INIT_LIST_HEAD(&mgmt_buf->list); mgmt_buf->id = id; + mgmt_buf->cookie = cookie; mgmt_buf->freq = freq; mgmt_buf->wait = wait; mgmt_buf->len = len; @@ -3190,7 +3191,7 @@ static bool ath6kl_is_p2p_go_ssid(const u8 *buf, size_t len) } static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); @@ -3222,7 +3223,6 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, * Send Probe Response frame in GO mode using a separate WMI * command to allow the target to fill in the generic IEs. */ - *cookie = 0; /* TX status not supported */ return ath6kl_send_go_probe_resp(vif, buf, len, freq); } @@ -3235,16 +3235,15 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, id = vif->send_action_id++; } - *cookie = id; - /* AP mode Power saving processing */ if (vif->nw_type == AP_NETWORK) { - queued = ath6kl_mgmt_powersave_ap(vif, id, freq, wait, buf, len, - &more_data, no_cck); + queued = ath6kl_mgmt_powersave_ap(vif, id, cookie, freq, wait, + buf, len, &more_data, no_cck); if (queued) return 0; } + ar->wmi->last_mgmt_tx_cookie = cookie; return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, freq, wait, buf, len, no_cck); } @@ -3437,7 +3436,7 @@ ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { }, }; -static struct cfg80211_ops ath6kl_cfg80211_ops = { +static const struct cfg80211_ops ath6kl_cfg80211_ops = { .add_virtual_intf = ath6kl_cfg80211_add_iface, .del_virtual_intf = ath6kl_cfg80211_del_iface, .change_virtual_intf = ath6kl_cfg80211_change_iface, diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h index 77e052336eb5..a0b236eeeab5 100644 --- a/drivers/net/wireless/ath/ath6kl/core.h +++ b/drivers/net/wireless/ath/ath6kl/core.h @@ -404,6 +404,7 @@ struct ath6kl_mgmt_buff { u32 freq; u32 wait; u32 id; + u64 cookie; bool no_cck; size_t len; u8 buf[]; @@ -631,8 +632,8 @@ struct ath6kl_vif { struct cfg80211_scan_request *scan_req; enum sme_state sme_state; int reconnect_flag; - u32 last_roc_id; - u32 last_cancel_roc_id; + u64 last_roc_id; + u64 last_cancel_roc_id; u32 send_action_id; bool probe_req_report; u16 assoc_bss_beacon_int; diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c index 782209dcb782..6481da4c1991 100644 --- a/drivers/net/wireless/ath/ath6kl/init.c +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -1570,7 +1570,7 @@ static int ath6kl_init_upload(struct ath6kl *ar) if (status) return status; - return status; + return 0; } int ath6kl_init_hw_params(struct ath6kl *ar) diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c index 8afc6589fc51..9c023d7ce305 100644 --- a/drivers/net/wireless/ath/ath6kl/main.c +++ b/drivers/net/wireless/ath/ath6kl/main.c @@ -898,6 +898,7 @@ void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) spin_unlock_bh(&conn->psq_lock); conn->sta_flags |= STA_PS_POLLED; + ar->wmi->last_mgmt_tx_cookie = mgmt_buf->cookie; ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, mgmt_buf->id, mgmt_buf->freq, mgmt_buf->wait, mgmt_buf->buf, diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index d81825413906..609d458ca6c5 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -1469,6 +1469,7 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) spin_unlock_bh(&conn->psq_lock); idx = vif->fw_vif_idx; + ar->wmi->last_mgmt_tx_cookie = mgmt->cookie; ath6kl_wmi_send_mgmt_cmd(ar->wmi, idx, mgmt->id, diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 2b0c5038ae04..2140fddba8ba 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -596,7 +596,7 @@ static int ath6kl_wmi_tx_status_event_rx(struct wmi *wmi, u8 *datap, int len, ath6kl_dbg(ATH6KL_DBG_WMI, "tx_status: id=%x ack_status=%u\n", id, ev->ack_status); if (wmi->last_mgmt_tx_frame) { - cfg80211_mgmt_tx_status(&vif->wdev, id, + cfg80211_mgmt_tx_status(&vif->wdev, wmi->last_mgmt_tx_cookie, wmi->last_mgmt_tx_frame, wmi->last_mgmt_tx_frame_len, !!ev->ack_status, GFP_ATOMIC); @@ -1296,6 +1296,9 @@ static int ath6kl_wmi_scan_complete_rx(struct wmi *wmi, u8 *datap, int len, { struct wmi_scan_complete_event *ev; + if (len < sizeof(*ev)) + return -EINVAL; + ev = (struct wmi_scan_complete_event *) datap; ath6kl_scan_complete_evt(vif, a_sle32_to_cpu(ev->status)); @@ -3372,7 +3375,12 @@ static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap, static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_addba_req_event *cmd = (struct wmi_addba_req_event *) datap; + struct wmi_addba_req_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_addba_req_event *)datap; aggr_recv_addba_req_evt(vif, cmd->tid, le16_to_cpu(cmd->st_seq_no), cmd->win_sz); @@ -3383,7 +3391,12 @@ static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, static int ath6kl_wmi_delba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_delba_event *cmd = (struct wmi_delba_event *) datap; + struct wmi_delba_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_delba_event *)datap; aggr_recv_delba_req_evt(vif, cmd->tid); diff --git a/drivers/net/wireless/ath/ath6kl/wmi.h b/drivers/net/wireless/ath/ath6kl/wmi.h index 8fbece3fdad9..89ce2abbb1cd 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.h +++ b/drivers/net/wireless/ath/ath6kl/wmi.h @@ -125,6 +125,7 @@ struct wmi { u8 *last_mgmt_tx_frame; size_t last_mgmt_tx_frame_len; + u64 last_mgmt_tx_cookie; u8 saved_pwr_mode; }; diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h index b13685e22a0d..e66e3e2ae952 100644 --- a/drivers/net/wireless/ath/carl9170/carl9170.h +++ b/drivers/net/wireless/ath/carl9170/carl9170.h @@ -381,6 +381,7 @@ struct ar9170 { unsigned int tx_ack_failures; unsigned int tx_fcs_errors; unsigned int rx_dropped; + unsigned int rx_phy_errors; /* EEPROM */ struct ar9170_eeprom eeprom; diff --git a/drivers/net/wireless/ath/carl9170/cmd.c b/drivers/net/wireless/ath/carl9170/cmd.c index 402fd0633e09..ad0a018119c9 100644 --- a/drivers/net/wireless/ath/carl9170/cmd.c +++ b/drivers/net/wireless/ath/carl9170/cmd.c @@ -52,7 +52,7 @@ int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val) (u8 *) buf, 0, NULL); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "writing reg %#x " + wiphy_dbg(ar->hw->wiphy, "writing reg %#x " "(val %#x) failed (%d)\n", reg, val, err); } } @@ -78,7 +78,7 @@ int carl9170_read_mreg(struct ar9170 *ar, const int nregs, 4 * nregs, (u8 *)res); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n", + wiphy_dbg(ar->hw->wiphy, "reading regs failed (%d)\n", err); } return err; diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c index 2d734567000a..0498df2a2160 100644 --- a/drivers/net/wireless/ath/carl9170/debug.c +++ b/drivers/net/wireless/ath/carl9170/debug.c @@ -794,6 +794,7 @@ DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago", DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped); DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped); +DEBUGFS_READONLY_FILE(rx_phy_errors, 20, "%d", ar->rx_phy_errors); DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled); DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d", @@ -830,6 +831,7 @@ void carl9170_debugfs_register(struct ar9170 *ar) DEBUGFS_ADD(tx_ampdu_list_len); DEBUGFS_ADD(rx_dropped); + DEBUGFS_ADD(rx_phy_errors); DEBUGFS_ADD(sniffer_enabled); DEBUGFS_ADD(rx_software_decryption); diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c index af632418fa06..61c7a1288743 100644 --- a/drivers/net/wireless/ath/carl9170/main.c +++ b/drivers/net/wireless/ath/carl9170/main.c @@ -908,7 +908,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change } if (changed & IEEE80211_CONF_CHANGE_SMPS) { - /* TODO */ + /* + * We advertise SM_PS disabled (all chains active). + * mac80211 may still request mode changes, which we + * accept but only support OFF (both chains active). + * Static/dynamic SMPS would require firmware support + * for chain control that the AR9170 does not provide. + */ err = 0; } diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c index 0383d5c9698b..fbe46a5e4e41 100644 --- a/drivers/net/wireless/ath/carl9170/rx.c +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -456,7 +456,9 @@ static void carl9170_rx_phy_status(struct ar9170 *ar, if (phy->rssi[i] & 0x80) phy->rssi[i] = ((~phy->rssi[i] & 0x7f) + 1) & 0x7f; - /* TODO: we could do something with phy_errors */ + if (phy->phy_err) + ar->rx_phy_errors++; + status->signal = ar->noise[0] + phy->rssi_combined; } diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index d6ef92cfcbaf..9ac33d827f80 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1432,7 +1432,7 @@ static int wil_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { const u8 *buf = params->buf; size_t len = params->len; @@ -1488,8 +1488,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, */ tx_status = (rc == 0); rc = (rc == -EAGAIN) ? 0 : rc; - cfg80211_mgmt_tx_status(wdev, cookie ? *cookie : 0, buf, len, - tx_status, GFP_KERNEL); + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, tx_status, GFP_KERNEL); return rc; } @@ -1735,7 +1734,7 @@ static int wil_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr) + u64 cookie, const u8 *rx_addr) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; @@ -2326,7 +2325,7 @@ static void wil_probe_client_handle(struct wil6210_priv *wil, */ bool alive = (sta->status == wil_sta_connected); - cfg80211_probe_status(ndev, sta->addr, req->cookie, alive, + cfg80211_probe_status(ndev, sta->addr, req->cookie, -1, alive, 0, false, GFP_KERNEL); } @@ -2379,9 +2378,9 @@ void wil_probe_client_flush(struct wil6210_vif *vif) mutex_unlock(&vif->probe_client_mutex); } -static int wil_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, - const u8 *peer, u64 *cookie) +static int wil_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, + const u8 *peer, u64 cookie) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); struct wil6210_vif *vif = ndev_to_vif(dev); @@ -2399,13 +2398,12 @@ static int wil_cfg80211_probe_client(struct wiphy *wiphy, return -ENOMEM; req->cid = cid; - req->cookie = cid; + req->cookie = cookie; mutex_lock(&vif->probe_client_mutex); list_add_tail(&req->list, &vif->probe_client_pending); mutex_unlock(&vif->probe_client_mutex); - *cookie = req->cookie; queue_work(wil->wq_service, &vif->probe_client_worker); return 0; } @@ -2660,7 +2658,7 @@ static const struct cfg80211_ops wil_cfg80211_ops = { .add_station = wil_cfg80211_add_station, .del_station = wil_cfg80211_del_station, .change_station = wil_cfg80211_change_station, - .probe_client = wil_cfg80211_probe_client, + .probe_peer = wil_cfg80211_probe_peer, .change_bss = wil_cfg80211_change_bss, /* P2P device */ .start_p2p_device = wil_cfg80211_start_p2p_device, diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c index b8cb736a7185..e06e45323272 100644 --- a/drivers/net/wireless/ath/wil6210/debugfs.c +++ b/drivers/net/wireless/ath/wil6210/debugfs.c @@ -985,7 +985,7 @@ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, params.buf = frame; params.len = len; - rc = wil_cfg80211_mgmt_tx(wiphy, wdev, ¶ms, NULL); + rc = wil_cfg80211_mgmt_tx(wiphy, wdev, ¶ms, 0); kfree(frame); wil_info(wil, "-> %d\n", rc); diff --git a/drivers/net/wireless/ath/wil6210/p2p.c b/drivers/net/wireless/ath/wil6210/p2p.c index f20caf1a3905..acd8a2e35103 100644 --- a/drivers/net/wireless/ath/wil6210/p2p.c +++ b/drivers/net/wireless/ath/wil6210/p2p.c @@ -124,7 +124,7 @@ int wil_p2p_search(struct wil6210_vif *vif, int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, unsigned int duration, struct ieee80211_channel *chan, - u64 *cookie) + u64 cookie) { struct wil6210_vif *vif = wdev_to_vif(wil, wdev); struct wil_p2p_info *p2p = &vif->p2p; @@ -144,7 +144,7 @@ int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, } memcpy(&p2p->listen_chan, chan, sizeof(*chan)); - *cookie = ++p2p->cookie; + p2p->cookie = cookie; p2p->listen_duration = duration; mutex_lock(&wil->vif_mutex); @@ -166,7 +166,7 @@ int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, if (vif->mid == 0) wil->radio_wdev = wdev; - cfg80211_ready_on_channel(wdev, *cookie, chan, duration, + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_KERNEL); out: diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index 31e107c81e2d..f8e60be631ca 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -1301,7 +1301,7 @@ int wil_p2p_search(struct wil6210_vif *vif, struct cfg80211_scan_request *request); int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, unsigned int duration, struct ieee80211_channel *chan, - u64 *cookie); + u64 cookie); u8 wil_p2p_stop_discovery(struct wil6210_vif *vif); int wil_p2p_cancel_listen(struct wil6210_vif *vif, u64 cookie); void wil_p2p_listen_expired(struct work_struct *work); @@ -1317,7 +1317,7 @@ int wmi_stop_discovery(struct wil6210_vif *vif); int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); void wil_cfg80211_ap_recovery(struct wil6210_priv *wil); int wil_cfg80211_iface_combinations_from_fw( struct wil6210_priv *wil, diff --git a/drivers/net/wireless/broadcom/b43/debugfs.c b/drivers/net/wireless/broadcom/b43/debugfs.c index acddae68947a..31a1ff00c1a4 100644 --- a/drivers/net/wireless/broadcom/b43/debugfs.c +++ b/drivers/net/wireless/broadcom/b43/debugfs.c @@ -495,7 +495,6 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, ssize_t ret; char *buf; const size_t bufsize = 1024 * 16; /* 16 kiB buffer */ - const size_t buforder = get_order(bufsize); int err = 0; if (!count) @@ -518,15 +517,14 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, dfile = fops_to_dfs_file(dev, dfops); if (!dfile->buffer) { - buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + buf = kzalloc(bufsize, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; } - memset(buf, 0, bufsize); ret = dfops->read(dev, buf, bufsize); if (ret <= 0) { - free_pages((unsigned long)buf, buforder); + kfree(buf); err = ret; goto out_unlock; } @@ -538,7 +536,7 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, dfile->buffer, dfile->data_len); if (*ppos >= dfile->data_len) { - free_pages((unsigned long)dfile->buffer, buforder); + kfree(dfile->buffer); dfile->buffer = NULL; dfile->data_len = 0; } @@ -577,7 +575,7 @@ static ssize_t b43_debugfs_write(struct file *file, goto out_unlock; } - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; @@ -591,7 +589,7 @@ static ssize_t b43_debugfs_write(struct file *file, goto out_freepage; out_freepage: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&dev->wl->mutex); diff --git a/drivers/net/wireless/broadcom/b43legacy/debugfs.c b/drivers/net/wireless/broadcom/b43legacy/debugfs.c index 3ad99124d522..a04d90d7307c 100644 --- a/drivers/net/wireless/broadcom/b43legacy/debugfs.c +++ b/drivers/net/wireless/broadcom/b43legacy/debugfs.c @@ -192,7 +192,6 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, ssize_t ret; char *buf; const size_t bufsize = 1024 * 16; /* 16 KiB buffer */ - const size_t buforder = get_order(bufsize); int err = 0; if (!count) @@ -215,12 +214,11 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, dfile = fops_to_dfs_file(dev, dfops); if (!dfile->buffer) { - buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + buf = kzalloc(bufsize, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; } - memset(buf, 0, bufsize); if (dfops->take_irqlock) { spin_lock_irq(&dev->wl->irq_lock); ret = dfops->read(dev, buf, bufsize); @@ -228,7 +226,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, } else ret = dfops->read(dev, buf, bufsize); if (ret <= 0) { - free_pages((unsigned long)buf, buforder); + kfree(buf); err = ret; goto out_unlock; } @@ -240,7 +238,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, dfile->buffer, dfile->data_len); if (*ppos >= dfile->data_len) { - free_pages((unsigned long)dfile->buffer, buforder); + kfree(dfile->buffer); dfile->buffer = NULL; dfile->data_len = 0; } @@ -279,7 +277,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file, goto out_unlock; } - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; @@ -298,7 +296,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file, goto out_freepage; out_freepage: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&dev->wl->mutex); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c index 869c4872d399..71c2f99cdb71 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -989,10 +989,10 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = { BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43364, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4335_4339, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4339, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, WCC), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43439, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, WCC), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, CYW), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4354, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4356, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4359, WCC), diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 89f61710a210..872c48806d09 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2174,6 +2175,9 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; break; + case WLAN_AKM_SUITE_WFA_DPP: + val = WFA_AUTH_DPP; + break; default: bphy_err(drvr, "invalid akm suite (%d)\n", sme->crypto.akm_suites[0]); @@ -2483,43 +2487,50 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, goto done; } - if (sme->crypto.psk && - profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { - err = -EINVAL; - goto done; - } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; - } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + u32 akm = sme->crypto.n_akm_suites ? sme->crypto.akm_suites[0] : 0; + bool is_sae_akm = akm == WLAN_AKM_SUITE_SAE || + akm == WLAN_AKM_SUITE_FT_OVER_SAE; - if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { - /* enable firmware supplicant for this interface */ - err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); - if (err < 0) { - bphy_err(drvr, "failed to enable fw supplicant\n"); - goto done; + if (sme->crypto.psk && !is_sae_akm && + profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { + if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } - } - - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); - else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { - /* clean up user-space RSNE */ - err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); - if (err) { - bphy_err(drvr, "failed to clean up user-space RSNE\n"); - goto done; + if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + bphy_err(drvr, "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0); } - err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); - if (!err && sme->crypto.psk) + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); + else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE && + sme->crypto.sae_pwd && + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) { + /* clean up user-space RSNE */ + if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + bphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); + if (!err && sme->crypto.psk) + err = brcmf_set_pmk(ifp, sme->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); + } + if (err) + goto done; } - if (err) - goto done; - /* Join with specific BSSID and cached SSID * If SSID is zero join based on BSSID only */ @@ -4538,6 +4549,11 @@ static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie) return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0); } +static bool brcmf_valid_dpp_suite(u8 *oui) +{ + return get_unaligned_be32(oui) == WLAN_AKM_SUITE_WFA_DPP; +} + static s32 brcmf_configure_wpaie(struct brcmf_if *ifp, const struct brcmf_vs_tlv *wpa_ie, @@ -4651,42 +4667,47 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, goto exit; } for (i = 0; i < count; i++) { - if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + if (brcmf_valid_dpp_suite(&data[offset])) { + wpa_auth |= WFA_AUTH_DPP; + offset += TLV_OUI_LEN; + } else if (brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + offset += TLV_OUI_LEN; + switch (data[offset]) { + case RSN_AKM_NONE: + brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); + wpa_auth |= WPA_AUTH_NONE; + break; + case RSN_AKM_UNSPECIFIED: + brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); + is_rsn_ie ? + (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : + (wpa_auth |= WPA_AUTH_UNSPECIFIED); + break; + case RSN_AKM_PSK: + brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); + is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : + (wpa_auth |= WPA_AUTH_PSK); + break; + case RSN_AKM_SHA256_PSK: + brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); + wpa_auth |= WPA2_AUTH_PSK_SHA256; + break; + case RSN_AKM_SHA256_1X: + brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); + wpa_auth |= WPA2_AUTH_1X_SHA256; + break; + case RSN_AKM_SAE: + brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); + wpa_auth |= WPA3_AUTH_SAE_PSK; + break; + default: + bphy_err(drvr, "Invalid key mgmt info\n"); + } + } else { err = -EINVAL; bphy_err(drvr, "invalid OUI\n"); goto exit; } - offset += TLV_OUI_LEN; - switch (data[offset]) { - case RSN_AKM_NONE: - brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); - wpa_auth |= WPA_AUTH_NONE; - break; - case RSN_AKM_UNSPECIFIED: - brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : - (wpa_auth |= WPA_AUTH_UNSPECIFIED); - break; - case RSN_AKM_PSK: - brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : - (wpa_auth |= WPA_AUTH_PSK); - break; - case RSN_AKM_SHA256_PSK: - brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); - wpa_auth |= WPA2_AUTH_PSK_SHA256; - break; - case RSN_AKM_SHA256_1X: - brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); - wpa_auth |= WPA2_AUTH_1X_SHA256; - break; - case RSN_AKM_SAE: - brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); - wpa_auth |= WPA3_AUTH_SAE_PSK; - break; - default: - bphy_err(drvr, "Invalid key mgmt info\n"); - } offset++; } @@ -4706,10 +4727,12 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, */ if (!(wpa_auth & (WPA2_AUTH_PSK_SHA256 | WPA2_AUTH_1X_SHA256 | + WFA_AUTH_DPP | WPA3_AUTH_SAE_PSK))) { err = -EINVAL; goto exit; } + /* Firmware has requirement that WPA2_AUTH_PSK/ * WPA2_AUTH_UNSPECIFIED be set, if SHA256 OUI * is to be included in the rsn ie. @@ -5531,7 +5554,7 @@ brcmf_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, int brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct ieee80211_channel *chan = params->chan; @@ -5550,8 +5573,6 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, brcmf_dbg(TRACE, "Enter\n"); - *cookie = 0; - mgmt = (const struct ieee80211_mgmt *)buf; if (!ieee80211_is_mgmt(mgmt->frame_control)) { @@ -5582,7 +5603,7 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, BRCMF_VNDR_IE_PRBRSP_FLAG, &buf[ie_offset], ie_len); - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, GFP_KERNEL); } else if (ieee80211_is_action(mgmt->frame_control)) { if (len > BRCMF_FIL_ACTION_FRAME_SIZE + DOT11_MGMT_HDR_LEN) { @@ -5598,7 +5619,7 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } action_frame = &af_params->action_frame; /* Add the packet Id */ - action_frame->packet_id = cpu_to_le32(*cookie); + action_frame->packet_id = cpu_to_le32(cookie); /* Add BSSID */ memcpy(&action_frame->da[0], &mgmt->da[0], ETH_ALEN); memcpy(&af_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); @@ -5626,12 +5647,12 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, le16_to_cpu(action_frame->len)); brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, channel=%d\n", - *cookie, le16_to_cpu(action_frame->len), + cookie, le16_to_cpu(action_frame->len), le32_to_cpu(af_params->channel)); ack = brcmf_p2p_send_action_frame(vif->ifp, af_params); - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, ack, GFP_KERNEL); free: kfree(af_params); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 6ceb30142905..63e534523f51 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -497,6 +497,6 @@ void brcmf_cfg80211_free_vif(struct net_device *ndev); int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags); int brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie); + struct cfg80211_mgmt_tx_params *params, u64 cookie); #endif /* BRCMFMAC_CFG80211_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 873754be5174..545eb9aae966 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -100,7 +100,7 @@ static int brcmf_cyw_activate_events(struct brcmf_if *ifp) static int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct ieee80211_channel *chan = params->chan; @@ -123,7 +123,6 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, if (!ieee80211_is_auth(mgmt->frame_control)) return brcmf_cfg80211_mgmt_tx(wiphy, wdev, params, cookie); - *cookie = 0; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); reinit_completion(&vif->mgmt_tx); @@ -155,7 +154,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN); memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); - mf_params->packet_id = cpu_to_le32(*cookie); + mf_params->packet_id = cpu_to_le32(cookie); memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN], le16_to_cpu(mf_params->len)); @@ -187,8 +186,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } tx_status: - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, - GFP_KERNEL); + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, ack, GFP_KERNEL); free: kfree(mf_params); return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index ba1ce1552e0f..069ba7016654 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -310,6 +310,7 @@ brcmf_msgbuf_init_pktids(u32 nr_array_entries, } pktids->array = array; pktids->array_size = nr_array_entries; + pktids->direction = direction; return pktids; } @@ -560,6 +561,28 @@ brcmf_msgbuf_remove_flowring(struct brcmf_msgbuf *msgbuf, u16 flowid) brcmf_flowring_delete(msgbuf->flow, flowid); } +static bool brcmf_msgbuf_get_flowid(struct brcmf_msgbuf *msgbuf, + u16 flow_ring_id, u16 *flowid) +{ + u32 id = flow_ring_id; + + if (id < BRCMF_H2D_MSGRING_FLOWRING_IDSTART) { + bphy_err(msgbuf->drvr, "invalid flowring id %u\n", + flow_ring_id); + return false; + } + + id -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + if (id >= msgbuf->max_flowrings) { + bphy_err(msgbuf->drvr, "invalid flowring id %u\n", + flow_ring_id); + return false; + } + + *flowid = id; + return true; +} + static struct brcmf_msgbuf_work_item * brcmf_msgbuf_dequeue_work(struct brcmf_msgbuf *msgbuf) @@ -880,17 +903,23 @@ brcmf_msgbuf_process_txstatus(struct brcmf_msgbuf *msgbuf, void *buf) struct msgbuf_tx_status *tx_status; u32 idx; struct sk_buff *skb; + u16 flow_ring_id; u16 flowid; tx_status = (struct msgbuf_tx_status *)buf; idx = le32_to_cpu(tx_status->msg.request_id) - 1; - flowid = le16_to_cpu(tx_status->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(tx_status->compl_hdr.flow_ring_id); skb = brcmf_msgbuf_get_pktid(msgbuf->drvr->bus_if->dev, msgbuf->tx_pktids, idx); if (!skb) return; + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) { + brcmf_txfinalize(brcmf_get_ifp(msgbuf->drvr, tx_status->msg.ifidx), + skb, false); + return; + } + set_bit(flowid, msgbuf->txstatus_done_map); commonring = msgbuf->flowrings[flowid]; atomic_dec(&commonring->outstanding_tx); @@ -1237,13 +1266,16 @@ brcmf_msgbuf_process_flow_ring_create_response(struct brcmf_msgbuf *msgbuf, struct msgbuf_flowring_create_resp *flowring_create_resp; u16 status; u16 flowid; + u16 flow_ring_id; flowring_create_resp = (struct msgbuf_flowring_create_resp *)buf; - flowid = le16_to_cpu(flowring_create_resp->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(flowring_create_resp->compl_hdr.flow_ring_id); status = le16_to_cpu(flowring_create_resp->compl_hdr.status); + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) + return; + if (status) { bphy_err(drvr, "Flowring creation failed, code %d\n", status); brcmf_msgbuf_remove_flowring(msgbuf, flowid); @@ -1266,13 +1298,16 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf, struct msgbuf_flowring_delete_resp *flowring_delete_resp; u16 status; u16 flowid; + u16 flow_ring_id; flowring_delete_resp = (struct msgbuf_flowring_delete_resp *)buf; - flowid = le16_to_cpu(flowring_delete_resp->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(flowring_delete_resp->compl_hdr.flow_ring_id); status = le16_to_cpu(flowring_delete_resp->compl_hdr.status); + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) + return; + if (status) { bphy_err(drvr, "Flowring deletion failed, code %d\n", status); brcmf_flowring_delete(msgbuf->flow, flowid); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 92c16a317328..813cc15d8b87 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,6 @@ #define BRCMF_SCB_TIMEOUT_VALUE 20 -#define P2P_VER 9 /* P2P version: 9=WiFi P2P v1.0 */ -#define P2P_PUB_AF_CATEGORY 0x04 -#define P2P_PUB_AF_ACTION 0x09 #define P2P_AF_CATEGORY 0x7f #define P2P_OUI "\x50\x6F\x9A" /* P2P OUI */ #define P2P_OUI_LEN 3 /* P2P OUI length */ @@ -143,10 +141,10 @@ struct brcmf_p2p_scan_le { /** * struct brcmf_p2p_pub_act_frame - WiFi P2P Public Action Frame * - * @category: P2P_PUB_AF_CATEGORY - * @action: P2P_PUB_AF_ACTION + * @category: WLAN_CATEGORY_PUBLIC + * @action: WLAN_PUB_ACTION_VENDOR_SPECIFIC * @oui: P2P_OUI - * @oui_type: OUI type - P2P_VER + * @oui_type: OUI type - WLAN_OUI_TYPE_WFA_P2P * @subtype: OUI subtype - P2P_TYPE_* * @dialog_token: nonzero, identifies req/rsp transaction * @elts: Variable length information elements. @@ -166,7 +164,7 @@ struct brcmf_p2p_pub_act_frame { * * @category: P2P_AF_CATEGORY * @oui: OUI - P2P_OUI - * @type: OUI Type - P2P_VER + * @type: OUI Type - WLAN_OUI_TYPE_WFA_P2P * @subtype: OUI Subtype - P2P_AF_* * @dialog_token: nonzero, identifies req/resp tranaction * @elts: Variable length information elements. @@ -228,10 +226,38 @@ static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len) if (frame_len < sizeof(*pact_frm)) return false; - if (pact_frm->category == P2P_PUB_AF_CATEGORY && - pact_frm->action == P2P_PUB_AF_ACTION && - pact_frm->oui_type == P2P_VER && - memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) + if (pact_frm->category == WLAN_CATEGORY_PUBLIC && + pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC && + pact_frm->oui_type == WLAN_OUI_TYPE_WFA_P2P && + get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA) + return true; + + return false; +} + +/** + * brcmf_p2p_is_dpp_pub_action() - true if dpp public type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is dpp public action type + */ +static bool brcmf_p2p_is_dpp_pub_action(void *frame, u32 frame_len) +{ + struct brcmf_p2p_pub_act_frame *pact_frm; + + if (!frame) + return false; + + pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2p_pub_act_frame) - 1) + return false; + + if (pact_frm->category == WLAN_CATEGORY_PUBLIC && + pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC && + pact_frm->oui_type == WLAN_OUI_TYPE_WFA_DPP && + get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA) return true; return false; @@ -257,7 +283,7 @@ static bool brcmf_p2p_is_p2p_action(void *frame, u32 frame_len) return false; if (act_frm->category == P2P_AF_CATEGORY && - act_frm->type == P2P_VER && + act_frm->type == WLAN_OUI_TYPE_WFA_P2P && memcmp(act_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) return true; @@ -953,10 +979,8 @@ brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) p2p->cfg->d11inf.encchspec(&ch); err = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_LISTEN, ch.chspec, (u16)duration); - if (!err) { + if (!err) set_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status); - p2p->remain_on_channel_cookie++; - } exit: return err; } @@ -974,7 +998,7 @@ brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) */ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); @@ -994,8 +1018,8 @@ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, goto exit; memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); - *cookie = p2p->remain_on_channel_cookie; - cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); + p2p->remain_on_channel_cookie = cookie; + cfg80211_ready_on_channel(wdev, cookie, channel, duration, GFP_KERNEL); exit: return err; @@ -1283,6 +1307,9 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg) brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (!vif) + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val, sizeof(s32)); if (err) @@ -1782,7 +1809,9 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, goto exit; } } else if (brcmf_p2p_is_p2p_action(action_frame->data, - action_frame_len)) { + action_frame_len) || + brcmf_p2p_is_dpp_pub_action(action_frame->data, + action_frame_len)) { /* do not configure anything. it will be */ /* sent with a default configuration */ } else { @@ -1819,6 +1848,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, /* validate channel and p2p ies */ if (config_af_params.search_channel && IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif && p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { afx_hdl = &p2p->afx_hdl; afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h index 9f3f01ade2b7..707800949170 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h @@ -157,7 +157,7 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, struct brcmf_cfg80211_vif *vif); int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr); int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 9f7ed1d293a0..381801af3ac9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -1827,17 +1827,18 @@ brcmf_sdio_read_control(struct brcmf_sdio *bus, u8 *hdr, uint len, uint doff) if (bus->rxctl) { brcmf_err("last control frame is being processed.\n"); spin_unlock_bh(&bus->rxctl_lock); - vfree(buf); goto done; } bus->rxctl = buf + doff; bus->rxctl_orig = buf; bus->rxlen = len - doff; spin_unlock_bh(&bus->rxctl_lock); + buf = NULL; done: /* Awake any waiters */ brcmf_sdio_dcmd_resp_wake(bus); + vfree(buf); } /* Pad read to blocksize for efficiency */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 7552bdb91991..c465208c4331 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -233,6 +233,8 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define WPA3_AUTH_SAE_PSK 0x40000 /* SAE with 4-way handshake */ +#define WFA_AUTH_DPP 0x200000 /* WFA DPP AUTH */ + #define DOT11_DEFAULT_RTS_LEN 2347 #define DOT11_DEFAULT_FRAG_LEN 2346 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 606362463dc7..1099d8cfe6aa 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_BZ_UCODE_CORE_MAX 106 +#define IWL_BZ_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MIN 102 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 946975294b4f..a89a2c75687f 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -9,7 +9,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_DR_UCODE_CORE_MAX 106 +#define IWL_DR_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_DR_UCODE_CORE_MIN 102 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index e8240c1782ac..16abf0147fe4 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_SC_UCODE_CORE_MAX 106 +#define IWL_SC_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_SC_UCODE_CORE_MIN 102 diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/main.c b/drivers/net/wireless/intel/iwlwifi/dvm/main.c index ca5a8140908a..6bd5b6d84b2a 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/main.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/main.c @@ -1511,10 +1511,10 @@ static struct iwl_op_mode *iwl_op_mode_dvm_start(struct iwl_trans *trans, priv->workqueue = NULL; out_uninit_drv: iwl_uninit_drv(priv); -out_free_eeprom_blob: - kfree(priv->eeprom_blob); out_free_eeprom: kfree(priv->nvm_data); +out_free_eeprom_blob: + kfree(priv->eeprom_blob); out_leave_trans: iwl_trans_op_mode_leave(priv->trans); out_free_hw: diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/tx.c b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c index a7806776a51e..1a0167f67c7b 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c @@ -604,8 +604,10 @@ int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, } ret = iwl_sta_tx_modify_enable_tid(priv, sta_id, tid); - if (ret) + if (ret) { + iwlagn_dealloc_agg_txq(priv, txq_id); return ret; + } spin_lock_bh(&priv->sta_lock); tid_data = &priv->tid_data[sta_id][tid]; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index 430a7642e01a..71e0950d0193 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2019-2025 Intel Corporation + * Copyright (C) 2019-2026 Intel Corporation */ #include #include "iwl-drv.h" @@ -181,6 +181,7 @@ static int iwl_acpi_load_dsm_values(struct iwl_fw_runtime *fwrt) fwrt->dsm_revision = ACPI_DSM_REV; fwrt->dsm_source = BIOS_SOURCE_ACPI; + fwrt->dsm_values[DSM_FUNC_QUERY] = (u32)query_func_val; IWL_DEBUG_RADIO(fwrt, "ACPI DSM validity bitmap 0x%x\n", (u32)query_func_val); @@ -246,7 +247,7 @@ int iwl_acpi_get_dsm(struct iwl_fw_runtime *fwrt, BUILD_BUG_ON(ARRAY_SIZE(fwrt->dsm_values) != DSM_FUNC_NUM_FUNCS); BUILD_BUG_ON(BITS_PER_TYPE(fwrt->dsm_funcs_valid) < DSM_FUNC_NUM_FUNCS); - if (WARN_ON(func >= ARRAY_SIZE(fwrt->dsm_values) || !func)) + if (WARN_ON(func >= ARRAY_SIZE(fwrt->dsm_values))) return -EINVAL; if (!(fwrt->dsm_funcs_valid & BIT(func))) { @@ -1193,6 +1194,8 @@ int iwl_acpi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) *value = wifi_pkg->package.elements[1].integer.value & IWL_ACPI_WBEM_REV0_MASK; + fwrt->wbem_source = BIOS_SOURCE_ACPI; + fwrt->wbem_revision = tbl_rev; IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from ACPI\n"); ret = 0; out_free: diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h index 51a57e57de7a..45eb35ffb637 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2023, 2025 Intel Corporation + * Copyright (C) 2018-2023, 2025-2026 Intel Corporation */ #ifndef __iwl_fw_acpi__ #define __iwl_fw_acpi__ @@ -111,9 +111,6 @@ #define ACPI_PPAG_WIFI_DATA_SIZE_V3 ((ACPI_PPAG_NUM_CHAINS * \ ACPI_PPAG_NUM_BANDS_V3) + 2) -#define IWL_SAR_ENABLE_MSK BIT(0) -#define IWL_REDUCE_POWER_FLAGS_POS 1 - /* The Inidcator whether UEFI WIFI GUID tables are locked is read from ACPI */ #define UEFI_WIFI_GUID_UNLOCKED 0 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index 0172c0747a47..47fe1dc9d835 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -46,6 +46,11 @@ enum iwl_regulatory_and_nvm_subcmd_ids { */ MCC_ALLOWED_AP_TYPE_CMD = 0x5, + /** + * @LARI_CONFIG_EXTENSION: &struct iwl_lari_config_extension_cmd + */ + LARI_CONFIG_EXTENSION = 0x8, + /** * @PNVM_INIT_COMPLETE_NTFY: &struct iwl_pnvm_init_complete_ntfy */ @@ -433,7 +438,10 @@ struct iwl_mcc_chub_notif { __le16 mcc; u8 source_id; u8 reserved1; -} __packed; /* LAR_MCC_NOTIFY_S */ +} __packed; +/* LAR_MCC_NOTIFY_S_VER_1 + * LAR_MCC_NOTIFY_S_VER_2 + */ enum iwl_mcc_update_status { MCC_RESP_NEW_CHAN_PROFILE, @@ -541,6 +549,27 @@ struct iwl_bios_config_hdr { u8 reserved[2]; } __packed; /* BIOS_CONFIG_HDR_API_S_VER_1 */ +/** + * struct iwl_lari_config_extension_cmd - extend LARI configuration + * + * LARI_CONFIG_CHANGE's version must remain stable for frozen firmware. + * Because the driver might not know this version but still load that + * frozen FW and then send some default old version of LARI, causing the FW to + * assert about the bad size of it. To handle this, we do the following: + * 1. For newer firmware: increase the LARI_CONFIG_CHANGE version to support the + * new firmware API with extra UHB bits. + * 2. For frozen firmware: add a special alternative API that doesn't require + * modifying the frozen LARI_CONFIG_CHANGE's version. + * @dsm_table_hdr: BIOS DSM table source and revision + * @oem_uhb_allow_extension_bitmap: extension bitmap for OEM UHB config + * @reserved: reserved + */ +struct iwl_lari_config_extension_cmd { + struct iwl_bios_config_hdr dsm_table_hdr; + __le32 oem_uhb_allow_extension_bitmap; + __le32 reserved[10]; +} __packed; /* LARI_CONFIG_EXTENSION_CMD_API_S_VER_1 */ + /** * struct bios_value_u32 - BIOS configuration. * @hdr: bios config header @@ -689,6 +718,14 @@ struct iwl_lari_config_change_cmd_v8 { * get the data from the BIOS. * @oem_unii9_enable: UNII-9 enablement as read from the BIOS * @bios_hdr: bios config header + * @oem_uhb_allow_extension_bitmap: DSM Function 4 data as an extension of UHB + * enabled MCC sets + * @bios_wcpe_hdr: puncturing config header + * @wcpe_bitmap: bitmap of puncturing enablement per MCC + * @bios_wbem_hdr: 320 MHz per-MCC WBEM config header + * @reserved: reserved + * @oem_supported_dsm_bitmap: DSM function 0 bitmap describing supported + * DSM function indices */ struct iwl_lari_config_change_cmd { __le32 config_bitmap; @@ -706,9 +743,19 @@ struct iwl_lari_config_change_cmd { __le32 oem_unii9_enable; /* since version 13 */ struct iwl_bios_config_hdr bios_hdr; + /* All the below are since version 14 */ + __le32 oem_uhb_allow_extension_bitmap; + struct iwl_bios_config_hdr bios_wcpe_hdr; + __le32 wcpe_bitmap; + struct iwl_bios_config_hdr bios_wbem_hdr; + __le32 reserved[10]; + /* since version 15 */ + __le32 oem_supported_dsm_bitmap; } __packed; /* LARI_CHANGE_CONF_CMD_S_VER_12 * LARI_CHANGE_CONF_CMD_S_VER_13 + * LARI_CHANGE_CONF_CMD_S_VER_14 + * LARI_CHANGE_CONF_CMD_S_VER_15 */ /* Activate UNII-1 (5.2GHz) for World Wide */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dump.c b/drivers/net/wireless/intel/iwlwifi/fw/dump.c index c2af66899a78..bbbf3669a555 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dump.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dump.c @@ -369,7 +369,7 @@ static void iwl_fwrt_dump_fseq_regs(struct iwl_fw_runtime *fwrt) void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt) { struct iwl_pc_data *pc_data; - u8 count; + u32 count; if (!iwl_trans_device_enabled(fwrt->trans)) { IWL_ERR(fwrt, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h index 07f1240df866..692bb17dfc33 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h @@ -352,7 +352,7 @@ struct iwl_fw_ini_error_dump_register { * struct iwl_fw_ini_dump_cfg_name - configuration name * @image_type: image type the configuration is related to * @cfg_name_len: length of the configuration name - * @cfg_name: name of the configuraiton + * @cfg_name: name of the configuration */ struct iwl_fw_ini_dump_cfg_name { __le32 image_type; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index a26ed82a8106..664ac25754e9 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -448,8 +448,9 @@ typedef unsigned int __bitwise iwl_ucode_tlv_capa_t; * @IWL_UCODE_TLV_CAPA_EXT_FSEQ_IMAGE_SUPPORT: external FSEQ image support * @IWL_UCODE_TLV_CAPA_RESET_DURING_ASSERT: FW reset handshake is needed * during assert handling even if the dump isn't split - * @IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE: Firmware has capability of - * handling raw DSM table data. + * @IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE: Firmware can handle raw DSM + * table data. For LARI_CONFIG_CHANGE command version 13 and above, this + * capability is obsolete since raw DSM values are accepted by default. * @IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT: Supports NAN synchronization * * @NUM_IWL_UCODE_TLV_CAPA: number of bits used diff --git a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c index ec0ff58ab312..545a213df71d 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c @@ -205,6 +205,12 @@ static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data, IWL_DEBUG_FW(trans, "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", tlv_len); + if (tlv_len < sizeof(*tlv_sku_id)) { + IWL_ERR(trans, "invalid PNVM SKU TLV len: %u\n", + tlv_len); + return -EINVAL; + } + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", le32_to_cpu(tlv_sku_id->data[0]), le32_to_cpu(tlv_sku_id->data[1]), diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h index 6fffc032efd3..22c97c75b83c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2023-2025 Intel Corporation + * Copyright (C) 2023-2026 Intel Corporation */ #ifndef __fw_regulatory_h__ @@ -30,6 +30,7 @@ #define BIOS_GEO_MIN_PROFILE_NUM 3 #define IWL_SAR_ENABLE_MSK BIT(0) +#define IWL_REDUCE_POWER_FLAGS_POS 1 /* PPAG gain value bounds in 1/8 dBm */ #define IWL_PPAG_MIN_LB -16 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h index d80ae610e56c..ac01ac0092ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #ifndef __iwl_fw_runtime_h__ #define __iwl_fw_runtime_h__ @@ -144,6 +144,11 @@ struct iwl_txf_iter_data { * @tpc_enabled: TPC enabled * @dsm_source: one of &enum bios_source. UEFI, ACPI or NONE * @dsm_revision: the revision of the DSM table + * @wbem_source: one of &enum bios_source for the WBEM table + * @wbem_revision: the revision of the WBEM table + * @puncturing_source: one of &enum bios_source for the puncturing table + * @puncturing_revision: the revision of the puncturing table + * @bios_puncturing: per-country puncturing enablement bitmap from BIOS */ struct iwl_fw_runtime { struct iwl_trans *trans; @@ -226,6 +231,12 @@ struct iwl_fw_runtime { u32 dsm_funcs_valid; u32 dsm_values[DSM_FUNC_NUM_FUNCS]; #endif + + enum bios_source wbem_source; + u8 wbem_revision; + enum bios_source puncturing_source; + u8 puncturing_revision; + u32 bios_puncturing; }; void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 014f22c42f41..bf6a31e6f149 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright(c) 2021-2025 Intel Corporation + * Copyright(c) 2021-2026 Intel Corporation */ #include "iwl-drv.h" @@ -248,6 +248,12 @@ int iwl_uefi_reduce_power_parse(struct iwl_trans *trans, IWL_DEBUG_FW(trans, "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", tlv_len); + if (tlv_len < sizeof(*tlv_sku_id)) { + IWL_ERR(trans, "invalid PNVM SKU TLV len: %u\n", + tlv_len); + return -EINVAL; + } + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", le32_to_cpu(tlv_sku_id->data[0]), le32_to_cpu(tlv_sku_id->data[1]), @@ -884,6 +890,8 @@ int iwl_uefi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) goto out; } *value = data->wbem_320mhz_per_mcc & IWL_UEFI_WBEM_REV0_MASK; + fwrt->wbem_source = BIOS_SOURCE_UEFI; + fwrt->wbem_revision = data->revision; IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from UEFI\n"); out: kfree(data); @@ -919,7 +927,7 @@ static int iwl_uefi_load_dsm_values(struct iwl_fw_runtime *fwrt) */ fwrt->dsm_funcs_valid |= BIT(DSM_FUNC_QUERY); - for (int func = 1; func < ARRAY_SIZE(fwrt->dsm_values); func++) { + for (int func = 0; func < ARRAY_SIZE(fwrt->dsm_values); func++) { if (!(fwrt->dsm_funcs_valid & BIT(func))) { IWL_DEBUG_RADIO(fwrt, "DSM func %d not in 0x%x\n", func, fwrt->dsm_funcs_valid); @@ -970,29 +978,29 @@ int iwl_uefi_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func, int iwl_uefi_get_puncturing(struct iwl_fw_runtime *fwrt) { struct uefi_cnv_var_puncturing_data *data; - /* default value is not enabled if there is any issue in reading - * uefi variable or revision is not supported - */ - int puncturing = 0; + int ret = 0; data = iwl_uefi_get_verified_variable(fwrt->trans, IWL_UEFI_PUNCTURING_NAME, "UefiCnvWlanPuncturing", sizeof(*data), NULL); if (IS_ERR(data)) - return puncturing; + return -EINVAL; if (data->revision != IWL_UEFI_PUNCTURING_REVISION) { IWL_DEBUG_RADIO(fwrt, "Unsupported UEFI PUNCTURING rev:%d\n", data->revision); + ret = -EINVAL; } else { - puncturing = data->puncturing & IWL_UEFI_PUNCTURING_REV0_MASK; + fwrt->puncturing_source = BIOS_SOURCE_UEFI; + fwrt->puncturing_revision = data->revision; + fwrt->bios_puncturing = data->puncturing; IWL_DEBUG_RADIO(fwrt, "Loaded puncturing bits from UEFI: %d\n", - puncturing); + fwrt->bios_puncturing); } kfree(data); - return puncturing; + return ret; } IWL_EXPORT_SYMBOL(iwl_uefi_get_puncturing); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h index 474f06db4d43..386ca3744408 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright(c) 2021-2025 Intel Corporation + * Copyright(c) 2021-2026 Intel Corporation */ #ifndef __iwl_fw_uefi__ #define __iwl_fw_uefi__ @@ -275,8 +275,6 @@ enum iwl_uefi_cnv_puncturing_flags { IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK = BIT(1), }; -#define IWL_UEFI_PUNCTURING_REV0_MASK (IWL_UEFI_CNV_PUNCTURING_USA_EN_MSK | \ - IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK) /** * struct uefi_cnv_var_puncturing_data - controlling channel * puncturing for few countries. diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c index b1a55909f0d4..11763dec77ec 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include "iwl-drv.h" @@ -611,6 +611,9 @@ static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH)) return 0; + if (!fw_mon_cfg->req_size) + return -EIO; + num_frags = le32_to_cpu(fw_mon_cfg->max_frags_num); if (fwrt->trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { if (alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) @@ -621,6 +624,9 @@ static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, return -EIO; } + if (!num_frags) + return -EIO; + remain_pages = DIV_ROUND_UP(le32_to_cpu(fw_mon_cfg->req_size), PAGE_SIZE); num_frags = min_t(u32, num_frags, BUF_ALLOC_MAX_NUM_FRAGS); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index bd5353fdde81..863d5e358152 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -225,6 +225,18 @@ enum iwl_reg_capa_flags_v5 { REG_CAPA_V5_11BN_DISABLED = BIT(17), }; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4, 5 */ +/** + * enum iwl_reg_capa_flags_v6 - global capability flags, + * applicable from MCC response version 10 onwards. + * Response v6 includes all members of iwl_reg_capa_flags_v5; only v6-specific + * additions are listed here. + * @REG_CAPA_V6_EHT_PUNCTURING_ENABLED: EHT puncturing is enabled for this + * regulatory domain. + */ +enum iwl_reg_capa_flags_v6 { + REG_CAPA_V6_EHT_PUNCTURING_ENABLED = BIT(18), +}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_6 */ + /* * API v2 for reg_capa_flags is relevant from version 6 and onwards of the * MCC update command response. @@ -236,6 +248,11 @@ enum iwl_reg_capa_flags_v5 { */ #define REG_CAPA_V4_RESP_VER 8 +/* API v6 for reg_capa_flags is relevant from version 10 and onwards of the + * MCC update command response. + */ +#define REG_CAPA_V6_RESP_VER 10 + static inline void iwl_nvm_print_channel_flags(struct device *dev, u32 level, int chan, u32 flags) { @@ -697,7 +714,8 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP | IEEE80211_UHR_MAC_CAP0_DPS_SUPP, - [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP, + [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP | + IEEE80211_UHR_MAC_CAP1_DBE_SUPP, }, }, }, @@ -1687,6 +1705,13 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver) { struct iwl_reg_capa reg_capa = {}; + if (resp_ver >= REG_CAPA_V6_RESP_VER) { + if (flags & REG_CAPA_V6_EHT_PUNCTURING_ENABLED) + reg_capa.puncturing_status = IWL_PUNCTURING_STATUS_ENABLED; + else + reg_capa.puncturing_status = IWL_PUNCTURING_STATUS_DISABLED; + } + if (resp_ver >= REG_CAPA_V4_RESP_VER) { reg_capa.allow_40mhz = true; reg_capa.allow_80mhz = flags & REG_CAPA_V5_80MHZ_ALLOWED; @@ -1713,7 +1738,8 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver) struct ieee80211_regdomain * iwl_parse_nvm_mcc_info(struct iwl_trans *trans, int num_of_ch, __le32 *channels, u16 fw_mcc, - u16 geo_info, u32 cap, u8 resp_ver) + u16 geo_info, u32 cap, u8 resp_ver, + enum iwl_puncturing_status *puncturing_status) { const struct iwl_rf_cfg *cfg = trans->cfg; struct device *dev = trans->dev; @@ -1776,6 +1802,9 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans, /* parse regulatory capability flags */ reg_capa = iwl_get_reg_capa(cap, resp_ver); + if (puncturing_status) + *puncturing_status = reg_capa.puncturing_status; + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { enum nl80211_band band = iwl_nl80211_band_from_channel_idx(ch_idx); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h index e676d7c2d6cc..9ebb72d3726a 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2015, 2018-2025 Intel Corporation + * Copyright (C) 2005-2015, 2018-2026 Intel Corporation * Copyright (C) 2016-2017 Intel Deutschland GmbH */ #ifndef __iwl_nvm_parse_h__ @@ -21,6 +21,19 @@ enum iwl_nvm_sbands_flags { IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ = BIT(1), }; +/** + * enum iwl_puncturing_status - EHT puncturing status from MCC capabilities + * @IWL_PUNCTURING_STATUS_UNKNOWN: puncturing status is not provided by FW + * or is not initialized yet + * @IWL_PUNCTURING_STATUS_ENABLED: puncturing is enabled for the current MCC + * @IWL_PUNCTURING_STATUS_DISABLED: puncturing is disabled for the current MCC + */ +enum iwl_puncturing_status { + IWL_PUNCTURING_STATUS_UNKNOWN, + IWL_PUNCTURING_STATUS_ENABLED, + IWL_PUNCTURING_STATUS_DISABLED, +}; + /** * struct iwl_reg_capa - struct for global regulatory capabilities, Used for * handling the different APIs of reg_capa_flags. @@ -36,6 +49,8 @@ enum iwl_nvm_sbands_flags { * @disable_11ax: 11ax is forbidden for this regulatory domain. * @disable_11be: 11be is forbidden for this regulatory domain. * @disable_11bn: UHR/11bn is not allowed for this regulatory domain + * @puncturing_status: EHT puncturing status for the current MCC. + * See &enum iwl_puncturing_status. */ struct iwl_reg_capa { bool allow_40mhz; @@ -45,6 +60,7 @@ struct iwl_reg_capa { bool disable_11ax; bool disable_11be; bool disable_11bn; + enum iwl_puncturing_status puncturing_status; }; /** @@ -131,11 +147,13 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, * @geo_info: geo info value * @cap: capability * @resp_ver: FW response version + * @puncturing_status: FW puncturing status for current MCC, when available */ struct ieee80211_regdomain * iwl_parse_nvm_mcc_info(struct iwl_trans *trans, int num_of_ch, __le32 *channels, u16 fw_mcc, - u16 geo_info, u32 cap, u8 resp_ver); + u16 geo_info, u32 cap, u8 resp_ver, + enum iwl_puncturing_status *puncturing_status); /** * struct iwl_nvm_section - describes an NVM section in memory. diff --git a/drivers/net/wireless/intel/iwlwifi/mei/main.c b/drivers/net/wireless/intel/iwlwifi/mei/main.c index c5ff1b1b720f..ce92e26c42d7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mei/main.c +++ b/drivers/net/wireless/intel/iwlwifi/mei/main.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021-2024 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #include @@ -457,7 +458,7 @@ static int iwl_mei_send_sap_msg_payload(struct mei_cl_device *cldev, notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF]; q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; - ret = iwl_mei_write_cyclic_buf(q_head, notif_q, q_head, hdr, q_sz); + ret = iwl_mei_write_cyclic_buf(cldev, notif_q, q_head, hdr, q_sz); if (ret < 0) return ret; @@ -1040,11 +1041,14 @@ static void iwl_mei_read_from_q(const u8 *q_head, u32 q_sz, u32 rd = *_rd; if (rd + len <= q_sz) { - memcpy(buf, q_head + rd, len); + if (buf) + memcpy(buf, q_head + rd, len); rd += len; } else { - memcpy(buf, q_head + rd, q_sz - rd); - memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd)); + if (buf) { + memcpy(buf, q_head + rd, q_sz - rd); + memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd)); + } rd = len - (q_sz - rd); } @@ -1085,24 +1089,29 @@ static void iwl_mei_handle_sap_data(struct mei_cl_device *cldev, break; } + valid_rx_sz -= len; + if (len < sizeof(*ethhdr)) { dev_err(&cldev->dev, "Data len is smaller than an ethernet header? len = %d\n", len); + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); + continue; } - valid_rx_sz -= len; - if (le16_to_cpu(hdr.type) != SAP_MSG_DATA_PACKET) { dev_err(&cldev->dev, "Unsupported Rx data: type %d, len %d\n", le16_to_cpu(hdr.type), len); + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); continue; } /* We need enough room for the WiFi header + SNAP + IV */ skb = netdev_alloc_skb(netdev, len + QOS_HDR_IV_SNAP_LEN); - if (!skb) + if (!skb) { + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); continue; + } skb_reserve(skb, QOS_HDR_IV_SNAP_LEN); ethhdr = skb_push(skb, sizeof(*ethhdr)); @@ -1147,6 +1156,11 @@ static void iwl_mei_handle_sap_rx_cmd(struct mei_cl_device *cldev, iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr, sizeof(*hdr)); valid_rx_sz -= sizeof(*hdr); len = le16_to_cpu(hdr->len); + if (len + sizeof(*hdr) > PAGE_SIZE) { + dev_err(&cldev->dev, + "SAP message is too big: %u\n", len); + break; + } if (valid_rx_sz < len) break; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index a464ebdec57f..96102d2af2cf 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -200,11 +200,11 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_mld_baid_data *baid_data; struct iwl_mld_reorder_buffer *buffer; struct iwl_mld_reorder_buf_entry *entries; - struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); struct iwl_mld_link_sta *mld_link_sta; u32 reorder = le32_to_cpu(desc->reorder_data); bool amsdu, last_subframe, is_old_sn, is_dup; u8 tid = ieee80211_get_tid(hdr); + struct iwl_mld_sta *mld_sta; u8 baid; u16 nssn, sn; u32 sta_mask = 0; @@ -228,10 +228,13 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, return IWL_MLD_PASS_SKB; /* no sta yet */ - if (WARN_ONCE(!sta, - "Got valid BAID without a valid station assigned\n")) + if (IWL_FW_CHECK(mld, !sta, + "Got valid BAID without a valid station assigned - %d\n", + baid)) return IWL_MLD_PASS_SKB; + mld_sta = iwl_mld_sta_from_mac80211(sta); + /* not a data packet */ if (!ieee80211_is_data_qos(hdr->frame_control) || is_multicast_ether_addr(hdr->addr1)) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 458a668ba916..55f9b809e764 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -59,6 +59,30 @@ struct iwl_mld_suspend_key_iter_data { __le32 bigtk_cipher; }; +struct iwl_mld_wake_pkt_iter_data { + bool multicast; + u32 ivlen; + u32 icvlen; +}; + +static void +iwl_mld_wake_pkt_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, void *_data) +{ + struct iwl_mld_wake_pkt_iter_data *data = _data; + bool is_group_key = !sta; + + /* ignore anything that is not a PTK / GTK */ + if (key->keyidx > 3) + return; + if (is_group_key != data->multicast) + return; + + data->ivlen = key->iv_len; + data->icvlen = key->icv_len; +} + struct iwl_mld_mcast_key_data { u8 key[WOWLAN_KEY_MAX_SIZE]; u8 len; @@ -573,18 +597,42 @@ iwl_mld_convert_wowlan_notif_v5(const struct iwl_wowlan_info_notif_v5 *notif_v5, } } -static bool iwl_mld_validate_wowlan_notif_size(struct iwl_mld *mld, - u32 len, - u32 expected_len, - u8 num_mlo_keys, +static bool iwl_mld_validate_wowlan_notif_size(struct iwl_mld *mld, u32 len, + const void *notif_data, int version) { u32 len_with_mlo_keys; + u32 expected_len; + u8 num_mlo_keys; - if (IWL_FW_CHECK(mld, len < expected_len, - "Invalid wowlan_info_notif v%d (expected=%u got=%u)\n", - version, expected_len, len)) + /* Extract num_mlo_keys from the void pointer based on version */ + if (version == 5) { + const struct iwl_wowlan_info_notif_v5 *notif_v5 = notif_data; + + expected_len = sizeof(*notif_v5); + + if (IWL_FW_CHECK(mld, len < expected_len, + "Invalid wowlan_info_notif v5 (expected=%u got=%u)\n", + expected_len, len)) + return false; + + num_mlo_keys = notif_v5->num_mlo_link_keys; + } else if (version == 6) { + const struct iwl_wowlan_info_notif *notif = notif_data; + + expected_len = sizeof(*notif); + + if (IWL_FW_CHECK(mld, len < expected_len, + "Invalid wowlan_info_notif v6 (expected=%u got=%u)\n", + expected_len, len)) + return false; + + num_mlo_keys = notif->num_mlo_link_keys; + } else { + IWL_WARN(mld, "Unsupported wowlan_info_notif version %d\n", + version); return false; + } len_with_mlo_keys = expected_len + (num_mlo_keys * sizeof(struct iwl_wowlan_mlo_gtk)); @@ -616,16 +664,14 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, if (wowlan_info_ver == 5) { /* v5 format - validate before conversion */ - const struct iwl_wowlan_info_notif_v5 *notif_v5 = (void *)pkt->data; + const struct iwl_wowlan_info_notif_v5 *_notif = + (void *)pkt->data; - if (!iwl_mld_validate_wowlan_notif_size(mld, len, - sizeof(*notif_v5), - notif_v5->num_mlo_link_keys, - 5)) + if (!iwl_mld_validate_wowlan_notif_size(mld, len, _notif, 5)) return true; converted_notif = kzalloc_flex(*converted_notif, mlo_gtks, - notif_v5->num_mlo_link_keys, + _notif->num_mlo_link_keys, GFP_ATOMIC); if (!converted_notif) { IWL_ERR(mld, @@ -633,15 +679,12 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, return true; } - iwl_mld_convert_wowlan_notif_v5(notif_v5, - converted_notif); + iwl_mld_convert_wowlan_notif_v5(_notif, converted_notif); notif = converted_notif; } else if (wowlan_info_ver == 6) { notif = (void *)pkt->data; - if (!iwl_mld_validate_wowlan_notif_size(mld, len, - sizeof(*notif), - notif->num_mlo_link_keys, - 6)) + + if (!iwl_mld_validate_wowlan_notif_size(mld, len, notif, 6)) return true; } else { /* smaller versions are not supported */ @@ -686,7 +729,7 @@ iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld, { const struct iwl_wowlan_wake_pkt_notif *notif = (void *)pkt->data; u32 actual_size, len = iwl_rx_packet_payload_len(pkt); - u32 expected_size = le32_to_cpu(notif->wake_packet_length); + u32 expected_size; if (IWL_FW_CHECK(mld, len < sizeof(*notif), "Invalid WoWLAN wake packet notification (expected size=%zu got=%u)\n", @@ -699,6 +742,7 @@ iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld, wowlan_status->wakeup_reasons)) return true; + expected_size = le32_to_cpu(notif->wake_packet_length); actual_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif, wake_packet); @@ -723,11 +767,17 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, struct cfg80211_wowlan_wakeup *wakeup, struct sk_buff **_pkt) { - int pkt_bufsize = wowlan_status->wake_packet_bufsize; - int expected_pktlen = wowlan_status->wake_packet_length; + u32 pkt_bufsize = wowlan_status->wake_packet_bufsize; + u32 expected_pktlen = wowlan_status->wake_packet_length; const u8 *pktdata = wowlan_status->wake_packet; const struct ieee80211_hdr *hdr = (const void *)pktdata; - int truncated = expected_pktlen - pkt_bufsize; + u32 truncated; + + if (IWL_FW_CHECK(mld, pkt_bufsize < sizeof(*hdr), + "pkt_bufsize is too short: %u\n", pkt_bufsize)) + return; + + truncated = expected_pktlen - pkt_bufsize; if (ieee80211_is_data(hdr->frame_control)) { int hdrlen = ieee80211_hdrlen(hdr->frame_control); @@ -738,9 +788,18 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, if (!pkt) return; - skb_put_data(pkt, pktdata, hdrlen); - pktdata += hdrlen; - pkt_bufsize -= hdrlen; + if (ieee80211_has_protected(hdr->frame_control)) { + struct iwl_mld_wake_pkt_iter_data iter_data = { + .multicast = + is_multicast_ether_addr(hdr->addr1), + }; + + ieee80211_iter_keys(mld->hw, vif, + iwl_mld_wake_pkt_key_iter, + &iter_data); + ivlen = iter_data.ivlen; + icvlen += iter_data.icvlen; + } /* if truncated, FCS/ICV is (partially) gone */ if (truncated >= icvlen) { @@ -751,6 +810,13 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, truncated = 0; } + if (IWL_FW_CHECK(mld, pkt_bufsize <= hdrlen + ivlen + icvlen, + "pkt_bufsize is too small %u\n", pkt_bufsize)) + return; + + skb_put_data(pkt, pktdata, hdrlen); + pktdata += hdrlen; + pkt_bufsize -= hdrlen; pkt_bufsize -= ivlen + icvlen; pktdata += ivlen; @@ -772,6 +838,11 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, fcslen -= truncated; truncated = 0; } + + if (IWL_FW_CHECK(mld, pkt_bufsize <= fcslen, + "pkt_bufsize is too small %u\n", pkt_bufsize)) + return; + pkt_bufsize -= fcslen; wakeup->packet = wowlan_status->wake_packet; wakeup->packet_present_len = pkt_bufsize; @@ -1449,8 +1520,16 @@ static bool iwl_mld_handle_d3_notif(struct iwl_notif_wait_data *notif_wait, } case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): { struct iwl_d3_end_notif *notif = (void *)pkt->data; + u32 len = iwl_rx_packet_payload_len(pkt); + + if (IWL_FW_CHECK(mld, len < sizeof(*notif), + "Invalid D3_END notification (expected=%zu got=%u)\n", + sizeof(*notif), len)) { + resume_data->notif_handling_err = true; + } else { + resume_data->d3_end_flags = le32_to_cpu(notif->flags); + } - resume_data->d3_end_flags = le32_to_cpu(notif->flags); resume_data->notifs_received |= IWL_D3_NOTIF_D3_END_NOTIF; break; } @@ -2140,6 +2219,9 @@ int iwl_mld_wowlan_resume(struct iwl_mld *mld) ret = iwl_mld_wait_d3_notif(mld, &resume_data, true); if (ret) { IWL_ERR(mld, "Couldn't get the d3 notifs %d\n", ret); + + if (bss_vif->cfg.assoc) + ieee80211_resume_disconnect(bss_vif); goto err; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c index 81df3fdfcbf5..a4b8037b5c76 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c @@ -459,3 +459,24 @@ void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld) mld->ftm_initiator.req, GFP_KERNEL); iwl_mld_ftm_reset(mld); } + +void iwl_mld_ftm_abort(struct iwl_mld *mld, struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_abort_cmd cmd = { + .request_id = req->cookie, + }; + + lockdep_assert_wiphy(mld->wiphy); + + if (req != mld->ftm_initiator.req) + return; + + if (iwl_mld_send_cmd_pdu(mld, WIDE_ID(LOCATION_GROUP, + TOF_RANGE_ABORT_CMD), + &cmd)) + IWL_ERR(mld, "failed to abort FTM process\n"); + + iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_FTM_REQ, + mld->ftm_initiator.req->cookie); + iwl_mld_ftm_reset(mld); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h index 3fab25a52508..e82237b31467 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h @@ -25,5 +25,6 @@ int iwl_mld_ftm_start(struct iwl_mld *mld, struct ieee80211_vif *vif, void iwl_mld_handle_ftm_resp_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld); +void iwl_mld_ftm_abort(struct iwl_mld *mld, struct cfg80211_pmsr_request *req); #endif /* __iwl_mld_ftm_initiator_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h b/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h index 64a8d4248324..19e1c9e509cf 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifndef __iwl_mld_hcmd_h__ #define __iwl_mld_hcmd_h__ @@ -42,7 +42,12 @@ __iwl_mld_send_cmd_with_flags_pdu(struct iwl_mld *mld, u32 id, #define _iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len, \ ignored...) \ - __iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len) + ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + __iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, \ + data, len); \ + }) #define iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len...) \ _iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, ##len, \ sizeof(*(data))) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 9ac17be30400..3a4c8fda68d0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -543,8 +543,7 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, iwl_mld_tx_skb(mld, skb, NULL); } -static void -iwl_mld_restart_cleanup(struct iwl_mld *mld) +void iwl_mld_restart_cleanup(struct iwl_mld *mld) { iwl_cleanup_mld(mld); @@ -1281,11 +1280,13 @@ void iwl_mld_unassign_vif_chanctx(struct ieee80211_hw *hw, iwl_mld_tlc_update_phy(mld, vif, link); /* in the non-MLO case, remove/re-add the link to clean up FW state. - * In MLO, it'll be done in drv_change_vif_link + * In MLO, it'll be done in drv_change_vif_link. + * Do not do so during restart or in case the device is dead. */ if (!ieee80211_vif_is_mld(vif) && !mld_vif->ap_sta && !WARN_ON_ONCE(vif->cfg.assoc) && - vif->type != NL80211_IFTYPE_AP && !mld->fw_status.in_hw_restart) { + vif->type != NL80211_IFTYPE_AP && !mld->fw_status.in_hw_restart && + !iwl_trans_is_dead(mld->trans)) { iwl_mld_remove_link(mld, link); iwl_mld_add_link(mld, link); } @@ -2853,6 +2854,15 @@ static int iwl_mld_start_pmsr(struct ieee80211_hw *hw, return iwl_mld_ftm_start(mld, vif, request); } +static void iwl_mld_abort_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); + + iwl_mld_ftm_abort(mld, request); +} + static enum ieee80211_neg_ttlm_res iwl_mld_can_neg_ttlm(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_neg_ttlm *neg_ttlm) @@ -2974,6 +2984,7 @@ const struct ieee80211_ops iwl_mld_hw_ops = { .prep_add_interface = iwl_mld_prep_add_interface, .set_hw_timestamp = iwl_mld_set_hw_timestamp, .start_pmsr = iwl_mld_start_pmsr, + .abort_pmsr = iwl_mld_abort_pmsr, .can_neg_ttlm = iwl_mld_can_neg_ttlm, .start_nan = iwl_mld_start_nan, .stop_nan = iwl_mld_stop_nan, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h index aad04d7b2617..f3302997b28f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #ifndef __iwl_mld_mac80211_h__ #define __iwl_mld_mac80211_h__ @@ -9,5 +10,6 @@ int iwl_mld_register_hw(struct iwl_mld *mld); void iwl_mld_recalc_multicast_filter(struct iwl_mld *mld); +void iwl_mld_restart_cleanup(struct iwl_mld *mld); #endif /* __iwl_mld_mac80211_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 830c251f43af..b4657e82468c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c @@ -95,6 +95,8 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, struct iwl_mcc_update_resp_v8 *resp; u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP, MCC_UPDATE_CMD, 0); + enum iwl_puncturing_status puncturing_status; + u16 mcc; IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2); @@ -116,12 +118,13 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, } IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver); + mcc = le16_to_cpu(resp->mcc); regd = iwl_parse_nvm_mcc_info(mld->trans, __le32_to_cpu(resp->n_channels), - resp->channels, - __le16_to_cpu(resp->mcc), + resp->channels, mcc, __le16_to_cpu(resp->geo_info), - le32_to_cpu(resp->cap), resp_ver); + le32_to_cpu(resp->cap), resp_ver, + &puncturing_status); if (IS_ERR(regd)) { IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n", @@ -135,18 +138,25 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, mld->mcc_src = resp->source_id; + if (puncturing_status == IWL_PUNCTURING_STATUS_ENABLED) + __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, + mld->hw->flags); + else if (puncturing_status == IWL_PUNCTURING_STATUS_DISABLED) + ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); + + if (resp_ver >= 10) + goto out; + /* FM follows BIOS/MCC policy, WH disallows puncturing only in US/CA. */ if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) { - if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing, - le16_to_cpu(resp->mcc))) + if (!iwl_puncturing_is_allowed_in_bios(mld->fwrt.bios_puncturing, + mcc)) ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); else __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, mld->hw->flags); } else if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == - IWL_CFG_RF_TYPE_WH) { - u16 mcc = le16_to_cpu(resp->mcc); - + IWL_CFG_RF_TYPE_WH) { if (mcc == IWL_MCC_US || mcc == IWL_MCC_CANADA) ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); else diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 78c78cf891cd..6e991fb99ff3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -212,6 +212,7 @@ static const struct iwl_hcmd_names iwl_mld_reg_and_nvm_names[] = { HCMD_NAME(TAS_CONFIG), HCMD_NAME(SAR_OFFSET_MAPPING_TABLE_CMD), HCMD_NAME(MCC_ALLOWED_AP_TYPE_CMD), + HCMD_NAME(PNVM_INIT_COMPLETE_NTFY), }; /* Please keep this array *SORTED* by hex value. @@ -418,8 +419,6 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, iwl_mld_construct_fw_runtime(mld, trans, fw, dbgfs_dir); iwl_mld_get_bios_tables(mld); - iwl_uefi_get_sgom_table(trans, &mld->fwrt); - mld->bios_enable_puncturing = iwl_uefi_get_puncturing(&mld->fwrt); iwl_mld_hw_set_regulatory(mld); @@ -446,6 +445,8 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, } if (ret) { + /* wiphy memory is about to be freed, we should cancel any pending work */ + wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk); wiphy_unlock(mld->wiphy); rtnl_unlock(); goto err; @@ -511,6 +512,7 @@ iwl_op_mode_mld_stop(struct iwl_op_mode *op_mode) iwl_mld_thermal_exit(mld); wiphy_lock(mld->wiphy); + wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk); iwl_mld_low_latency_stop(mld); iwl_mld_deinit_time_sync(mld); wiphy_unlock(mld->wiphy); @@ -745,6 +747,7 @@ static void iwl_mld_device_powered_off(struct iwl_op_mode *op_mode) wiphy_lock(mld->wiphy); iwl_mld_stop_fw(mld); + iwl_mld_restart_cleanup(mld); mld->fw_status.in_d3 = false; wiphy_unlock(mld->wiphy); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index 922aa3dbff54..37f41b504b54 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h @@ -172,7 +172,6 @@ * @debug_max_sleep: maximum sleep time in D3 (for debug purposes) * @led: the led device * @mcc_src: the source id of the MCC, comes from the firmware - * @bios_enable_puncturing: is puncturing enabled by bios * @fw_id_to_ba: maps a fw (BA) id to a corresponding Block Ack session data. * @num_rx_ba_sessions: tracks the number of active Rx Block Ack (BA) sessions. * the driver ensures that new BA sessions are blocked once the maximum @@ -279,7 +278,6 @@ struct iwl_mld { struct led_classdev led; #endif enum iwl_mcc_source mcc_src; - bool bios_enable_puncturing; struct iwl_mld_baid_data __rcu *fw_id_to_ba[IWL_MAX_BAID]; u8 num_rx_ba_sessions; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 7574689e4088..90733bbaf733 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -298,7 +298,8 @@ CMD_VERSIONS(channel_survey_notif, CMD_VERSIONS(mfuart_notif, CMD_VER_ENTRY(2, iwl_mfuart_load_notif)) CMD_VERSIONS(update_mcc, - CMD_VER_ENTRY(1, iwl_mcc_chub_notif)) + CMD_VER_ENTRY(1, iwl_mcc_chub_notif) + CMD_VER_ENTRY(2, iwl_mcc_chub_notif)) CMD_VERSIONS(session_prot_notif, CMD_VER_ENTRY(3, iwl_session_prot_notif)) CMD_VERSIONS(missed_beacon_notif, @@ -517,7 +518,7 @@ iwl_mld_notif_is_valid(struct iwl_mld *mld, struct iwl_rx_packet *pkt, handler->cmd_id, notif_ver, handler->sizes[handler->n_sizes - 1].ver); - return size < handler->sizes[handler->n_sizes - 1].size; + return false; } struct iwl_async_handler_entry { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index 659243ada86c..533870cf443f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -13,6 +13,26 @@ #include "mld.h" #include "hcmd.h" +static ssize_t iwl_mld_get_lari_config_cmd_size(u8 cmd_ver) +{ + switch (cmd_ver) { + case 15: + return sizeof(struct iwl_lari_config_change_cmd); + case 14: + return offsetof(struct iwl_lari_config_change_cmd, + oem_supported_dsm_bitmap); + case 13: + return offsetof(struct iwl_lari_config_change_cmd, + oem_uhb_allow_extension_bitmap); + case 12: + return offsetof(struct iwl_lari_config_change_cmd, + oem_11bn_allow_bitmap); + default: + WARN(true, "unsupported version: %d", cmd_ver); + return -EINVAL; + } +} + void iwl_mld_get_bios_tables(struct iwl_mld *mld) { int ret; @@ -67,6 +87,9 @@ void iwl_mld_get_bios_tables(struct iwl_mld *mld) iwl_uefi_get_uneb_table(mld->trans, &mld->fwrt); iwl_bios_get_phy_filters(&mld->fwrt); + + iwl_uefi_get_sgom_table(mld->trans, &mld->fwrt); + iwl_uefi_get_puncturing(&mld->fwrt); } static int iwl_mld_geo_sar_init(struct iwl_mld *mld) @@ -325,11 +348,23 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) struct iwl_lari_config_change_cmd cmd = { .config_bitmap = iwl_mld_get_lari_config_bitmap(fwrt), }; - bool has_raw_dsm_capa = fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE); + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), 12); + /* + * For LARI_CONFIG_CHANGE command version 13 and above, firmware accepts + * raw DSM values by default and this TLV is no longer needed. + */ + bool has_raw_dsm_capa = cmd_ver >= 13 || + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE); + ssize_t cmd_len = iwl_mld_get_lari_config_cmd_size(cmd_ver); int ret; u32 value; + if (cmd_len < 0) + return; + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_11AX_ENABLEMENT, &value); if (!ret) { if (!has_raw_dsm_capa) @@ -371,8 +406,11 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) } ret = iwl_bios_get_wbem(fwrt, &value); - if (!ret) + if (!ret) { cmd.oem_320mhz_allow_bitmap = cpu_to_le32(value); + cmd.bios_wbem_hdr.table_source = fwrt->wbem_source; + cmd.bios_wbem_hdr.table_revision = fwrt->wbem_revision; + } ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENABLE_11BE, &value); if (!ret) @@ -386,8 +424,21 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) if (!ret) cmd.oem_unii9_enable = cpu_to_le32(value); + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_REGULATORY_CONFIG, &value); + if (!ret) + cmd.oem_uhb_allow_extension_bitmap = cpu_to_le32(value); + + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_QUERY, &value); + if (!ret) + cmd.oem_supported_dsm_bitmap = cpu_to_le32(value); + + cmd.bios_wcpe_hdr.table_source = fwrt->puncturing_source; + cmd.bios_wcpe_hdr.table_revision = fwrt->puncturing_revision; + cmd.wcpe_bitmap = cpu_to_le32(fwrt->bios_puncturing); + if (!cmd.config_bitmap && !cmd.oem_uhb_allow_bitmap && + !cmd.oem_uhb_allow_extension_bitmap && !cmd.oem_11ax_allow_bitmap && !cmd.oem_unii4_allow_bitmap && !cmd.chan_state_active_bitmap && @@ -396,7 +447,9 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) !cmd.oem_320mhz_allow_bitmap && !cmd.oem_11be_allow_bitmap && !cmd.oem_11bn_allow_bitmap && - !cmd.oem_unii9_enable) + !cmd.oem_unii9_enable && + !cmd.wcpe_bitmap && + !cmd.oem_supported_dsm_bitmap) return; cmd.bios_hdr.table_source = fwrt->dsm_source; @@ -414,6 +467,9 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) "sending LARI_CONFIG_CHANGE, oem_uhb_allow_bitmap=0x%x, force_disable_channels_bitmap=0x%x\n", le32_to_cpu(cmd.oem_uhb_allow_bitmap), le32_to_cpu(cmd.force_disable_channels_bitmap)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, oem_uhb_allow_extension_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_extension_bitmap)); IWL_DEBUG_RADIO(mld, "sending LARI_CONFIG_CHANGE, edt_bitmap=0x%x, oem_320mhz_allow_bitmap=0x%x\n", le32_to_cpu(cmd.edt_bitmap), @@ -427,22 +483,16 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) IWL_DEBUG_RADIO(mld, "sending LARI_CONFIG_CHANGE, oem_unii9_enable=0x%x\n", le32_to_cpu(cmd.oem_unii9_enable)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, wcpe_bitmap=0x%x\n", + le32_to_cpu(cmd.wcpe_bitmap)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, oem_supported_dsm_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_supported_dsm_bitmap)); - if (iwl_fw_lookup_cmd_ver(mld->fw, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), 12) == 12) { - int cmd_size = offsetof(typeof(cmd), oem_11bn_allow_bitmap); - - ret = iwl_mld_send_cmd_pdu(mld, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), - &cmd, cmd_size); - } else { - ret = iwl_mld_send_cmd_pdu(mld, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), - &cmd); - } + ret = iwl_mld_send_cmd_pdu(mld, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), &cmd, cmd_len); if (ret) IWL_DEBUG_RADIO(mld, "Failed to send LARI_CONFIG_CHANGE (%d)\n", @@ -488,6 +538,8 @@ void iwl_mld_init_ap_type_tables(struct iwl_mld *mld) if (ret) IWL_ERR(mld, "failed to send MCC_ALLOWED_AP_TYPE_CMD (%d)\n", ret); + else + IWL_DEBUG_RADIO(mld, "MCC_ALLOWED_AP_TYPE_CMD sent to FW\n"); } void iwl_mld_init_tas(struct iwl_mld *mld) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/scan.c b/drivers/net/wireless/intel/iwlwifi/mld/scan.c index d80a1cfc2ed5..0faca402bc14 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/scan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/scan.c @@ -1681,7 +1681,8 @@ iwl_mld_scan_send_abort_cmd_status(struct iwl_mld *mld, int uid, u32 *status) static int iwl_mld_scan_abort(struct iwl_mld *mld, int type, int uid, bool *wait) { - enum iwl_umac_scan_abort_status status; + enum iwl_umac_scan_abort_status status = + IWL_UMAC_SCAN_ABORT_STATUS_NOT_FOUND; int ret; *wait = true; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index e18d86f021dc..7957ac11b0cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -355,10 +355,9 @@ static void iwl_mld_fill_pkt_ext(struct iwl_mld *mld, for (int i = 0; i < MAX_HE_SUPP_NSS; i++) { for (int bw = 0; - bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]); + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); bw++) { - u8 *qam_th = - &pkt_ext->pkt_ext_qam_th[i][bw][0]; + u8 *qam_th = pkt_ext->pkt_ext_qam_th[i][bw]; IWL_DEBUG_HT(mld, "PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n", diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index fa523be91d8a..43a7230f1ab4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -1517,12 +1517,48 @@ static int iwl_mvm_fill_lari_config(struct iwl_fw_runtime *fwrt, return 0; } +static void iwl_mvm_send_lari_cfg_extension(struct iwl_mvm *mvm) +{ + struct iwl_fw_runtime *fwrt = &mvm->fwrt; + struct iwl_lari_config_extension_cmd cmd = {}; + u32 cmd_id = WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_EXTENSION); + u32 value; + int ret; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 0) < 1) + return; + + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_REGULATORY_CONFIG, &value); + if (ret) + return; + + cmd.dsm_table_hdr.table_source = fwrt->dsm_source; + cmd.dsm_table_hdr.table_revision = fwrt->dsm_revision; + cmd.oem_uhb_allow_extension_bitmap = cpu_to_le32(value); + + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_EXTENSION, oem_uhb_allow_extension_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_extension_bitmap)); + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Failed to send LARI_CONFIG_EXTENSION (%d)\n", + ret); +} + static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) { struct iwl_lari_config_change_cmd cmd; size_t cmd_size; int ret; + /* + * LARI_CONFIG_CHANGE triggers a profile update, so send + * LARI_CONFIG_EXTENSION first to make sure its data is applied + * in the same update. + */ + iwl_mvm_send_lari_cfg_extension(mvm); ret = iwl_mvm_fill_lari_config(&mvm->fwrt, &cmd, &cmd_size); if (!ret) { ret = iwl_mvm_send_cmd_pdu(mvm, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 3e73a6195fd9..5bd246e37943 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -152,7 +152,7 @@ struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, resp->channels, __le16_to_cpu(resp->mcc), __le16_to_cpu(resp->geo_info), - le32_to_cpu(resp->cap), resp_ver); + le32_to_cpu(resp->cap), resp_ver, NULL); /* Store the return source id */ src_id = resp->source_id; if (IS_ERR_OR_NULL(regd)) { @@ -1127,7 +1127,7 @@ static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, RCU_INIT_POINTER(mvmvif->deflink.probe_resp_data, NULL); } -static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) { iwl_mvm_stop_device(mvm); @@ -2350,10 +2350,9 @@ int iwl_mvm_set_sta_pkt_ext(struct iwl_mvm *mvm, int bw; for (bw = 0; - bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]); + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); bw++) { - u8 *qam_th = - &pkt_ext->pkt_ext_qam_th[i][bw][0]; + u8 *qam_th = pkt_ext->pkt_ext_qam_th[i][bw]; IWL_DEBUG_HT(mvm, "PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n", diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 31912f4d0175..c64e4ed00986 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1657,6 +1657,7 @@ void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, u8 iwl_mvm_rate_idx_to_fw_idx(const struct iwl_fw *fw, int rate_idx); u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac); bool iwl_mvm_is_nic_ack_enabled(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm); static inline void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) { @@ -1672,14 +1673,24 @@ u32 iwl_mvm_get_systime(struct iwl_mvm *mvm); /* Tx / Host Commands */ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd); -int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, - u32 flags, u16 len, const void *data); +int _iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data); +#define iwl_mvm_send_cmd_pdu(mvm, id, flags, len, data) ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + _iwl_mvm_send_cmd_pdu(mvm, id, flags, len, data); \ +}) int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, u32 *status); -int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, - u16 len, const void *data, - u32 *status); +int _iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, + u16 len, const void *data, + u32 *status); +#define iwl_mvm_send_cmd_pdu_status(mvm, id, len, data, status) ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + _iwl_mvm_send_cmd_pdu_status(mvm, id, len, data, status); \ +}) int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, struct ieee80211_sta *sta); int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); @@ -2392,14 +2403,12 @@ void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, bool sync, const void *data, u32 size); struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm); -struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid); bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm); #define MVM_TCM_PERIOD_MSEC 500 #define MVM_TCM_PERIOD (HZ * MVM_TCM_PERIOD_MSEC / 1000) #define MVM_LL_PERIOD (10 * HZ) void iwl_mvm_tcm_work(struct work_struct *work); -void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm); void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel); void iwl_mvm_resume_tcm(struct iwl_mvm *mvm); void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index 2297392db955..1dd27292af9b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -155,8 +155,8 @@ static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm, if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA)) return; - /* FIXME: should fetch the link and not the vif */ - vif = iwl_mvm_get_vif_by_macid(mvm, notif->link_id); + /* mac_id = link_id since we don't support MLO */ + vif = iwl_mvm_rcu_dereference_vif_id(mvm, notif->link_id, false); if (!vif || vif->type != NL80211_IFTYPE_STATION) return; @@ -707,6 +707,7 @@ static const struct iwl_hcmd_names iwl_mvm_regulatory_and_nvm_names[] = { HCMD_NAME(NVM_ACCESS_COMPLETE), HCMD_NAME(NVM_GET_INFO), HCMD_NAME(TAS_CONFIG), + HCMD_NAME(LARI_CONFIG_EXTENSION), }; /* Please keep this array *SORTED* by hex value. @@ -954,7 +955,7 @@ static void iwl_mvm_frob_txf_key_iter(struct ieee80211_hw *hw, } match++; if (match == keylen) { - memset(txf->buf + i - keylen, 0xAA, keylen); + memset(txf->buf + i + 1 - keylen, 0xAA, keylen); match = 0; } } @@ -1535,6 +1536,7 @@ void iwl_mvm_stop_device(struct iwl_mvm *mvm) iwl_fw_cancel_timestamp(&mvm->fwrt); clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + mvm->sf_state = SF_UNINIT; iwl_mvm_pause_tcm(mvm, false); @@ -2090,7 +2092,7 @@ static void iwl_op_mode_mvm_device_powered_off(struct iwl_op_mode *op_mode) mutex_lock(&mvm->mutex); clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); - iwl_mvm_stop_device(mvm); + iwl_mvm_restart_cleanup(mvm); mvm->fast_resume = false; mutex_unlock(&mvm->mutex); } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h b/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h index 2cfd0fb5e781..7bdbacb64078 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2022 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #ifndef __TIME_SYNC_H__ #define __TIME_SYNC_H__ @@ -19,6 +20,9 @@ int iwl_mvm_time_sync_config(struct iwl_mvm *mvm, const u8 *addr, static inline bool iwl_mvm_time_sync_frame(struct iwl_mvm *mvm, struct sk_buff *skb, u8 *addr) { + if (!mvm->time_sync.active) + return false; + if (ether_addr_equal(mvm->time_sync.peer_addr, addr) && (ieee80211_is_timing_measurement(skb) || ieee80211_is_ftm(skb))) { skb_queue_tail(&mvm->time_sync.frame_list, skb); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index 2e12f93ad32b..2fd4a9961145 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -73,8 +73,8 @@ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) return ret; } -int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, - u32 flags, u16 len, const void *data) +int _iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data) { struct iwl_host_cmd cmd = { .id = id, @@ -137,8 +137,8 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, /* * We assume that the caller set the status to the sucess value */ -int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, - const void *data, u32 *status) +int _iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, + const void *data, u32 *status) { struct iwl_host_cmd cmd = { .id = id, @@ -689,36 +689,6 @@ struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm) return bss_iter_data.vif; } -struct iwl_bss_find_iter_data { - struct ieee80211_vif *vif; - u32 macid; -}; - -static void iwl_mvm_bss_find_iface_iterator(void *_data, u8 *mac, - struct ieee80211_vif *vif) -{ - struct iwl_bss_find_iter_data *data = _data; - struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - - if (mvmvif->id == data->macid) - data->vif = vif; -} - -struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid) -{ - struct iwl_bss_find_iter_data data = { - .macid = macid, - }; - - lockdep_assert_held(&mvm->mutex); - - ieee80211_iterate_active_interfaces_atomic( - mvm->hw, IEEE80211_IFACE_ITER_NORMAL, - iwl_mvm_bss_find_iface_iterator, &data); - - return data.vif; -} - struct iwl_sta_iter_data { bool assoc; }; @@ -1055,7 +1025,7 @@ static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, /* * If the current load isn't low we need to force re-evaluation * in the TCM period, so that we can return to low load if there - * was no traffic at all (and thus iwl_mvm_recalc_tcm didn't get + * was no traffic at all (and thus iwl_mvm_tcm_work() didn't get * triggered by traffic). */ if (load != IWL_MVM_TRAFFIC_LOW) @@ -1081,8 +1051,11 @@ static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, return 0; } -void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) +void iwl_mvm_tcm_work(struct work_struct *work) { + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + tcm.work); unsigned long ts = jiffies; bool handle_uapsd = time_after(ts, mvm->tcm.uapsd_nonagg_ts + @@ -1119,15 +1092,6 @@ void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) iwl_mvm_tcm_results(mvm); } -void iwl_mvm_tcm_work(struct work_struct *work) -{ - struct delayed_work *delayed_work = to_delayed_work(work); - struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, - tcm.work); - - iwl_mvm_recalc_tcm(mvm); -} - void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel) { spin_lock_bh(&mvm->tcm.lock); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index 7a7b10152e5a..a3e6c9e09a3b 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -548,6 +548,7 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct pci_device_id iwl_hw_card_ids[] = { {IWL_PCI_DEVICE(0xD340, PCI_ANY_ID, iwl_sc_mac_cfg)}, {IWL_PCI_DEVICE(0x6E70, PCI_ANY_ID, iwl_sc_mac_cfg)}, {IWL_PCI_DEVICE(0xD240, PCI_ANY_ID, iwl_sc_mac_cfg)}, + {IWL_PCI_DEVICE(0x9327, PCI_ANY_ID, iwl_sc_mac_cfg)}, #endif /* CONFIG_IWLMVM || CONFIG_IWLMLD */ {0} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c index bda9f807321e..27f4973791d0 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c @@ -773,6 +773,8 @@ int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, tfd = iwl_txq_gen2_build_tfd(trans, txq, dev_cmd, skb, out_meta); if (!tfd) { + txq->entries[idx].skb = NULL; + txq->entries[idx].cmd = NULL; spin_unlock(&txq->lock); return -1; } diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c index fdb3ba4f63c0..dc2fe0c4f68c 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2003-2014, 2018-2021, 2023-2025 Intel Corporation + * Copyright (C) 2003-2014, 2018-2021, 2023-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -1149,10 +1149,17 @@ bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn, unsigned int wdg_timeout) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); - struct iwl_txq *txq = trans_pcie->txqs.txq[txq_id]; + struct iwl_txq *txq; int fifo = -1; bool scd_bug = false; + if (WARN_ONCE(txq_id < 0 || + txq_id >= trans->mac_cfg->base->num_of_queues, + "queue %d out of range", txq_id)) + return false; + + txq = trans_pcie->txqs.txq[txq_id]; + if (test_and_set_bit(txq_id, trans_pcie->txqs.queue_used)) WARN_ONCE(1, "queue %d already used - expect issues", txq_id); diff --git a/drivers/net/wireless/intersil/p54/Kconfig b/drivers/net/wireless/intersil/p54/Kconfig index 003c378ed131..44b0f1a724aa 100644 --- a/drivers/net/wireless/intersil/p54/Kconfig +++ b/drivers/net/wireless/intersil/p54/Kconfig @@ -10,7 +10,7 @@ config P54_COMMON also need to be enabled in order to support any devices. These devices require softmac firmware which can be found at - + If you choose to build a module, it'll be called p54common. @@ -22,7 +22,7 @@ config P54_USB This driver is for USB isl38xx based wireless cards. These devices require softmac firmware which can be found at - + If you choose to build a module, it'll be called p54usb. @@ -36,7 +36,7 @@ config P54_PCI supported by the fullmac driver/firmware. This driver requires softmac firmware which can be found at - + If you choose to build a module, it'll be called p54pci. diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c index 3baf8ab01e22..a3d9053f043c 100644 --- a/drivers/net/wireless/intersil/p54/fwio.c +++ b/drivers/net/wireless/intersil/p54/fwio.c @@ -131,9 +131,7 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) if (priv->fw_var < 0x500) wiphy_info(priv->hw->wiphy, - "you are using an obsolete firmware. " - "visit http://wireless.wiki.kernel.org/en/users/Drivers/p54 " - "and grab one for \"kernel >= 2.6.28\"!\n"); + "you are using an obsolete firmware. visit https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html and grab one for \"kernel >= 2.6.28\"!\n"); if (priv->fw_var >= 0x300) { /* Firmware supports QoS, use it! */ diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c index c0d3b5329f4e..b88a3dadddc0 100644 --- a/drivers/net/wireless/intersil/p54/p54usb.c +++ b/drivers/net/wireless/intersil/p54/p54usb.c @@ -36,7 +36,7 @@ static struct usb_driver p54u_driver; * Note: * * Always update our wiki's device list (located at: - * http://wireless.wiki.kernel.org/en/users/Drivers/p54/devices ), + * https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54/devices.html ), * whenever you add a new device. */ diff --git a/drivers/net/wireless/marvell/libertas/debugfs.c b/drivers/net/wireless/marvell/libertas/debugfs.c index 9ebd69134940..9428f954837a 100644 --- a/drivers/net/wireless/marvell/libertas/debugfs.c +++ b/drivers/net/wireless/marvell/libertas/debugfs.c @@ -35,8 +35,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, { struct lbs_private *priv = file->private_data; size_t pos = 0; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); ssize_t res; if (!buf) return -ENOMEM; @@ -48,7 +47,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return res; } @@ -96,8 +95,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, ssize_t ret; size_t pos = 0; struct sleep_params sp; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -113,7 +111,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); out_unlock: - free_page(addr); + kfree(buf); return ret; } @@ -165,8 +163,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t ret; size_t pos = 0; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -174,7 +171,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf, ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } @@ -228,7 +225,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask, u8 freq; int events = 0; - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -261,7 +258,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask, kfree(subscribed); out_page: - free_page((unsigned long)buf); + kfree(buf); return ret; } @@ -436,8 +433,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val = 0; if (!buf) @@ -450,7 +446,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, priv->mac_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -506,8 +502,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val; if (!buf) @@ -520,7 +515,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, priv->bbp_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -578,8 +573,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val; if (!buf) @@ -592,7 +586,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, priv->rf_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -812,8 +806,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, char *p; int i; struct debug_data *d; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -836,7 +829,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, res = simple_read_from_buffer(userbuf, count, ppos, p, pos); - free_page(addr); + kfree(buf); return res; } diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index abc703441c5d..7a1ba32f1fb3 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -196,7 +196,7 @@ mwifiex_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) */ static int mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { const u8 *buf = params->buf; size_t len = params->len; @@ -259,14 +259,13 @@ mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, tx_info->pkt_len = pkt_len; mwifiex_form_mgmt_frame(skb, buf, len); - *cookie = get_random_u32() | 1; if (ieee80211_is_action(mgmt->frame_control)) skb = mwifiex_clone_skb_for_tx_status(priv, skb, - MWIFIEX_BUF_FLAG_ACTION_TX_STATUS, cookie); + MWIFIEX_BUF_FLAG_ACTION_TX_STATUS, &cookie); else - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, GFP_ATOMIC); mwifiex_queue_tx_pkt(priv, skb); @@ -304,13 +303,13 @@ static int mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); int ret; - if (!chan || !cookie) { + if (!chan) { mwifiex_dbg(priv->adapter, ERROR, "Invalid parameter for ROC\n"); return -EINVAL; } @@ -326,15 +325,14 @@ mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, duration); if (!ret) { - *cookie = get_random_u32() | 1; - priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.cookie = cookie; priv->roc_cfg.chan = *chan; - cfg80211_ready_on_channel(wdev, *cookie, chan, + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_ATOMIC); mwifiex_dbg(priv->adapter, INFO, - "info: ROC, cookie = 0x%llx\n", *cookie); + "info: ROC, cookie = 0x%llx\n", cookie); } return ret; @@ -4558,9 +4556,9 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy, } static int -mwifiex_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, const u8 *peer, - u64 *cookie) +mwifiex_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 cookie) { /* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, * it requires monitor-mode support (which mwifiex doesn't support). @@ -4726,7 +4724,7 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) ops->disassoc = mwifiex_cfg80211_disassociate; ops->disconnect = NULL; ops->connect = NULL; - ops->probe_client = mwifiex_cfg80211_probe_client; + ops->probe_peer = mwifiex_cfg80211_probe_peer; } wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH; wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN; diff --git a/drivers/net/wireless/marvell/mwifiex/debugfs.c b/drivers/net/wireless/marvell/mwifiex/debugfs.c index 9deaf59dcb62..573768b6ad91 100644 --- a/drivers/net/wireless/marvell/mwifiex/debugfs.c +++ b/drivers/net/wireless/marvell/mwifiex/debugfs.c @@ -6,6 +6,7 @@ */ #include +#include #include "main.h" #include "11n.h" @@ -67,8 +68,8 @@ mwifiex_info_read(struct file *file, char __user *ubuf, struct net_device *netdev = priv->netdev; struct netdev_hw_addr *ha; struct netdev_queue *txq; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page, fmt[64]; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page, fmt[64]; struct mwifiex_bss_info info; ssize_t ret; int i = 0; @@ -133,11 +134,10 @@ mwifiex_info_read(struct file *file, char __user *ubuf, } p += sprintf(p, "\n"); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -168,8 +168,8 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; ssize_t ret; struct mwifiex_ds_get_stats stats; @@ -220,11 +220,10 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf, stats.bcn_miss_cnt); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -247,8 +246,8 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf, ssize_t ret; struct mwifiex_histogram_data *phist_data; int i, value; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *)page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; if (!p) return -ENOMEM; @@ -309,11 +308,10 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf, i, value); } - ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, - (unsigned long)p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -383,8 +381,8 @@ mwifiex_debug_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; ssize_t ret; if (!p) @@ -396,11 +394,10 @@ mwifiex_debug_read(struct file *file, char __user *ubuf, p += mwifiex_debug_info_to_buffer(priv, p, &info); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -457,8 +454,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *) addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos = 0, ret = 0; u32 reg_value; @@ -497,7 +493,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf, ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); done: - free_page(addr); + kfree(buf); return ret; } @@ -511,8 +507,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *)file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)page; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); size_t ret = 0; int pos = 0; @@ -523,7 +518,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf, priv->adapter->debug_mask); ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(page); + kfree(buf); return ret; } @@ -652,8 +647,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct mwifiex_private *priv = (void *)file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int ret, pos = 0; if (!buf) @@ -663,7 +657,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf, priv->mem_rw.value); ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } @@ -719,8 +713,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *) addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos, ret, i; u8 value[MAX_EEPROM_DATA]; @@ -749,7 +742,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf, done: ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); out_free: - free_page(addr); + kfree(buf); return ret; } @@ -820,8 +813,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct mwifiex_private *priv = (void *)file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos, ret; struct mwifiex_ds_hs_cfg hscfg; @@ -836,7 +828,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf, ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c index a8eab6b1e63b..c8df3edff675 100644 --- a/drivers/net/wireless/marvell/mwifiex/main.c +++ b/drivers/net/wireless/marvell/mwifiex/main.c @@ -1550,7 +1550,7 @@ mwifiex_reinit_sw(struct mwifiex_adapter *adapter) adapter->workqueue = alloc_workqueue("MWIFIEX_WORK_QUEUE", - WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (!adapter->workqueue) goto err_kmalloc; @@ -1716,7 +1716,7 @@ mwifiex_add_card(void *card, struct completion *fw_done, adapter->workqueue = alloc_workqueue("MWIFIEX_WORK_QUEUE", - WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (!adapter->workqueue) goto err_kmalloc; diff --git a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c index 9460d5352b23..19196848778c 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c @@ -57,6 +57,18 @@ int mwifiex_wait_queue_complete(struct mwifiex_adapter *adapter, mwifiex_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", status); mwifiex_cancel_all_pending_cmd(adapter); + + /* The command response path writes through cmd_node->data_buf. + * On an interrupted wait, the caller can return and release a + * stack-allocated data_buf before a late firmware response is + * processed. Detach the caller-owned buffer from the current + * command so a late response cannot corrupt freed stack memory. + */ + spin_lock_bh(&adapter->mwifiex_cmd_lock); + if (adapter->curr_cmd == cmd_queued) + adapter->curr_cmd->data_buf = NULL; + spin_unlock_bh(&adapter->mwifiex_cmd_lock); + return status; } diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 6edcb3b8f279..28ad7bcaffd4 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -186,68 +186,74 @@ int mt76_switch_vif_chanctx(struct ieee80211_hw *hw, int n_vifs, enum ieee80211_chanctx_switch_mode mode) { - struct mt76_chanctx *old_ctx = (struct mt76_chanctx *)vifs->old_ctx->drv_priv; - struct mt76_chanctx *new_ctx = (struct mt76_chanctx *)vifs->new_ctx->drv_priv; - struct ieee80211_chanctx_conf *conf = vifs->new_ctx; - struct mt76_phy *old_phy = old_ctx->phy; - struct mt76_phy *phy = hw->priv; + struct ieee80211_vif_chanctx_switch *v; + struct mt76_chanctx *old_ctx, *new_ctx; + struct mt76_phy *old_phy, *phy = hw->priv; struct mt76_dev *dev = phy->dev; struct mt76_vif_link *mlink; - bool update_chan; + bool need_update[__MT_MAX_BAND] = {}; int i, ret = 0; - if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS) - phy = new_ctx->phy = dev->band_phys[conf->def.chan->band]; - else - phy = new_ctx->phy; - if (!phy) - return -EINVAL; + for (i = 0; i < n_vifs; i++) { + v = &vifs[i]; + new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv; + if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS) + phy = new_ctx->phy = dev->band_phys[v->new_ctx->def.chan->band]; + else + phy = new_ctx->phy; - update_chan = phy->chanctx != new_ctx; - if (update_chan) { - if (dev->scan.phy == phy) - mt76_abort_scan(dev); + if (!phy) + return -EINVAL; - cancel_delayed_work_sync(&phy->mac_work); + if (need_update[phy->band_idx]) + continue; + + if (phy->chanctx != new_ctx) { + if (dev->scan.phy == phy) + mt76_abort_scan(dev); + + cancel_delayed_work_sync(&phy->mac_work); + need_update[phy->band_idx] = true; + } } mutex_lock(&dev->mutex); - if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS && - phy != old_phy && old_phy->chanctx == old_ctx) - old_phy->chanctx = NULL; - - if (update_chan) - ret = mt76_phy_update_channel(phy, vifs->new_ctx); - - if (ret) - goto out; - - if (old_phy == phy) - goto skip_link_replace; - for (i = 0; i < n_vifs; i++) { - mlink = mt76_vif_conf_link(dev, vifs[i].vif, vifs[i].link_conf); + v = &vifs[i]; + old_ctx = (struct mt76_chanctx *)v->old_ctx->drv_priv; + old_phy = old_ctx->phy; + + new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv; + phy = new_ctx->phy; + + if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS && old_phy->chanctx && + old_phy->chanctx == old_ctx && phy != old_phy) + old_phy->chanctx = NULL; + + if (need_update[phy->band_idx]) { + ret = mt76_phy_update_channel(phy, v->new_ctx); + if (ret) + goto out; + + need_update[phy->band_idx] = false; + } + + mlink = mt76_vif_conf_link(dev, v->vif, v->link_conf); if (!mlink) continue; - dev->drv->vif_link_remove(old_phy, vifs[i].vif, - vifs[i].link_conf, mlink); + if (old_phy != phy) { + dev->drv->vif_link_remove(old_phy, v->vif, v->link_conf, + mlink); - ret = dev->drv->vif_link_add(phy, vifs[i].vif, - vifs[i].link_conf, mlink); - if (ret) - goto out; + ret = dev->drv->vif_link_add(phy, v->vif, v->link_conf, + mlink); + if (ret) + goto out; + } - } - -skip_link_replace: - for (i = 0; i < n_vifs; i++) { - mlink = mt76_vif_conf_link(dev, vifs[i].vif, vifs[i].link_conf); - if (!mlink) - continue; - - mlink->ctx = vifs->new_ctx; + mlink->ctx = v->new_ctx; if (mlink->beacon_mon_interval) WRITE_ONCE(mlink->beacon_mon_last, jiffies); } diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f8c2fe5f2f58..a67880a9281c 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -186,6 +186,18 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) } } +/* A hung bus (e.g. after a PCIe AER error) reads 0xffffffff from every + * register, so clamp an out-of-range index to the fallback to keep it from + * corrupting q->head/q->tail. + */ +static int +mt76_dma_read_dma_idx(struct mt76_queue *q, int fallback) +{ + u32 idx = Q_READ(q, dma_idx); + + return idx < q->ndesc ? idx : fallback; +} + static void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) { @@ -201,7 +213,8 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) } Q_WRITE(q, desc_base, q->desc_dma); - q->head = Q_READ(q, dma_idx); + + q->head = mt76_dma_read_dma_idx(q, 0); q->tail = q->head; } @@ -419,7 +432,7 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush) if (flush) last = -1; else - last = Q_READ(q, dma_idx); + last = mt76_dma_read_dma_idx(q, -1); while (q->queued > 0 && q->tail != last) { mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry); @@ -432,7 +445,7 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush) } if (!flush && q->tail == last) - last = Q_READ(q, dma_idx); + last = mt76_dma_read_dma_idx(q, -1); } spin_unlock_bh(&q->cleanup_lock); @@ -534,8 +547,13 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, t->ptr = NULL; mt76_put_rxwi(dev, t); - if (drop) +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + /* the WO MCU owns the RX path only on WED v2, on newer + * versions this buf1 bit carries no drop information + */ + if (drop && dev->mmio.wed.version == 2) *drop |= !!(buf1 & MT_DMA_CTL_WO_DROP); +#endif } else { dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0], SKB_WITH_OVERHEAD(q->buf_size), @@ -595,6 +613,15 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, q->desc[idx].ctrl |= cpu_to_le32(MT_DMA_CTL_DMA_DONE); else if (!(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE))) return NULL; +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + /* on WED v3 the M_DONE bit signals that WED is done reading + * the txfree descriptor; WED v2 does not set it + */ + else if (dev->mmio.wed.version > 2 && + mt76_queue_is_wed_tx_free(q) && + !(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_M_DONE))) + return NULL; +#endif } done: q->tail = (q->tail + 1) % q->ndesc; @@ -625,8 +652,8 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q, buf.len = skb->len; spin_lock_bh(&q->lock); - mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL); - mt76_dma_kick_queue(dev, q); + if (mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL) >= 0) + mt76_dma_kick_queue(dev, q); spin_unlock_bh(&q->lock); return 0; @@ -977,23 +1004,24 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) struct sk_buff *skb; unsigned char *data; bool check_ddone = false; - bool allow_direct = !mt76_queue_is_wed_rx(q); + bool allow_direct = !mt76_queue_is_wed_rx(q) && + !mt76_queue_is_wed_rro_rxdmad_c(q); bool more; if ((q->flags & MT_QFLAG_WED_RRO_EN) || (IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) && mt76_queue_is_wed_tx_free(q))) { - dma_idx = Q_READ(q, dma_idx); + dma_idx = mt76_dma_read_dma_idx(q, q->tail); check_ddone = true; } while (done < budget) { bool drop = false; - u32 info; + u32 info = 0; if (check_ddone) { if (q->tail == dma_idx) - dma_idx = Q_READ(q, dma_idx); + dma_idx = mt76_dma_read_dma_idx(q, q->tail); if (q->tail == dma_idx) break; diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index 2a0226c83f3c..7694764febe7 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -5,13 +5,15 @@ #ifndef __MT76_DMA_H #define __MT76_DMA_H +#include + #define DMA_DUMMY_DATA ((void *)~0) #define MT_RING_SIZE 0x10 #define MT_DMA_CTL_SD_LEN1 GENMASK(13, 0) #define MT_DMA_CTL_LAST_SEC1 BIT(14) -#define MT_DMA_CTL_BURST BIT(15) +#define MT_DMA_CTL_M_DONE BIT(15) #define MT_DMA_CTL_SD_LEN0 GENMASK(29, 16) #define MT_DMA_CTL_LAST_SEC0 BIT(30) #define MT_DMA_CTL_DMA_DONE BIT(31) @@ -46,73 +48,75 @@ #define MT_FCE_INFO_LEN 4 #define MT_RX_RXWI_LEN 32 +static inline bool +mt76_dma_handle_read(struct mt76_queue *q, u32 offset, u32 *val) +{ #if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) + if (q->flags & MT_QFLAG_WED) { + *val = mtk_wed_device_reg_read(q->wed, q->wed_regs + offset); + + return true; + } +#endif +#if IS_ENABLED(CONFIG_MT76_NPU) + if (q->flags & MT_QFLAG_NPU) { + struct airoha_npu *npu; + + *val = 0; + rcu_read_lock(); + npu = rcu_dereference(q->dev->mmio.npu); + if (npu) + regmap_read(npu->regmap, q->wed_regs + offset, val); + rcu_read_unlock(); + + return true; + } +#endif + + return false; +} + +static inline bool +mt76_dma_handle_write(struct mt76_queue *q, u32 offset, u32 val) +{ +#if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) + if (q->flags & MT_QFLAG_WED) { + mtk_wed_device_reg_write(q->wed, q->wed_regs + offset, val); + + return true; + } +#endif +#if IS_ENABLED(CONFIG_MT76_NPU) + if (q->flags & MT_QFLAG_NPU) { + struct airoha_npu *npu; + + rcu_read_lock(); + npu = rcu_dereference(q->dev->mmio.npu); + if (npu) + regmap_write(npu->regmap, q->wed_regs + offset, val); + rcu_read_unlock(); + + return true; + } +#endif + + return false; +} #define Q_READ(_q, _field) ({ \ u32 _offset = offsetof(struct mt76_queue_regs, _field); \ u32 _val; \ - if ((_q)->flags & MT_QFLAG_WED) \ - _val = mtk_wed_device_reg_read((_q)->wed, \ - ((_q)->wed_regs + \ - _offset)); \ - else \ + if (!mt76_dma_handle_read(_q, _offset, &_val)) \ _val = readl(&(_q)->regs->_field); \ _val; \ }) #define Q_WRITE(_q, _field, _val) do { \ u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - if ((_q)->flags & MT_QFLAG_WED) \ - mtk_wed_device_reg_write((_q)->wed, \ - ((_q)->wed_regs + _offset), \ - _val); \ - else \ + if (!mt76_dma_handle_write(_q, _offset, _val)) \ writel(_val, &(_q)->regs->_field); \ } while (0) -#elif IS_ENABLED(CONFIG_MT76_NPU) - -#define Q_READ(_q, _field) ({ \ - u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - u32 _val = 0; \ - if ((_q)->flags & MT_QFLAG_NPU) { \ - struct airoha_npu *npu; \ - \ - rcu_read_lock(); \ - npu = rcu_dereference(q->dev->mmio.npu); \ - if (npu) \ - regmap_read(npu->regmap, \ - ((_q)->wed_regs + _offset), &_val); \ - rcu_read_unlock(); \ - } else { \ - _val = readl(&(_q)->regs->_field); \ - } \ - _val; \ -}) - -#define Q_WRITE(_q, _field, _val) do { \ - u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - if ((_q)->flags & MT_QFLAG_NPU) { \ - struct airoha_npu *npu; \ - \ - rcu_read_lock(); \ - npu = rcu_dereference(q->dev->mmio.npu); \ - if (npu) \ - regmap_write(npu->regmap, \ - ((_q)->wed_regs + _offset), _val); \ - rcu_read_unlock(); \ - } else { \ - writel(_val, &(_q)->regs->_field); \ - } \ -} while (0) - -#else - -#define Q_READ(_q, _field) readl(&(_q)->regs->_field) -#define Q_WRITE(_q, _field, _val) writel(_val, &(_q)->regs->_field) - -#endif - struct mt76_desc { __le32 buf0; __le32 ctrl; diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 13c4e8abe281..abbe65cbcd89 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -439,8 +439,9 @@ mt76_phy_init(struct mt76_phy *phy, struct ieee80211_hw *hw) SET_IEEE80211_DEV(hw, dev->dev); SET_IEEE80211_PERM_ADDR(hw, phy->macaddr); - wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | - NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE; + wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE; + if (!phy->no_active_monitor) + wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR; wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH | WIPHY_FLAG_SUPPORTS_TDLS | WIPHY_FLAG_AP_UAPSD; @@ -681,6 +682,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size, dev = hw->priv; dev->hw = hw; dev->dev = pdev; + dev->init_wiphy = NULL; dev->drv = drv_ops; dev->dma_dev = pdev; @@ -779,6 +781,12 @@ int mt76_register_device(struct mt76_dev *dev, bool vht, mt76_check_sband(&dev->phy, &phy->sband_5g, NL80211_BAND_5GHZ); mt76_check_sband(&dev->phy, &phy->sband_6g, NL80211_BAND_6GHZ); + if (dev->init_wiphy) { + ret = dev->init_wiphy(dev); + if (ret) + return ret; + } + if (IS_ENABLED(CONFIG_MT76_LEDS)) { ret = mt76_led_init(phy); if (ret) @@ -886,6 +894,7 @@ static void mt76_rx_release_amsdu(struct mt76_phy *phy, enum mt76_rxq_id q) struct sk_buff *skb = phy->rx_amsdu[q].head; struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; struct mt76_dev *dev = phy->dev; + struct mt76_queue *rxq = &dev->q_rx[q]; phy->rx_amsdu[q].head = NULL; phy->rx_amsdu[q].tail = NULL; @@ -914,6 +923,13 @@ static void mt76_rx_release_amsdu(struct mt76_phy *phy, enum mt76_rxq_id q) return; } } + + /* RRO 3.0 data queue skbs are processed and completed in the context + * of the indicator queue NAPI, which only polls its own skb list + */ + if (mt76_queue_is_wed_rro_data(rxq) && dev->hwrro_mode == MT76_HWRRO_V3) + q = MT_RXQ_RRO_IND; + __skb_queue_tail(&dev->rx_skb[q], skb); } diff --git a/drivers/net/wireless/mediatek/mt76/mcu.c b/drivers/net/wireless/mediatek/mt76/mcu.c index cbfb3bbec503..7149b2f7aafd 100644 --- a/drivers/net/wireless/mediatek/mt76/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mcu.c @@ -78,15 +78,19 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb, unsigned long expires; int ret, seq; - if (mt76_is_sdio(dev)) - if (test_bit(MT76_RESET, &dev->phy.state) && atomic_read(&dev->bus_hung)) - return -EIO; - if (ret_skb) *ret_skb = NULL; mutex_lock(&dev->mcu.mutex); + if ((mt76_is_mmio(dev) && atomic_read(&dev->bus_hung)) || + (mt76_is_sdio(dev) && test_bit(MT76_RESET, &dev->phy.state) && + atomic_read(&dev->bus_hung))) { + orig_skb = skb; + ret = -EIO; + goto out; + } + if (dev->mcu_ops->mcu_skb_prepare_msg) { orig_skb = skb; ret = dev->mcu_ops->mcu_skb_prepare_msg(dev, skb, cmd, &seq); diff --git a/drivers/net/wireless/mediatek/mt76/mmio.c b/drivers/net/wireless/mediatek/mt76/mmio.c index 05d74cd7248e..73d47608bf42 100644 --- a/drivers/net/wireless/mediatek/mt76/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mmio.c @@ -35,9 +35,16 @@ static void mt76_mmio_write_copy(struct mt76_dev *dev, u32 offset, { int i; - for (i = 0; i < ALIGN(len, 4); i += 4) + for (i = 0; i + 4 <= len; i += 4) writel(get_unaligned_le32(data + i), dev->mmio.regs + offset + i); + + if (i < len) { + u8 tmp[4] = {}; + + memcpy(tmp, data + i, len - i); + writel(get_unaligned_le32(tmp), dev->mmio.regs + offset + i); + } } static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset, @@ -45,9 +52,16 @@ static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset, { int i; - for (i = 0; i < ALIGN(len, 4); i += 4) + for (i = 0; i + 4 <= len; i += 4) put_unaligned_le32(readl(dev->mmio.regs + offset + i), data + i); + + if (i < len) { + u8 tmp[4]; + + put_unaligned_le32(readl(dev->mmio.regs + offset + i), tmp); + memcpy(data + i, tmp, len - i); + } } static int mt76_mmio_wr_rp(struct mt76_dev *dev, u32 base, diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 3822eb8fd88f..62b41c8bb7c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -539,6 +539,7 @@ struct mt76_hw_cap { #define MT_DRV_HW_MGMT_TXQ BIT(4) #define MT_DRV_AMSDU_OFFLOAD BIT(5) #define MT_DRV_IGNORE_TXS_FAILED BIT(6) +#define MT_DRV_HW_PS_BUFFERING BIT(7) struct mt76_driver_ops { u32 drv_flags; @@ -672,6 +673,7 @@ struct mt76_usb { u8 out_ep[__MT_EP_OUT_MAX]; u8 in_ep[__MT_EP_IN_MAX]; + void (*ctrl_timeout)(struct mt76_dev *dev, int err); bool sg_en; struct mt76u_mcu { @@ -871,6 +873,7 @@ struct mt76_phy { struct cfg80211_chan_def main_chandef; bool offchannel; bool radar_enabled; + bool no_active_monitor; struct delayed_work roc_work; struct ieee80211_vif *roc_vif; @@ -940,6 +943,9 @@ struct mt76_dev { const struct mt76_bus_ops *bus; const struct mt76_driver_ops *drv; const struct mt76_mcu_ops *mcu_ops; + + /* Optional callback to finalize wiphy state before registration. */ + int (*init_wiphy)(struct mt76_dev *dev); struct device *dev; struct device *dma_dev; @@ -1537,6 +1543,8 @@ void mt76_release_buffered_frames(struct ieee80211_hw *hw, u16 tids, int nframes, enum ieee80211_frame_release_type reason, bool more_data); +void mt76_sta_ps_transition(struct mt76_dev *dev, struct mt76_wcid *wcid, + bool ps); bool mt76_has_tx_pending(struct mt76_phy *phy); int mt76_update_channel(struct mt76_phy *phy); void mt76_update_survey(struct mt76_phy *phy); @@ -2124,6 +2132,9 @@ mt76_vif_link(struct mt76_dev *dev, struct ieee80211_vif *vif, int link_id) if (!link_id) return mlink; + if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) + return NULL; + return mt76_dereference(mvif->link[link_id], dev); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c index 300a7f9c2ef1..acca98139f92 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c @@ -56,6 +56,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BCN)); if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) { dev->beacon_check = MT7603_WATCHDOG_TIMEOUT; + dev_kfree_skb(skb); goto out; } @@ -63,6 +64,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BMC)); if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) { dev->beacon_check = MT7603_WATCHDOG_TIMEOUT; + dev_kfree_skb(skb); goto out; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/dma.c b/drivers/net/wireless/mediatek/mt76/mt7603/dma.c index 3a16851524a0..477a9b7a8cf7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/dma.c @@ -39,7 +39,6 @@ mt7603_rx_loopback_skb(struct mt7603_dev *dev, struct sk_buff *skb) val = le32_to_cpu(txd[1]); idx = FIELD_GET(MT_TXD1_WLAN_IDX, val); - skb->priority = FIELD_GET(MT_TXD1_TID, val); if (idx >= MT7603_WTBL_STA - 1) goto free; @@ -72,6 +71,7 @@ mt7603_rx_loopback_skb(struct mt7603_dev *dev, struct sk_buff *skb) hwq = MT_TX_HW_QUEUE_MGMT; } + skb->priority = tid; ieee80211_sta_set_buffered(sta, tid, true); val = le32_to_cpu(txd[0]); diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c index b89db2db6573..05f7aaefaf89 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c @@ -142,6 +142,7 @@ static int mt7603_check_eeprom(struct mt76_dev *dev) case 0x7628: case 0x7603: case 0x7600: + case 0x7592: return 0; default: return -EINVAL; diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c index d3110eeb45d7..fa0bf9a201ce 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c @@ -235,16 +235,17 @@ void mt7603_wtbl_set_smps(struct mt7603_dev *dev, struct mt7603_sta *sta, sta->smps = enabled; } -void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, - bool enabled) +static void +__mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool enabled, bool filter) { int idx = sta->wcid.idx; u32 addr; - spin_lock_bh(&dev->ps_lock); + lockdep_assert_held(&dev->ps_lock); if (sta->ps == enabled) - goto out; + return; mt76_wr(dev, MT_PSE_RTA, FIELD_PREP(MT_PSE_RTA_TAG_ID, idx) | @@ -255,7 +256,7 @@ void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, mt76_poll(dev, MT_PSE_RTA, MT_PSE_RTA_BUSY, 0, 5000); - if (enabled) + if (enabled && filter) mt7603_filter_tx(dev, sta->vif->idx, idx, false); addr = mt7603_wtbl1_addr(idx); @@ -264,8 +265,34 @@ void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, enabled * MT_WTBL1_W3_POWER_SAVE); mt76_clear(dev, MT_WTBL1_OR, MT_WTBL1_OR_PSM_WRITE); sta->ps = enabled; +} -out: +void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool enabled) +{ + spin_lock_bh(&dev->ps_lock); + __mt7603_wtbl_set_ps(dev, sta, enabled, enabled); + spin_unlock_bh(&dev->ps_lock); +} + +void mt7603_wtbl_sta_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool ps) +{ + spin_lock_bh(&dev->ps_lock); + sta->ps_sleeping = ps; + __mt7603_wtbl_set_ps(dev, sta, ps, ps); + spin_unlock_bh(&dev->ps_lock); +} + +void mt7603_wtbl_restore_ps(struct mt7603_dev *dev, struct mt7603_sta *sta) +{ + spin_lock_bh(&dev->ps_lock); + /* + * Frames that are already queued for the station belong to the service + * period that has just been served, so unlike on a sleep transition + * they must not be pulled back into the PS queue. + */ + if (sta->ps_sleeping) + __mt7603_wtbl_set_ps(dev, sta, true, false); spin_unlock_bh(&dev->ps_lock); } @@ -1816,6 +1843,39 @@ mt7603_false_cca_check(struct mt7603_dev *dev) mt7603_adjust_sensitivity(dev); } +/* + * Releasing buffered frames turns off the PSE redirect for a station, since + * the released frames would otherwise be looped back into the driver PS queue + * again. mac80211 never tells us when the service period is over, so hardware + * buffering has to be re-armed here for every station that is still known to + * be asleep. Waiting for the PSD queue to drain makes sure that the released + * frames have already passed the redirect stage. + */ +static void +mt7603_mac_ps_check(struct mt7603_dev *dev) +{ + int i; + + if (dev->mphy.q_tx[MT_TXQ_PSD]->queued) + return; + + rcu_read_lock(); + for (i = 0; i < MT7603_WTBL_STA; i++) { + struct mt76_wcid *wcid = mt76_wcid_ptr(dev, i); + struct mt7603_sta *msta; + + if (!wcid || !wcid->sta) + continue; + + msta = container_of(wcid, struct mt7603_sta, wcid); + if (msta->ps || !msta->ps_sleeping) + continue; + + mt7603_wtbl_restore_ps(dev, msta); + } + rcu_read_unlock(); +} + void mt7603_mac_work(struct work_struct *work) { struct mt7603_dev *dev = container_of(work, struct mt7603_dev, @@ -1830,6 +1890,7 @@ void mt7603_mac_work(struct work_struct *work) dev->mphy.mac_work_count++; mt76_update_survey(&dev->mphy); mt7603_edcca_check(dev); + mt7603_mac_ps_check(dev); for (i = 0, idx = 0; i < 2; i++) { u32 val = mt76_rr(dev, MT_TX_AGG_CNT(i)); diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/main.c b/drivers/net/wireless/mediatek/mt76/mt7603/main.c index 0f3a7508996c..757664e9e1f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/main.c @@ -410,7 +410,7 @@ mt7603_sta_ps(struct mt76_dev *mdev, struct ieee80211_sta *sta, bool ps) struct sk_buff_head list; mt76_stop_tx_queues(&dev->mphy, sta, true); - mt7603_wtbl_set_ps(dev, msta, ps); + mt7603_wtbl_sta_ps(dev, msta, ps); if (ps) return; @@ -423,13 +423,33 @@ mt7603_sta_ps(struct mt76_dev *mdev, struct ieee80211_sta *sta, bool ps) mt7603_ps_tx_list(dev, &list); } -static void -mt7603_ps_set_more_data(struct sk_buff *skb) +static struct ieee80211_hdr * +mt7603_ps_skb_hdr(struct sk_buff *skb) { - struct ieee80211_hdr *hdr; + return (struct ieee80211_hdr *)&skb->data[MT_TXD_SIZE]; +} - hdr = (struct ieee80211_hdr *)&skb->data[MT_TXD_SIZE]; - hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); +/* + * Buffered frames can be recycled into the PS queue by mt7603_filter_tx(), so + * both bits have to be assigned, not just set. + */ +static void +mt7603_ps_set_flags(struct sk_buff *skb, bool more_data, bool eosp) +{ + struct ieee80211_hdr *hdr = mt7603_ps_skb_hdr(skb); + + if (more_data) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); + else + hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_MOREDATA); + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return; + + if (eosp) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_EOSP; + else + *ieee80211_get_qos_ctl(hdr) &= ~IEEE80211_QOS_CTL_EOSP; } static void @@ -442,7 +462,12 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, struct mt7603_dev *dev = hw->priv; struct mt7603_sta *msta = (struct mt7603_sta *)sta->drv_priv; struct sk_buff_head list; - struct sk_buff *skb, *tmp; + struct sk_buff *skb, *tmp, *last; + bool eosp_null, uapsd; + unsigned long drained; + u16 pending = 0; + u8 last_tid; + int i; __skb_queue_head_init(&list); @@ -458,20 +483,58 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, skb_set_queue_mapping(skb, MT_TXQ_PSD); __skb_unlink(skb, &msta->psq); - mt7603_ps_set_more_data(skb); __skb_queue_tail(&list, skb); nframes--; } + + skb_queue_walk(&msta->psq, skb) + pending |= BIT(skb->priority); spin_unlock_bh(&dev->ps_lock); - if (!skb_queue_empty(&list)) - ieee80211_sta_eosp(sta); + /* + * Without this, mac80211 keeps the TIM bit set for the station and + * keeps routing every service period to the driver, even though there + * is nothing left to release. + */ + drained = tids & ~pending; + for_each_set_bit(i, &drained, IEEE80211_NUM_TIDS) + ieee80211_sta_set_buffered(sta, i, false); + + last = skb_peek_tail(&list); + if (!last) { + mt76_release_buffered_frames(hw, sta, tids, nframes, reason, + more_data); + return; + } + + /* + * End the service period here instead of passing the remaining frame + * budget on to mt76_release_buffered_frames(), which would signal the + * end of the same service period a second time. + */ + uapsd = reason == IEEE80211_FRAME_RELEASE_UAPSD; + more_data |= !!(pending & tids); + + skb_queue_walk(&list, skb) + mt7603_ps_set_flags(skb, skb != last || more_data, + skb == last && uapsd); + + /* + * EOSP lives in the QoS control field, so a bufferable MMPDU cannot + * terminate a U-APSD service period on its own. In that case mac80211 + * has to append a QoS-Null frame, which ends the SP through its tx + * status. + */ + eosp_null = uapsd && + !ieee80211_is_data_qos(mt7603_ps_skb_hdr(last)->frame_control); + last_tid = __fls(tids); mt7603_ps_tx_list(dev, &list); - if (nframes) - mt76_release_buffered_frames(hw, sta, tids, nframes, reason, - more_data); + if (eosp_null) + ieee80211_send_eosp_nullfunc(sta, last_tid); + else + ieee80211_sta_eosp(sta); } static int diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h b/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h index 071bfab3af7c..5b0ecd01aad9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h +++ b/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h @@ -80,6 +80,7 @@ struct mt7603_sta { u8 smps; u8 ps; + u8 ps_sleeping; }; struct mt7603_vif { @@ -229,6 +230,9 @@ int mt7603_wtbl_set_key(struct mt7603_dev *dev, int wcid, struct ieee80211_key_conf *key); void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool enabled); +void mt7603_wtbl_sta_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool ps); +void mt7603_wtbl_restore_ps(struct mt7603_dev *dev, struct mt7603_sta *sta); void mt7603_wtbl_set_smps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool enabled); void mt7603_filter_tx(struct mt7603_dev *dev, int mac_idx, int idx, bool abort); diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/main.c b/drivers/net/wireless/mediatek/mt76/mt7615/main.c index fc619acbb40d..67f56e428d9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/main.c @@ -1239,11 +1239,12 @@ static int mt7615_suspend(struct ieee80211_hw *hw, cancel_delayed_work_sync(&dev->pm.ps_work); mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); + cancel_delayed_work_sync(&phy->scan_work); + cancel_delayed_work_sync(&phy->mt76->mac_work); + mt7615_mutex_acquire(dev); clear_bit(MT76_STATE_RUNNING, &phy->mt76->state); - cancel_delayed_work_sync(&phy->scan_work); - cancel_delayed_work_sync(&phy->mt76->mac_work); set_bit(MT76_STATE_SUSPEND, &phy->mt76->state); ieee80211_iterate_active_interfaces(hw, diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c index 53cb1eed1e4f..0aca590f2bae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c @@ -68,10 +68,11 @@ int mt7615_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, int pid, id; u8 *txwi = (u8 *)txwi_ptr; struct mt76_txwi_cache *t; - struct mt7615_sta *msta; + struct mt7615_sta *msta = NULL; void *txp; - msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL; + if (wcid && wcid->sta) + msta = container_of(wcid, struct mt7615_sta, wcid); if (!wcid) wcid = &dev->mt76.global_wcid; diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c index f4169de939c4..9525d8062b44 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c @@ -187,10 +187,11 @@ int mt7663_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, struct sk_buff *skb = tx_info->skb; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_key_conf *key = info->control.hw_key; - struct mt7615_sta *msta; + struct mt7615_sta *msta = NULL; int pad, err, pktid; - msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL; + if (wcid && wcid->sta) + msta = container_of(wcid, struct mt7615_sta, wcid); if (!wcid) wcid = &dev->mt76.global_wcid; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 2aa6078993e9..b1677ca24703 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -48,6 +48,11 @@ enum rx_pkt_type { #define MT_TXD_LEN_MSDU_LAST BIT(14) #define MT_TXD_LEN_AMSDU_LAST BIT(15) +/* PCIE part */ +#define PCIE_AER_UNC_STATUS_OFFSET 0x204 +#define PCIE_AER_UNC_MASK_OFFSET 0x208 +#define PCIE_AER_CO_STATUS_OFFSET 0x210 + enum { CMD_CBW_20MHZ = IEEE80211_STA_RX_BW_20, CMD_CBW_40MHZ = IEEE80211_STA_RX_BW_40, @@ -174,7 +179,7 @@ extern const struct wiphy_wowlan_support mt76_connac_wowlan_support; static inline bool is_connac3(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927; + return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927 || mt76_chip(dev) == 0x7928; } static inline bool is_mt7925(struct mt76_dev *dev) @@ -187,6 +192,11 @@ static inline bool is_mt7927(struct mt76_dev *dev) return mt76_chip(dev) == 0x7927; } +static inline bool is_mt7928(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7928; +} + static inline bool is_320mhz_supported(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7927; @@ -245,7 +255,8 @@ static inline bool is_mt798x(struct mt76_dev *dev) static inline bool is_mt7996(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7990; + u16 chip = mt76_chip(dev); + return chip == 0x7990 || chip == 0x7991; } static inline bool is_mt7992(struct mt76_dev *dev) @@ -295,6 +306,7 @@ static inline bool is_mt76_fw_txp(struct mt76_dev *dev) case 0x7902: case 0x7925: case 0x7927: + case 0x7928: case 0x7663: case 0x7622: return false; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h index 247e2e7a47d8..d2d63767bfd0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h @@ -18,6 +18,11 @@ enum { MT_LMAC_PSMP0, }; +enum { + TXS_FM_MPDU = 0, + TXS_FM_PPDU = 2, +}; + #define MT_CT_PARSE_LEN 72 #define MT_CT_DMA_BUF_NUM 2 @@ -236,7 +241,9 @@ enum tx_frag_idx { #define MT_TXD2_HDR_PAD GENMASK(11, 10) #define MT_TXD2_RTS BIT(9) #define MT_TXD2_OWN_MAC_MAP BIT(8) +#define MT_TXD2_OWN_MAC_MAP_V2 BIT(9) #define MT_TXD2_BF_TYPE GENMASK(6, 7) +#define MT_TXD2_BF_TYPE_V2 GENMASK(6, 8) #define MT_TXD2_FRAME_TYPE GENMASK(5, 4) #define MT_TXD2_SUB_TYPE GENMASK(3, 0) @@ -261,6 +268,7 @@ enum tx_frag_idx { #define MT_TXD5_BYPASS_TBB BIT(14) #define MT_TXD5_BYPASS_RBB BIT(13) #define MT_TXD5_BSS_COLOR_ZERO BIT(12) +#define MT_TXD5_OCUP_BY_OTHER_LNK BIT(11) #define MT_TXD5_TX_STATUS_HOST BIT(10) #define MT_TXD5_TX_STATUS_MCU BIT(9) #define MT_TXD5_TX_STATUS_FMT BIT(8) @@ -270,15 +278,19 @@ enum tx_frag_idx { #define MT_TXD6_VTA BIT(28) #define MT_TXD6_FIXED_BW BIT(25) #define MT_TXD6_BW GENMASK(24, 22) +#define MT_TXD6_BW_V2 GENMASK(25, 22) #define MT_TXD6_TX_RATE GENMASK(21, 16) #define MT_TXD6_TIMESTAMP_OFS_EN BIT(15) +#define MT_TXD6_TIMESTAMP_OFS_EN_V2 GENMASK(15, 13) #define MT_TXD6_TIMESTAMP_OFS_IDX GENMASK(14, 10) +#define MT_TXD6_TIMESTAMP_OFS_IDX_V2 GENMASK(12, 8) #define MT_TXD6_TID_ADDBA GENMASK(10, 8) #define MT_TXD6_MSDU_CNT GENMASK(9, 4) #define MT_TXD6_MSDU_CNT_V2 GENMASK(15, 10) #define MT_TXD6_DIS_MAT BIT(3) #define MT_TXD6_DAS BIT(2) #define MT_TXD6_AMSDU_CAP BIT(1) +#define MT_TXD6_MLD BIT(0) #define MT_TXD7_TXD_LEN GENMASK(31, 30) #define MT_TXD7_IP_SUM BIT(29) @@ -287,6 +299,9 @@ enum tx_frag_idx { #define MT_TXD7_CTXD BIT(26) #define MT_TXD7_CTXD_CNT GENMASK(25, 22) #define MT_TXD7_UDP_TCP_SUM BIT(15) +#define MT_TXD7_IMMEDIATE_TX BIT(14) +#define MT_TXD7_FORCE_RTS_CTS BIT(13) +#define MT_TXD7_ENABLE_ICI BIT(12) #define MT_TXD7_TX_TIME GENMASK(9, 0) #define MT_TXD9_WLAN_IDX GENMASK(23, 8) @@ -397,4 +412,31 @@ enum tx_frag_idx { #define MT_TXS7_MPDU_RETRY_BYTE_SCALE BIT(15) #define MT_TXS7_MPDU_RETRY_BYTE GENMASK(14, 0) +struct mt7928_uni_txdone_event { + __le16 tag; + __le16 len; + + u8 pid; /* HW packet ID */ + u8 status; /* TX_RESULT_xx */ + __le16 seq; /* packet sequence number */ + + u8 wcid; /* WLAN index (WTBL) */ + u8 tx_count; /* TX attempts including retries */ + __le16 tx_rate; + + u8 flag; /* TXS_WITH_ADVANCED_INFO or TXS_IS_EXIST */ + u8 tid; + u8 rsp_rate; + u8 rate_tbl_idx; /* last TX rate index from WLAN table */ + + u8 bw; /* bandwidth used for this PPDU */ + u8 tx_pwr; /* dBm */ + u8 flush_reason; + u8 rsv[1]; + + __le32 tx_delay; /* unit: 32us, UMAC TX to TX status */ + __le32 timestamp; /* local TSF at first bit of MAC header */ + __le32 applied_flags; +} __packed; + #endif /* __MT76_CONNAC3_MAC_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index fc9f782032ef..8a8d608dd56e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1069,6 +1069,10 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev, bw = FIELD_GET(MT_CRXV_FRAME_MODE, v2); } + /* the hardware reports NSTS; report the data NSS for STBC frames */ + if (stbc && nss > 1) + nss >>= 1; + switch (*mode) { case MT_PHY_TYPE_CCK: cck = true; @@ -1114,7 +1118,7 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev, case IEEE80211_STA_RX_BW_20: break; case IEEE80211_STA_RX_BW_40: - if (*mode & MT_PHY_TYPE_HE_EXT_SU && + if (*mode == MT_PHY_TYPE_HE_EXT_SU && (idx & MT_PRXV_TX_ER_SU_106T)) { status->bw = RATE_INFO_BW_HE_RU; status->he_ru = @@ -1152,8 +1156,6 @@ void mt76_connac2_tx_check_aggr(struct ieee80211_sta *sta, __le32 *txwi) return; tid = le32_get_bits(txwi[1], MT_TXD1_TID); - if (tid >= 6) /* skip VO queue */ - return; val = le32_to_cpu(txwi[2]); fc = FIELD_GET(MT_TXD2_FRAME_TYPE, val) << 2 | diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 58b0b15e4fd6..2f925d22b9aa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -67,7 +67,8 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, if ((!is_connac_v1(dev) && addr == MCU_PATCH_ADDRESS) || (is_connac2(dev) && addr == 0x900000) || - (is_connac3(dev) && (addr == 0x900000 || addr == 0xe0002800)) || + ((is_mt7925(dev) || is_mt7927(dev)) && (addr == 0x900000 || addr == 0xe0002800)) || + (is_mt7928(dev) && (addr == 0x900000 || addr == 0xe0002000)) || (is_mt799x(dev) && addr == 0x900000)) cmd = MCU_CMD(PATCH_START_REQ); else @@ -77,6 +78,48 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, } EXPORT_SYMBOL_GPL(mt76_connac_mcu_init_download); +int mt76_connac_cb_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get) +{ + u8 op = get ? PATCH_SEM_GET : PATCH_SEM_RELEASE; + struct { + u8 op; + u8 reserved[3]; + } req = { + .op = op, + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_SEM_CONTROL), + &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_patch_sem_ctrl); + +int mt76_connac_cb_mcu_start_patch(struct mt76_dev *dev) +{ + struct { + __le32 reserved; + } req = { + .reserved = 0, + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_FINISH_REQ), + &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_start_patch); + +int mt76_connac_cb_mcu_init_download(struct mt76_dev *dev, u32 len) +{ + struct { + __le32 addr; + __le32 len; + } req = { + .addr = 0, /* addr is meaningless for cbmcu fwdl */ + .len = cpu_to_le32(len), + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_START_REQ), &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_init_download); + int mt76_connac_mcu_set_channel_domain(struct mt76_phy *phy) { int len, i, n_max_channels, n_2ch = 0, n_5ch = 0, n_6ch = 0; @@ -422,6 +465,10 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb, basic->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); basic->aid = cpu_to_le16(link_sta->sta->aid); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic->conn_type = cpu_to_le32(CONNECTION_NAN); + break; default: WARN_ON(1); break; @@ -753,7 +800,7 @@ mt76_connac_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta) HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]); he->dcm_rx_max_nss = HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]); - he->dcm_rx_max_nss = + he->dcm_max_ru = HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]); he->pkt_ext = 2; @@ -1217,6 +1264,11 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy, case NL80211_IFTYPE_ADHOC: basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req.basic.conn_state = !enable; + break; default: WARN_ON(1); break; @@ -1627,6 +1679,11 @@ int mt76_connac_mcu_uni_add_bss(struct mt76_phy *phy, case NL80211_IFTYPE_ADHOC: basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req.basic.active = enable; + break; default: WARN_ON(1); break; @@ -3044,6 +3101,57 @@ mt76_connac_mcu_send_ram_firmware(struct mt76_dev *dev, return mt76_connac_mcu_start_firmware(dev, override, option); } +static int +mt76_connac_mcu_send_phy_ram_firmware(struct mt76_dev *dev, + const struct mt76_connac2_fw_trailer *hdr, + const u8 *data) +{ + int i, offset = 0, max_len = mt76_is_sdio(dev) ? 2048 : 4096; + u32 override = 0, option = 0; + + for (i = 0; i < hdr->n_region; i++) { + const struct mt76_connac2_fw_region *region; + u32 len, addr, mode; + int err; + + region = (const void *)((const u8 *)hdr - + (hdr->n_region - i) * sizeof(*region)); + mode = mt76_connac_mcu_gen_dl_mode(dev, region->feature_set, + true); + len = le32_to_cpu(region->len); + addr = le32_to_cpu(region->addr); + + if (region->feature_set & FW_FEATURE_NON_DL) + goto next; + + if (region->feature_set & FW_FEATURE_OVERRIDE_ADDR) + override = addr; + + err = mt76_connac_mcu_init_download(dev, addr, len, mode); + if (err) { + dev_err(dev->dev, + "The request to dowload PHY firmware failed.\n"); + return err; + } + + err = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + data + offset, len, max_len); + if (err) { + dev_err(dev->dev, "Failed to send PHY firmware.\n"); + return err; + } + +next: + offset += len; + } + + if (override) + option |= FW_START_OVERRIDE; + option |= FW_START_WORKING_PDA_DSP; + + return mt76_connac_mcu_start_firmware(dev, override, option); +} + int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, const char *fw_wa) { @@ -3111,6 +3219,39 @@ int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, } EXPORT_SYMBOL_GPL(mt76_connac2_load_ram); +int mt76_connac3_load_phy_ram(struct mt76_dev *dev, const char *fw_name) +{ + const struct mt76_connac2_fw_trailer *hdr; + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, fw_name, dev->dev); + if (ret) + return ret; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) { + dev_err(dev->dev, "Invalid PHY firmware\n"); + ret = -EINVAL; + goto out; + } + + hdr = (const void *)(fw->data + fw->size - sizeof(*hdr)); + dev_info(dev->dev, "PHY Firmware Version: %.10s, Build Time: %.15s\n", + hdr->fw_ver, hdr->build_date); + + ret = mt76_connac_mcu_send_phy_ram_firmware(dev, hdr, fw->data); + if (ret) { + dev_err(dev->dev, "Failed to start PHY firmware\n"); + goto out; + } + +out: + release_firmware(fw); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76_connac3_load_phy_ram); + static u32 mt76_connac2_get_data_mode(struct mt76_dev *dev, u32 info) { u32 mode = DL_MODE_NEED_RSP; @@ -3224,6 +3365,128 @@ int mt76_connac2_load_patch(struct mt76_dev *dev, const char *fw_name) } EXPORT_SYMBOL_GPL(mt76_connac2_load_patch); +int mt76_connac3_load_cb_patch(struct mt76_dev *dev, const char *fw_name) +{ + const struct mt76_connac3_multi_header_v2_sec_raw_format *sect = NULL; + const struct mt76_connac3_multi_header_v2_raw_format *hdr = NULL; + int i, ret, sem, max_len = mt76_is_sdio(dev) ? 2048 : 4096; + const struct firmware *fw = NULL; + const u8 *dl; + u32 len; + + sem = mt76_connac_cb_mcu_patch_sem_ctrl(dev, true); + switch (sem) { + case CB_PATCH_IS_DL: + return 0; + case CB_PATCH_GET_SEM_NEED_PATCH: + break; + default: + dev_err(dev->dev, "Failed to get cb patch semaphore %u\n", sem); + return -EAGAIN; + } + + ret = request_firmware(&fw, fw_name, dev->dev); + if (ret) + goto out; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) { + dev_err(dev->dev, "Invalid firmware\n"); + ret = -EINVAL; + goto out; + } + + hdr = (const void *)fw->data; + dev_info(dev->dev, "HW/SW Version: 0x%x, Pack Time: %.21s\n", + le32_to_cpu(hdr->patch_ver), hdr->pack_time); + + if (le32_to_cpu(hdr->sec_num) < 2) { + dev_err(dev->dev, "Invalid section numbers %u\n", + le32_to_cpu(hdr->sec_num)); + ret = -EINVAL; + goto out; + } + + sect = &hdr->sects[0]; + /* check the fw bin basic info */ + if (((le32_to_cpu(sect->type) & SEC_TYPE_SUBSYS_MASK) + != SEC_TYPE_SUBSYS_CBMCU) || + ((le32_to_cpu(sect->type) & SEC_TYPE_SUBSYS_SEC_MASK) + != SEC_TYPE_SUBSYS_SEC_IMG_SIGN)) { + dev_err(dev->dev, "Invalid CBMCU firmware\n"); + ret = -EINVAL; + goto out; + } + + /* download global desc + sect map + cert section */ + len = sizeof(struct mt76_connac3_multi_header_v2_raw_format) - + offsetof(struct mt76_connac3_multi_header_v2_raw_format, + global_desc_head) + + sizeof(struct mt76_connac3_multi_header_v2_sec_raw_format) * + le32_to_cpu(hdr->sec_num) + + le32_to_cpu(hdr->sects[0].size); + dl = &hdr->global_desc_head[0]; + + ret = mt76_connac_cb_mcu_init_download(dev, len); + if (ret) { + dev_err(dev->dev, "Download cb cert section request failed\n"); + goto out; + } + + ret = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + dl, len, max_len); + if (ret) { + dev_err(dev->dev, "Failed to send cb patch cert section\n"); + goto out; + } + + ret = mt76_connac_cb_mcu_start_patch(dev); + if (ret) { + dev_err(dev->dev, "CBMCU cert check fail\n"); + goto out; + } + + /* download cbmcu idlm */ + for (i = 1; i < le32_to_cpu(hdr->sec_num); i++) { + dl = (uint8_t *)hdr + le32_to_cpu(hdr->sects[i].offset); + len = le32_to_cpu(hdr->sects[i].size); + + ret = mt76_connac_cb_mcu_init_download(dev, len); + if (ret) { + dev_err(dev->dev, + "Download cb section %u request failed\n", i); + goto out; + } + + ret = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + dl, len, max_len); + if (ret) { + dev_err(dev->dev, + "Failed to send cb patch section %u\n", i); + goto out; + } + } + + ret = mt76_connac_cb_mcu_start_patch(dev); + if (ret) + dev_err(dev->dev, "Failed to start cb patch\n"); + +out: + sem = mt76_connac_cb_mcu_patch_sem_ctrl(dev, false); + switch (sem) { + case CB_PATCH_REL_SEM_SUCCESS: + break; + default: + ret = -EAGAIN; + dev_err(dev->dev, "Failed to release cb patch semaphore\n"); + break; + } + + release_firmware(fw); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76_connac3_load_cb_patch); + int mt76_connac2_mcu_fill_message(struct mt76_dev *dev, struct sk_buff *skb, int cmd, int *wait_seq) { diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 78f633ad81a0..0ededa569e9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -35,6 +35,12 @@ #define PATCH_SEC_ENC_SCRAMBLE_INFO_MASK GENMASK(15, 0) #define PATCH_SEC_ENC_AES_KEY_MASK GENMASK(7, 0) +#define SEC_TYPE_SUBSYS_MASK GENMASK(23, 16) +#define SEC_TYPE_SUBSYS_CBMCU BIT(16) + +#define SEC_TYPE_SUBSYS_SEC_MASK GENMASK(15, 0) +#define SEC_TYPE_SUBSYS_SEC_IMG_SIGN 0x05 + enum { FW_TYPE_DEFAULT = 0, FW_TYPE_CLC = 2, @@ -193,6 +199,25 @@ struct mt76_connac2_fw_region { u8 rsv1[14]; } __packed; +struct mt76_connac3_multi_header_v2_sec_raw_format { + __le32 type; + __le32 offset; + __le32 size; + u8 spec[52]; +} __packed; + +struct mt76_connac3_multi_header_v2_raw_format { + u8 pack_time[20]; + u8 chip_id_eco_ver[8]; + __le32 patch_ver; + u8 global_desc_head[4]; + __le32 global_subsys; + u8 rsv2[4]; + __le32 sec_num; + u8 rsv3[48]; + struct mt76_connac3_multi_header_v2_sec_raw_format sects[]; +} __packed; + struct tlv { __le16 tag; __le16 len; @@ -842,6 +867,7 @@ enum { STA_REC_HDRT = 0x28, STA_REC_EML_OP = 0x29, STA_REC_HDR_TRANS = 0x2B, + STA_REC_PS_LEAVE = 0x45, STA_REC_MAX_NUM }; @@ -876,6 +902,7 @@ enum { #define NETWORK_P2P BIT(17) #define NETWORK_IBSS BIT(18) #define NETWORK_WDS BIT(21) +#define NETWORK_NAN BIT(22) #define SCAN_FUNC_RANDOM_MAC BIT(0) #define SCAN_FUNC_RNR_SCAN BIT(3) @@ -888,6 +915,7 @@ enum { #define CONNECTION_IBSS_ADHOC (STA_TYPE_ADHOC | NETWORK_IBSS) #define CONNECTION_WDS (STA_TYPE_WDS | NETWORK_WDS) #define CONNECTION_INFRA_BC (STA_TYPE_BC | NETWORK_INFRA) +#define CONNECTION_NAN (NETWORK_NAN) #define CONN_STATE_DISCONNECT 0 #define CONN_STATE_CONNECT 1 @@ -1066,6 +1094,7 @@ enum { MCU_UNI_EVENT_IE_COUNTDOWN = 0x09, MCU_UNI_EVENT_COREDUMP = 0x0a, MCU_UNI_EVENT_BSS_BEACON_LOSS = 0x0c, + MCU_UNI_EVENT_PS_SYNC = 0x0d, MCU_UNI_EVENT_SCAN_DONE = 0x0e, MCU_UNI_EVENT_RDD_REPORT = 0x11, MCU_UNI_EVENT_ROC = 0x27, @@ -1074,6 +1103,7 @@ enum { MCU_UNI_EVENT_THERMAL = 0x35, MCU_UNI_EVENT_RSSI_MONITOR = 0x41, MCU_UNI_EVENT_NIC_CAPAB = 0x43, + MCU_UNI_EVENT_NAN = 0x56, MCU_UNI_EVENT_WED_RRO = 0x57, MCU_UNI_EVENT_PER_STA_INFO = 0x6d, MCU_UNI_EVENT_ALL_STA_INFO = 0x6e, @@ -1104,6 +1134,13 @@ enum { PATCH_REL_SEM_SUCCESS }; +enum { + CB_PATCH_GET_SEM_NEED_PATCH, + CB_PATCH_IS_DL, + CB_PATCH_NO_SEM_NEED_PATCH, + CB_PATCH_REL_SEM_SUCCESS +}; + enum { FW_STATE_INITIAL, FW_STATE_FW_DOWNLOAD, @@ -1313,6 +1350,7 @@ enum { MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40, MCU_UNI_CMD_RSSI_MONITOR = 0x41, MCU_UNI_CMD_TESTMODE_CTRL = 0x46, + MCU_UNI_CMD_NAN = 0x56, MCU_UNI_CMD_RRO = 0x57, MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58, MCU_UNI_CMD_PER_STA_INFO = 0x6d, @@ -1332,6 +1370,9 @@ enum { MCU_CMD_PATCH_START_REQ = 0x05, MCU_CMD_PATCH_FINISH_REQ = 0x07, MCU_CMD_PATCH_SEM_CONTROL = 0x10, + MCU_CMD_CB_PATCH_SEM_CONTROL = 0x30, + MCU_CMD_CB_PATCH_START_REQ = 0x31, + MCU_CMD_CB_PATCH_FINISH_REQ = 0x32, MCU_CMD_WA_PARAM = 0xc4, MCU_CMD_EXT_CID = 0xed, MCU_CMD_FW_SCATTER = 0xee, @@ -1364,6 +1405,7 @@ enum { MCU_CE_CMD_FWLOG_2_HOST = 0xc5, MCU_CE_CMD_GET_WTBL = 0xcd, MCU_CE_CMD_GET_TXPWR = 0xd0, + MCU_CE_CMD_SET_REGD_CH = 0xd1, }; enum { @@ -2019,6 +2061,9 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, u32 mode); int mt76_connac_mcu_start_patch(struct mt76_dev *dev); int mt76_connac_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get); +int mt76_connac_cb_mcu_init_download(struct mt76_dev *dev, u32 len); +int mt76_connac_cb_mcu_start_patch(struct mt76_dev *dev); +int mt76_connac_cb_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get); int mt76_connac_mcu_start_firmware(struct mt76_dev *dev, u32 addr, u32 option); void mt76_connac_mcu_build_rnr_scan_param(struct mt76_dev *mdev, @@ -2102,7 +2147,9 @@ int mt76_connac_mcu_rdd_cmd(struct mt76_dev *dev, int cmd, u8 index, int mt76_connac_mcu_sta_wed_update(struct mt76_dev *dev, struct sk_buff *skb); int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, const char *fw_wa); +int mt76_connac3_load_phy_ram(struct mt76_dev *dev, const char *fw_name); int mt76_connac2_load_patch(struct mt76_dev *dev, const char *fw_name); +int mt76_connac3_load_cb_patch(struct mt76_dev *dev, const char *fw_name); int mt76_connac2_mcu_fill_message(struct mt76_dev *mdev, struct sk_buff *skb, int cmd, int *wait_seq); #endif /* __MT76_CONNAC_MCU_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c index 14ee5b3b94d3..21f8b1e64101 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c @@ -792,6 +792,9 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb, if (rxinfo & MT_RXINFO_L2PAD) pad_len += 2; + if (rxinfo & MT_RXINFO_CRCERR) + status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (rxinfo & MT_RXINFO_DECRYPT) { status->flag |= RX_FLAG_DECRYPTED; status->flag |= RX_FLAG_MMIC_STRIPPED; @@ -848,7 +851,7 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb, } } - if (WARN_ON_ONCE(len > skb->len)) + if (len > skb->len) return -EINVAL; if (pskb_trim(skb, len)) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c index 4d0854fe785b..0413b4fd2d9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c @@ -52,23 +52,12 @@ mt7915_sys_recovery_set(struct file *file, const char __user *user_buf, struct mt7915_phy *phy = file->private_data; struct mt7915_dev *dev = phy->dev; bool band = phy->mt76->band_idx; - char buf[16]; int ret = 0; u16 val; - if (count >= sizeof(buf)) - return -EINVAL; - - if (copy_from_user(buf, user_buf, count)) - return -EFAULT; - - if (count && buf[count - 1] == '\n') - buf[count - 1] = '\0'; - else - buf[count] = '\0'; - - if (kstrtou16(buf, 0, &val)) - return -EINVAL; + ret = kstrtou16_from_user(user_buf, count, 0, &val); + if (ret) + return ret; switch (val) { /* diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c index eb92cbf1a284..fe7b29ebc0bf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c @@ -257,7 +257,7 @@ void mt7915_eeprom_parse_hw_cap(struct mt7915_dev *dev, nss = min_t(u8, min_t(u8, nss_max, nss), path); mphy->chainmask = BIT(path) - 1; - if (band) + if (band && dev->dbdc_support) mphy->chainmask <<= dev->chainshift; mphy->antenna_mask = BIT(nss) - 1; dev->chainmask |= mphy->chainmask; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 250c2d2479b0..ca46a203aa48 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -177,6 +177,25 @@ static const struct thermal_cooling_device_ops mt7915_thermal_ops = { .set_cur_state = mt7915_thermal_set_cur_throttle_state, }; +static int mt7915_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mt7915_phy *phy = thermal_zone_device_priv(tz); + int val; + + mutex_lock(&phy->dev->mt76.mutex); + val = mt7915_mcu_get_temperature(phy); + mutex_unlock(&phy->dev->mt76.mutex); + if (val < 0) + return val; + + *temp = val * 1000; + return 0; +} + +static const struct thermal_zone_device_ops mt7915_tz_ops = { + .get_temp = mt7915_thermal_get_temp, +}; + static void mt7915_unregister_thermal(struct mt7915_phy *phy) { struct wiphy *wiphy = phy->mt76->hw->wiphy; @@ -213,6 +232,17 @@ static int mt7915_thermal_init(struct mt7915_phy *phy) phy->throttle_temp[MT7915_CRIT_TEMP_IDX] = MT7915_CRIT_TEMP; phy->throttle_temp[MT7915_MAX_TEMP_IDX] = MT7915_MAX_TEMP; + phy->tzone = devm_thermal_of_zone_register(phy->dev->mt76.dev, + phy->mt76->band_idx, phy, + &mt7915_tz_ops); + if (IS_ERR(phy->tzone)) { + if (PTR_ERR(phy->tzone) != -ENODEV) + dev_warn(phy->dev->mt76.dev, + "failed to register thermal zone: %ld\n", + PTR_ERR(phy->tzone)); + phy->tzone = NULL; + } + if (!IS_REACHABLE(CONFIG_HWMON)) return 0; @@ -1272,14 +1302,19 @@ int mt7915_register_device(struct mt7915_dev *dev) ret = mt7915_init_debugfs(&dev->phy); if (ret) - goto unreg_thermal; + goto unreg_ext_phy; ret = mt7915_coredump_register(dev); if (ret) - goto unreg_thermal; + goto unreg_ext_phy; return 0; +unreg_ext_phy: + if (phy2) { + mt7915_unregister_ext_phy(dev); + phy2 = NULL; + } unreg_thermal: mt7915_unregister_thermal(&dev->phy); unreg_dev: @@ -1295,6 +1330,8 @@ int mt7915_register_device(struct mt7915_dev *dev) void mt7915_unregister_device(struct mt7915_dev *dev) { cancel_work_sync(&dev->dump_work); + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->rc_work); mt7915_unregister_ext_phy(dev); mt7915_coredump_unregister(dev); mt7915_unregister_thermal(&dev->phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 334c19ab2b22..b3d4abf6a9ed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -221,7 +221,7 @@ static void mt7915_mac_sta_poll(struct mt7915_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); msta->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); @@ -437,7 +437,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb, if (v0 & MT_PRXV_HT_AD_CODE) status->enc_flags |= RX_ENC_FLAG_LDPC; - status->chains = mphy->antenna_mask; + status->chains = mt7915_band_chainmask(phy); status->chain_signal[0] = to_rssi(MT_PRXV_RCPI0, v1); status->chain_signal[1] = to_rssi(MT_PRXV_RCPI1, v1); status->chain_signal[2] = to_rssi(MT_PRXV_RCPI2, v1); @@ -1292,6 +1292,7 @@ mt7915_mac_restart(struct mt7915_dev *dev) struct mt7915_phy *phy2; struct mt76_phy *ext_phy; struct mt76_dev *mdev = &dev->mt76; + bool run_main, run_ext; int i, ret; ext_phy = dev->mt76.phys[MT_BAND1]; @@ -1387,13 +1388,17 @@ mt7915_mac_restart(struct mt7915_dev *dev) mt7915_init_txpower(phy2); ret = mt7915_txbf_init(dev); - if (test_bit(MT76_STATE_RUNNING, &dev->mphy.state)) { + run_main = test_and_clear_bit(MT76_STATE_RUNNING, &dev->mphy.state); + run_ext = ext_phy && + test_and_clear_bit(MT76_STATE_RUNNING, &ext_phy->state); + + if (run_main) { ret = mt7915_run(dev->mphy.hw); if (ret) goto out; } - if (ext_phy && test_bit(MT76_STATE_RUNNING, &ext_phy->state)) { + if (run_ext) { ret = mt7915_run(ext_phy->hw); if (ret) goto out; @@ -1464,6 +1469,10 @@ mt7915_mac_full_reset(struct mt7915_dev *dev) mutex_unlock(&dev->mt76.mutex); + ieee80211_wake_queues(mt76_hw(dev)); + if (ext_phy) + ieee80211_wake_queues(ext_phy->hw); + ieee80211_restart_hw(mt76_hw(dev)); if (ext_phy) ieee80211_restart_hw(ext_phy->hw); @@ -1925,7 +1934,7 @@ void mt7915_mac_update_stats(struct mt7915_phy *phy) static void mt7915_mac_severe_check(struct mt7915_phy *phy) { struct mt7915_dev *dev = phy->dev; - u32 trb; + u32 trb, ple_err; if (!phy->omac_mask) return; @@ -1945,6 +1954,17 @@ static void mt7915_mac_severe_check(struct mt7915_phy *phy) phy->mt76->band_idx); phy->trb_ts = trb; + + ple_err = mt76_rr(dev, MT_SWDEF_PLE1_STATS); + if ((ple_err & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR) && + !(dev->ple1_sts & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR)) { + dev_warn(dev->mt76.dev, + "band%d: PLE error 0x%x detected, triggering L1 SER\n", + phy->mt76->band_idx, ple_err); + mt7915_mcu_set_ser(dev, SER_RECOVER, SER_SET_RECOVER_L1, + phy->mt76->band_idx); + } + dev->ple1_sts = ple_err; } void mt7915_mac_sta_rc_work(struct work_struct *work) @@ -2345,8 +2365,10 @@ void mt7915_mac_add_twt_setup(struct ieee80211_hw *hw, } flow->tsf = le64_to_cpu(twt_agrt->twt); - if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD)) + if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD)) { + list_del(&flow->list); goto unlock; + } setup_cmd = TWT_SETUP_CMD_ACCEPT; dev->twt.table_mask |= BIT(table_id); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 51643a48ed15..a8286f8becf9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -249,7 +249,7 @@ static int mt7915_add_interface(struct ieee80211_hw *hw, idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev)); if (idx < 0) { ret = -ENOSPC; - goto out; + goto err; } INIT_LIST_HEAD(&mvif->sta.rc_list); @@ -277,7 +277,17 @@ static int mt7915_add_interface(struct ieee80211_hw *hw, mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_PORT_SECURE, true); rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid); + mutex_unlock(&dev->mt76.mutex); + + return 0; + +err: + dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx); + phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx); + mt7915_mcu_add_dev_info(phy, vif, false); out: + if (phy->monitor_vif == vif) + phy->monitor_vif = NULL; mutex_unlock(&dev->mt76.mutex); return ret; @@ -294,7 +304,6 @@ static void mt7915_remove_interface(struct ieee80211_hw *hw, mt7915_mcu_add_bss_info(phy, vif, false); mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_DISCONNECT, false); - mt76_wcid_mask_clear(dev->mt76.wcid_mask, mvif->sta.wcid.idx); mutex_lock(&dev->mt76.mutex); mt76_testmode_reset(phy->mt76, true); @@ -310,6 +319,7 @@ static void mt7915_remove_interface(struct ieee80211_hw *hw, mutex_lock(&dev->mt76.mutex); dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx); phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx); + mt76_wcid_mask_clear(dev->mt76.wcid_mask, mvif->sta.wcid.idx); mutex_unlock(&dev->mt76.mutex); spin_lock_bh(&dev->mt76.sta_poll_lock); @@ -493,7 +503,7 @@ static int mt7915_config(struct ieee80211_hw *hw, int radio_idx, mt76_rmw_field(dev, MT_DMA_DCR0(band), MT_DMA_DCR0_RXD_G5_EN, enabled); - mt76_rmw_field(dev, MT_DMA_DCR0(band), MT_MDP_DCR0_RX_HDR_TRANS_EN, + mt76_rmw_field(dev, MT_MDP_DCR0, MT_MDP_DCR0_RX_HDR_TRANS_EN, !dev->monitor_mask); mt76_testmode_reset(phy->mt76, true); mt76_wr(dev, MT_WF_RFCR(band), rxfilter); @@ -1128,7 +1138,7 @@ mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_an struct mt7915_dev *dev = mt7915_hw_dev(hw); struct mt7915_phy *phy = mt7915_hw_phy(hw); int max_nss = hweight8(hw->wiphy->available_antennas_tx); - u8 chainshift = dev->chainshift; + u8 shift = mt7915_band_chainshift(phy); u8 band = phy->mt76->band_idx; if (!tx_ant || tx_ant != rx_ant || ffs(tx_ant) > max_nss) @@ -1141,13 +1151,14 @@ mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_an /* handle a variant of mt7916/mt7981 which has 3T3R but nss2 on 5 GHz band */ if ((is_mt7916(&dev->mt76) || is_mt7981(&dev->mt76)) && band && hweight8(tx_ant) == max_nss) - phy->mt76->chainmask = (dev->chainmask >> chainshift) << chainshift; + phy->mt76->chainmask = (dev->chainmask >> shift) << shift; else - phy->mt76->chainmask = tx_ant << (chainshift * band); + phy->mt76->chainmask = tx_ant << shift; mt76_set_stream_caps(phy->mt76, true); mt7915_set_stream_vht_txbf_caps(phy); mt7915_set_stream_he_caps(phy); + mt7915_mcu_set_txpower_sku(phy); mutex_unlock(&dev->mt76.mutex); @@ -1743,7 +1754,7 @@ mt7915_net_fill_forward_path(struct ieee80211_hw *hw, path->mtk_wdma.wdma_idx = wed->wdma_idx; path->mtk_wdma.bss = mvif->mt76.idx; path->mtk_wdma.wcid = is_mt7915(&dev->mt76) ? msta->wcid.idx : 0x3ff; - path->mtk_wdma.queue = phy != &dev->phy; + path->mtk_wdma.queue = phy->mt76->band_idx; ctx->dev = NULL; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index e8fe86f93309..5bfe0366562f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -51,7 +51,7 @@ mt7915_mcu_get_sta_nss(u16 mcs_map) break; } - return nss - 1; + return nss ? nss - 1 : 0; } static void @@ -402,6 +402,21 @@ mt7915_mcu_rx_bcc_notify(struct mt7915_dev *dev, struct sk_buff *skb) mt7915_mcu_cca_finish, mphy->hw); } +static void +mt7915_mcu_rx_ps_sync(struct mt7915_dev *dev, struct sk_buff *skb) +{ + struct mt7915_mcu_ps_notify *p = (void *)skb->data; + struct mt76_wcid *wcid; + u16 wcid_idx; + + wcid_idx = p->wtbl_lower | (p->wtbl_higher << 8); + wcid = mt76_wcid_ptr(dev, wcid_idx); + if (!wcid || !wcid_to_sta(wcid)) + return; + + mt76_sta_ps_transition(&dev->mt76, wcid, !!p->ps_bit); +} + static void mt7915_mcu_rx_ext_event(struct mt7915_dev *dev, struct sk_buff *skb) { @@ -424,6 +439,9 @@ mt7915_mcu_rx_ext_event(struct mt7915_dev *dev, struct sk_buff *skb) case MCU_EXT_EVENT_BCC_NOTIFY: mt7915_mcu_rx_bcc_notify(dev, skb); break; + case MCU_EXT_EVENT_PS_SYNC: + mt7915_mcu_rx_ps_sync(dev, skb); + break; default: break; } @@ -576,9 +594,9 @@ mt7915_mcu_bss_ra_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, ra->rx_streams = max_nss; ra->algo = 4; ra->train_up_rule = 2; - ra->train_up_high_thres = 110; - ra->train_up_rule_rssi = -70; - ra->low_traffic_thres = 2; + ra->train_up_high_thres = cpu_to_le16(110); + ra->train_up_rule_rssi = cpu_to_le16(-70); + ra->low_traffic_thres = cpu_to_le16(2); ra->phy_cap = cpu_to_le32(0xfdf); ra->interval = cpu_to_le32(500); ra->fast_interval = cpu_to_le32(100); @@ -902,7 +920,7 @@ mt7915_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta, HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]); he->dcm_rx_max_nss = HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]); - he->dcm_rx_max_nss = + he->dcm_max_ru = HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]); he->pkt_ext = 2; @@ -1625,7 +1643,7 @@ mt7915_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7915_dev *dev, cap |= STA_CAP_VHT_SGI_160; if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC) cap |= STA_CAP_VHT_TX_STBC; - if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1) + if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK) cap |= STA_CAP_VHT_RX_STBC; if (mvif->cap.vht_ldpc && (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC)) @@ -2804,7 +2822,7 @@ int mt7915_mcu_set_chan_info(struct mt7915_phy *phy, int cmd) .center_ch = ieee80211_frequency_to_channel(freq1), .bw = mt76_connac_chan_bw(chandef), .tx_path_num = hweight16(phy->mt76->chainmask), - .rx_path = phy->mt76->chainmask >> (dev->chainshift * band), + .rx_path = mt7915_band_chainmask(phy), .band_idx = band, .channel_band = ch_band[chandef->chan->band], }; @@ -2917,8 +2935,15 @@ int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset, u8 *read_buf) return ret; res = (struct mt7915_mcu_eeprom_info *)skb->data; - if (!buf) - buf = dev->mt76.eeprom.data + le32_to_cpu(res->addr); + if (!buf) { + u32 addr = le32_to_cpu(res->addr); + + if (addr > dev->mt76.eeprom.size - MT7915_EEPROM_BLOCK_SIZE) { + dev_kfree_skb(skb); + return -EINVAL; + } + buf = dev->mt76.eeprom.data + addr; + } memcpy(buf, res->data, MT7915_EEPROM_BLOCK_SIZE); dev_kfree_skb(skb); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h index 22f73a5ed425..76e39e899797 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h @@ -54,6 +54,15 @@ struct mt7915_mcu_bcc_notify { u8 rsv; } __packed; +struct mt7915_mcu_ps_notify { + struct mt76_connac2_mcu_rxd_hdr rxd; + + u8 wtbl_lower; + u8 ps_bit; + u8 wtbl_higher; + u8 rsv; +} __packed; + struct mt7915_mcu_rdd_report { struct mt76_connac2_mcu_rxd_hdr rxd; @@ -318,9 +327,9 @@ struct bss_info_ra { u8 antenna_idx; u8 train_up_rule; u8 rsv[3]; - unsigned short train_up_high_thres; - short train_up_rule_rssi; - unsigned short low_traffic_thres; + __le16 train_up_high_thres; + __le16 train_up_rule_rssi; + __le16 low_traffic_thres; __le16 max_phyrate; __le32 phy_cap; __le32 interval; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c index 2708b1556f40..28377c964843 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c @@ -921,7 +921,8 @@ struct mt7915_dev *mt7915_mmio_probe(struct device *pdev, /* txwi_size = txd size + txp size */ .txwi_size = MT_TXD_SIZE + sizeof(struct mt76_connac_fw_txp), .drv_flags = MT_DRV_TXWI_NO_FREE | MT_DRV_HW_MGMT_TXQ | - MT_DRV_AMSDU_OFFLOAD, + MT_DRV_AMSDU_OFFLOAD | + MT_DRV_HW_PS_BUFFERING, .survey_flags = SURVEY_INFO_TIME_TX | SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_BSS_RX, diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h index bf1d915a3ca2..30fb119ce872 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h @@ -205,6 +205,7 @@ struct mt7915_phy { struct ieee80211_vif *monitor_vif; + struct thermal_zone_device *tzone; struct thermal_cooling_device *cdev; u8 cdev_state; u8 throttle_state; @@ -295,6 +296,7 @@ struct mt7915_dev { spinlock_t reg_lock; u32 hw_pattern; + u32 ple1_sts; bool dbdc_support; bool flash_mode; @@ -397,6 +399,24 @@ mt7915_ext_phy(struct mt7915_dev *dev) return phy->priv; } +/* without dbdc, the chainmask is stored unshifted, even if the phy is + * bound to band 1 + */ +static inline u8 mt7915_band_chainshift(struct mt7915_phy *phy) +{ + struct mt7915_dev *dev = phy->dev; + + if (!dev->dbdc_support) + return 0; + + return phy->mt76->band_idx * dev->chainshift; +} + +static inline u16 mt7915_band_chainmask(struct mt7915_phy *phy) +{ + return phy->mt76->chainmask >> mt7915_band_chainshift(phy); +} + static inline u32 mt7915_check_adie(struct mt7915_dev *dev, bool sku) { u32 mask = sku ? MT_CONNINFRA_SKU_MASK : MT_ADIE_TYPE_MASK; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c index f6b03211a879..8007e620048b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c @@ -135,7 +135,6 @@ static int mt7915_pci_probe(struct pci_dev *pdev, mdev = &dev->mt76; mt7915_wfsys_reset(dev); - hif2 = mt7915_pci_init_hif2(pdev); ret = mt7915_mmio_wed_init(dev, pdev, true, &irq); if (ret < 0) @@ -145,16 +144,20 @@ static int mt7915_pci_probe(struct pci_dev *pdev, hif2 = mt7915_pci_init_hif2(pdev); ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); - if (ret < 0) + if (ret < 0) { + mt7915_put_hif2(hif2); goto free_device; + } irq = pdev->irq; } ret = devm_request_irq(mdev->dev, irq, mt7915_irq_handler, IRQF_SHARED, KBUILD_MODNAME, dev); - if (ret) + if (ret) { + mt7915_put_hif2(hif2); goto free_wed_or_irq_vector; + } /* master switch of PCIe tnterrupt enable */ mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h index 307bf6a75674..dc7bb9a1f223 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h @@ -1044,6 +1044,7 @@ enum offs_rev { #define MT_SWDEF_SER_STATS MT_SWDEF(0x040) #define MT_SWDEF_PLE_STATS MT_SWDEF(0x044) #define MT_SWDEF_PLE1_STATS MT_SWDEF(0x048) +#define MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR BIT(3) #define MT_SWDEF_PLE_AMSDU_STATS MT_SWDEF(0x04C) #define MT_SWDEF_PSE_STATS MT_SWDEF(0x050) #define MT_SWDEF_PSE1_STATS MT_SWDEF(0x054) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/soc.c b/drivers/net/wireless/mediatek/mt76/mt7915/soc.c index 54ff6de96f3e..13fba2a061c7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/soc.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/soc.c @@ -908,7 +908,7 @@ static void mt7986_wmac_clock_enable(struct mt7915_dev *dev, u32 adie_type) read_poll_timeout(mt76_rr, cur, !(cur & MT_SLP_CTRL_BSY_MASK), USEC_PER_MSEC, 50 * USEC_PER_MSEC, false, - dev, MT_ADIE_SLP_CTRL_CK0(0)); + dev, MT_ADIE_SLP_CTRL_CK0(1)); } mt76_wmac_spi_unlock(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c b/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c index 618a5c2bdd29..7576973f4d4e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c @@ -694,9 +694,7 @@ mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb, { struct mt76_testmode_data *td = &mphy->test; struct mt7915_phy *phy = mphy->priv; - struct mt7915_dev *dev = phy->dev; - u32 chainmask = mphy->chainmask, changed = 0; - bool ext_phy = phy != &dev->phy; + u32 chainmask = mt7915_band_chainmask(phy), changed = 0; int i; BUILD_BUG_ON(NUM_TM_CHANGED >= 32); @@ -705,7 +703,6 @@ mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb, td->state == MT76_TM_STATE_OFF) return 0; - chainmask = ext_phy ? chainmask >> dev->chainshift : chainmask; if (td->tx_antenna_mask > chainmask) return -EINVAL; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index f7d54472da1b..e69978184f68 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -156,7 +156,7 @@ static void mt7921_mac_sta_poll(struct mt792x_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); @@ -674,6 +674,9 @@ void mt7921_mac_reset_work(struct work_struct *work) cancel_work_sync(&pm->wake_work); for (i = 0; i < 10; i++) { + if (atomic_read(&dev->mt76.bus_hung)) + return; + mutex_lock(&dev->mt76.mutex); ret = mt792x_dev_reset(dev); mutex_unlock(&dev->mt76.mutex); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 25b9437250f7..a118a301564c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -351,8 +351,10 @@ void mt7921_mcu_rx_event(struct mt792x_dev *dev, struct sk_buff *skb) { struct mt76_connac2_mcu_rxd *rxd; - if (skb_linearize(skb)) + if (skb_linearize(skb)) { + dev_kfree_skb(skb); return; + } rxd = (struct mt76_connac2_mcu_rxd *)skb->data; @@ -415,7 +417,8 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) struct mt76_dev *mdev = &dev->mt76; struct mt792x_phy *phy = &dev->phy; const struct firmware *fw; - int ret, i, len, offset = 0; + size_t clc_len, fw_data_len, len, offset = 0; + int ret, i; u8 *clc_base = NULL, hw_encap = 0; dev->phy.clc_chan_conf = 0xff; @@ -441,13 +444,21 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) } hdr = (const void *)(fw->data + fw->size - sizeof(*hdr)); + if (hdr->n_region > (fw->size - sizeof(*hdr)) / sizeof(*region)) { + dev_err(mdev->dev, "Invalid firmware region table\n"); + ret = -EINVAL; + goto out; + } + fw_data_len = fw->size - sizeof(*hdr) - + hdr->n_region * sizeof(*region); + for (i = 0; i < hdr->n_region; i++) { region = (const void *)((const u8 *)hdr - (hdr->n_region - i) * sizeof(*region)); len = le32_to_cpu(region->len); /* check if we have valid buffer size */ - if (offset + len > fw->size) { + if (len > fw_data_len - offset) { dev_err(mdev->dev, "Invalid firmware region\n"); ret = -EINVAL; goto out; @@ -464,8 +475,19 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) if (!clc_base) goto out; - for (offset = 0; offset < len; offset += le32_to_cpu(clc->len)) { + for (offset = 0; offset < len; offset += clc_len) { + if (len - offset < sizeof(*clc)) { + ret = -EINVAL; + goto out; + } + clc = (const struct mt7921_clc *)(clc_base + offset); + clc_len = le32_to_cpu(clc->len); + if (clc_len < sizeof(*clc) || clc_len > len - offset || + clc->idx >= ARRAY_SIZE(phy->clc)) { + ret = -EINVAL; + goto out; + } /* do not init buf again if chip reset triggered */ if (phy->clc[clc->idx]) @@ -477,7 +499,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) continue; phy->clc[clc->idx] = devm_kmemdup(mdev->dev, clc, - le32_to_cpu(clc->len), + clc_len, GFP_KERNEL); if (!phy->clc[clc->idx]) { @@ -1403,6 +1425,9 @@ int mt7921_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, /* submit all clc config */ for (i = 0; i < ARRAY_SIZE(phy->clc); i++) { + if (i == MT792x_CLC_REGD) + continue; + ret = __mt7921_mcu_set_clc(dev, alpha2, env_cap, phy->clc[i], i); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 7728c5ae6791..56a914aaa81e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -286,6 +286,10 @@ static int mt7921_pci_probe(struct pci_dev *pdev, .drv_own = mt792xe_mcu_drv_pmctrl, .fw_own = mt792xe_mcu_fw_pmctrl, }; + static const struct mt792x_pcie_reg mt7921_pcie_reg = { + .imask = MT_PCIE_MAC_INT_ENABLE, + .pm = MT_PCIE_MAC_PM, + }; static const struct mt792x_irq_map irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -293,6 +297,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev, .mcu_complete_mask = MT_INT_TX_DONE_MCU, }, .rx = { + .all_complete_mask = MT_INT_RX_DONE_ALL, .data_complete_mask = MT_INT_RX_DONE_DATA, .wm_complete_mask = MT_INT_RX_DONE_WM, .wm2_complete_mask = MT_INT_RX_DONE_WM2, @@ -354,6 +359,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev, dev->fw_features = features; dev->hif_ops = &mt7921_pcie_ops; + dev->pcie_reg = &mt7921_pcie_reg; dev->irq_map = &irq_map; mt76_mmio_init(&dev->mt76, regs); @@ -560,7 +566,8 @@ static int mt7921_pci_resume(struct device *device) mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.all_complete_mask | + MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); /* put dma enabled */ @@ -600,6 +607,108 @@ static int mt7921_pci_resume(struct device *device) return err; } +static u32 mt7921_aer_rr(struct mt76_dev *mdev, u32 offset) +{ + return 0; +} + +static void mt7921_aer_wr(struct mt76_dev *mdev, u32 offset, u32 val) +{ + ; +} + +static u32 mt791_aer_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) +{ + return 0; +} + +static const struct mt76_bus_ops mt7921_aer_bus_hung_ops = { + .rr = mt7921_aer_rr, + .wr = mt7921_aer_wr, + .rmw = mt791_aer_rmw, + .type = MT76_BUS_MMIO +}; + +static void mt7921_pci_set_aer_bus_hung_ops(struct mt792x_dev *dev) +{ + if (READ_ONCE(dev->mt76.bus) == &mt7921_aer_bus_hung_ops) + return; + + atomic_set(&dev->mt76.bus_hung, true); + WRITE_ONCE(dev->mt76.bus, &mt7921_aer_bus_hung_ops); +} + +static pci_ers_result_t mt7921_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct mt76_dev *mdev = pci_get_drvdata(pdev); + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + u32 aer_unc_val = 0, aer_co_val = 0; + + dev_err(mdev->dev, "PCIE error detect state: %d\n", state); + + /* Clear SW IRQ tasklet first */ + tasklet_kill(&mdev->irq_tasklet); + + if (state == pci_channel_io_perm_failure) { + mt7921_pci_set_aer_bus_hung_ops(dev); + return PCI_ERS_RESULT_DISCONNECT; + } + + pci_read_config_dword(pdev, PCIE_AER_UNC_STATUS_OFFSET, &aer_unc_val); + pci_read_config_dword(pdev, PCIE_AER_CO_STATUS_OFFSET, &aer_co_val); + + dev_warn(mdev->dev, "PCIE_AER_UNC_STATUS_OFFSET: 0x%x\n", aer_unc_val); + dev_warn(mdev->dev, "PCIE_AER_CO_STATUS_OFFSET: 0x%x\n", aer_co_val); + + /** + * Due to this error is from link error and this AER is un-correctable, + * so can't covered by device + **/ + if (aer_unc_val != 0) { + mt7921_pci_set_aer_bus_hung_ops(dev); + return PCI_ERS_RESULT_DISCONNECT; + } + + /** + * Try to recover it when state is pci_channel_io_frozen or + * AER is correctable error + **/ + if (state == pci_channel_io_frozen || aer_co_val != 0) { + /* Disable PCIE activity first. */ + pci_disable_device(pdev); + return PCI_ERS_RESULT_NEED_RESET; + } + + return PCI_ERS_RESULT_NONE; +} + +static pci_ers_result_t mt7921_slot_reset(struct pci_dev *pdev) +{ + struct mt76_dev *mdev = pci_get_drvdata(pdev); + int ret = 0; + + ret = pci_enable_device_mem(pdev); + + if (ret) { + dev_err(mdev->dev, "pci_enable_device_mem failed: %d\n", ret); + return PCI_ERS_RESULT_DISCONNECT; + } + + pci_set_master(pdev); + pci_restore_state(pdev); + pci_save_state(pdev); + /* Also try do the vendor reset to let it more clear. */ + mt792x_reset(mdev); + + return PCI_ERS_RESULT_RECOVERED; +} + +static const struct pci_error_handlers mt7921_err_handler = { + .error_detected = mt7921_error_detected, + .slot_reset = mt7921_slot_reset, +}; + static void mt7921_pci_shutdown(struct pci_dev *pdev) { mt7921_pci_remove(pdev); @@ -614,6 +723,7 @@ static struct pci_driver mt7921_pci_driver = { .remove = mt7921_pci_remove, .shutdown = mt7921_pci_shutdown, .driver.pm = pm_sleep_ptr(&mt7921_pm_ops), + .err_handler = &mt7921_err_handler, }; module_pci_driver(mt7921_pci_driver); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c index 0db7acb3a637..98b3469ac597 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c @@ -96,7 +96,8 @@ int mt7921e_mac_reset(struct mt792x_dev *dev) mt76_wr(dev, dev->irq_map->host_irq_enable, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.all_complete_mask | + MT_INT_MCU_CMD); mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); err = mt7921e_driver_own(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index 4722007abafd..4a8ea4624fee 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -10,6 +10,15 @@ static bool mt7921_disable_clc; module_param_named(disable_clc, mt7921_disable_clc, bool, 0644); MODULE_PARM_DESC(disable_clc, "disable CLC support"); +static struct ieee80211_regdomain mt7921_regd_ww = { + .n_reg_rules = 1, + .alpha2 = "00", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412 - 10, 2462 + 10, 40, 6, 20, 0), + } +}; + bool mt7921_regd_clc_supported(struct mt792x_dev *dev) { if (mt7921_disable_clc || @@ -33,6 +42,9 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) np = mt76_find_power_limits_node(mdev); sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (!sband) + return; + band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL; for (i = 0; i < sband->n_channels; i++) { ch = &sband->channels[i]; @@ -71,14 +83,15 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) } } -/* Internal version that assumes mutex is already held by caller */ int __mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env) { struct mt76_dev *mdev = &dev->mt76; struct ieee80211_hw *hw = mdev->hw; struct wiphy *wiphy = hw->wiphy; - int ret = 0; + int ret; + + lockdep_assert_held(&dev->mt76.mutex); if (!dev->regd_change) return 0; @@ -101,7 +114,7 @@ int __mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env) { - int ret = 0; + int ret; dev->regd_in_progress = true; @@ -150,10 +163,176 @@ void mt7921_regd_notifier(struct wiphy *wiphy, if (pm->suspended) return; + if (MT7921_REGD_SUPPORTED(&dev->phy)) { + mt7921_regd_update(&dev->phy, req->alpha2); + + return; + } + mt7921_mcu_regd_update(dev, req->alpha2, req->country_ie_env); } +static struct sk_buff * +mt7921_regd_query_regdb(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7921_clc *clc = phy->clc[MT792x_CLC_REGD]; + struct mt7921_regd_query_req *req; + struct mt7921_regd_cc *regd_cc; + struct sk_buff *ret_skb = NULL; + u8 *pos, *last_pos; + int ret = 0; + + if (!clc) + return NULL; + + pos = clc->data; + last_pos = pos + le32_to_cpu(clc->len) - sizeof(struct mt7921_clc); + while (pos < last_pos) { + u32 req_len = 0; + u32 rules_len = 0; + u32 sign_len = 4; + u32 n_reg_rules; + + if (pos + sizeof(*regd_cc) > last_pos) + break; + + regd_cc = (struct mt7921_regd_cc *)pos; + n_reg_rules = le32_to_cpu(regd_cc->n_reg_rules); + if (n_reg_rules > NL80211_MAX_SUPP_REG_RULES) + break; + + rules_len = sizeof(struct mt7921_regd_rule_header) + + sizeof(struct mt7921_regd_rule) * n_reg_rules; + + if (pos + sizeof(*regd_cc) + rules_len + sign_len > last_pos) + break; + + pos += sizeof(*regd_cc) + rules_len + sign_len; + if (memcmp(regd_cc->alpha2, alpha2, 2)) + continue; + + req_len = sizeof(*req) + rules_len + sign_len; + req = kzalloc(req_len, GFP_KERNEL); + + if (!req) + return NULL; + + req->ver = regd_cc->ver; + req->sign_type = regd_cc->sign_type; + req->size = cpu_to_le32(rules_len + sign_len); + req->n_reg_rules = regd_cc->n_reg_rules; + + memcpy(req->alpha2, regd_cc->alpha2, 2); + memcpy(req->data, regd_cc->data, rules_len + sign_len); + + ret = mt76_mcu_send_and_get_msg(&dev->mt76, + MCU_CE_CMD(SET_REGD_CH), + req, req_len, true, &ret_skb); + + kfree(req); + + return ret < 0 ? NULL : ret_skb; + } + + return NULL; +} + +int mt7921_regd_update(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7921_regd_rule *mt7921_rule; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + struct mt7921_regd_rule_ev *ev; + int i, num_of_rules = 0; + struct sk_buff *skb; + int ret = 0; + + if (dev->hw_full_reset) + return 0; + + if (!MT7921_REGD_SUPPORTED(phy)) + return -EOPNOTSUPP; + + mt792x_mutex_acquire(dev); + skb = mt7921_regd_query_regdb(phy, alpha2); + mt792x_mutex_release(dev); + + if (!skb) { + ret = -EINVAL; + goto err; + } + + if (skb->len < sizeof(*ev) + 4) { + ret = -EINVAL; + goto err; + } + + ev = (struct mt7921_regd_rule_ev *)(skb->data + 4); + num_of_rules = le32_to_cpu(ev->n_reg_rules); + + if (!num_of_rules || + WARN_ON_ONCE(num_of_rules > NL80211_MAX_SUPP_REG_RULES)) { + ret = -EINVAL; + goto err; + } + + if (skb->len < struct_size(ev, reg_rule, num_of_rules) + 4) { + ret = -EINVAL; + goto err; + } + + regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + if (!regd) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < num_of_rules; i++) { + mt7921_rule = &ev->reg_rule[i]; + rule = ®d->reg_rules[i]; + + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->start_freq)); + rule->freq_range.end_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->end_freq)); + rule->freq_range.max_bandwidth_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->max_bw)); + /* not used by fw */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = DBM_TO_MBM(22); + rule->flags = le32_to_cpu(mt7921_rule->flags); + } + + regd->n_reg_rules = num_of_rules; + regd->dfs_region = ev->dfs_region; + + memcpy(regd->alpha2, alpha2, 2); + memcpy(mdev->alpha2, alpha2, 2); + + dev->regd_change = true; + mt7921_mcu_regd_update(dev, alpha2, ENVIRON_ANY); + + ret = regulatory_set_wiphy_regd(wiphy, regd); + + kfree(regd); +err: + dev_kfree_skb(skb); + + if (ret < 0) + return regulatory_set_wiphy_regd(wiphy, &mt7921_regd_ww); + + return ret; +} +EXPORT_SYMBOL_GPL(mt7921_regd_update); + static bool mt7921_regd_is_valid_alpha2(const char *alpha2) { @@ -191,7 +370,9 @@ int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2) if (!memcmp(alpha2, mdev->alpha2, 2)) return 0; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + if (MT7921_REGD_SUPPORTED(phy)) + return mt7921_regd_update(phy, alpha2); + else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) return regulatory_hint(wiphy, alpha2); else return mt7921_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR); @@ -204,7 +385,11 @@ int mt7921_regd_init(struct mt792x_phy *phy) struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt76_dev *mdev = &dev->mt76; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + if (MT7921_REGD_SUPPORTED(phy)) { + wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED | + REGULATORY_DISABLE_BEACON_HINTS; + return mt7921_regd_update(phy, "00"); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE | REGULATORY_DISABLE_BEACON_HINTS; else diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index 5b24d0902c36..d4223ba8fbd5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -4,9 +4,57 @@ #ifndef __MT7921_REGD_H #define __MT7921_REGD_H -struct mt792x_dev; -struct wiphy; -struct regulatory_request; +#include "mt7921.h" + +struct mt7921_regd_rule_header { + u8 alpha2[2]; + u8 dfs_region; + u8 rsv[13]; +}; + +struct mt7921_regd_rule { + __le32 start_freq; + __le32 end_freq; + __le32 max_bw; + __le32 eirp; + __le32 flags; + u8 rsv[12]; +}; + +struct mt7921_regd_cc { + u8 alpha2[2]; + u8 ver; + u8 rsv; + __le32 n_reg_rules; + u8 sign_type; + u8 rsv1[7]; + u8 data[]; +}; + +struct mt7921_regd_rule_ev { + __le16 tag; + __le16 len; + __le32 n_reg_rules; + u8 dfs_region; + u8 rsv[15]; + struct mt7921_regd_rule reg_rule[]; +}; + +struct mt7921_regd_query_req { + u8 ver; + u8 sign_type; + u8 rsv1[2]; + __le32 size; + u8 alpha2[2]; + u8 rsv2[2]; + __le32 n_reg_rules; + u8 rsv3[64]; + u8 data[]; +}; + +#define MT7921_REGD_SUPPORTED(phy) \ + (((phy)->chip_cap & MT792x_CHIP_CAP_REGD_EN) && \ + (phy)->clc[MT792x_CLC_REGD]) int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env); @@ -17,5 +65,6 @@ void mt7921_regd_notifier(struct wiphy *wiphy, bool mt7921_regd_clc_supported(struct mt792x_dev *dev); int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2); int mt7921_regd_init(struct mt792x_phy *phy); +int mt7921_regd_update(struct mt792x_phy *phy, char *alpha2); #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regs.h b/drivers/net/wireless/mediatek/mt76/mt7921/regs.h index 4d9eaf1e0692..75b4e81c7224 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regs.h @@ -78,4 +78,9 @@ #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(9, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(12) +#define MT_PCIE_MAC_BASE 0x10000 +#define MT_PCIE_MAC(ofs) (MT_PCIE_MAC_BASE + (ofs)) +#define MT_PCIE_MAC_INT_ENABLE MT_PCIE_MAC(0x188) +#define MT_PCIE_MAC_PM MT_PCIE_MAC(0x194) + #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile index 8f1078ce3231..f9dcc0bba393 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile +++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o obj-$(CONFIG_MT7925E) += mt7925e.o obj-$(CONFIG_MT7925U) += mt7925u.o -mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o +mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o mt7925e-y := pci.o pci_mac.o pci_mcu.o mt7925u-y := usb.o diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index e85b0d104fbe..cd22fcc021b1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -137,10 +137,18 @@ static int mt7925_init_hardware(struct mt792x_dev *dev) set_bit(MT76_STATE_INITIALIZED, &dev->mphy.state); for (i = 0; i < MT792x_MCU_INIT_RETRY_COUNT; i++) { + if (atomic_read(&dev->mt76.bus_hung)) { + ret = -EIO; + break; + } + ret = __mt7925_init_hardware(dev); if (!ret) break; + if (atomic_read(&dev->mt76.bus_hung)) + break; + mt792x_init_reset(dev); } @@ -152,6 +160,33 @@ static int mt7925_init_hardware(struct mt792x_dev *dev) return 0; } +static int mt7925_init_nan_cap(struct mt76_dev *mdev) +{ + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + const struct ieee80211_sta_he_cap *he_cap; + struct ieee80211_supported_band *sband; + struct wiphy *wiphy = mdev->hw->wiphy; + + if (!(dev->fw_features & MT792x_FW_CAP_NAN)) + return 0; + + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (sband) + wiphy->nan_capa.phy.ht = sband->ht_cap; + + sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (sband) + wiphy->nan_capa.phy.vht = sband->vht_cap; + + sband = wiphy->bands[NL80211_BAND_2GHZ]; + he_cap = sband ? ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_NAN) + : NULL; + if (he_cap) + wiphy->nan_capa.phy.he = *he_cap; + + return 0; +} + static void mt7925_init_work(struct work_struct *work) { struct mt792x_dev *dev = container_of(work, struct mt792x_dev, @@ -172,6 +207,8 @@ static void mt7925_init_work(struct work_struct *work) return; } + dev->mt76.init_wiphy = mt7925_init_nan_cap; + ret = mt76_register_device(&dev->mt76, true, mt76_rates, ARRAY_SIZE(mt76_rates)); if (ret) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 6b0cd1996ecb..101f571b027f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -12,10 +12,17 @@ bool mt7925_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask) { - mt76_rmw(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_WLAN_IDX, + u32 wtbl_update; + + if (is_mt7928(&dev->mt76)) + wtbl_update = MT7928_WTBL_UPDATE; + else + wtbl_update = MT7925_WTBL_UPDATE; + + mt76_rmw(dev, wtbl_update, MT_WTBL_UPDATE_WLAN_IDX, FIELD_PREP(MT_WTBL_UPDATE_WLAN_IDX, idx) | mask); - return mt76_poll(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, + return mt76_poll(dev, wtbl_update, MT_WTBL_UPDATE_BUSY, 0, 5000); } @@ -145,7 +152,7 @@ static void mt7925_mac_sta_poll(struct mt792x_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); @@ -159,10 +166,17 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev, { u32 ctrl = MT_WTBL_ITCR_WR | MT_WTBL_ITCR_EXEC | tbl_idx; - mt76_wr(dev, MT_WTBL_ITDR0, rate_idx); - /* use wtbl spe idx */ - mt76_wr(dev, MT_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); - mt76_wr(dev, MT_WTBL_ITCR, ctrl); + if (is_mt7928(&dev->mt76)) { + mt76_wr(dev, MT7928_WTBL_ITDR0, rate_idx); + /* use wtbl spe idx */ + mt76_wr(dev, MT7928_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); + mt76_wr(dev, MT7928_WTBL_ITCR, ctrl); + } else { + mt76_wr(dev, MT_WTBL_ITDR0, rate_idx); + /* use wtbl spe idx */ + mt76_wr(dev, MT_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); + mt76_wr(dev, MT_WTBL_ITCR, ctrl); + } } /* The HW does not translate the mac header to 802.3 for mesh point */ @@ -705,6 +719,9 @@ mt7925_mac_write_txwi_80211(struct mt76_dev *dev, __le32 *txwi, txwi[2] |= cpu_to_le32(val); + if (is_mt7928(dev) && ieee80211_is_mgmt(hdr->frame_control)) + txwi[3] &= ~cpu_to_le32(MT_TXD3_HW_AMSDU); + txwi[3] |= cpu_to_le32(FIELD_PREP(MT_TXD3_BCM, multicast)); if (ieee80211_is_beacon(fc)) txwi[3] |= cpu_to_le32(MT_TXD3_REM_TX_COUNT); @@ -808,7 +825,11 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, txwi[5] = cpu_to_le32(val); - val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT, 1); + if (is_mt7928(dev)) + val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT_V2, 1); + else + val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT, 1); + if (vif && (!ieee80211_vif_is_mld(vif) || (q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0))) val |= MT_TXD6_DIS_MAT; @@ -836,6 +857,10 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, } txwi[6] |= cpu_to_le32(FIELD_PREP(MT_TXD6_TX_RATE, idx)); + + if (is_mt7928(dev)) + txwi[6] |= cpu_to_le32(FIELD_PREP(MT_TXD6_BW_V2, 8)); + txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE); } } @@ -916,7 +941,6 @@ mt7925_mac_add_txs_skb(struct mt792x_dev *dev, struct mt76_wcid *wcid, goto out_no_skb; txs = le32_to_cpu(txs_data[0]); - info = IEEE80211_SKB_CB(skb); if (!(txs & MT_TXS0_ACK_ERROR_MASK)) info->flags |= IEEE80211_TX_STAT_ACK; @@ -1033,17 +1057,192 @@ mt7925_mac_add_txs_skb(struct mt792x_dev *dev, struct mt76_wcid *wcid, return !!skb; } -void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data) +static bool +mt7928_mac_add_txs_skb_msg(struct mt792x_dev *dev, struct mt76_wcid *wcid, + int pid, struct mt7928_uni_txdone_event *pevt) { + struct mt76_sta_stats *stats = &wcid->stats; + struct ieee80211_supported_band *sband; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_tx_info *info; + struct sk_buff_head list; + u32 txrate, mode, stbc; + struct rate_info rate; + struct mt76_phy *mphy; + struct sk_buff *skb; + bool cck = false; + + mt76_tx_status_lock(mdev, &list); + skb = mt76_tx_status_skb_get(mdev, wcid, pid, &list); + if (!skb) + goto out_no_skb; + + info = IEEE80211_SKB_CB(skb); + if (!pevt->status) + info->flags |= IEEE80211_TX_STAT_ACK; + + info->status.ampdu_len = 1; + info->status.ampdu_ack_len = !!(info->flags & + IEEE80211_TX_STAT_ACK); + + info->status.rates[0].idx = -1; + + txrate = le16_to_cpu(pevt->tx_rate); + + rate.mcs = FIELD_GET(MT_TX_RATE_IDX, txrate); + rate.nss = FIELD_GET(MT_TX_RATE_NSS, txrate) + 1; + stbc = FIELD_GET(MT_TX_RATE_STBC, txrate); + + if (stbc && rate.nss > 1) + rate.nss >>= 1; + + if (rate.nss - 1 < ARRAY_SIZE(stats->tx_nss)) + stats->tx_nss[rate.nss - 1]++; + if (rate.mcs < ARRAY_SIZE(stats->tx_mcs)) + stats->tx_mcs[rate.mcs]++; + + mode = FIELD_GET(MT_TX_RATE_MODE, txrate); + switch (mode) { + case MT_PHY_TYPE_CCK: + cck = true; + fallthrough; + case MT_PHY_TYPE_OFDM: + mphy = mt76_dev_phy(mdev, wcid->phy_idx); + + if (mphy->chandef.chan->band == NL80211_BAND_5GHZ) + sband = &mphy->sband_5g.sband; + else if (mphy->chandef.chan->band == NL80211_BAND_6GHZ) + sband = &mphy->sband_6g.sband; + else + sband = &mphy->sband_2g.sband; + + rate.mcs = mt76_get_rate(mphy->dev, sband, rate.mcs, cck); + rate.legacy = sband->bitrates[rate.mcs].bitrate; + break; + case MT_PHY_TYPE_HT: + case MT_PHY_TYPE_HT_GF: + if (rate.mcs > 31) + goto out; + + rate.flags = RATE_INFO_FLAGS_MCS; + if (wcid->rate.flags & RATE_INFO_FLAGS_SHORT_GI) + rate.flags |= RATE_INFO_FLAGS_SHORT_GI; + break; + case MT_PHY_TYPE_VHT: + if (rate.mcs > 9) + goto out; + + rate.flags = RATE_INFO_FLAGS_VHT_MCS; + break; + case MT_PHY_TYPE_HE_SU: + case MT_PHY_TYPE_HE_EXT_SU: + case MT_PHY_TYPE_HE_TB: + case MT_PHY_TYPE_HE_MU: + if (rate.mcs > 11) + goto out; + + rate.he_gi = wcid->rate.he_gi; + rate.he_dcm = FIELD_GET(MT_TX_RATE_DCM, txrate); + rate.flags = RATE_INFO_FLAGS_HE_MCS; + break; + case MT_PHY_TYPE_EHT_SU: + case MT_PHY_TYPE_EHT_TRIG: + case MT_PHY_TYPE_EHT_MU: + if (rate.mcs > 13) + goto out; + + rate.eht_gi = wcid->rate.eht_gi; + rate.flags = RATE_INFO_FLAGS_EHT_MCS; + break; + default: + goto out; + } + + stats->tx_mode[mode]++; + + switch (pevt->bw) { + case IEEE80211_STA_RX_BW_160: + rate.bw = RATE_INFO_BW_160; + stats->tx_bw[3]++; + break; + case IEEE80211_STA_RX_BW_80: + rate.bw = RATE_INFO_BW_80; + stats->tx_bw[2]++; + break; + case IEEE80211_STA_RX_BW_40: + rate.bw = RATE_INFO_BW_40; + stats->tx_bw[1]++; + break; + default: + rate.bw = RATE_INFO_BW_20; + stats->tx_bw[0]++; + break; + } + wcid->rate = rate; + +out: + mt76_tx_status_skb_done(mdev, skb, &list); + +out_no_skb: + mt76_tx_status_unlock(mdev, &list); + + return !!skb; +} + +void mt7928_mac_add_txs_msg(struct mt792x_dev *dev, + void *evt) +{ + struct mt7928_uni_txdone_event *done_evt; struct mt792x_link_sta *mlink = NULL; struct mt76_wcid *wcid; - __le32 *txs_data = data; u16 wcidx; u8 pid; - if (le32_get_bits(txs_data[0], MT_TXS0_TXS_FORMAT) > 1) + done_evt = (struct mt7928_uni_txdone_event *)evt; + wcidx = done_evt->wcid; + pid = done_evt->pid; + + if (pid < MT_PACKET_ID_FIRST) return; + if (wcidx >= MT792x_WTBL_SIZE) + return; + + rcu_read_lock(); + + wcid = mt76_wcid_ptr(dev, wcidx); + if (!wcid) + goto out; + + mlink = container_of(wcid, struct mt792x_link_sta, wcid); + + mt7928_mac_add_txs_skb_msg(dev, wcid, pid, done_evt); + if (!wcid->sta) + goto out; + + mt76_wcid_add_poll(&dev->mt76, &mlink->wcid); + +out: + rcu_read_unlock(); +} + +void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data) +{ + struct mt792x_link_sta *mlink = NULL; + __le32 *txs_data = data; + struct mt76_wcid *wcid; + u8 pid, txs_fm; + u16 wcidx; + + txs_fm = le32_get_bits(txs_data[0], MT_TXS0_TXS_FORMAT); + + if (is_mt7928(&dev->mt76)) { + if (txs_fm != TXS_FM_MPDU && txs_fm != TXS_FM_PPDU) + return; + } else if (txs_fm > 1) { + return; + } + wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID); pid = le32_get_bits(txs_data[3], MT_TXS3_PID); @@ -1317,15 +1516,22 @@ void mt7925_mac_reset_work(struct work_struct *work) struct mt76_connac_pm *pm = &dev->pm; int i, ret; + if (atomic_read(&dev->mt76.bus_hung)) + return; + dev_dbg(dev->mt76.dev, "chip reset\n"); dev->hw_full_reset = true; ieee80211_stop_queues(hw); cancel_delayed_work_sync(&dev->mphy.mac_work); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); for (i = 0; i < 10; i++) { + if (test_bit(MT76_REMOVED, &dev->mphy.state)) + goto out; + mutex_lock(&dev->mt76.mutex); ret = mt792x_dev_reset(dev); mutex_unlock(&dev->mt76.mutex); @@ -1334,6 +1540,9 @@ void mt7925_mac_reset_work(struct work_struct *work) break; } + if (atomic_read(&dev->mt76.bus_hung)) + return; + if (i == 10) dev_err(dev->mt76.dev, "chip reset failed\n"); @@ -1345,8 +1554,12 @@ void mt7925_mac_reset_work(struct work_struct *work) ieee80211_scan_completed(dev->mphy.hw, &info); } +out: dev->hw_full_reset = false; pm->suspended = false; + if (test_bit(MT76_REMOVED, &dev->mphy.state)) + return; + ieee80211_wake_queues(hw); ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL, @@ -1434,6 +1647,10 @@ int mt7925_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, if (!wcid) wcid = &dev->mt76.global_wcid; + err = skb_cow_head(skb, MT_SDIO_TXD_SIZE + MT_SDIO_HDR_SIZE); + if (err) + return err; + if (sta) { struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h index 83ea9021daea..567d072cba02 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h @@ -14,7 +14,14 @@ static inline u32 mt7925_mac_wtbl_lmac_addr(struct mt792x_dev *dev, u16 wcid, u8 dw) { - mt76_wr(dev, MT_WTBLON_TOP_WDUCR, + u32 wdu_cr; + + if (is_mt7928(&dev->mt76)) + wdu_cr = MT7928_WTBLON_TOP_WDUCR; + else + wdu_cr = MT7925_WTBLON_TOP_WDUCR; + + mt76_wr(dev, wdu_cr, FIELD_PREP(MT_WTBLON_TOP_WDUCR_GROUP, (wcid >> 7))); return MT_WTBL_LMAC_OFFS(wcid, dw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 2b6cc8e253c0..84b55f008b3d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -11,6 +11,7 @@ #include "regd.h" #include "mcu.h" #include "mac.h" +#include "nan.h" static void mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band, @@ -187,19 +188,21 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, eht_cap_elem->phy_cap_info[0] |= IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + val = (sts > 3) ? sts - 1 : 3; + eht_cap_elem->phy_cap_info[0] |= - u8_encode_bits(u8_get_bits(sts - 1, BIT(0)), + u8_encode_bits(u8_get_bits(val, BIT(0)), IEEE80211_EHT_PHY_CAP0_BEAMFORMEE_SS_80MHZ_MASK); eht_cap_elem->phy_cap_info[1] = - u8_encode_bits(u8_get_bits(sts - 1, GENMASK(2, 1)), + u8_encode_bits(u8_get_bits(val, GENMASK(2, 1)), IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_80MHZ_MASK) | - u8_encode_bits(sts - 1, + u8_encode_bits(val, IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_160MHZ_MASK); if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) eht_cap_elem->phy_cap_info[1] |= - u8_encode_bits(sts - 1, + u8_encode_bits(val, IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK); eht_cap_elem->phy_cap_info[2] = @@ -412,7 +415,8 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev, 0 : mconf->mt76.idx % MT76_CONNAC_MAX_WMM_SETS; mconf->mt76.link_idx = hweight16(mvif->valid_links); - if (mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ) + if (mvif->phy->mt76->chandef.chan && + mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ) mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL + 4; else mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL; @@ -474,12 +478,32 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) INIT_WORK(&mvif->csa_work, mt7925_csa_work); timer_setup(&mvif->csa_timer, mt792x_csa_timer, 0); + if (vif->type == NL80211_IFTYPE_NAN) + dev->nan_vif = vif; out: mt792x_mutex_release(dev); return ret; } +static void +mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt792x_bss_conf *mconf; + + mt792x_mutex_acquire(dev); + + if (dev->nan_vif == vif) + dev->nan_vif = NULL; + + mconf = mt792x_link_conf_to_mconf(&vif->bss_conf); + mt792x_mac_link_bss_remove(dev, mconf, &mvif->sta.deflink); + + mt792x_mutex_release(dev); +} + static void mt7925_roc_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) { @@ -1217,20 +1241,37 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev, int mt7925_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, enum mt76_sta_event ev) { + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); struct ieee80211_link_sta *link_sta = &sta->deflink; - if (ev != MT76_STA_EVENT_ASSOC) - return 0; + switch (ev) { + case MT76_STA_EVENT_ASSOC: + if (ieee80211_vif_is_mld(vif)) { + struct mt792x_sta *msta = + (struct mt792x_sta *)sta->drv_priv; - if (ieee80211_vif_is_mld(vif)) { - struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + link_sta = mt792x_sta_to_link_sta(vif, sta, + msta->deflink_id); + mt7925_mac_set_links(mdev, vif); + } - link_sta = mt792x_sta_to_link_sta(vif, sta, msta->deflink_id); - mt7925_mac_set_links(mdev, vif); + mt7925_mac_link_sta_assoc(mdev, vif, link_sta); + break; + case MT76_STA_EVENT_AUTHORIZE: + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + int ret; + + mt792x_mutex_acquire(dev); + ret = mt792x_nan_map_sta_rec(mdev, vif, sta); + mt792x_mutex_release(dev); + + return ret; + } + break; + default: + break; } - mt7925_mac_link_sta_assoc(mdev, vif, link_sta); - return 0; } EXPORT_SYMBOL_GPL(mt7925_mac_sta_event); @@ -1357,6 +1398,36 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + /* Release NAN peer record before tearing down the STA. */ + if (vif->type == NL80211_IFTYPE_NAN || + vif->type == NL80211_IFTYPE_NAN_DATA) { + int ret = mt792x_nan_set_peer_rec(mdev, sta); + + if (ret) + dev_err(mdev->dev, + "NAN: failed to deactivate peer record: %d\n", + ret); + } + + /* Release NDP context ID for NAN_DATA sta. */ + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + struct ieee80211_sta *nmi_sta; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (nmi_sta) { + struct mt792x_sta *nmi_msta = + (struct mt792x_sta *)nmi_sta->drv_priv; + + if (msta->nan_sched.ndp_ctx_assigned) { + clear_bit(msta->nan_sched.ndp_ctx_id, + &nmi_msta->nan_sched.ndp_ctx_bitmap); + msta->nan_sched.ndp_ctx_assigned = false; + } + } + rcu_read_unlock(); + } + if (ieee80211_vif_is_mld(vif)) { mt7925_mac_sta_remove_links(dev, vif, sta, msta->valid_links); mt7925_mcu_del_dev(mdev, vif); @@ -1493,7 +1564,7 @@ void mt7925_scan_work(struct work_struct *work) while (true) { struct sk_buff *skb; struct tlv *tlv; - int tlv_len; + u32 tlv_len; spin_lock_bh(&phy->dev->mt76.lock); skb = __skb_dequeue(&phy->scan_event_list); @@ -1506,7 +1577,7 @@ void mt7925_scan_work(struct work_struct *work) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { struct mt7925_mcu_scan_chinfo_event *evt; switch (le16_to_cpu(tlv->tag)) { @@ -1519,6 +1590,9 @@ void mt7925_scan_work(struct work_struct *work) } break; case UNI_EVENT_SCAN_DONE_CHNLINFO: + if (le16_to_cpu(tlv->len) < sizeof(*tlv) + sizeof(*evt)) + break; + evt = (struct mt7925_mcu_scan_chinfo_event *)tlv->data; mt7925_regd_change(phy, evt->alpha2); @@ -1530,9 +1604,6 @@ void mt7925_scan_work(struct work_struct *work) default: break; } - - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } dev_kfree_skb(skb); @@ -1640,6 +1711,7 @@ static int mt7925_suspend(struct ieee80211_hw *hw, cancel_delayed_work_sync(&phy->mt76->mac_work); cancel_delayed_work_sync(&dev->pm.ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); mt792x_mutex_acquire(dev); @@ -1827,9 +1899,15 @@ static int mt7925_set_sar_specs(struct ieee80211_hw *hw, int err; mt792x_mutex_acquire(dev); - err = mt7925_set_tx_sar_pwr(hw, sar); - mt792x_mutex_release(dev); + err = mt7925_mcu_set_clc(dev, dev->mt76.alpha2, + dev->country_ie_env); + if (err < 0) + goto out; + err = mt7925_set_tx_sar_pwr(hw, sar); + +out: + mt792x_mutex_release(dev); return err; } @@ -2062,6 +2140,11 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw, } mt792x_mutex_release(dev); + + if (vif->type == NL80211_IFTYPE_NAN && + changed & BSS_CHANGED_NAN_LOCAL_SCHED) { + mt7925_nan_local_sched_changed(dev, vif); + } } static void mt7925_link_info_changed(struct ieee80211_hw *hw, @@ -2484,19 +2567,165 @@ static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw, } } +static int mt7925_start_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf) +{ + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct ieee80211_channel *chan; + int err = 0; + + mt792x_mutex_acquire(dev); + + chan = conf->band_cfgs[NL80211_BAND_2GHZ].chan; + if (!chan) { + err = -EINVAL; + goto out; + } + + cfg80211_chandef_create(&link_conf->chanreq.oper, chan, + NL80211_CHAN_NO_HT); + + err = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, + NULL, true); + if (err < 0) + goto out; + + dev->nan_vif = vif; + + err = mt7925_nan_set_nmi_addr(dev, vif->addr); + if (err) + goto rollback_bss; + + err = mt7925_nan_enable(vif, dev, conf); + if (err) + goto rollback_bss; + + goto out; + +rollback_bss: + dev->nan_vif = NULL; + mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, NULL, false); + +out: + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_stop_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err, ret; + + mt792x_mutex_acquire(dev); + + err = mt7925_nan_disable(vif, dev); + + ret = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, + NULL, false); + if (!err) + err = ret; + + if (dev->nan_vif == vif) + dev->nan_vif = NULL; + + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_nan_change_conf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf, + u32 changes) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err = 0; + + mt792x_mutex_acquire(dev); + + err = mt7925_nan_change_configure(vif, dev, conf); + + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_nan_peer_sched_changed(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err = 0; + + mt792x_mutex_acquire(dev); + + err = mt792x_nan_set_peer_schedule(dev, sta); + + mt792x_mutex_release(dev); + + return err; +} + +static void mt7925_sta_pre_rcu_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76_phy *phy = hw->priv; + struct mt76_dev *dev = phy->dev; + struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; + + mutex_lock(&dev->mutex); + spin_lock_bh(&dev->status_lock); + + if (ieee80211_vif_is_mld(vif)) { + struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + unsigned long valid = mvif->valid_links; + struct mt792x_link_sta *mlink; + unsigned int link_id; + + for_each_set_bit(link_id, &valid, IEEE80211_MLD_MAX_NUM_LINKS) { + mlink = mt792x_sta_to_link(msta, link_id); + if (!mlink || !mlink->wcid.sta) + continue; + if (mlink->wcid.idx < ARRAY_SIZE(dev->wcid)) + rcu_assign_pointer(dev->wcid[mlink->wcid.idx], + NULL); + } + } else { + rcu_assign_pointer(dev->wcid[wcid->idx], NULL); + } + + spin_unlock_bh(&dev->status_lock); + mutex_unlock(&dev->mutex); +} + +static void mt7925_stop(struct ieee80211_hw *hw, bool suspend) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + + cancel_delayed_work_sync(&dev->mlo_pm_work); + + mt792x_stop(hw, suspend); +} + const struct ieee80211_ops mt7925_ops = { .tx = mt792x_tx, .start = mt7925_start, - .stop = mt792x_stop, + .stop = mt7925_stop, .add_interface = mt7925_add_interface, - .remove_interface = mt792x_remove_interface, + .remove_interface = mt7925_remove_interface, .config = mt7925_config, .conf_tx = mt7925_conf_tx, .configure_filter = mt7925_configure_filter, .start_ap = mt7925_start_ap, .stop_ap = mt7925_stop_ap, .sta_state = mt76_sta_state, - .sta_pre_rcu_remove = mt76_sta_pre_rcu_remove, + .sta_pre_rcu_remove = mt7925_sta_pre_rcu_remove, .set_key = mt7925_set_key, .sta_set_decap_offload = mt7925_sta_set_decap_offload, #if IS_ENABLED(CONFIG_IPV6) @@ -2553,6 +2782,10 @@ const struct ieee80211_ops mt7925_ops = { .channel_switch = mt7925_channel_switch, .abort_channel_switch = mt7925_abort_channel_switch, .channel_switch_rx_beacon = mt7925_channel_switch_rx_beacon, + .start_nan = mt7925_start_nan, + .stop_nan = mt7925_stop_nan, + .nan_change_conf = mt7925_nan_change_conf, + .nan_peer_sched_changed = mt7925_nan_peer_sched_changed, }; EXPORT_SYMBOL_GPL(mt7925_ops); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index cb265a6fc7ad..fa29c486a455 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -7,9 +7,17 @@ #include "regd.h" #include "mcu.h" #include "mac.h" +#include "nan.h" #define MT_STA_BFER BIT(0) #define MT_STA_BFEE BIT(1) +#define MT7925_WOW_PATTERN_NEW_FW_DATE "20260414153105" + +static bool mt7925_vif_is_nan(struct ieee80211_vif *vif) +{ + return vif->type == NL80211_IFTYPE_NAN || + vif->type == NL80211_IFTYPE_NAN_DATA; +} int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, struct sk_buff *skb, int seq) @@ -26,18 +34,30 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, } rxd = (struct mt7925_mcu_rxd *)skb->data; - if (seq != rxd->seq) - return -EAGAIN; + if (seq != rxd->seq) { + if (!is_mt7928(mdev)) + return -EAGAIN; + else if (is_mt7928(mdev) && + cmd != MCU_CMD(CB_PATCH_SEM_CONTROL) && + cmd != MCU_CMD(CB_PATCH_FINISH_REQ)) + return -EAGAIN; + } if (cmd == MCU_CMD(PATCH_SEM_CONTROL) || cmd == MCU_CMD(PATCH_FINISH_REQ)) { skb_pull(skb, sizeof(*rxd) - 4); ret = *skb->data; + } else if (is_mt7928(mdev) && + (cmd == MCU_CMD(CB_PATCH_SEM_CONTROL) || + cmd == MCU_CMD(CB_PATCH_FINISH_REQ))) { + skb_pull(skb, sizeof(*rxd) - 4); + ret = *skb->data; } else if (cmd == MCU_UNI_CMD(DEV_INFO_UPDATE) || cmd == MCU_UNI_CMD(BSS_INFO_UPDATE) || cmd == MCU_UNI_CMD(STA_REC_UPDATE) || cmd == MCU_UNI_CMD(OFFLOAD) || - cmd == MCU_UNI_CMD(SUSPEND)) { + cmd == MCU_UNI_CMD(SUSPEND) || + cmd == MCU_UNI_CMD(NAN)) { struct mt7925_mcu_uni_event *event; skb_pull(skb, sizeof(*rxd)); @@ -210,6 +230,23 @@ mt7925_connac_mcu_set_wow_ctrl(struct mt76_phy *phy, struct ieee80211_vif *vif, sizeof(req), true); } +static bool mt7925_mcu_wow_pattern_old_tlv(struct mt76_dev *dev) +{ + const char *fw_version = dev->hw->wiphy->fw_version; + const char *build_date = strrchr(fw_version, '-'); + + if (!is_mt7925(dev)) + return false; + + if (!build_date) + return false; + + build_date++; + + return strncmp(build_date, MT7925_WOW_PATTERN_NEW_FW_DATE, + strlen(MT7925_WOW_PATTERN_NEW_FW_DATE)) < 0; +} + static int mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, struct ieee80211_vif *vif, @@ -219,6 +256,8 @@ mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, struct mt76_vif_link *mvif = (struct mt76_vif_link *)vif->drv_priv; struct mt7925_wow_pattern_tlv *tlv; struct sk_buff *skb; + int tlv_len; + bool old_tlv; struct { u8 bss_idx; u8 pad[3]; @@ -226,14 +265,18 @@ mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, .bss_idx = mvif->idx, }; - skb = mt76_mcu_msg_alloc(dev, NULL, sizeof(hdr) + sizeof(*tlv)); + old_tlv = mt7925_mcu_wow_pattern_old_tlv(dev); + tlv_len = old_tlv ? sizeof(struct mt7925_wow_pattern_tlv) : + MT7925_WOW_PATTERN_TLV_V2_SIZE; + + skb = mt76_mcu_msg_alloc(dev, NULL, sizeof(hdr) + tlv_len); if (!skb) return -ENOMEM; skb_put_data(skb, &hdr, sizeof(hdr)); - tlv = (struct mt7925_wow_pattern_tlv *)skb_put(skb, sizeof(*tlv)); + tlv = (struct mt7925_wow_pattern_tlv *)skb_put_zero(skb, tlv_len); tlv->tag = cpu_to_le16(UNI_SUSPEND_WOW_PATTERN); - tlv->len = cpu_to_le16(sizeof(*tlv)); + tlv->len = cpu_to_le16(tlv_len); tlv->bss_idx = 0xF; tlv->data_len = pattern->pattern_len; tlv->enable = enable; @@ -366,16 +409,18 @@ mt7925_mcu_uni_hif_ctrl_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_HIF_CTRL_BASIC: + if (le16_to_cpu(tlv->len) < + sizeof(struct mt7925_mcu_hif_ctrl_basic_tlv)) + break; + mt7925_mcu_handle_hif_ctrl_basic(dev, tlv); break; default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } @@ -383,22 +428,24 @@ static void mt7925_mcu_uni_roc_event(struct mt792x_dev *dev, struct sk_buff *skb) { struct tlv *tlv; - int i = 0; + u32 tlv_len; skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4); + tlv = (struct tlv *)skb->data; + tlv_len = skb->len; - while (i < skb->len) { - tlv = (struct tlv *)(skb->data + i); - + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_ROC_GRANT: + if (le16_to_cpu(tlv->len) < + sizeof(struct mt7925_roc_grant_tlv)) + break; + mt7925_mcu_roc_handle_grant(dev, tlv); break; case UNI_EVENT_ROC_GRANT_SUB_LINK: break; } - - i += le16_to_cpu(tlv->len); } } @@ -426,6 +473,7 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) u8 rsv[3]; u8 data[]; } __packed * txs; + struct mt7928_uni_txdone_event *evt; struct tlv *tlv; u32 tlv_len; @@ -433,8 +481,34 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { + case UNI_EVENT_TX_DONE_MSG: + if (!is_mt7928(&dev->mt76)) + break; + + if (le16_to_cpu(tlv->len) < sizeof(*evt)) + break; + + evt = (struct mt7928_uni_txdone_event *)tlv; + if (evt->status) { + dev_info(dev->mt76.dev, + "TxDone: pid=%u status=%#x sn=%#x wcid=%u " + "cnt=%u rate=%#x flag=%#x tid=%u pwr=%u " + "rsp_rate=%#x rate_idx=%u bw=%u flush=%#x " + "delay=%#x ts=%#x flags=%#x\n", + evt->pid, evt->status, + le16_to_cpu(evt->seq), evt->wcid, + evt->tx_count, le16_to_cpu(evt->tx_rate), + evt->flag, evt->tid, evt->tx_pwr, + evt->rsp_rate, evt->rate_tbl_idx, + evt->bw, evt->flush_reason, + le32_to_cpu(evt->tx_delay), + le32_to_cpu(evt->timestamp), + le32_to_cpu(evt->applied_flags)); + } + mt7928_mac_add_txs_msg(dev, evt); + break; case UNI_EVENT_TX_DONE_RAW: txs = (struct mt7925_mcu_txs_event *)tlv->data; mt7925_mac_add_txs(dev, txs->data); @@ -442,8 +516,6 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } @@ -480,10 +552,13 @@ mt7925_mcu_rssi_monitor_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_RSSI_MONITOR_INFO: - event = (struct mt7925_uni_rssi_monitor_event *)skb->data; + if (le16_to_cpu(tlv->len) < sizeof(*event)) + break; + + event = (struct mt7925_uni_rssi_monitor_event *)tlv; ieee80211_iterate_active_interfaces_atomic(dev->mt76.hw, IEEE80211_IFACE_ITER_RESUME_ALL, mt7925_mcu_rssi_monitor_iter, @@ -492,8 +567,6 @@ mt7925_mcu_rssi_monitor_event(struct mt792x_dev *dev, struct sk_buff *skb) default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } @@ -631,6 +704,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, dev->fw_assert = true; mt76_connac_mcu_coredump_event(&dev->mt76, skb, &dev->coredump); return; + case MCU_UNI_EVENT_NAN: + mt7925_nan_mcu_event(dev, skb); + break; default: break; } @@ -1546,7 +1622,8 @@ int mt7925_mcu_set_eeprom(struct mt792x_dev *dev) .tag = cpu_to_le16(UNI_EFUSE_BUFFER_MODE), .len = cpu_to_le16(sizeof(req) - 4), .buffer_mode = EE_MODE_EFUSE, - .format = EE_FORMAT_WHOLE + .format = EE_FORMAT_WHOLE, + .buf_len = 0 }; return mt76_mcu_send_and_get_msg(&dev->mt76, MCU_UNI_CMD(EFUSE_CTRL), @@ -1835,9 +1912,20 @@ mt7925_mcu_sta_phy_tlv(struct sk_buff *skb, tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_PHY, sizeof(*phy)); phy = (struct sta_rec_phy *)tlv; - phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, - chandef->chan->band, - link_sta); + + if (mt7925_vif_is_nan(vif)) { + enum nl80211_band band = chandef->chan ? chandef->chan->band + : NL80211_BAND_2GHZ; + phy->phy_type = PHY_TYPE_BIT_OFDM | PHY_TYPE_BIT_ERP; + phy->phy_type |= mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, + band, + link_sta); + } else { + phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, + chandef->chan->band, + link_sta); + } + phy->basic_rate = cpu_to_le16((u16)link_conf->basic_rates); if (link_sta->ht_cap.ht_supported) { af = link_sta->ht_cap.ampdu_factor; @@ -1910,11 +1998,15 @@ mt7925_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, mconf = mt792x_vif_to_link(mvif, link_sta->link_id); chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def : &link_conf->chanreq.oper; - band = chandef->chan->band; tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA, sizeof(*ra_info)); ra_info = (struct sta_rec_ra_info *)tlv; + if (mt7925_vif_is_nan(vif)) + band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ; + else + band = chandef->chan->band; + supp_rates = link_sta->supp_rates[band]; if (band == NL80211_BAND_2GHZ) supp_rates = FIELD_PREP(RA_LEGACY_OFDM, supp_rates >> 4) | @@ -2174,6 +2266,8 @@ int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, struct mt7925_txp int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif, bool enable) { + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct ieee80211_chanctx_conf *ctx = mvif->bss_conf.mt76.ctx; struct { struct { u8 band_idx; @@ -2196,6 +2290,15 @@ int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif, }, }; + if (is_mt7927(&dev->mt76)) { + struct ieee80211_channel *chan; + + chan = ctx ? ctx->def.chan : mvif->phy->mt76->chandef.chan; + + if (chan) + req.hdr.band_idx = mt7927_band_idx(chan->band); + } + return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(SNIFFER), &req, sizeof(req), true); } @@ -2255,6 +2358,9 @@ int mt7925_mcu_config_sniffer(struct mt792x_vif *vif, }, }; + if (is_mt7927(mphy->dev)) + req.hdr.band_idx = mt7927_band_idx(chandef->chan->band); + if (chandef->chan->band < ARRAY_SIZE(ch_band)) req.tlv.ch_band = ch_band[chandef->chan->band]; if (chandef->width < ARRAY_SIZE(ch_width)) @@ -2364,11 +2470,18 @@ void mt7925_mcu_bss_rlm_tlv(struct sk_buff *skb, struct mt76_phy *phy, { struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; - int freq1 = chandef->center_freq1, freq2 = chandef->center_freq2; - enum nl80211_band band = chandef->chan->band; struct bss_rlm_tlv *req; + enum nl80211_band band; + int freq1, freq2; struct tlv *tlv; + if (WARN_ON_ONCE(!chandef || !chandef->chan)) + return; + + freq1 = chandef->center_freq1; + freq2 = chandef->center_freq2; + band = chandef->chan->band; + tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_RLM, sizeof(*req)); req = (struct bss_rlm_tlv *)tlv; req->control_channel = chandef->chan->hw_value; @@ -2506,8 +2619,8 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, enum nl80211_band band, struct ieee80211_link_sta *link_sta) { - struct ieee80211_he_6ghz_capa *he_6ghz_capa; - const struct ieee80211_sta_eht_cap *eht_cap; + struct ieee80211_he_6ghz_capa *he_6ghz_capa = NULL; + const struct ieee80211_sta_eht_cap *eht_cap = NULL; __le16 capa = 0; u8 mode = 0; @@ -2515,11 +2628,18 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, he_6ghz_capa = &link_sta->he_6ghz_capa; eht_cap = &link_sta->eht_cap; } else { + const struct ieee80211_sta_he_cap *he_cap; struct ieee80211_supported_band *sband; sband = phy->hw->wiphy->bands[band]; - capa = ieee80211_get_he_6ghz_capa(sband, vif->type); - he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa; + + he_cap = (band == NL80211_BAND_6GHZ) ? + ieee80211_get_he_iftype_cap(sband, vif->type) : NULL; + + if (he_cap) { + capa = ieee80211_get_he_6ghz_capa(sband, vif->type); + he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa; + } eht_cap = ieee80211_get_eht_iftype_cap(sband, vif->type); } @@ -2547,6 +2667,29 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, return mode; } +static void +mt7925_mcu_bss_basic_tlv_nan(struct mt76_phy *phy, + struct ieee80211_vif *vif, + struct ieee80211_link_sta *link_sta, + struct mt76_connac_bss_basic_tlv *basic_req) +{ + u8 mode_2g, mode_5g; + + mode_2g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_2GHZ, + link_sta); + mode_5g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_5GHZ, + link_sta); + basic_req->phymode_ext = mode_2g | mode_5g; + + basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX); + + mode_2g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_2GHZ, + link_sta); + mode_5g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_5GHZ, + link_sta); + basic_req->phymode = (mode_2g | mode_5g) & ~PHY_MODE_B; +} + static void mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, @@ -2561,7 +2704,7 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; - enum nl80211_band band = chandef->chan->band; + enum nl80211_band band = NL80211_BAND_2GHZ; struct mt76_connac_bss_basic_tlv *basic_req; struct tlv *tlv; int conn_type; @@ -2574,16 +2717,25 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, mconf->mt76.omac_idx; basic_req->hw_bss_idx = idx; - basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band, - link_sta); + if (mt7925_vif_is_nan(vif)) { + mt7925_mcu_bss_basic_tlv_nan(phy, vif, link_sta, basic_req); + } else { + band = chandef->chan->band; + basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band, + link_sta); - if (band == NL80211_BAND_2GHZ) - basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX); - else - basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_OFDM_INDEX); + if (band == NL80211_BAND_2GHZ) + basic_req->nonht_basic_phy = + cpu_to_le16(PHY_TYPE_ERP_INDEX); + else + basic_req->nonht_basic_phy = + cpu_to_le16(PHY_TYPE_OFDM_INDEX); + + memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN); + basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, + link_sta); + } - memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN); - basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, link_sta); basic_req->bcn_interval = cpu_to_le16(link_conf->beacon_int); basic_req->dtim_period = link_conf->dtim_period; basic_req->bmc_tx_wlan_idx = cpu_to_le16(bmc_tx_wlan_idx); @@ -2616,6 +2768,11 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, basic_req->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); basic_req->active = true; break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req->conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req->active = enable; + break; default: WARN_ON(1); break; @@ -2674,10 +2831,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy, struct ieee80211_chanctx_conf *ctx, struct ieee80211_bss_conf *link_conf) { + struct ieee80211_vif *vif = link_conf->vif; struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); - enum nl80211_band band = chandef->chan->band; + enum nl80211_band band = NL80211_BAND_2GHZ; struct mt76_vif_link *mvif = &mconf->mt76; struct bss_rate_tlv *bmc; struct tlv *tlv; @@ -2688,6 +2846,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy, bmc = (struct bss_rate_tlv *)tlv; + if (mt7925_vif_is_nan(vif)) + band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ; + else + band = chandef->chan->band; + if (band == NL80211_BAND_2GHZ) bmc->basic_rate = cpu_to_le16(HR_DSSS_ERP_BASIC_RATE); else @@ -3002,11 +3165,11 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, struct ieee80211_scan_ies *scan_ies) { u32 max_len = sizeof(struct scan_ie_tlv) + MT76_CONNAC_SCAN_IE_LEN; + u32 ies_len, alloc_len; struct scan_ie_tlv *ie; enum nl80211_band i; struct tlv *tlv; const u8 *ies; - u16 ies_len; for (i = 0; i <= NL80211_BAND_6GHZ; i++) { if (i == NL80211_BAND_60GHZ) @@ -3018,11 +3181,16 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, if (!ies || !ies_len) continue; - if (ies_len > max_len) + if (is_mt7928(mdev)) + alloc_len = ALIGN(sizeof(*ie) + ies_len, 4); + else + alloc_len = sizeof(*ie) + ies_len; + + if (alloc_len > max_len) return; tlv = mt76_connac_mcu_add_tlv(skb, UNI_SCAN_IE, - sizeof(*ie) + ies_len); + alloc_len); ie = (struct scan_ie_tlv *)tlv; memcpy(ie->ies, ies, ies_len); @@ -3040,7 +3208,7 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, break; } - max_len -= (sizeof(*ie) + ies_len); + max_len -= alloc_len; } } @@ -3541,7 +3709,8 @@ int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, /* submit all clc config */ for (i = 0; i < ARRAY_SIZE(phy->clc); i++) { - if (i == MT792x_CLC_BE_CTRL) + if (i == MT792x_CLC_BE_CTRL || + i == MT792x_CLC_REGD) continue; ret = __mt7925_mcu_set_clc(dev, alpha2, env_cap, @@ -3829,7 +3998,7 @@ mt7925_mcu_rate_txpower_band(struct mt76_phy *phy, memcpy(tx_power_tlv->alpha2, dev->alpha2, sizeof(dev->alpha2)); tx_power_tlv->n_chan = num_ch; tx_power_tlv->tag = cpu_to_le16(0x1); - tx_power_tlv->len = cpu_to_le16(msg_len); + tx_power_tlv->len = cpu_to_le16(msg_len - 4); switch (band) { case NL80211_BAND_2GHZ: diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index 293f173b23dd..11f9eac13ffc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -175,6 +175,27 @@ enum connac3_mcu_cipher_type { CONNAC3_CIPHER_GCMP_256 = 12, }; +enum DMASHDL_GROUP_IDX { + DMASHDL_GROUP_0 = 0, + DMASHDL_GROUP_1, + DMASHDL_GROUP_2, + DMASHDL_GROUP_3, + DMASHDL_GROUP_4, + DMASHDL_GROUP_5, + DMASHDL_GROUP_6, + DMASHDL_GROUP_7, + DMASHDL_GROUP_8, + DMASHDL_GROUP_9, + DMASHDL_GROUP_10, + DMASHDL_GROUP_11, + DMASHDL_GROUP_12, + DMASHDL_GROUP_13, + DMASHDL_GROUP_14, + DMASHDL_GROUP_15, + DMASHDL_GROUP_NUM, + DMASHDL_LITE_GROUP_NUM = 64 +}; + struct mt7925_mcu_scan_chinfo_event { u8 nr_chan; u8 alpha2[3]; @@ -587,6 +608,9 @@ struct mt7925_wow_pattern_tlv { u8 rsv[4]; }; +#define MT7925_WOW_PATTERN_TLV_V2_SIZE \ + (offsetof(struct mt7925_wow_pattern_tlv, rsv) + 3) + struct roc_acquire_tlv { __le16 tag; __le16 len; @@ -675,6 +699,25 @@ mt7925_mcu_get_cipher(int cipher) } } +static inline bool +mt7925_mcu_tlv_valid(struct tlv *tlv, u32 rem) +{ + u16 len; + + if (rem < sizeof(*tlv)) + return false; + + len = le16_to_cpu(tlv->len); + + /* a length below the header size would not advance the cursor */ + return len >= sizeof(*tlv) && len <= rem; +} + +#define mt7925_for_each_tlv(tlv, rem) \ + for (; mt7925_mcu_tlv_valid(tlv, rem); \ + (rem) -= le16_to_cpu((tlv)->len), \ + (tlv) = (struct tlv *)((u8 *)(tlv) + le16_to_cpu((tlv)->len))) + int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable); int mt7925_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif, struct ieee80211_scan_request *scan_req); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 4cc259418afc..321e732347f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -15,6 +15,7 @@ #define MT7925_RX_RING_SIZE 1536 #define MT7925_RX_MCU_RING_SIZE 512 +#define MT7928_RX_MCU_WA_RING_SIZE 512 #define MT7925_EEPROM_SIZE 3584 #define MT7925_TOKEN_SIZE 8192 @@ -139,6 +140,13 @@ enum mt7927_rxq_id { MT7927_RXQ_DATA2 = 7, }; +enum mt7928_rxq_id { + MT7928_RXQ_BAND0, + MT7928_RXQ_BAND1 = 2, + MT7928_RXQ_MCU_WM = 3, + MT7928_RXQ_MCU_WM2 = 1, /* for tx done */ +}; + enum { MODE_OPEN = 0, MODE_SHARED = 1, @@ -346,6 +354,7 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, int mt7925e_mac_reset(struct mt792x_dev *dev); int mt7925e_mcu_init(struct mt792x_dev *dev); void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data); +void mt7928_mac_add_txs_msg(struct mt792x_dev *dev, void *evt); void mt7925_set_runtime_pm(struct mt792x_dev *dev); void mt7925_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c new file mode 100644 index 000000000000..d260e803d056 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c @@ -0,0 +1,1098 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* Copyright (C) 2025-2026 MediaTek Inc. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt7925.h" +#include "mcu.h" +#include "nan.h" +#include "regd.h" + +static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev, + struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + struct ieee80211_channel *chan; + u32 ch5g = 0; + + chan = conf->band_cfgs[NL80211_BAND_5GHZ].chan; + + if (!chan) + return; + + if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan)) + return; + + req->config_support_5g = 1; + req->support_5g_val = 1; + req->config_5g_channel = 1; + + if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL) + ch5g |= BIT(0); + else if (chan->hw_value == NAN_5G_HIGH_DISC_CHANNEL) + ch5g |= BIT(1); + + req->channel_5g_val = cpu_to_le32(ch5g); +} + +static void mt7925_nan_set_2g_support(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (!conf->band_cfgs[NL80211_BAND_2GHZ].chan) + return; + + req->config_2dot4g_support = 1; + req->support_2dot4g_val = 1; +} + +static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req, + const u8 *cluster_id) +{ + if (!cluster_id) + return; + + req->cluster_high = cpu_to_le16(cluster_id[4] | cluster_id[5] << 8); + req->cluster_low = cpu_to_le16((u16)cluster_id[3]); +} + +static void mt7925_nan_set_dw_interval(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) { + req->config_dw.config_2dot4g_dw_band = 1; + req->config_dw.dw_2dot4g_interval_val = + cpu_to_le32(conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval); + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) { + req->config_dw.config_5g_dw_band = 1; + req->config_dw.dw_5g_interval_val = + cpu_to_le32(conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval); + } +} + +static void mt7925_nan_set_disc_beacon(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->discovery_beacon_interval > 0) { + req->config_2dot4g_beacons = true; + req->beacon_2dot4g_val = conf->discovery_beacon_interval; + } +} + +static void mt7925_nan_set_rssi_thresholds(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) { + req->config_2dot4g_rssi_close = 1; + req->rssi_close_2dot4g_val = + abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close); + req->config_2dot4g_rssi_middle = 1; + req->rssi_middle_2dot4g_val = + abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle); + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) { + req->config_5g_rssi_close = 1; + req->rssi_close_5g_val = + abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close); + req->config_5g_rssi_middle = 1; + req->rssi_middle_5g_val = + abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle); + } +} + +static void mt7925_nan_set_scan_params(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + req->scan_params_val.scan_period[0] = + cpu_to_le16(conf->scan_period < 255 ? conf->scan_period : 255); + req->scan_params_val.dwell_time[0] = + conf->scan_dwell_time < 255 ? conf->scan_dwell_time : 255; +} + +static u16 +mt7925_nan_avail_attr_ctrl(const struct ieee80211_nan_sched_cfg *sched) +{ + if (sched->avail_blob_len < NAN_AVAIL_ATTR_CTRL_OFFSET + 2) + return 0; + + return sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET] | + sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET + 1] << 8; +} + +static void +mt7925_nan_update_conf(struct mt792x_vif *mvif, + const struct cfg80211_nan_conf *conf) +{ + mvif->nan.conf.master_pref = conf->master_pref; + mvif->nan.conf.bands = conf->bands; + mvif->nan.conf.discovery_beacon_interval = + conf->discovery_beacon_interval; + mvif->nan.conf.enable_dw_notification = + conf->enable_dw_notification; + + memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN); +} + +int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr) +{ + struct mt76_dev *mdev; + struct { + u8 rsv[4]; + struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv; + } nmi_cmd = { + .rsv = { 0 }, + .nmi_addr_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS), + .len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)), + }, + }; + int ret; + + if (!dev || !addr) + return -EINVAL; + + if (is_zero_ether_addr(addr) || is_multicast_ether_addr(addr)) { + dev_err(dev->mt76.dev, "NAN: invalid NMI address %pM\n", addr); + return -EINVAL; + } + + mdev = &dev->mt76; + memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, addr, ETH_ALEN); + + ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd, + sizeof(nmi_cmd), true); + + return ret; +} + +int mt7925_nan_enable(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt76_dev *mdev = &dev->mt76; + struct { + u8 rsv[4]; + struct mt7925_nan_enable_req_tlv nan_req_tlv; + } nan_cmd = { + .rsv = { 0 }, + .nan_req_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_ENABLE_REQUEST), + .len = cpu_to_le16(sizeof(struct mt7925_nan_enable_req_tlv)), + .config_random_factor_force = 0, + .random_factor_force_val = 0, + .config_hop_count_force = 0, + .hop_count_force_val = 0, + }, + }; + struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv; + int ret; + + if (!vif || !dev || !conf) + return -EINVAL; + + p_nan_req_tlv->master_pref = conf->master_pref; + + mt7925_nan_set_2g_support(p_nan_req_tlv, conf); + mt7925_nan_set_5g_channel(dev, p_nan_req_tlv, conf); + mt7925_nan_set_cluster_id(p_nan_req_tlv, conf->cluster_id); + mt7925_nan_set_dw_interval(p_nan_req_tlv, conf); + mt7925_nan_set_disc_beacon(p_nan_req_tlv, conf); + mt7925_nan_set_rssi_thresholds(p_nan_req_tlv, conf); + mt7925_nan_set_scan_params(p_nan_req_tlv, conf); + + mt7925_nan_update_conf(mvif, conf); + + ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); + + return ret; +} + +int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev) +{ + struct mt76_dev *mdev = &dev->mt76; + struct { + u8 rsv[4]; + struct tlv nan_dis_tlv; + } nan_cmd = { + .rsv = { 0 }, + .nan_dis_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_DISABLE_REQUEST), + .len = cpu_to_le16(sizeof(struct tlv)), + }, + }; + + if (!dev) + return -EINVAL; + + return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); +} + +static int +mt7925_nan_mp_tlv(struct sk_buff *skb, u8 master_pref) +{ + struct mt7925_nan_master_preference_tlv *mp_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_MASTER_PREFERENCE, + sizeof(struct mt7925_nan_master_preference_tlv)); + if (!tlv) + return -ENOMEM; + + mp_tlv = (struct mt7925_nan_master_preference_tlv *)tlv; + + if (master_pref > NAN_MAX_MASTER_PREFERENCE) + return 0; + + mp_tlv->master_preference = master_pref; + + return 0; +} + +static int +mt7925_nan_dw_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf) +{ + struct mt7925_nan_dw_interval_tlv *dw_tlv = NULL; + struct tlv *tlv = NULL; + u16 interval; + + if (!skb || !conf) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_DW_INTERVAL, + sizeof(struct mt7925_nan_dw_interval_tlv)); + + if (!tlv) + return -ENOMEM; + + dw_tlv = (struct mt7925_nan_dw_interval_tlv *)tlv; + + /* Set DW interval for 2.4GHz and 5GHz bands if available */ + if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) { + dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval; + } else if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) { + dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval; + } else { + /* Fallback to a default value or log a warning */ + dw_tlv->dw_interval = NAN_DEFAULT_DW_INTERVAL; + } + + /* Validate and set NAN Discovery Beacon Interval */ + interval = conf->discovery_beacon_interval > 0 ? + conf->discovery_beacon_interval : + NAN_DEFAULT_DISC_BCN_INTERVAL; + + dw_tlv->disc_bcn_interval = cpu_to_le16(interval); + + return 0; +} + +static int +mt7925_nan_cluster_id_tlv(struct sk_buff *skb, const u8 *cluster_id) +{ + struct mt7925_nan_cluster_id_tlv *cluster_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb || !cluster_id) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_CLUSTER_ID, + sizeof(struct mt7925_nan_cluster_id_tlv)); + + if (!tlv) + return -ENOMEM; + + cluster_tlv = (struct mt7925_nan_cluster_id_tlv *)tlv; + + memcpy(cluster_tlv->cluster_id, cluster_id, ETH_ALEN); + + return 0; +} + +static int +mt7925_nan_sync_rssi_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf) +{ + struct mt7925_nan_sync_rssi_tlv *rssi_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb || !conf) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_SYNC_RSSI, + sizeof(struct mt7925_nan_sync_rssi_tlv)); + + if (!tlv) + return -ENOMEM; + + rssi_tlv = (struct mt7925_nan_sync_rssi_tlv *)tlv; + + if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) { + rssi_tlv->rssi_close_2g = + conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close; + rssi_tlv->rssi_middle_2g = + conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle; + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) { + rssi_tlv->rssi_close_5g = + conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close; + rssi_tlv->rssi_middle_5g = + conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle; + } + + return 0; +} + +int mt7925_nan_change_configure(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt7925_nan_common_hdr *hdr = NULL; + struct mt76_dev *mdev = &dev->mt76; + struct sk_buff *skb = NULL; + + if (!vif || !dev || !conf) + return -EINVAL; + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_CONF_MAX_SIZE); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + if (mt7925_nan_mp_tlv(skb, conf->master_pref) || + mt7925_nan_dw_tlv(skb, conf) || + mt7925_nan_cluster_id_tlv(skb, conf->cluster_id) || + mt7925_nan_sync_rssi_tlv(skb, conf)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + mt7925_nan_update_conf(mvif, conf); + + return mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} + +static void +mt7925_nan_handle_dw_ind(struct mt792x_dev *dev, struct tlv *tlv) +{ + struct ieee80211_channel *chan; + struct nan_rpt_dw_evt *evt; + struct wireless_dev *wdev; + u16 len, channel, dw_num; + struct mt792x_vif *mvif; + enum nl80211_band band; + int freq; + + if (!dev || !tlv) + return; + + len = le16_to_cpu(tlv->len); + if (len < sizeof(*tlv) + sizeof(*evt)) { + dev_warn(dev->mt76.dev, + "nan: short dw event tlv len=%u\n", len); + return; + } + + if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) + return; + + wdev = ieee80211_vif_to_wdev(dev->nan_vif); + if (!wdev) + return; + + mvif = (struct mt792x_vif *)dev->nan_vif->drv_priv; + if (!mvif->nan.conf.enable_dw_notification) + return; + + evt = (struct nan_rpt_dw_evt *)tlv->data; + channel = le16_to_cpu(evt->channel); + dw_num = le16_to_cpu(evt->dw_num); + + band = channel > 13 ? NL80211_BAND_5GHZ : NL80211_BAND_2GHZ; + freq = ieee80211_channel_to_frequency(channel, band); + chan = ieee80211_get_channel(dev->mt76.hw->wiphy, freq); + if (!chan) { + dev_dbg(dev->mt76.dev, + "nan: no channel for dw end event ch=%u dw=%u\n", + channel, dw_num); + return; + } + + cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL); +} + +static void +mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv) +{ + u8 cluster_id[ETH_ALEN] __aligned(2) = {0x50, 0x6f, 0x9a, 0x01, 0x00, 0x00}; + struct mt7925_nan_de_event *de_evt = NULL; + u16 len; + + if (!dev || !tlv) { + if (dev) + dev_warn(dev->mt76.dev, "nan: failed to parse TLV\n"); + return; + } + + len = le16_to_cpu(tlv->len); + if (len < sizeof(*tlv) + sizeof(*de_evt)) { + dev_warn(dev->mt76.dev, + "nan: short de_event tlv len=%u\n", len); + return; + } + + de_evt = (struct mt7925_nan_de_event *)tlv->data; + if (!de_evt) { + dev_warn(dev->mt76.dev, "nan: missing DE event payload\n"); + return; + } + + if (de_evt->event_type == NAN_EVENT_ID_DISC_MAC_ADDR) + return; + + memcpy(cluster_id, de_evt->cluster_id, ETH_ALEN); + + dev_dbg(dev->mt76.dev, "nan: evt=%u cluster=%pM\n", + de_evt->event_type, de_evt->cluster_id); + + if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER) + return; + + if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) { + dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n"); + return; + } + + dev_dbg(dev->mt76.dev, "nan: anchor_master_rank=%*phN\n", + NAN_ANCHOR_MASTER_RANK_NUM, de_evt->anchor_master_rank); + + dev_dbg(dev->mt76.dev, "nan: own_nmi=%pM master_nmi=%pM\n", + de_evt->own_nmi, de_evt->master_nmi); + + ieee80211_nan_cluster_joined(dev->nan_vif, cluster_id, true, GFP_KERNEL); +} + +void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb) +{ + struct tlv *tlv; + u32 tlv_len; + + if (!dev || !skb) + return; + + if (skb->len < sizeof(struct mt7925_mcu_rxd) + 4) + return; + + skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4); + tlv = (struct tlv *)skb->data; + tlv_len = skb->len; + + while (tlv_len >= sizeof(*tlv)) { + u16 len = le16_to_cpu(tlv->len); + + if (len < sizeof(*tlv) || len > tlv_len) + break; + + switch (le16_to_cpu(tlv->tag)) { + case NAN_UNI_EVENT_ID_DE_EVENT_IND: + mt7925_nan_mcu_handle_de_event(dev, tlv); + break; + case NAN_UNI_EVENT_REPORT_DW_END: + mt7925_nan_handle_dw_ind(dev, tlv); + break; + default: + break; + } + + tlv_len -= len; + tlv = (struct tlv *)((u8 *)tlv + len); + } +} + +static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_avail_ctrl_tlv *avail_ctrl_tlv; + struct ieee80211_nan_sched_cfg *sched; + struct tlv *tlv; + u8 seq_id = 0; + u16 ctrl = 0; + + if (!skb || !vif) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL, + sizeof(struct mt7925_nan_avail_ctrl_tlv)); + + if (!tlv) + return -ENOMEM; + + sched = &vif->cfg.nan_sched; + + ctrl = mt7925_nan_avail_attr_ctrl(sched); + if (sched->avail_blob_len >= NAN_AVAIL_ATTR_CTRL_OFFSET + 2) + seq_id = sched->avail_blob[NAN_AVAIL_SEQ_ID_OFFSET]; + + avail_ctrl_tlv = (struct mt7925_nan_avail_ctrl_tlv *)tlv; + avail_ctrl_tlv->avail_ctrl = + cpu_to_le16(ctrl & NAN_AVAIL_CTRL_CHECK_FOR_CHANGED); + avail_ctrl_tlv->seq_id = seq_id; + + return 0; +} + +static u32 mt7925_nan_slot_to_bitmap(struct ieee80211_vif *vif, + struct mt7925_nan_ch_timeline *ch_list) +{ + struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule; + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + u32 num_channels = 0; + u32 i, j; + + for (i = 0; i < ARRAY_SIZE(mvif->nan.local_sched); i++) { + struct cfg80211_chan_def *slot_chan = &mvif->nan.local_sched[i]; + struct ieee80211_nan_channel *slot = slots[i]; + bool is_found = false; + + if (slot && !IS_ERR(slot) && slot->chanctx_conf) { + *slot_chan = slot->chanctx_conf->def; + } else { + memset(slot_chan, 0, sizeof(*slot_chan)); + continue; + } + + for (j = 0; j < num_channels; j++) { + u32 raw = le32_to_cpu(ch_list[j].ch_info); + + if (FIELD_GET(NAN_CH_CTRL_PRIMARY_CH, raw) == + slot_chan->chan->hw_value) { + u32 map = le32_to_cpu(ch_list[j].avail_map[0]); + + ch_list[j].avail_map[0] = cpu_to_le32(map | BIT(i)); + le32_add_cpu(&ch_list[j].num, 1); + is_found = true; + break; + } + } + + if (!is_found && num_channels < NAN_TIMELINE_MGMT_CHNL_LIST_NUM) { + ch_list[num_channels].ch_info = + cpu_to_le32(FIELD_PREP(NAN_CH_CTRL_OP_CLASS, + slot->channel_entry[0]) | + FIELD_PREP(NAN_CH_CTRL_PRIMARY_CH, + slot_chan->chan->hw_value)); + ch_list[num_channels].avail_map[0] = cpu_to_le32(BIT(i)); + le32_add_cpu(&ch_list[num_channels].num, 1); + ch_list[num_channels].is_valid++; + num_channels++; + } + } + + return num_channels; +} + +static int mt7925_nan_avail_tlv(struct sk_buff *skb, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_avail_entry_tlv *avail_tlv; + struct ieee80211_nan_sched_cfg *sched; + struct tlv *tlv; + u16 ctrl = 0; + + if (!skb || !vif) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY, + sizeof(struct mt7925_nan_avail_entry_tlv)); + + if (!tlv) + return -ENOMEM; + + sched = &vif->cfg.nan_sched; + + ctrl = mt7925_nan_avail_attr_ctrl(sched); + + avail_tlv = (struct mt7925_nan_avail_entry_tlv *)tlv; + avail_tlv->map_id = ctrl & NAN_AVAIL_CTRL_MAPID; + avail_tlv->is_cond_avail = false; + avail_tlv->timeline_idx = 0; + + mt7925_nan_slot_to_bitmap(vif, avail_tlv->ch_list); + + avail_tlv->is_multi_map = false; + + return 0; +} + +void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_common_hdr *hdr; + struct mt76_dev *mdev; + bool deferred; + struct sk_buff *skb; + int ret = -ENOMEM; + + if (!dev || !vif) + return; + + mdev = &dev->mt76; + deferred = vif->cfg.nan_sched.deferred; + + mt792x_mutex_acquire(dev); + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE); + if (!skb) + goto out; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + if (mt7925_nan_avail_ctrl_tlv(skb, vif) || + mt7925_nan_avail_tlv(skb, vif)) { + dev_kfree_skb(skb); + goto out; + } + + ret = mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +out: + mt792x_mutex_release(dev); + + if (deferred) { + if (ret) + dev_err(mdev->dev, + "NAN: local schedule update failed: %d\n", + ret); + + ieee80211_nan_sched_update_done(vif); + } +} + +static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta, + u8 is_activate) +{ + struct mt7925_nan_sched_manage_peer_rec_tlv *peer_rec_tlv; + struct tlv *tlv; + + if (!skb || !sta || !msta) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD, + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv)); + + if (!tlv) + return -ENOMEM; + + peer_rec_tlv = (struct mt7925_nan_sched_manage_peer_rec_tlv *)tlv; + peer_rec_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + peer_rec_tlv->is_activate = is_activate; + memcpy(peer_rec_tlv->nmi_addr, sta->addr, ETH_ALEN); + + return 0; +} + +static u8 mt7925_nan_get_supported_bands(struct mt792x_vif *mvif) +{ + struct wiphy *wiphy; + u8 bands = 0; + + if (!mvif || !mvif->phy) + return BIT(NAN_SUPPORTED_BAND_ID_2P4G); + + wiphy = mvif->phy->mt76->hw->wiphy; + if (wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ)) + bands |= BIT(NAN_SUPPORTED_BAND_ID_2P4G); + if (wiphy->nan_supported_bands & BIT(NL80211_BAND_5GHZ)) + bands |= BIT(NAN_SUPPORTED_BAND_ID_5G); + + return bands ?: BIT(NAN_SUPPORTED_BAND_ID_2P4G); +} + +static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta) +{ + struct mt7925_nan_sched_update_peer_cap_tlv *peer_cap_tlv; + struct ieee80211_nan_peer_sched *sched; + enum nl80211_band band; + struct tlv *tlv; + u16 primary_ch; + u32 i; + + if (!skb || !sta || !msta) + return -EINVAL; + + sched = sta->nan_sched; + if (!sched) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_PEER_CAPABILITY, + sizeof(struct mt7925_nan_sched_update_peer_cap_tlv)); + + if (!tlv) + return -ENOMEM; + + peer_cap_tlv = (struct mt7925_nan_sched_update_peer_cap_tlv *)tlv; + peer_cap_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + peer_cap_tlv->supported_bands = + mt7925_nan_get_supported_bands(msta->vif); + peer_cap_tlv->max_chnl_switch_time = cpu_to_le16(sched->max_chan_switch); + + for (i = 0; i < sched->n_channels; i++) { + if (!sched->channels[i].chanctx_conf) + continue; + + band = sched->channels[i].chanctx_conf->def.chan->band; + primary_ch = + sched->channels[i].chanctx_conf->def.chan->hw_value; + + if (band == NL80211_BAND_2GHZ) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_2G); + else if (primary_ch >= UNII1_LOWER_BOUND && + primary_ch <= UNII1_UPPER_BOUND) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_5G_LOW); + else if (primary_ch >= UNII3_LOWER_BOUND && + primary_ch <= UNII3_UPPER_BOUND) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_5G_HIGH); + } + + return 0; +} + +static void +mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv, + struct ieee80211_vif *vif, + struct ieee80211_nan_peer_sched *sched) +{ + struct ieee80211_nan_sched_cfg *local_sched; + u8 local_map_id; + u32 m, slot; + + if (!vif || !sched) + return; + + local_sched = &vif->cfg.nan_sched; + local_map_id = mt7925_nan_avail_attr_ctrl(local_sched) & + NAN_AVAIL_CTRL_MAPID; + + for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS && + m < NAN_TIMELINE_MGMT_SIZE; m++) { + struct mt7925_nan_sched_timeline *tl = + &crb_tlv->comm_faw_timeline[m]; + struct ieee80211_nan_peer_map *map = &sched->maps[m]; + u32 avail_map = 0; + + if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) + continue; + + tl->map_id = map->map_id; + tl->local_map_id = local_map_id; + + for (slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; + slot++) { + struct ieee80211_nan_channel *local_ch; + struct ieee80211_nan_channel *peer_ch; + + local_ch = local_sched->schedule[slot]; + peer_ch = map->slots[slot]; + + if (!local_ch || !local_ch->chanctx_conf || + !peer_ch || !peer_ch->chanctx_conf) + continue; + + if (local_ch->chanctx_conf != peer_ch->chanctx_conf) + continue; + + avail_map |= BIT(slot); + } + + tl->avail_map[0] = cpu_to_le32(avail_map); + } +} + +static int mt7925_nan_update_crb_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta) +{ + struct mt7925_nan_sched_update_crb_tlv *crb_tlv; + struct tlv *tlv; + + if (!skb || !sta || !msta) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_CRB, + sizeof(struct mt7925_nan_sched_update_crb_tlv)); + + if (!tlv) + return -ENOMEM; + + crb_tlv = (struct mt7925_nan_sched_update_crb_tlv *)tlv; + crb_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + crb_tlv->flags = NAN_CRB_USE_DATA_PATH; + crb_tlv->is_use_ranging = false; + crb_tlv->comm_ndc_ctrl.is_valid = false; + + mt7925_nan_fill_crb_committed(crb_tlv, msta->vif->phy->dev->nan_vif, + sta->nan_sched); + + return 0; +} + +int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_common_hdr *hdr; + bool idx_allocated = false; + struct mt792x_sta *msta; + struct mt792x_nan *nan; + struct mt76_dev *mdev; + struct sk_buff *skb; + int ret; + + if (!dev || !sta) + return -EINVAL; + + mdev = &dev->mt76; + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_PEER_MAX_SIZE); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + msta = (struct mt792x_sta *)sta->drv_priv; + nan = &msta->vif->nan; + + /* Allocate connection index on first call for this peer */ + if (!msta->nan_sched.idx_assigned) { + int idx = find_first_zero_bit(&nan->conn_bitmap, + NAN_MAX_CONN_CFG); + if (idx >= NAN_MAX_CONN_CFG) { + dev_kfree_skb(skb); + return -ENOSPC; + } + + set_bit(idx, &nan->conn_bitmap); + msta->nan_sched.sch_idx = idx; + msta->nan_sched.idx_assigned = true; + idx_allocated = true; + + if (mt7925_nan_peer_rec_tlv(skb, sta, msta, true) || + mt7925_nan_peer_cap_tlv(skb, sta, msta)) { + ret = -ENOMEM; + goto free_skb; + } + } + + if (mt7925_nan_update_crb_tlv(skb, sta, msta)) { + ret = -ENOMEM; + goto free_skb; + } + + ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true); + if (ret && idx_allocated) + goto clear_idx; + + return ret; + +free_skb: + dev_kfree_skb(skb); + if (!idx_allocated) + return ret; + +clear_idx: + clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap); + msta->nan_sched.idx_assigned = false; + + return ret; +} + +int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_common_hdr *hdr; + struct mt792x_sta *msta; + struct mt792x_nan *nan; + struct sk_buff *skb; + int ret; + + if (!mdev || !sta) + return -EINVAL; + + skb = mt76_mcu_msg_alloc(mdev, NULL, + sizeof(struct mt7925_nan_common_hdr) + + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv)); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + msta = (struct mt792x_sta *)sta->drv_priv; + nan = &msta->vif->nan; + + if (!msta->nan_sched.idx_assigned) { + dev_kfree_skb(skb); + return 0; + } + + if (mt7925_nan_peer_rec_tlv(skb, sta, msta, false)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true); + if (ret) + return ret; + + clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap); + msta->nan_sched.idx_assigned = false; + + return 0; +} + +int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_sched_map_sta_rec_tlv *map_tlv; + struct mt7925_nan_common_hdr *hdr; + struct ieee80211_sta *nmi_sta; + struct mt792x_sta *nmi_msta; + struct mt792x_vif *mvif; + struct mt792x_sta *msta; + u8 nmi_addr[ETH_ALEN]; + struct sk_buff *skb; + int ndp_ctx_id = 0; + int ret = -ENOMEM; + struct mt792x_dev *dev; + struct tlv *tlv; + + if (!mdev || !vif || !sta) + return -EINVAL; + + dev = container_of(mdev, struct mt792x_dev, mt76); + msta = (struct mt792x_sta *)sta->drv_priv; + mvif = (struct mt792x_vif *)vif->drv_priv; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (!nmi_sta) { + rcu_read_unlock(); + dev_err(mdev->dev, "NAN: NMI sta not found for NDI sta %pM\n", + sta->addr); + return -EINVAL; + } + + memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN); + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + + if (!nmi_msta->nan_sched.idx_assigned) { + if (!nmi_sta->nan_sched) { + rcu_read_unlock(); + dev_err(mdev->dev, + "NAN: peer schedule missing for NDI sta %pM\n", + sta->addr); + return -EAGAIN; + } + + rcu_read_unlock(); + ret = mt792x_nan_set_peer_schedule(dev, nmi_sta); + if (ret) + return ret; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (!nmi_sta) { + rcu_read_unlock(); + dev_err(mdev->dev, + "NAN: NMI sta not found for NDI sta %pM\n", + sta->addr); + return -EINVAL; + } + + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + } + + ndp_ctx_id = find_first_zero_bit(&nmi_msta->nan_sched.ndp_ctx_bitmap, + NAN_MAX_NDP_CXT); + if (ndp_ctx_id >= NAN_MAX_NDP_CXT) { + rcu_read_unlock(); + return -ENOSPC; + } + + set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap); + rcu_read_unlock(); + + msta->nan_sched.ndp_ctx_id = ndp_ctx_id; + msta->nan_sched.ndp_ctx_assigned = true; + + skb = mt76_mcu_msg_alloc(mdev, NULL, + sizeof(struct mt7925_nan_common_hdr) + + sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); + if (!skb) + goto clear_ndp_ctx; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MAP_STA_RECORD, + sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); + if (!tlv) { + dev_kfree_skb(skb); + ret = -ENOMEM; + goto clear_ndp_ctx; + } + + map_tlv = (struct mt7925_nan_sched_map_sta_rec_tlv *)tlv; + memcpy(map_tlv->nmi_addr, nmi_addr, ETH_ALEN); + map_tlv->sta_rec_idx = msta->deflink.wcid.idx; + map_tlv->ndp_ctx_id = ndp_ctx_id; + map_tlv->role_idx = cpu_to_le32(mvif->bss_conf.mt76.idx); + memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN); + + ret = mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); + if (ret) + goto clear_ndp_ctx; + + return 0; + +clear_ndp_ctx: + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (nmi_sta) { + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + clear_bit(msta->nan_sched.ndp_ctx_id, + &nmi_msta->nan_sched.ndp_ctx_bitmap); + } + rcu_read_unlock(); + msta->nan_sched.ndp_ctx_assigned = false; + + return ret ?: -ENOMEM; +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h new file mode 100644 index 000000000000..f55730e25f46 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* Copyright (C) 2025-2026 MediaTek Inc. */ + +#ifndef __MT7925_NAN_H +#define __MT7925_NAN_H + +#include +#include + +#include "../mt76_connac_mcu.h" + +#define NAN_MAX_SOCIAL_CHANNELS 3 +#define NAN_ANCHOR_MASTER_RANK_NUM 8 +#define NAN_5G_LOW_DISC_CHANNEL 44 +#define NAN_5G_HIGH_DISC_CHANNEL 149 +#define NAN_MAX_MASTER_PREFERENCE 255 +#define NAN_DEFAULT_DW_INTERVAL 1 +#define NAN_DEFAULT_DISC_BCN_INTERVAL 100 +#define NAN_TOTAL_DW 16 +#define NAN_SUPPORTED_2G_FAW_CH_NUM 4 +#define NAN_SUPPORTED_5G_FAW_CH_NUM 4 +#define NAN_TIMELINE_MGMT_SIZE 2 +#define NAN_TIMELINE_MGMT_CHNL_LIST_NUM \ + ((NAN_SUPPORTED_2G_FAW_CH_NUM + \ + NAN_SUPPORTED_5G_FAW_CH_NUM) / NAN_TIMELINE_MGMT_SIZE) +#define NAN_NUM_AVAIL_DB 2 +#define NAN_NDC_ATTRIBUTE_ID_LENGTH 6 +#define NAN_MAX_CONN_CFG 8 +#define NAN_MAX_NDP_CXT 4 + +#define MT7925_NAN_CONF_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_master_preference_tlv) + \ + sizeof(struct mt7925_nan_dw_interval_tlv) + \ + sizeof(struct mt7925_nan_cluster_id_tlv) + \ + sizeof(struct mt7925_nan_sync_rssi_tlv)) + +#define MT7925_NAN_AVAIL_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_avail_ctrl_tlv) + \ + sizeof(struct mt7925_nan_avail_entry_tlv)) + +#define MT7925_NAN_PEER_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv) + \ + sizeof(struct mt7925_nan_sched_update_peer_cap_tlv) + \ + sizeof(struct mt7925_nan_sched_update_crb_tlv)) + +/* NAN Availability Attribute */ +#define NAN_AVAIL_ATTR_ID_OFFSET 0 +#define NAN_AVAIL_ATTR_LEN_OFFSET 1 +#define NAN_AVAIL_SEQ_ID_OFFSET 3 +#define NAN_AVAIL_ATTR_CTRL_OFFSET 4 + +/* NAN Availability Attribute - Attribute Control Field */ +#define NAN_AVAIL_CTRL_MAPID GENMASK(3, 0) +#define NAN_AVAIL_CTRL_COMMIT_CHANGED BIT(4) +#define NAN_AVAIL_CTRL_POTN_CHANGED BIT(5) +#define NAN_AVAIL_CTRL_PUBLIC_AVAIL_CHANGED BIT(6) +#define NAN_AVAIL_CTRL_NDC_CHANGED BIT(7) +#define NAN_AVAIL_CTRL_CHECK_FOR_CHANGED GENMASK(7, 4) + +#define UNII1_LOWER_BOUND 36 +#define UNII1_UPPER_BOUND 50 +#define UNII3_LOWER_BOUND 149 +#define UNII3_UPPER_BOUND 165 + +enum nan_uni_cmd_tag { + NAN_UNI_CMD_SET_MASTER_PREFERENCE = 0, + NAN_UNI_CMD_ENABLE_REQUEST = 7, + NAN_UNI_CMD_DISABLE_REQUEST = 8, + NAN_UNI_CMD_UPDATE_AVAILABILITY = 9, + NAN_UNI_CMD_UPDATE_CRB = 10, + NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD = 12, + NAN_UNI_CMD_MAP_STA_RECORD = 13, + NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL = 20, + NAN_UNI_CMD_UPDATE_PEER_CAPABILITY = 21, + NAN_UNI_CMD_CHANGE_NMI_ADDRESS = 24, + NAN_UNI_CMD_SET_DW_INTERVAL = 26, + NAN_UNI_CMD_SET_SYNC_RSSI = 39, + NAN_UNI_CMD_SET_CLUSTER_ID = 40, + NAN_UNI_CMD_KEY_MANAGEMENT = 53, +}; + +enum nan_uni_event_tag { + NAN_UNI_EVENT_ID_DE_EVENT_IND = 19, + NAN_UNI_EVENT_REPORT_DW_END = 60, +}; + +enum nan_disc_event_type { + NAN_EVENT_ID_DISC_MAC_ADDR = 0, + NAN_EVENT_ID_JOINED_CLUSTER = 2, +}; + +/* NAN 4.0 Table 79. Device Capability attribute format, Supported Bands */ +enum nan_supported_bands { + NAN_SUPPORTED_BAND_ID_2P4G = 2, + NAN_SUPPORTED_BAND_ID_5G = 4, + NAN_PROPRIETARY_BAND_ID_6G = 6, + NAN_SUPPORTED_BAND_ID_6G = 7, +}; + +enum nan_peer_supported_bands { + NAN_SUPPORTED_BN_2G = 0, + NAN_SUPPORTED_BN_5G_LOW, + NAN_SUPPORTED_BN_5G_HIGH, + NAN_SUPPORTED_BN_6G, + NAN_SUPPORTED_BN_NUM +}; + +#define NAN_CH_CTRL_OP_CLASS GENMASK(15, 8) +#define NAN_CH_CTRL_PRIMARY_CH GENMASK(23, 16) + +#define NAN_CRB_USE_DATA_PATH BIT(0) +#define NAN_CRB_AVAIL_6G_FORMAT GENMASK(2, 1) + +struct mt7925_nan_social_ch_scan_params { + u8 dwell_time[NAN_MAX_SOCIAL_CHANNELS]; + __le16 scan_period[NAN_MAX_SOCIAL_CHANNELS]; +} __packed; + +/* Firmware-reported NAN device information */ +struct nan_dev_info_evt { + u8 is_enabled; + u8 my_addr[ETH_ALEN]; + u8 en_fw_election; + __le32 nan_dev_role; + __le32 nan_dev_state; + u8 mst_preference; + u8 random_factor; + u8 cnt_hop; + u8 cluster_id[ETH_ALEN]; + u8 anchor_mst_addr[ETH_ALEN]; + u8 am_preference; + u8 am_random_factor; + u8 parent_mac[ETH_ALEN]; + u8 parent_am_preference; + u8 parent_am_factor; + __le32 ambtt; + __le32 tsf[2]; + u8 pn_igtk[6]; + u8 pn_bigtk[6]; +}; + +/* Firmware NAN discovery window event */ +struct nan_rpt_dw_evt { + struct nan_dev_info_evt device_info; + __le32 expected_tsf_h; + __le32 expected_tsf_l; + __le32 actual_tsf_h; + __le32 actual_tsf_l; + __le16 channel; + __le16 dw_num; +}; + +struct mt7925_nan_conf_dw { + u8 config_2dot4g_dw_band; + __le32 dw_2dot4g_interval_val; + + u8 config_5g_dw_band; + __le32 dw_5g_interval_val; +} __packed; + +struct mt7925_nan_enable_req_tlv { + __le16 tag; + __le16 len; + + u8 master_pref; + __le16 cluster_low; + __le16 cluster_high; + + u8 config_support_5g; + u8 support_5g_val; + + u8 config_sid_beacon; + u8 sid_beacon_val; + + u8 config_2dot4g_rssi_close; + u8 rssi_close_2dot4g_val; + u8 config_2dot4g_rssi_middle; + u8 rssi_middle_2dot4g_val; + + u8 config_2dot4g_rssi_proximity; + u8 rssi_proximity_2dot4g_val; + u8 config_hop_count_limit; + u8 hop_count_limit_val; + + u8 config_2dot4g_support; + u8 support_2dot4g_val; + + u8 config_2dot4g_beacons; + u8 beacon_2dot4g_val; + + u8 config_2dot4g_sdf; + u8 sdf_2dot4g_val; + + u8 config_5g_beacons; + u8 beacon_5g_val; + + u8 config_5g_sdf; + u8 sdf_5g_val; + + u8 config_5g_rssi_close; + u8 rssi_close_5g_val; + + u8 config_5g_rssi_middle; + u8 rssi_middle_5g_val; + + u8 config_5g_rssi_close_proximity; + u8 rssi_close_proximity_5g_val; + + u8 config_rssi_window_size; + u8 rssi_window_size_val; + + u8 config_oui; + __le32 oui_val; + + u8 config_intf_addr; + u8 intf_addr_val[ETH_ALEN]; + + u8 config_cluster_attribute_val; + + u8 config_scan_params; + struct mt7925_nan_social_ch_scan_params scan_params_val; + + u8 config_random_factor_force; + u8 random_factor_force_val; + + u8 config_hop_count_force; + u8 hop_count_force_val; + + u8 config_24g_channel; + __le32 channel_24g_val; + + u8 config_5g_channel; + __le32 channel_5g_val; + + struct mt7925_nan_conf_dw config_dw; + + u8 config_disc_mac_addr_randomization; + __le32 disc_mac_addr_rand_interval_sec; + + u8 discovery_indication_cfg; + + u8 config_subscribe_sid_beacon; + __le32 subscribe_sid_beacon_val; + + u8 enable_log_slot_statistics; +} __packed __aligned(4); + +struct mt7925_nan_common_hdr { + u8 reserved[4]; +}; + +struct mt7925_nan_master_preference_tlv { + __le16 tag; + __le16 len; + u8 master_preference; + u8 reserved[3]; +} __packed __aligned(4); + +struct mt7925_nan_dw_interval_tlv { + __le16 tag; + __le16 len; + u8 dw_interval; + u8 vendor_ioctl; + __le16 disc_bcn_interval; +} __packed __aligned(4); + +struct mt7925_nan_cluster_id_tlv { + __le16 tag; + __le16 len; + u8 cluster_id[ETH_ALEN]; + u8 reserved[2]; +} __packed __aligned(4); + +struct mt7925_nan_sync_rssi_tlv { + __le16 tag; + __le16 len; + s8 rssi_close_2g; + s8 rssi_middle_2g; + s8 rssi_close_5g; + s8 rssi_middle_5g; +} __packed __aligned(4); + +struct mt7925_nan_de_event { + u8 event_type; + u8 cluster_id[ETH_ALEN]; + u8 anchor_master_rank[NAN_ANCHOR_MASTER_RANK_NUM]; + u8 own_nmi[ETH_ALEN]; + u8 master_nmi[ETH_ALEN]; +}; + +struct mt7925_nan_nmi_addr_tlv { + __le16 tag; + __le16 len; + u8 nmi_addr[ETH_ALEN]; +} __packed __aligned(4); + +struct mt7925_nan_avail_ctrl_tlv { + __le16 tag; + __le16 len; + __le16 avail_ctrl; + u8 seq_id; + u8 reserved[1]; +} __packed __aligned(4); + +struct mt7925_nan_ch_timeline { + u8 is_valid; + u8 reserved[3]; + + __le32 ch_info; + + __le32 num; + __le32 avail_map[NAN_TOTAL_DW]; +}; + +struct mt7925_nan_avail_entry_tlv { + __le16 tag; + __le16 len; + u8 map_id; + u8 is_cond_avail; + u8 timeline_idx; + u8 is_multi_map; + + struct mt7925_nan_ch_timeline ch_list[NAN_TIMELINE_MGMT_CHNL_LIST_NUM]; +} __packed __aligned(4); + +struct mt7925_nan_sched_manage_peer_rec_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 is_activate; + u8 nmi_addr[ETH_ALEN]; + u8 reserved[1]; +} __packed __aligned(4); + +struct mt7925_nan_sched_update_peer_cap_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 supported_bands; + __le16 max_chnl_switch_time; + u8 peer_supported_bands; +} __packed __aligned(4); + +struct mt7925_nan_sched_timeline { + u8 map_id; + u8 local_map_id; + u8 reserved[2]; + union { + __le32 avail_map[NAN_TOTAL_DW]; + u8 avail_block[NAN_TOTAL_DW * 4]; + }; +}; + +struct mt7925_nan_sched_faw_ndc_timeline { + __le32 avail_map[NAN_TOTAL_DW]; +}; + +struct mt7925_nan_sched_ndc_ctrl { + u8 is_valid; + u8 ndc_id[NAN_NDC_ATTRIBUTE_ID_LENGTH]; + u8 ndc_idx; + struct mt7925_nan_sched_timeline timeline[NAN_NUM_AVAIL_DB]; +}; + +struct mt7925_nan_sched_update_crb_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 flags; + u8 is_use_ranging; + u8 reserved[2]; + struct mt7925_nan_sched_timeline comm_ranging_timeline[NAN_TIMELINE_MGMT_SIZE]; + struct mt7925_nan_sched_timeline comm_faw_timeline[NAN_TIMELINE_MGMT_SIZE]; + struct mt7925_nan_sched_ndc_ctrl comm_ndc_ctrl; + struct mt7925_nan_sched_faw_ndc_timeline faw_ndc_timeline[NAN_TIMELINE_MGMT_SIZE]; +} __packed __aligned(4); + +struct mt7925_nan_sched_map_sta_rec_tlv { + __le16 tag; + __le16 len; + u8 nmi_addr[ETH_ALEN]; + u8 sta_rec_idx; + u8 ndp_ctx_id; + + __le32 role_idx; + u8 ndi_addr[ETH_ALEN]; + u8 reserved[2]; +} __packed __aligned(4); + +int mt7925_nan_enable(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf); + +int mt7925_nan_disable(struct ieee80211_vif *vif, + struct mt792x_dev *dev); + +int mt7925_nan_change_configure(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf); + +void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb); + +int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr); + +void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, + struct ieee80211_vif *vif); + +int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, + struct ieee80211_sta *sta); + +int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, + struct ieee80211_sta *sta); + +int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index ea64303283ed..02ef09dd797d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -22,6 +22,10 @@ static const struct pci_device_id mt7925_pci_device_table[] = { .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0738), .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7928), + .driver_data = (kernel_ulong_t)MT7928_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7935), + .driver_data = (kernel_ulong_t)MT7928_FIRMWARE_WM }, { }, }; @@ -43,13 +47,14 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev) if (dev->phy.chip_cap & MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN) wiphy_rfkill_stop_polling(hw->wiphy); + cancel_work_sync(&dev->reset_work); cancel_work_sync(&dev->init_work); mt76_unregister_device(&dev->mt76); mt76_for_each_q_rx(&dev->mt76, i) napi_disable(&dev->mt76.napi[i]); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); - cancel_work_sync(&dev->reset_work); mt7925_tx_token_put(dev); __mt792x_mcu_drv_pmctrl(dev); @@ -110,7 +115,7 @@ static u32 mt7925_reg_map_l2(struct mt792x_dev *dev, u32 addr) static u32 __mt7925_reg_addr(struct mt792x_dev *dev, u32 addr) { - static const struct mt76_connac_reg_map fixed_map[] = { + static const struct mt76_connac_reg_map default_fixed_map[] = { { 0x830c0000, 0x000000, 0x0001000 }, /* WF_MCU_BUS_CR_REMAP */ { 0x54000000, 0x002000, 0x0001000 }, /* WFDMA PCIE0 MCU DMA0 */ { 0x55000000, 0x003000, 0x0001000 }, /* WFDMA PCIE0 MCU DMA1 */ @@ -161,17 +166,112 @@ static u32 __mt7925_reg_addr(struct mt792x_dev *dev, u32 addr) { 0x7c060000, 0x0e0000, 0x0010000 }, /* CONN_INFRA, conn_host_csr_top */ { 0x7c000000, 0x0f0000, 0x0010000 }, /* CONN_INFRA */ { 0x70020000, 0x1f0000, 0x0010000 }, /* Reserved for CBTOP, can't switch */ - { 0x7c500000, 0x060000, 0x2000000 }, /* remap */ + { 0x7c500000, 0x060000, 0x200000 }, /* remap */ { 0x0, 0x0, 0x0 } /* End */ }; - int i; + /* The remap table was ordered from highest to lowest frequency + * to improve lookup efficiency. + */ + static const struct mt76_connac_reg_map mt7928_fixed_map[] = { + {0x54000000, 0x002000, 0x01000}, /* WFDMA_0 (PCIE0 MCU DMA0) */ + {0x55000000, 0x003000, 0x01000}, /* WFDMA_1 (PCIE0 MCU DMA1) */ + {0x57000000, 0x005000, 0x01000}, /* WFDMA_3 (MCU wrap CR) */ + {0x58000000, 0x006000, 0x01000}, /* WFDMA_4 (PCIE1 MCU DMA0) */ + {0x59000000, 0x007000, 0x01000}, /* WFDMA_5 (PCIE1 MCU DMA1) */ + {0x56000000, 0x004000, 0x01000}, /* WFDMA_2 (Reserved) */ + {0x820D0000, 0x030000, 0x10000}, /* WF_LMAC_TOP (WF_WTBLON) */ + {0x820C4000, 0x0A8000, 0x04000}, /* WF_LMAC_TOP (WF_UWTBL) */ + {0x820C0000, 0x008000, 0x04000}, /* WF_UMAC_TOP (PLE) */ + {0x820C8000, 0x00C000, 0x02000}, /* WF_UMAC_TOP (PSE) */ + {0x820CC000, 0x00E000, 0x02000}, /* WF_UMAC_TOP (PP) */ + {0x820F0000, 0x0A0000, 0x00400}, /* WF_LMAC_TOP (WF_CFG) */ + {0x820F1000, 0x0A0600, 0x00200}, /* WF_LMAC_TOP (WF_TRB) */ + {0x820F2000, 0x0A0800, 0x00400}, /* WF_LMAC_TOP (WF_AGG) */ + {0x820F3000, 0x0A0C00, 0x00400}, /* WF_LMAC_TOP (WF_ARB) */ + {0x820F4000, 0x0A1000, 0x00400}, /* WF_LMAC_TOP (WF_TMAC) */ + {0x820F5000, 0x0A1400, 0x00800}, /* WF_LMAC_TOP (WF_RMAC) */ + {0x820F7000, 0x0A1E00, 0x00200}, /* WF_LMAC_TOP (WF_DMA) */ + {0x820F9000, 0x0A3400, 0x00200}, /* WF_LMAC_TOP (WF_WTBLOFF) */ + {0x820FA000, 0x0A4000, 0x00200}, /* WF_LMAC_TOP (WF_ETBF) */ + {0x820FB000, 0x0A4200, 0x00400}, /* WF_LMAC_TOP (WF_LPON) */ + {0x820FC000, 0x0A4600, 0x00200}, /* WF_LMAC_TOP (WF_INT) */ + {0x820FD000, 0x0A4800, 0x00800}, /* WF_LMAC_TOP (WF_MIB) */ + {0x820E0000, 0x020000, 0x00400}, /* WF_LMAC_TOP (WF_CFG) */ + {0x820E1000, 0x020400, 0x00200}, /* WF_LMAC_TOP (WF_TRB) */ + {0x820E2000, 0x020800, 0x00400}, /* WF_LMAC_TOP (WF_AGG) */ + {0x820E3000, 0x020C00, 0x00400}, /* WF_LMAC_TOP (WF_ARB) */ + {0x820E4000, 0x021000, 0x00400}, /* WF_LMAC_TOP (WF_TMAC) */ + {0x820E5000, 0x021400, 0x00800}, /* WF_LMAC_TOP (WF_RMAC) */ + {0x820CE000, 0x021C00, 0x00200}, /* WF_LMAC_TOP (WF_SEC) */ + {0x820E7000, 0x021E00, 0x00200}, /* WF_LMAC_TOP (WF_DMA) */ + {0x820CF000, 0x022000, 0x01000}, /* WF_LMAC_TOP (WF_PF) */ + {0x820E9000, 0x023400, 0x00200}, /* WF_LMAC_TOP (WF_WTBLOFF) */ + {0x820EA000, 0x024000, 0x00200}, /* WF_LMAC_TOP (WF_ETBF) */ + {0x820EB000, 0x024200, 0x00400}, /* WF_LMAC_TOP (WF_LPON) */ + {0x820EC000, 0x024600, 0x00200}, /* WF_LMAC_TOP (WF_INT) */ + {0x820ED000, 0x024800, 0x00800}, /* WF_LMAC_TOP (WF_MIB) */ + {0x820CA000, 0x026000, 0x02000}, /* WF_LMAC_TOP (WF_MUCOP) */ + {0x7C500000, 0x060000, 0x200000}, /* remap */ + {0x7C000000, 0x0F0000, 0x10000}, /* CONN_INFRA (off2on) */ + {0x7C060000, 0x0E0000, 0x10000}, /* remap MT_CONN_ON_LPCTL and MT_CONN_ON_MISC */ + {0x20060000, 0x0E0000, 0x10000}, /* CONN_INFRA conn_host_csr_top */ + {0x7C010000, 0x100000, 0x10000}, /* CONN_INFRA (gpio clkgen cfg) */ + {0x7C050000, 0x1A0000, 0x10000}, /* CONN_INFRA SYSRAM */ + {0x7C080000, 0x190000, 0x10000}, /* CONN_INFRA (coex, pta) */ + {0x7C070000, 0x180000, 0x10000}, /* CONN_INFRA Semaphore */ + {0x7C040000, 0x170000, 0x10000}, /* CONN_INFRA (bus, afe) */ + {0x7C026000, 0x0D6000, 0x0019C}, /* remap DMASHL TOP */ + {0x20020000, 0x0D0000, 0x0C000}, /* CONN_INFRA wf_dma_host_side_cr */ + {0x200B0000, 0x050000, 0x10000}, /* CONN_INFRA conn_von_sysram */ + {0x20090000, 0x150000, 0x08000}, /* CONN_INFRA von_connsys_s0-s7 */ + {0x7C098000, 0x158000, 0x08000}, /* CONN_INFRA von_connsys_hclk_s0-s7 */ + {0x20030000, 0x160000, 0x10000}, /* CONN_INFRA CCIF */ + {0x70000000, 0x1E0000, 0x10000}, /* CONN_INFRA CONN2AP */ + {0x830C0000, 0x000000, 0x01000}, /* WF_MCU_BUS_CR_REMAP */ + {0x81020000, 0x0C0000, 0x10000}, /* WF_TOP_MISC_ON */ + {0x80020000, 0x0B0000, 0x10000}, /* WF_TOP_MISC_OFF */ + {0x81040000, 0x120000, 0x01000}, /* WF_MCU_CFG_ON */ + {0x00400000, 0x080000, 0x10000}, /* WF_MCU_SYSRAM */ + {0x00410000, 0x090000, 0x10000}, /* WF_MCU_SYSRAM (Common driver) */ + {0x88000000, 0x140000, 0x10000}, /* WF_MCU_CFG_LS */ + {0x80010000, 0x124000, 0x01000}, /* WF_AXIDMA */ + {0x81050000, 0x121000, 0x01000}, /* WF_MCU_EINT */ + {0x81060000, 0x122000, 0x01000}, /* WF_MCU_GPT */ + {0x81070000, 0x123000, 0x01000}, /* WF_MCU_WDT */ + {0x830A0000, 0x040000, 0x10000}, /* WF_PHY_MAP0 */ + {0x83090000, 0x060000, 0x10000}, /* WF_PHY_MAP2 */ + {0x83000000, 0x110000, 0x10000}, /* WF_PHY_MAP3 */ + {0x83010000, 0x130000, 0x10000}, /* WF_PHY_MAP4 */ + {0x81030000, 0x0AE000, 0x00100}, /* WFSYS_AON */ + {0x81031000, 0x0AE100, 0x00100}, /* WFSYS_AON */ + {0x81032000, 0x0AE200, 0x00100}, /* WFSYS_AON */ + {0x81033000, 0x0AE300, 0x00100}, /* WFSYS_AON */ + {0x81034000, 0x0AE400, 0x00100}, /* WFSYS_AON */ + {0xE0400000, 0x070000, 0x10000}, /* WF_UMCA_SYSRAM */ + {0x70010000, 0x1C0000, 0x10000}, /* CB Infra1 */ + {0x70020000, 0x1F0000, 0x10000}, /* Reserved for CBTOP, can't switch */ + {0x74040000, 0x1D0000, 0x10000}, /* CB PCIe (cbtop remap) */ + {0x18010000, 0x100000, 0x10000}, /* remap MT_HW_EMI_CTRL */ + {0x00000000, 0x000000, 0x00000}, /* END */ + }; + const struct mt76_connac_reg_map *fixed_map; + size_t array_size; + u32 i; if (addr < 0x200000) return addr; mt7925_reg_remap_restore(dev); - for (i = 0; i < ARRAY_SIZE(fixed_map); i++) { + if (is_mt7928(&dev->mt76)) { + fixed_map = mt7928_fixed_map; + array_size = ARRAY_SIZE(mt7928_fixed_map); + } else { + fixed_map = default_fixed_map; + array_size = ARRAY_SIZE(default_fixed_map); + } + + for (i = 0; i < array_size; i++) { u32 ofs; if (addr < fixed_map[i].phys) @@ -252,6 +352,27 @@ static const struct mt792x_dma_layout mt7927_dma_layout = { MT_RX_DATA_RING_BASE), }; +static const struct mt792x_dma_layout mt7928_dma_layout = { + .tx_data0 = mt792x_dma_ring(MT7925_TXQ_BAND0, + MT7925_TX_RING_SIZE, + MT_TX_RING_BASE), + .tx_mcu = mt792x_dma_ring(MT7925_TXQ_MCU_WM, + MT7925_TX_MCU_RING_SIZE, + MT_TX_RING_BASE), + .tx_fwdl = mt792x_dma_ring(MT7925_TXQ_FWDL, + MT7925_TX_FWDL_RING_SIZE, + MT_TX_RING_BASE), + .tx_done = mt792x_dma_ring(MT7928_RXQ_MCU_WM2, + MT7928_RX_MCU_WA_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_mcu = mt792x_dma_ring(MT7928_RXQ_MCU_WM, + MT7925_RX_MCU_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_data = mt792x_dma_ring(MT7928_RXQ_BAND0, + MT7925_RX_RING_SIZE, + MT_RX_DATA_RING_BASE), +}; + static int mt7927_dma_init(struct mt792x_dev *dev) { int ret; @@ -279,11 +400,62 @@ static int mt7927_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static void mt7928_dma_shdl_lite_init(struct mt792x_dev *dev) +{ + u32 addr, idx, grp1_5_quota, grp15_quota; + u32 q2group[8] = { + 0x04000000, /* AC00->G0,..., AC03->G4 */ + 0x04010101, /* AC10->G1,..., AC13->G4 */ + 0x04020202, /* AC20->G2,..., AC23->G4 */ + 0x04030303, /* AC30->G3,..., AC33->G4 */ + 0x00000005, /* ALTX->G5,BMC->G0,BCN->G0 */ + 0x00000005, /* TGID=1 ALTX->G5 */ + 0x00000000, /* NAF/NBCN/FIXFID -> G0 */ + 0x00000005, /* TGID=2 ALTX->G5 */ + }; + + /* RST */ + mt76_wr(dev, MT_DMASHDL_LITE_MAIN_CONTROL, MT_DMASHDL_LITE_MAIN_CONTROL_SW_RST); + /* pse page size 0x10, ple page size 0x7e0 */ + mt76_wr(dev, MT_DMASHDL_LITE_PAGE_SIZE, + FIELD_PREP(MT_DMASHDL_LITE_PSE_PAGE_SIZE_MASK, 0x10) | + FIELD_PREP(MT_DMASHDL_LITE_PLE_PAGE_SIZE_MASK, 0x7e0)); + /* pse max page 8, ple max page 1 */ + mt76_wr(dev, MT_DMASHDL_LITE_PKT_MAX_SIZE, + FIELD_PREP(MT_DMASHDL_LITE_PSE_PKT_MAX_SIZE_MASK, 8) | + FIELD_PREP(MT_DMASHDL_LITE_PLE_PKT_MAX_SIZE_MASK, 1)); + /* SN/UDF check */ + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK0, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK1, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK0, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK1, 0xffffffff); + /* q mapping */ + for (addr = MT_DMASHDL_LITE_Q_MAPPING0, idx = 0; + idx < ARRAY_SIZE(q2group); + idx++, addr += 4) + mt76_wr(dev, addr, q2group[idx]); + /* refill, set 0 to enable group 0,1,2,3,4,5 & 15 */ + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_DISABLE0, 0xffff7fc0); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_DISABLE1, 0xffffffff); + /* max/min quota */ + grp1_5_quota = FIELD_PREP(MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK, 0x3f0) | + FIELD_PREP(MT_DMASHDL_LITE_GROUP_MIN_QUOTA_MASK, 0x10); + grp15_quota = FIELD_PREP(MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK, 0x30); + + for (addr = MT_DMASHDL_LITE_GROUP0_QUOTA, idx = 0; + idx < DMASHDL_LITE_GROUP_NUM; + idx++, addr += 4) + mt76_wr(dev, addr, (idx <= 5) ? grp1_5_quota : + ((idx == 15) ? grp15_quota : 0)); +} + static int mt7925_dma_init(struct mt792x_dev *dev) { int ret; + const struct mt792x_dma_layout *layout = + is_mt7928(&dev->mt76) ? &mt7928_dma_layout : &mt7925_dma_layout; - ret = mt792x_dma_alloc_queues(dev, &mt7925_dma_layout); + ret = mt792x_dma_alloc_queues(dev, layout); if (ret) return ret; @@ -291,6 +463,9 @@ static int mt7925_dma_init(struct mt792x_dev *dev) if (ret < 0) return ret; + if (is_mt7928(&dev->mt76)) + mt7928_dma_shdl_lite_init(dev); + netif_napi_add_tx(dev->mt76.tx_napi_dev, &dev->mt76.tx_napi, mt792x_poll_tx); napi_enable(&dev->mt76.tx_napi); @@ -298,6 +473,30 @@ static int mt7925_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static const struct mt792x_pcie_reg mt7925_pcie_reg = { + .imask = MT7925_PCIE_MAC_INT_ENABLE, + .pm = MT7925_PCIE_MAC_PM, +}; + +static const struct mt792x_pcie_reg mt7928_pcie_reg = { + .imask = MT7928_PCIE_MAC_INT_ENABLE, + .pm = MT7928_PCIE_MAC_PM, +}; + +static const struct mt792x_irq_map mt7925_irq_map = { + .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, + .tx = { + .all_complete_mask = MT_INT_TX_DONE_ALL, + .mcu_complete_mask = MT_INT_TX_DONE_MCU, + }, + .rx = { + .all_complete_mask = MT7925_INT_RX_DONE_ALL, + .data_complete_mask = MT7925_INT_RX_DONE_DATA, + .wm_complete_mask = MT7925_INT_RX_DONE_WM, + .wm2_complete_mask = MT_INT_RX_DONE_WM2, + }, +}; + static const struct mt792x_irq_map mt7927_irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -305,11 +504,27 @@ static const struct mt792x_irq_map mt7927_irq_map = { .mcu_complete_mask = MT_INT_TX_DONE_MCU, }, .rx = { - .data_complete_mask = MT7927_RX_DONE_INT_ENA4, - .wm_complete_mask = MT7927_RX_DONE_INT_ENA6, - .wm2_complete_mask = MT7927_RX_DONE_INT_ENA7, + .all_complete_mask = MT7927_INT_RX_DONE_ALL, + .data_complete_mask = MT7927_INT_RX_DONE_DATA, + .wm_complete_mask = MT7927_INT_RX_DONE_WM, + .wm2_complete_mask = MT7927_INT_RX_DONE_WM2, }, }; + +static const struct mt792x_irq_map mt7928_irq_map = { + .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, + .tx = { + .all_complete_mask = MT_INT_TX_DONE_ALL, + .mcu_complete_mask = MT_INT_TX_DONE_MCU, + }, + .rx = { + .all_complete_mask = MT7928_INT_RX_DONE_ALL, + .data_complete_mask = MT7928_INT_RX_DONE_DATA, + .wm_complete_mask = MT7928_INT_RX_DONE_WM, + .wm2_complete_mask = MT_INT_RX_DONE_WM2, + }, +}; + static int mt7925_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -339,22 +554,11 @@ static int mt7925_pci_probe(struct pci_dev *pdev, .drv_own = mt792xe_mcu_drv_pmctrl, .fw_own = mt792xe_mcu_fw_pmctrl, }; - static const struct mt792x_irq_map irq_map = { - .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, - .tx = { - .all_complete_mask = MT_INT_TX_DONE_ALL, - .mcu_complete_mask = MT_INT_TX_DONE_MCU, - }, - .rx = { - .data_complete_mask = HOST_RX_DONE_INT_ENA2, - .wm_complete_mask = HOST_RX_DONE_INT_ENA0, - }, - }; - struct ieee80211_ops *ops; + bool is_mt7927_hw, is_mt7928_hw; struct mt76_bus_ops *bus_ops; + struct ieee80211_ops *ops; struct mt792x_dev *dev; struct mt76_dev *mdev; - bool is_mt7927_hw; u8 features; int ret; u16 cmd; @@ -378,12 +582,17 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret < 0) return ret; - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); - if (ret) - goto err_free_pci_vec; - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927 || pdev->device == 0x0738); + is_mt7928_hw = (pdev->device == 0x7928 || pdev->device == 0x7935); + + if (is_mt7928_hw) + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(34)); + else + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + + if (ret) + goto err_free_pci_vec; /* MT7927: ASPM L1 causes unreliable WFDMA register access */ if (mt7925_disable_aspm || is_mt7927_hw) @@ -407,7 +616,18 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; - dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &irq_map; + dev->pcie_reg = &mt7925_pcie_reg; + + if (is_mt7928_hw) { + dev->pcie_reg = &mt7928_pcie_reg; + dev->irq_map = &mt7928_irq_map; + mdev->rev = 0x7928 << 16; + } else if (is_mt7927_hw) { + dev->irq_map = &mt7927_irq_map; + } else { + dev->irq_map = &mt7925_irq_map; + } + mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); @@ -430,6 +650,9 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (!mt7925_disable_aspm && mt76_pci_aspm_supported(pdev)) dev->aspm_supported = true; + if (is_mt7928(&dev->mt76)) + dev->skip_wpdma_reinit = true; + ret = __mt792x_mcu_fw_pmctrl(dev); if (ret) goto err_free_dev; @@ -448,6 +671,11 @@ static int mt7925_pci_probe(struct pci_dev *pdev, "MT7927 raw CHIPID=0x%04x, forcing chip=0x7927\n", mt76_chip(mdev)); mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff); + } else if (is_mt7928_hw && mt76_chip(mdev) != 0x7928) { + dev_info(mdev->dev, + "MT7928 raw CHIPID=0x%04x, forcing chip=0x7928\n", + mt76_chip(mdev)); + mdev->rev = (0x7928 << 16) | (mdev->rev & 0xff); } mt76_rmw_field(dev, MT_HW_EMI_CTL, MT_HW_EMI_CTL_SLPPROT_EN, 1); @@ -456,9 +684,8 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret) goto err_free_dev; - mt76_wr(dev, irq_map.host_irq_enable, 0); - - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->irq_map->host_irq_enable, 0); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); ret = devm_request_irq(mdev->dev, pdev->irq, mt792x_irq_handler, IRQF_SHARED, KBUILD_MODNAME, dev); @@ -467,7 +694,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (is_mt7927(&dev->mt76)) ret = mt7927_dma_init(dev); - else if (is_mt7925(&dev->mt76)) + else if (is_mt7925(&dev->mt76) || is_mt7928(&dev->mt76)) ret = mt7925_dma_init(dev); else ret = -EINVAL; @@ -498,8 +725,8 @@ static void mt7925_pci_remove(struct pci_dev *pdev) struct mt76_dev *mdev = pci_get_drvdata(pdev); struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); - mt7925e_unregister_device(dev); set_bit(MT76_REMOVED, &mdev->phy.state); + mt7925e_unregister_device(dev); devm_free_irq(&pdev->dev, pdev->irq, dev); mt76_free_device(&dev->mt76); pci_free_irq_vectors(pdev); @@ -517,6 +744,7 @@ static int mt7925_pci_suspend(struct device *device) dev->hif_resumed = false; flush_work(&dev->reset_work); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); mt7925_roc_abort_sync(dev); @@ -559,8 +787,7 @@ static int mt7925_pci_suspend(struct device *device) /* disable interrupt */ mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + mt76_wr(dev, dev->pcie_reg->imask, 0x0); synchronize_irq(pdev->irq); tasklet_kill(&mdev->irq_tasklet); @@ -610,12 +837,10 @@ static int _mt7925_pci_resume(struct device *device, bool restore) mt792x_wpdma_reinit_cond(dev); /* enable interrupt */ - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm_complete_mask | - dev->irq_map->rx.wm2_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); @@ -702,6 +927,10 @@ MODULE_FIRMWARE(MT7925_FIRMWARE_WM); MODULE_FIRMWARE(MT7925_ROM_PATCH); MODULE_FIRMWARE(MT7927_FIRMWARE_WM); MODULE_FIRMWARE(MT7927_ROM_PATCH); +MODULE_FIRMWARE(MT7928_FIRMWARE_WM); +MODULE_FIRMWARE(MT7928_ROM_PATCH); +MODULE_FIRMWARE(MT7928_CB_ROM_PATCH); +MODULE_FIRMWARE(MT7928_PHY_RAM); MODULE_AUTHOR("Deren Wu "); MODULE_AUTHOR("Lorenzo Bianconi "); MODULE_DESCRIPTION("MediaTek MT7925E (PCIe) wireless driver"); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c index 97683949a305..8477d21abc66 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c @@ -78,7 +78,7 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + mt76_wr(dev, dev->pcie_reg->imask, 0x0); set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); @@ -119,11 +119,9 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) mt76_wr(dev, dev->irq_map->host_irq_enable, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm_complete_mask | - dev->irq_map->rx.wm2_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); err = mt792xe_mcu_fw_pmctrl(dev); if (err) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c index 6cceff88c656..72707eddc3db 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c @@ -43,7 +43,7 @@ int mt7925e_mcu_init(struct mt792x_dev *dev) if (err) return err; - mt76_rmw_field(dev, MT_PCIE_MAC_PM, MT_PCIE_MAC_PM_L0S_DIS, 1); + mt76_rmw_field(dev, dev->pcie_reg->pm, MT_PCIE_MAC_PM_L0S_DIS, 1); err = mt7925_run_firmware(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c index 16f56ee879d4..f4beb7f52043 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c @@ -9,6 +9,15 @@ static bool mt7925_disable_clc; module_param_named(disable_clc, mt7925_disable_clc, bool, 0644); MODULE_PARM_DESC(disable_clc, "disable CLC support"); +static struct ieee80211_regdomain mt7925_regd_ww = { + .n_reg_rules = 1, + .alpha2 = "00", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412 - 10, 2462 + 10, 40, 6, 20, 0), + } +}; + bool mt7925_regd_clc_supported(struct mt792x_dev *dev) { if (mt7925_disable_clc || @@ -128,35 +137,37 @@ mt7925_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) } } -int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, - enum environment_cap country_ie_env) +static int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap env) { struct ieee80211_hw *hw = mt76_hw(dev); struct wiphy *wiphy = hw->wiphy; - int ret = 0; + int ret; - dev->regd_in_progress = true; - - mt792x_mutex_acquire(dev); - if (!dev->regd_change) - goto err; - - ret = mt7925_mcu_set_clc(dev, alpha2, country_ie_env); + ret = mt7925_mcu_set_clc(dev, alpha2, env); if (ret < 0) - goto err; + return ret; mt7925_regd_be_ctrl(dev, alpha2); mt7925_regd_channel_update(wiphy, dev); ret = mt7925_mcu_set_channel_domain(hw->priv); if (ret < 0) - goto err; + return ret; - ret = mt7925_set_tx_sar_pwr(hw, NULL); - if (ret < 0) - goto err; + return mt7925_set_tx_sar_pwr(hw, NULL); +} -err: +int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env) +{ + int ret = 0; + + dev->regd_in_progress = true; + + mt792x_mutex_acquire(dev); + if (dev->regd_change) + ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env); mt792x_mutex_release(dev); dev->regd_change = false; dev->regd_in_progress = false; @@ -197,11 +208,178 @@ void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req) /* postpone the mcu update to resume */ return; + if (MT7925_REGD_SUPPORTED(&dev->phy)) { + mt7925_regd_update(&dev->phy, req->alpha2); + + return; + } + mt7925_mcu_regd_update(dev, req->alpha2, req->country_ie_env); return; } +static struct sk_buff * +mt7925_regd_query_regdb(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7925_clc *clc = phy->clc[MT792x_CLC_REGD]; + struct mt7925_regd_query_req *req; + struct mt7925_regd_cc *regd_cc; + struct sk_buff *ret_skb = NULL; + u8 *pos, *last_pos; + int ret = 0; + + if (!clc) + return NULL; + + pos = clc->data; + last_pos = pos + le32_to_cpu(clc->len) - sizeof(struct mt7925_clc); + while (pos < last_pos) { + u32 req_len = 0; + u32 rules_len = 0; + u32 sign_len = 4; + u32 n_reg_rules; + + if (pos + sizeof(*regd_cc) > last_pos) + break; + + regd_cc = (struct mt7925_regd_cc *)pos; + n_reg_rules = le32_to_cpu(regd_cc->n_reg_rules); + if (n_reg_rules > NL80211_MAX_SUPP_REG_RULES) + break; + + rules_len = sizeof(struct mt7925_regd_rule_header) + + sizeof(struct mt7925_regd_rule) * n_reg_rules; + + if (pos + sizeof(*regd_cc) + rules_len + sign_len > last_pos) + break; + + pos += sizeof(*regd_cc) + rules_len + sign_len; + if (memcmp(regd_cc->alpha2, alpha2, 2)) + continue; + + req_len = sizeof(*req) + rules_len + sign_len; + req = kzalloc(req_len, GFP_KERNEL); + + if (!req) + return NULL; + + req->tag = cpu_to_le16(0x6); + req->len = cpu_to_le16(req_len - 4); + req->ver = regd_cc->ver; + req->sign_type = regd_cc->sign_type; + req->size = cpu_to_le32(rules_len + sign_len); + req->n_reg_rules = regd_cc->n_reg_rules; + + memcpy(req->alpha2, regd_cc->alpha2, 2); + memcpy(req->data, regd_cc->data, rules_len + sign_len); + + ret = mt76_mcu_send_and_get_msg(&dev->mt76, + MCU_UNI_CMD(SET_POWER_LIMIT), + req, req_len, true, &ret_skb); + kfree(req); + + return ret < 0 ? NULL : ret_skb; + } + + return NULL; +} + +int mt7925_regd_update(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7925_regd_rule *mt7925_rule; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + struct mt7925_regd_rule_ev *ev; + int i, num_of_rules = 0; + struct sk_buff *skb; + int ret = 0; + + if (dev->hw_full_reset) + return 0; + + if (!MT7925_REGD_SUPPORTED(phy)) + return -EOPNOTSUPP; + + mt792x_mutex_acquire(dev); + skb = mt7925_regd_query_regdb(phy, alpha2); + mt792x_mutex_release(dev); + + if (!skb) { + ret = -EINVAL; + goto err; + } + + if (skb->len < sizeof(*ev) + 4) { + ret = -EINVAL; + goto err; + } + + ev = (struct mt7925_regd_rule_ev *)(skb->data + 4); + num_of_rules = le32_to_cpu(ev->n_reg_rules); + + if (!num_of_rules || + WARN_ON_ONCE(num_of_rules > NL80211_MAX_SUPP_REG_RULES)) { + ret = -EINVAL; + goto err; + } + + if (skb->len < struct_size(ev, reg_rule, num_of_rules) + 4) { + ret = -EINVAL; + goto err; + } + + regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + if (!regd) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < num_of_rules; i++) { + mt7925_rule = &ev->reg_rule[i]; + rule = ®d->reg_rules[i]; + + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->start_freq)); + rule->freq_range.end_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->end_freq)); + rule->freq_range.max_bandwidth_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->max_bw)); + /* not used by fw */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = DBM_TO_MBM(22); + rule->flags = le32_to_cpu(mt7925_rule->flags); + } + + regd->n_reg_rules = num_of_rules; + regd->dfs_region = ev->dfs_region; + + memcpy(regd->alpha2, alpha2, 2); + memcpy(mdev->alpha2, alpha2, 2); + + dev->regd_change = true; + mt7925_mcu_regd_update(dev, alpha2, ENVIRON_ANY); + + ret = regulatory_set_wiphy_regd(wiphy, regd); + + kfree(regd); +err: + dev_kfree_skb(skb); + + if (ret < 0) + return regulatory_set_wiphy_regd(wiphy, &mt7925_regd_ww); + + return ret; +} +EXPORT_SYMBOL_GPL(mt7925_regd_update); + static bool mt7925_regd_is_valid_alpha2(const char *alpha2) { @@ -217,6 +395,36 @@ mt7925_regd_is_valid_alpha2(const char *alpha2) return false; } +bool +mt7925_regd_is_valid_channel(struct mt792x_dev *dev, + enum nl80211_band band, + struct ieee80211_channel *chan) +{ + struct ieee80211_hw *hw = mt76_hw(dev); + struct wiphy *wiphy = hw->wiphy; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + int i; + + if (!chan) + return false; + + sband = wiphy->bands[band]; + if (!sband) + return false; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->hw_value == chan->hw_value && + ((ch->flags & IEEE80211_CHAN_DISABLED) == 0)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(mt7925_regd_is_valid_channel); + int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2) { struct wiphy *wiphy = phy->mt76->hw->wiphy; @@ -240,7 +448,9 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2) if (!memcmp(alpha2, mdev->alpha2, 2)) return 0; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { + if (MT7925_REGD_SUPPORTED(phy)) { + return mt7925_regd_update(phy, alpha2); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { return regulatory_hint(wiphy, alpha2); } else { return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR); @@ -255,7 +465,11 @@ int mt7925_regd_init(struct mt792x_phy *phy) struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt76_dev *mdev = &dev->mt76; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { + if (MT7925_REGD_SUPPORTED(phy)) { + wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED | + REGULATORY_DISABLE_BEACON_HINTS; + return mt7925_regd_update(phy, "00"); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE | REGULATORY_DISABLE_BEACON_HINTS; } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h index 0767f078862e..65ef2b140f51 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h @@ -6,13 +6,70 @@ #include "mt7925.h" +struct mt7925_regd_rule_header { + u8 alpha2[2]; + u8 dfs_region; + u8 rsv[13]; +}; + +struct mt7925_regd_rule { + __le32 start_freq; + __le32 end_freq; + __le32 max_bw; + __le32 eirp; + __le32 flags; + u8 rsv[12]; +}; + +struct mt7925_regd_cc { + u8 alpha2[2]; + u8 ver; + u8 rsv; + __le32 n_reg_rules; + u8 sign_type; + u8 rsv1[7]; + u8 data[]; +}; + +struct mt7925_regd_rule_ev { + __le16 tag; + __le16 len; + __le32 n_reg_rules; + u8 dfs_region; + u8 rsv[15]; + struct mt7925_regd_rule reg_rule[]; +}; + +struct mt7925_regd_query_req { + u8 rsv[4]; + __le16 tag; + __le16 len; + u8 ver; + u8 sign_type; + u8 rsv1[2]; + __le32 size; + u8 alpha2[2]; + u8 rsv2[2]; + __le32 n_reg_rules; + u8 rsv3[64]; + u8 data[]; +}; + +#define MT7925_REGD_SUPPORTED(phy) \ + (((phy)->chip_cap & MT792x_CHIP_CAP_REGD_EN) && \ + (phy)->clc[MT792x_CLC_REGD]) + int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env); void mt7925_regd_be_ctrl(struct mt792x_dev *dev, u8 *alpha2); void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req); bool mt7925_regd_clc_supported(struct mt792x_dev *dev); +int mt7925_regd_update(struct mt792x_phy *phy, char *alpha2); int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2); +bool mt7925_regd_is_valid_channel(struct mt792x_dev *dev, + enum nl80211_band band, + struct ieee80211_channel *chan); int mt7925_regd_init(struct mt792x_phy *phy); #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 24985bba1b90..ab6c33b4a180 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -43,11 +43,25 @@ #define HOST_TX_DONE_INT_ENA17 BIT(27) /* WFDMA interrupt */ -#define MT_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA2 -#define MT_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA0 #define MT_INT_RX_DONE_WM2 HOST_RX_DONE_INT_ENA1 -#define MT_INT_RX_DONE_ALL (MT_INT_RX_DONE_DATA | \ - MT_INT_RX_DONE_WM | \ + +#define MT7925_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA2 +#define MT7925_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA0 +#define MT7925_INT_RX_DONE_ALL (MT7925_INT_RX_DONE_DATA | \ + MT7925_INT_RX_DONE_WM | \ + MT_INT_RX_DONE_WM2) + +#define MT7927_INT_RX_DONE_DATA MT7927_RX_DONE_INT_ENA4 +#define MT7927_INT_RX_DONE_WM MT7927_RX_DONE_INT_ENA6 +#define MT7927_INT_RX_DONE_WM2 MT7927_RX_DONE_INT_ENA7 +#define MT7927_INT_RX_DONE_ALL (MT7927_INT_RX_DONE_DATA | \ + MT7927_INT_RX_DONE_WM | \ + MT7927_INT_RX_DONE_WM2) + +#define MT7928_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA0 +#define MT7928_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA3 +#define MT7928_INT_RX_DONE_ALL (MT7928_INT_RX_DONE_DATA | \ + MT7928_INT_RX_DONE_WM | \ MT_INT_RX_DONE_WM2) #define MT_INT_TX_DONE_MCU_WM (HOST_TX_DONE_INT_ENA15 | \ @@ -74,19 +88,52 @@ #define MT_HIF_REMAP_BASE_L1 0x130000 #define MT_HIF_REMAP_L2 0x0120 -#if IS_ENABLED(CONFIG_MT76_DEV) -#define MT_HIF_REMAP_BASE_L2 (0x7c500000 - (0x7c000000 - 0x18000000)) -#else #define MT_HIF_REMAP_BASE_L2 0x18500000 -#endif #define MT_WFSYS_SW_RST_B 0x7c000140 -#define MT_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x370) +#define MT7925_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x370) +#define MT7928_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x400) #define MT_WTBLON_TOP_WDUCR_GROUP GENMASK(4, 0) -#define MT_WTBL_UPDATE MT_WTBLON_TOP(0x380) +#define MT7925_WTBL_UPDATE MT_WTBLON_TOP(0x380) +#define MT7928_WTBL_UPDATE MT_WTBLON_TOP(0x410) #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(11, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(14) +#define MT7928_WTBL_ITCR MT_WTBLON_TOP(0x440) +#define MT7928_WTBL_ITDR0 MT_WTBLON_TOP(0x448) +#define MT7928_WTBL_ITDR1 MT_WTBLON_TOP(0x44c) + +#define MT7925_PCIE_MAC_BASE 0x10000 +#define MT7925_PCIE_MAC(ofs) (MT7925_PCIE_MAC_BASE + (ofs)) +#define MT7925_PCIE_MAC_INT_ENABLE MT7925_PCIE_MAC(0x188) +#define MT7925_PCIE_MAC_PM MT7925_PCIE_MAC(0x194) + +#define MT7928_PCIE_MAC_BASE 0x74040000 +#define MT7928_PCIE_MAC(ofs) (MT7928_PCIE_MAC_BASE + (ofs)) +#define MT7928_PCIE_MAC_INT_ENABLE MT7928_PCIE_MAC(0x188) +#define MT7928_PCIE_MAC_PM MT7928_PCIE_MAC(0x194) + +#define MT_DMASHDL_LITE_BASE 0x20026200 +#define MT_DMASHDL_LITE(ofs) (MT_DMASHDL_LITE_BASE + (ofs)) +#define MT_DMASHDL_LITE_MAIN_CONTROL MT_DMASHDL_LITE(0x004) +#define MT_DMASHDL_LITE_MAIN_CONTROL_SW_RST BIT(18) +#define MT_DMASHDL_LITE_PAGE_SIZE MT_DMASHDL_LITE(0x008) +#define MT_DMASHDL_LITE_PLE_PAGE_SIZE_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_PSE_PAGE_SIZE_MASK GENMASK(28, 16) +#define MT_DMASHDL_LITE_PKT_MAX_SIZE MT_DMASHDL_LITE(0x00c) +#define MT_DMASHDL_LITE_PLE_PKT_MAX_SIZE_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_PSE_PKT_MAX_SIZE_MASK GENMASK(28, 16) +#define MT_DMASHDL_LITE_GROUP_DISABLE0 MT_DMASHDL_LITE(0x010) +#define MT_DMASHDL_LITE_GROUP_DISABLE1 MT_DMASHDL_LITE(0x014) +#define MT_DMASHDL_LITE_GROUP_SN_CHK0 MT_DMASHDL_LITE(0x018) +#define MT_DMASHDL_LITE_GROUP_SN_CHK1 MT_DMASHDL_LITE(0x01c) +#define MT_DMASHDL_LITE_GROUP_UDF_CHK0 MT_DMASHDL_LITE(0x020) +#define MT_DMASHDL_LITE_GROUP_UDF_CHK1 MT_DMASHDL_LITE(0x024) +#define MT_DMASHDL_LITE_Q_MAPPING0 MT_DMASHDL_LITE(0x028) +#define MT_DMASHDL_LITE_GROUP0_QUOTA MT_DMASHDL_LITE(0x100) +#define MT_DMASHDL_LITE_GROUP_MIN_QUOTA_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK GENMASK(28, 16) + #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index e9f58492bf7d..1757023ea70d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -81,6 +81,13 @@ static int mt7925u_mac_reset(struct mt792x_dev *dev) { int err; + if (atomic_read(&dev->mt76.bus_hung)) + return 0; + + mt792xu_reset_on_bus_error(dev); + if (atomic_read(&dev->mt76.bus_hung)) + return 0; + mt76_txq_schedule_all(&dev->mphy); mt76_worker_disable(&dev->mt76.tx_worker); @@ -269,6 +276,7 @@ static int mt7925u_suspend(struct usb_interface *intf, pm_message_t state) pm->suspended = true; dev->hif_resumed = false; flush_work(&dev->reset_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); mt76_connac_mcu_set_hif_suspend(&dev->mt76, true, false); ret = wait_event_timeout(dev->wait, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 70073b43af54..9efc251cb745 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -23,17 +23,19 @@ #define MT792x_CFEND_RATE_11B 0x03 /* 11B LP, 11M */ #define MT792x_FW_TAG_FEATURE 4 +#define MT792x_FW_CAP_NAN BIT(5) #define MT792x_FW_CAP_CNM BIT(7) #define MT792x_CHIP_CAP_CLC_EVT_EN BIT(0) #define MT792x_CHIP_CAP_RSSI_NOTIFY_EVT_EN BIT(1) #define MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN BIT(3) #define MT792x_CHIP_CAP_11D_EN BIT(4) +#define MT792x_CHIP_CAP_REGD_EN BIT(5) #define MT792x_CHIP_CAP_MLO_EN BIT(8) #define MT792x_CHIP_CAP_MLO_EML_EN BIT(9) /* NOTE: used to map mt76_rates. idx may change if firmware expands table */ -#define MT792x_BASIC_RATES_TBL 11 +#define MT792x_BASIC_RATES_TBL 14 #define MT792x_WATCHDOG_TIME (HZ / 4) @@ -47,6 +49,7 @@ #define MT7922_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7922_1.bin" #define MT7925_FIRMWARE_WM "mediatek/mt7925/WIFI_RAM_CODE_MT7925_1_1.bin" #define MT7927_FIRMWARE_WM "mediatek/mt7927/WIFI_RAM_CODE_MT6639_2_1.bin" +#define MT7928_FIRMWARE_WM "mediatek/mt7928/WIFI_RAM_CODE_MT7935_1_1.bin" #define MT7902_ROM_PATCH "mediatek/WIFI_MT7902_patch_mcu_1_1_hdr.bin" #define MT7920_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1a_2_hdr.bin" @@ -54,6 +57,10 @@ #define MT7922_ROM_PATCH "mediatek/WIFI_MT7922_patch_mcu_1_1_hdr.bin" #define MT7925_ROM_PATCH "mediatek/mt7925/WIFI_MT7925_PATCH_MCU_1_1_hdr.bin" #define MT7927_ROM_PATCH "mediatek/mt7927/WIFI_MT6639_PATCH_MCU_2_1_hdr.bin" +#define MT7928_ROM_PATCH "mediatek/mt7928/WIFI_MT7935_PATCH_MCU_1_1_hdr.bin" + +#define MT7928_CB_ROM_PATCH "mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin" +#define MT7928_PHY_RAM "mediatek/mt7928/WIFI_MT7935_PHY_RAM_CODE_1_1.bin" #define MT792x_SDIO_HDR_TX_BYTES GENMASK(15, 0) #define MT792x_SDIO_HDR_PKT_TYPE GENMASK(17, 16) @@ -77,6 +84,7 @@ enum { MT792x_CLC_POWER, MT792x_CLC_POWER_EXT, MT792x_CLC_BE_CTRL, + MT792x_CLC_REGD, MT792x_CLC_MAX_NUM, }; @@ -115,6 +123,20 @@ struct mt792x_link_sta { struct ieee80211_link_sta *pri_link; }; +struct mt792x_sta_nan_sched { + /* protects NAN peer schedule state */ + u16 committed_dw; + u32 sch_idx; + bool idx_assigned; + unsigned long ndp_ctx_bitmap; + bool ndp_ctx_assigned; + u8 ndp_ctx_id; /* assigned NDP context ID (for NDI sta) */ + struct { + u8 map_id; + struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS]; + } maps[CFG80211_NAN_MAX_PEER_MAPS]; +}; + struct mt792x_sta { struct mt792x_link_sta deflink; /* must be first */ struct mt792x_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; @@ -123,6 +145,9 @@ struct mt792x_sta { u16 valid_links; u8 deflink_id; + + /* NAN peer schedule */ + struct mt792x_sta_nan_sched nan_sched; }; DECLARE_EWMA(rssi, 10, 8); @@ -139,6 +164,25 @@ struct mt792x_bss_conf { unsigned int link_id; }; +struct mt792x_nan_conf { + u8 master_pref; + u8 bands; + u8 cluster_id[ETH_ALEN]; + u32 discovery_beacon_interval; + bool enable_dw_notification; +}; + +struct mt792x_nan { + struct mt792x_nan_conf conf; + + /* Scheduler */ + struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS]; + u32 seq_id; + + /* Connection index bitmap, up to NAN_MAX_CONN_CFG peers */ + unsigned long conn_bitmap; +}; + struct mt792x_vif { struct mt792x_bss_conf bss_conf; /* must be first */ struct mt792x_bss_conf __rcu *link_conf[IEEE80211_MLD_MAX_NUM_LINKS]; @@ -153,6 +197,8 @@ struct mt792x_vif { struct work_struct csa_work; struct timer_list csa_timer; + + struct mt792x_nan nan; }; struct mt792x_phy { @@ -200,6 +246,7 @@ struct mt792x_irq_map { u32 mcu_complete_mask; } tx; struct { + u32 all_complete_mask; u32 data_complete_mask; u32 wm_complete_mask; u32 wm2_complete_mask; @@ -216,6 +263,7 @@ struct mt792x_dma_layout { struct mt792x_dma_ring tx_data0; struct mt792x_dma_ring tx_mcu; struct mt792x_dma_ring tx_fwdl; + struct mt792x_dma_ring tx_done; struct mt792x_dma_ring rx_data; struct mt792x_dma_ring rx_mcu; }; @@ -237,6 +285,11 @@ struct mt792x_hif_ops { int (*fw_own)(struct mt792x_dev *dev); }; +struct mt792x_pcie_reg { + u32 imask; + u32 pm; +}; + struct mt792x_dev { union { /* must be first */ struct mt76_dev mt76; @@ -259,6 +312,7 @@ struct mt792x_dev { bool hif_idle:1; bool hif_resumed:1; bool regd_change:1; + bool skip_wpdma_reinit:1; wait_queue_head_t wait; struct work_struct init_work; @@ -272,6 +326,7 @@ struct mt792x_dev { struct mt76_connac_coredump coredump; const struct mt792x_hif_ops *hif_ops; const struct mt792x_irq_map *irq_map; + const struct mt792x_pcie_reg *pcie_reg; struct work_struct ipv6_ns_work; struct delayed_work mlo_pm_work; @@ -283,6 +338,10 @@ struct mt792x_dev { u32 backup_l2; struct ieee80211_chanctx_conf *new_ctx; + + struct ieee80211_vif *nan_vif; + const struct ieee80211_iface_combination *iface_combinations; + int n_iface_combinations; }; static inline struct mt792x_bss_conf * @@ -486,6 +545,8 @@ static inline char *mt792x_ram_name(struct mt792x_dev *dev) return MT7925_FIRMWARE_WM; case 0x7927: return MT7927_FIRMWARE_WM; + case 0x7928: + return MT7928_FIRMWARE_WM; default: return MT7921_FIRMWARE_WM; } @@ -504,11 +565,33 @@ static inline char *mt792x_patch_name(struct mt792x_dev *dev) return MT7925_ROM_PATCH; case 0x7927: return MT7927_ROM_PATCH; + case 0x7928: + return MT7928_ROM_PATCH; default: return MT7921_ROM_PATCH; } } +static inline char *mt792x_cb_patch_name(struct mt792x_dev *dev) +{ + switch (mt76_chip(&dev->mt76)) { + case 0x7928: + return MT7928_CB_ROM_PATCH; + default: + return NULL; + } +} + +static inline char *mt792x_phy_ram_name(struct mt792x_dev *dev) +{ + switch (mt76_chip(&dev->mt76)) { + case 0x7928: + return MT7928_PHY_RAM; + default: + return NULL; + } +} + int mt792x_load_firmware(struct mt792x_dev *dev); /* usb */ diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index b50825eccdaf..0ad33f74c228 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -3,6 +3,7 @@ #include #include +#include #include "mt792x.h" #include "dma.h" @@ -60,7 +61,41 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = { } }; -static const struct ieee80211_iface_combination if_comb_chanctx[] = { +static const struct ieee80211_iface_limit if_limits_nan_mcc[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_NAN), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_NAN_DATA), + }, +}; + +static const struct ieee80211_iface_limit if_limits_nan_scc[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_NAN), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_NAN_DATA), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP), + }, +}; + +static const struct ieee80211_iface_combination if_comb_chanctx_base[] = { { .limits = if_limits_chanctx_mcc, .n_limits = ARRAY_SIZE(if_limits_chanctx_mcc), @@ -77,6 +112,60 @@ static const struct ieee80211_iface_combination if_comb_chanctx[] = { } }; +static const struct ieee80211_iface_combination if_comb_chanctx_nan[] = { + { + .limits = if_limits_nan_mcc, + .n_limits = ARRAY_SIZE(if_limits_nan_mcc), + .max_interfaces = MT792x_MAX_INTERFACES, + .num_different_channels = 2, + .beacon_int_infra_match = false, + }, + { + .limits = if_limits_nan_scc, + .n_limits = ARRAY_SIZE(if_limits_nan_scc), + .max_interfaces = MT792x_MAX_INTERFACES, + .num_different_channels = 1, + .beacon_int_infra_match = false, + }, +}; + +static int mt792x_setup_iface_combinations(struct mt792x_dev *dev, + struct wiphy *wiphy) +{ + const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM); + const bool nan = !!(dev->fw_features & MT792x_FW_CAP_NAN); + const int n_base = ARRAY_SIZE(if_comb_chanctx_base); + const int n_nan = ARRAY_SIZE(if_comb_chanctx_nan); + struct ieee80211_iface_combination *comb; + + if (!cnm) { + dev->iface_combinations = if_comb; + dev->n_iface_combinations = ARRAY_SIZE(if_comb); + return 0; + } + + /* CNM enabled, NAN optional */ + if (!nan) { + dev->iface_combinations = if_comb_chanctx_base; + dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base); + return 0; + } + + /* CNM + NAN: dynamically build base + nan list */ + comb = devm_kcalloc(&wiphy->dev, n_base + n_nan, sizeof(*comb), + GFP_KERNEL); + if (!comb) + return -ENOMEM; + + memcpy(comb, if_comb_chanctx_base, sizeof(if_comb_chanctx_base)); + memcpy(comb + n_base, if_comb_chanctx_nan, sizeof(if_comb_chanctx_nan)); + + dev->iface_combinations = comb; + dev->n_iface_combinations = n_base + n_nan; + + return 0; +} + void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb) { @@ -663,6 +752,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) struct mt792x_phy *phy = mt792x_hw_phy(hw); struct mt792x_dev *dev = phy->dev; struct wiphy *wiphy = hw->wiphy; + int err; hw->queues = 4; if (dev->has_eht) { @@ -683,15 +773,17 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) hw->vif_data_size = sizeof(struct mt792x_vif); hw->chanctx_data_size = sizeof(struct mt792x_chanctx); - if (dev->fw_features & MT792x_FW_CAP_CNM) { + if (dev->fw_features & MT792x_FW_CAP_CNM) wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - wiphy->iface_combinations = if_comb_chanctx; - wiphy->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx); - } else { + else wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - wiphy->iface_combinations = if_comb; - wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); - } + + err = mt792x_setup_iface_combinations(dev, wiphy); + if (err) + return err; + + wiphy->iface_combinations = dev->iface_combinations; + wiphy->n_iface_combinations = dev->n_iface_combinations; wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP | WIPHY_FLAG_4ADDR_STATION); wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | @@ -699,6 +791,22 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_DEVICE); + + if ((dev->fw_features & MT792x_FW_CAP_CNM) && + (dev->fw_features & MT792x_FW_CAP_NAN)) { + wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN) | + BIT(NL80211_IFTYPE_NAN_DATA); + wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ) | + BIT(NL80211_BAND_5GHZ); + wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC | + WIPHY_NAN_FLAGS_USERSPACE_DE; + wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK; + wiphy->nan_capa.n_antennas = 0x22; + wiphy->nan_capa.max_channel_switch_time = 12; + wiphy->nan_capa.dev_capabilities = NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED; + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SECURE_NAN); + } + wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN; wiphy->max_scan_ssids = 4; wiphy->max_sched_scan_plan_interval = @@ -712,6 +820,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR; + phy->mt76->no_active_monitor = true; wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_LEGACY); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_HT); @@ -719,12 +828,16 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_HE); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS); ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS); ieee80211_hw_set(hw, HAS_RATE_CONTROL); ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD); ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD); - ieee80211_hw_set(hw, WANT_MONITOR_VIF); + if (is_mt7927(&dev->mt76)) + ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); + else + ieee80211_hw_set(hw, WANT_MONITOR_VIF); ieee80211_hw_set(hw, SUPPORTS_PS); ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); @@ -985,6 +1098,15 @@ int mt792x_load_firmware(struct mt792x_dev *dev) dev_warn(dev->mt76.dev, "MCU is not ready for firmware download\n"); + if (is_mt7928(&dev->mt76)) { + dev_info(dev->mt76.dev, "Loading CB firmware patch: %s\n", + mt792x_cb_patch_name(dev)); + ret = mt76_connac3_load_cb_patch(&dev->mt76, mt792x_cb_patch_name(dev)); + if (ret) + return ret; + } + + dev_info(dev->mt76.dev, "Loading firmware patch: %s\n", mt792x_patch_name(dev)); ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev)); if (ret) return ret; @@ -996,6 +1118,12 @@ int mt792x_load_firmware(struct mt792x_dev *dev) ret = __mt792x_mcu_drv_pmctrl(dev); } + if (is_mt7928(&dev->mt76)) { + ret = mt76_connac3_load_phy_ram(&dev->mt76, mt792x_phy_ram_name(dev)); + if (ret) + return ret; + } + ret = mt76_connac2_load_ram(&dev->mt76, mt792x_ram_name(dev), NULL); if (ret) return ret; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index fc326447c792..e74b95fdc767 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -39,9 +39,7 @@ void mt792x_irq_tasklet(unsigned long data) trace_dev_irq(&dev->mt76, intr, dev->mt76.mmio.irqmask); - mask |= intr & (irq_map->rx.data_complete_mask | - irq_map->rx.wm_complete_mask | - irq_map->rx.wm2_complete_mask); + mask |= intr & irq_map->rx.all_complete_mask; if (intr & dev->irq_map->tx.mcu_complete_mask) mask |= dev->irq_map->tx.mcu_complete_mask; @@ -124,6 +122,17 @@ int mt792x_dma_alloc_queues(struct mt792x_dev *dev, if (ret) return ret; + /* tx done */ + if (layout->tx_done.ring_base) { + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU_WA], + layout->tx_done.qid, + layout->tx_done.n_desc, + MT_RX_BUF_SIZE, + layout->tx_done.ring_base); + if (ret) + return ret; + } + /* rx event */ ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU], layout->rx_mcu.qid, @@ -197,9 +206,38 @@ static void mt7927_wfdma_setup(struct mt792x_dev *dev) mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); } +static void mt7928_dma_prefetch_setup(struct mt792x_dev *dev) +{ + /* rx ring */ + mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0000, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING1_EXT_CTRL, PREFETCH(0x0080, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING2_EXT_CTRL, PREFETCH(0x00c0, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING3_EXT_CTRL, PREFETCH(0x0140, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING4_EXT_CTRL, PREFETCH(0x0180, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING5_EXT_CTRL, PREFETCH(0x01c0, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING6_EXT_CTRL, PREFETCH(0x0200, 0x4)); + /* tx ring */ + mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, PREFETCH(0x0240, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING1_EXT_CTRL, PREFETCH(0x0340, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING2_EXT_CTRL, PREFETCH(0x0440, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING3_EXT_CTRL, PREFETCH(0x0540, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING4_EXT_CTRL, PREFETCH(0x0640, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING5_EXT_CTRL, PREFETCH(0x0740, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x0840, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0880, 0x4)); +} + +static void mt7928_wfdma_setup(struct mt792x_dev *dev) +{ + mt76_wr(dev, MT_WFDMA0_INT_RX_PRI, 0); + mt76_wr(dev, MT_WFDMA0_INT_TX_PRI, 0); +} + static void mt792x_dma_prefetch(struct mt792x_dev *dev) { - if (is_mt7927(&dev->mt76)) { + if (is_mt7928(&dev->mt76)) { + mt7928_dma_prefetch_setup(dev); + } else if (is_mt7927(&dev->mt76)) { mt7927_dma_prefetch_setup(dev); } else if (is_mt7925(&dev->mt76)) { mt7925_dma_prefetch_setup(dev); @@ -241,32 +279,69 @@ static void mt792x_dma_prefetch(struct mt792x_dev *dev) int mt792x_dma_enable(struct mt792x_dev *dev) { - /* configure perfetch settings */ + u32 addr; + + /* configure prefetch settings */ mt792x_dma_prefetch(dev); /* reset dma idx */ mt76_wr(dev, MT_WFDMA0_RST_DTX_PTR, ~0); - if (is_mt7925(&dev->mt76)) + if (is_mt7925(&dev->mt76) || is_mt7928(&dev->mt76)) mt76_wr(dev, MT_WFDMA0_RST_DRX_PTR, ~0); /* configure delay interrupt */ mt76_wr(dev, MT_WFDMA0_PRI_DLY_INT_CFG0, 0); - mt76_set(dev, MT_WFDMA0_GLO_CFG, - MT_WFDMA0_GLO_CFG_TX_WB_DDONE | - MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | - MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | - MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | - FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 3) | - MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | - MT_WFDMA0_GLO_CFG_RX_WB_DDONE | - MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | - MT_WFDMA0_GLO_CFG_OMIT_RX_INFO_PFET2); + if (is_mt7928(&dev->mt76)) { + mt76_wr(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_TX_WB_DDONE | + MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | + MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | + MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | + FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 1) | + MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | + MT_WFDMA0_GLO_CFG_RX_WB_DDONE | + MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | + MT_WFDMA0_GLO_CFG_ADDR_EXT_EN | + MT_WFDMA0_GLO_CFG_CSR_LBK_RX_Q_SEL_EN); + /* set rxq threshold to 2 */ + for (addr = MT_WFDMA0_WPDMA_PAUSE_RXQ_TH10; + addr <= MT_WFDMA0_WPDMA_PAUSE_RXQ_TH76; + addr += 4) { + mt76_wr(dev, addr, + FIELD_PREP(MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_L_TH_MASK, 2) | + FIELD_PREP(MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_H_TH_MASK, 2)); + } + mt76_wr(dev, MT_WFDMA0_GLO_CFG_EXT0, + MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_ARB_LOCK_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_LITE_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_WB_KEEP_RSVD | + MT_WFDMA0_GLO_CFG_EXT0_CSR_BID_CHECK_BYPASS_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_INFO_WB_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AWUSER_LOCK_EN | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_MAX_PREFETCH_CNT_MASK, 3) | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_BST_SIZE_MASK, 3) | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AW_OUTSTANDING_NUM_MASK, + 8)); + } else { + mt76_set(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_TX_WB_DDONE | + MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | + MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | + MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | + FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 3) | + MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | + MT_WFDMA0_GLO_CFG_RX_WB_DDONE | + MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | + MT_WFDMA0_GLO_CFG_OMIT_RX_INFO_PFET2); + } mt76_set(dev, MT_WFDMA0_GLO_CFG, MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN); - if (is_mt7927(&dev->mt76)) + if (is_mt7928(&dev->mt76)) + mt7928_wfdma_setup(dev); + else if (is_mt7927(&dev->mt76)) mt7927_wfdma_setup(dev); else if (is_mt7925(&dev->mt76)) mt7925_wfdma_setup(dev); @@ -276,9 +351,7 @@ int mt792x_dma_enable(struct mt792x_dev *dev) /* enable interrupts for TX/RX rings */ mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm2_complete_mask | - dev->irq_map->rx.wm_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); @@ -345,11 +418,15 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev) struct mt76_connac_pm *pm = &dev->pm; int err; + if (dev->skip_wpdma_reinit) + return 0; + /* check if the wpdma must be reinitialized */ if (mt792x_dma_need_reinit(dev)) { /* disable interrutpts */ mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + if (dev->pcie_reg) + mt76_wr(dev, dev->pcie_reg->imask, 0x0); err = mt792x_wpdma_reset(dev, false); if (err) { @@ -358,7 +435,8 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev) } /* enable interrutpts */ - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + if (dev->pcie_reg) + mt76_wr(dev, dev->pcie_reg->imask, 0xff); pm->stats.lp_wake++; } @@ -388,7 +466,7 @@ int mt792x_dma_disable(struct mt792x_dev *dev, bool force) /* disable dmashdl */ mt76_clear(dev, MT_WFDMA0_GLO_CFG_EXT0, - MT_WFDMA0_CSR_TX_DMASHDL_ENABLE); + MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN); mt76_set(dev, MT_DMASHDL_SW_CONTROL, MT_DMASHDL_DMASHDL_BYPASS); if (force) { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 4cd5b33b640e..3587f61e098a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -331,11 +331,29 @@ #define MT_INT_MCU_CMD MCU2HOST_SW_INT_ENA #define MT_WFDMA0_RST_DTX_PTR MT_WFDMA0(0x20c) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH10 MT_WFDMA0(0x260) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH32 MT_WFDMA0(0x264) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH54 MT_WFDMA0(0x268) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH76 MT_WFDMA0(0x26c) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_L_TH_MASK GENMASK(11, 0) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_H_TH_MASK GENMASK(27, 16) + #define MT_WFDMA0_RST_DRX_PTR MT_WFDMA0(0x280) #define MT_WFDMA0_INT_RX_PRI MT_WFDMA0(0x298) #define MT_WFDMA0_INT_TX_PRI MT_WFDMA0(0x29c) #define MT_WFDMA0_GLO_CFG_EXT0 MT_WFDMA0(0x2b0) -#define MT_WFDMA0_CSR_TX_DMASHDL_ENABLE BIT(6) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_ARB_LOCK_EN BIT(4) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN BIT(6) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_LITE_EN BIT(7) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_WB_KEEP_RSVD BIT(10) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_BID_CHECK_BYPASS_EN BIT(22) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_INFO_WB_EN BIT(23) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AWUSER_LOCK_EN BIT(29) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MAX_PREFETCH_CNT_MASK GENMASK(1, 0) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_BST_SIZE_MASK GENMASK(3, 2) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AW_OUTSTANDING_NUM_MASK GENMASK(27, 24) + +#define MT_WFDMA0_GLO_CFG_EXT1 MT_WFDMA0(0x2b4) #define MT_WFDMA0_PRI_DLY_INT_CFG0 MT_WFDMA0(0x2f0) #define MT_WFDMA0_TX_RING0_EXT_CTRL MT_WFDMA0(0x600) @@ -375,7 +393,6 @@ #define MT_WFDMA_PREFETCH_CFG1 MT_WFDMA_EXT_CSR(0xf4) #define MT_WFDMA_PREFETCH_CFG2 MT_WFDMA_EXT_CSR(0xf8) #define MT_WFDMA_PREFETCH_CFG3 MT_WFDMA_EXT_CSR(0xfc) -#define MT_WFDMA0_GLO_CFG_EXT1 MT_WFDMA0(0x2b4) #define MT_SWDEF_BASE 0x41f200 #define MT_SWDEF(ofs) (MT_SWDEF_BASE + (ofs)) @@ -415,10 +432,6 @@ #define MT_HW_EMI_CTL 0x18011100 #define MT_HW_EMI_CTL_SLPPROT_EN BIT(1) -#define MT_PCIE_MAC_BASE 0x10000 -#define MT_PCIE_MAC(ofs) (MT_PCIE_MAC_BASE + (ofs)) -#define MT_PCIE_MAC_INT_ENABLE MT_PCIE_MAC(0x188) -#define MT_PCIE_MAC_PM MT_PCIE_MAC(0x194) #define MT_PCIE_MAC_PM_L0S_DIS BIT(8) #define MT_DMA_SHDL(ofs) (0x7c026000 + (ofs)) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 910132e94956..850f6b2cfc4a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -11,6 +11,9 @@ #include "mt792x.h" #include "mt76_connac2_mac.h" +#define MT792X_USB_TX_TIMEOUT_LIMIT 50000 +#define MT792X_USB_UDMA_IDLE_TIMEOUT 1000 + static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf) { return __mt76u_vendor_request(dev, MT_VEND_READ_EXT, @@ -31,10 +34,75 @@ static void mt792xu_reset_work(struct work_struct *work) atomic_set(&dev->usb_reset_pending, 0); } +static void mt792xu_queue_usb_reset(struct mt792x_dev *dev, int err) +{ + if (!atomic_xchg(&dev->usb_reset_pending, 1)) { + dev_warn(dev->mt76.dev, + "USB transport access failed (%d), queueing device reset\n", + err); + + schedule_work(&dev->usb_reset_work); + } +} + +static u32 mt792xu_bus_hung_rr(struct mt76_dev *mdev, u32 offset) +{ + return 0; +} + +static void mt792xu_bus_hung_wr(struct mt76_dev *mdev, u32 offset, u32 val) +{ +} + +static u32 mt792xu_bus_hung_rmw(struct mt76_dev *mdev, u32 offset, + u32 mask, u32 val) +{ + return 0; +} + +static void mt792xu_bus_hung_write_copy(struct mt76_dev *mdev, u32 offset, + const void *data, int len) +{ +} + +static void mt792xu_bus_hung_read_copy(struct mt76_dev *mdev, u32 offset, + void *data, int len) +{ + memset(data, 0, len); +} + +static const struct mt76_bus_ops mt792xu_bus_hung_ops = { + .rr = mt792xu_bus_hung_rr, + .wr = mt792xu_bus_hung_wr, + .rmw = mt792xu_bus_hung_rmw, + .write_copy = mt792xu_bus_hung_write_copy, + .read_copy = mt792xu_bus_hung_read_copy, + .type = MT76_BUS_USB, +}; + +static void mt792xu_set_bus_hung(struct mt792x_dev *dev) +{ + atomic_set(&dev->mt76.bus_hung, true); + + if (READ_ONCE(dev->mt76.bus) == &mt792xu_bus_hung_ops) + return; + + WRITE_ONCE(dev->mt76.bus, &mt792xu_bus_hung_ops); +} + +static void mt792xu_ctrl_timeout(struct mt76_dev *mdev, int err) +{ + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + + mt792xu_set_bus_hung(dev); + mt792xu_queue_usb_reset(dev, err); +} + void mt792xu_reset_work_init(struct mt792x_dev *dev) { INIT_WORK(&dev->usb_reset_work, mt792xu_reset_work); atomic_set(&dev->usb_reset_pending, 0); + dev->mt76.usb.ctrl_timeout = mt792xu_ctrl_timeout; } EXPORT_SYMBOL_GPL(mt792xu_reset_work_init); @@ -62,26 +130,23 @@ EXPORT_SYMBOL_GPL(mt792xu_check_bus); int mt792xu_reset_on_bus_error(struct mt792x_dev *dev) { - int err = 0; + int err; - if (!atomic_read(&dev->mt76.bus_hung)) - err = mt792xu_check_bus(dev); + /* Once hung, the no-op bus ops stay installed until the queued USB + * reset re-probes the device. Do not clear bus_hung here, or the caller + * would run a full reset over dropped register I/O and report success. + */ + if (atomic_read(&dev->mt76.bus_hung)) + return -EIO; + err = mt792xu_check_bus(dev); if (err) { - atomic_set(&dev->mt76.bus_hung, true); - - if (!atomic_xchg(&dev->usb_reset_pending, 1)) { - dev_warn(dev->mt76.dev, - "USB transport access failed (%d), queueing device reset\n", - err); - - schedule_work(&dev->usb_reset_work); - } + mt792xu_set_bus_hung(dev); + mt792xu_queue_usb_reset(dev, err); return err; } - atomic_set(&dev->mt76.bus_hung, false); return 0; } EXPORT_SYMBOL_GPL(mt792xu_reset_on_bus_error); @@ -169,9 +234,9 @@ EXPORT_SYMBOL_GPL(mt792xu_mcu_power_on); static void mt792xu_cleanup(struct mt792x_dev *dev) { clear_bit(MT76_STATE_INITIALIZED, &dev->mphy.state); - mt792xu_wfsys_reset(dev); skb_queue_purge(&dev->mt76.mcu.res_q); mt76u_queues_deinit(&dev->mt76); + mt792xu_wfsys_reset(dev); } static u32 mt792xu_uhw_rr(struct mt76_dev *dev, u32 addr) @@ -281,6 +346,23 @@ static void mt792xu_epctl_rst_opt(struct mt792x_dev *dev, bool reset) mt792xu_uhw_wr(&dev->mt76, MT_SSUSB_EPCTL_CSR_EP_RST_OPT, val); } +static void mt792xu_wait_udma_idle(struct mt792x_dev *dev) +{ + u32 mask = MT_WL_RX_BUSY | MT_WL_TX_BUSY; + u32 val; + + mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_FLUSH); + + if (mt76_poll_msec(dev, MT_UDMA_WLCFG_0, mask, 0, + MT792X_USB_UDMA_IDLE_TIMEOUT)) + return; + + val = mt76_rr(dev, MT_UDMA_WLCFG_0); + + dev_warn(dev->mt76.dev, + "UDMA busy before WFSYS reset: WLCFG0=0x%08x\n", val); +} + struct mt792xu_wfsys_desc { u32 rst_reg; u32 done_reg; @@ -319,6 +401,10 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume) mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_EN | MT_WL_TX_EN | MT_WL_RX_MPSZ_PAD0 | MT_TICK_1US_EN); + mt76_rmw(dev, MT_UDMA_WLCFG_1, MT_WL_TX_TMOUT_LMT, + FIELD_PREP(MT_WL_TX_TMOUT_LMT, + MT792X_USB_TX_TIMEOUT_LIMIT)); + mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_TX_TMOUT_FUNC_EN); mt76_clear(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT); mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT); @@ -344,6 +430,10 @@ int mt792xu_wfsys_reset(struct mt792x_dev *dev) u32 val; int i; + if (atomic_read(&dev->mt76.bus_hung)) + return -EIO; + + mt792xu_wait_udma_idle(dev); mt792xu_epctl_rst_opt(dev, false); val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg); @@ -408,13 +498,27 @@ void mt792xu_disconnect(struct usb_interface *usb_intf) { struct mt792x_dev *dev = usb_get_intfdata(usb_intf); - mt792xu_reset_work_cleanup(dev); - cancel_work_sync(&dev->init_work); - if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) + if (!dev) return; + set_bit(MT76_RESET, &dev->mphy.state); + set_bit(MT76_MCU_RESET, &dev->mphy.state); + clear_bit(MT76_STATE_RUNNING, &dev->mphy.state); + wake_up(&dev->mt76.mcu.wait); + skb_queue_purge(&dev->mt76.mcu.res_q); + + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->init_work); + mt76_worker_disable(&dev->mt76.tx_worker); + mt792xu_reset_work_cleanup(dev); + if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) { + set_bit(MT76_REMOVED, &dev->mphy.state); + return; + } + mt76_unregister_device(&dev->mt76); mt792xu_cleanup(dev); + set_bit(MT76_REMOVED, &dev->mphy.state); usb_set_intfdata(usb_intf, NULL); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c index ac05f7d75d63..ec33521db564 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c @@ -150,6 +150,12 @@ mt7996_eeprom_check_or_use_default(struct mt7996_dev *dev, bool use_default) goto out; } + if (fw->size < MT7996_EEPROM_SIZE) { + dev_err(dev->mt76.dev, "Invalid default bin size\n"); + ret = -EINVAL; + goto out; + } + if (!use_default && mt7996_eeprom_variant_valid(dev, fw->data)) goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index d6f9aa1ab52d..fb635a092584 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -34,7 +34,6 @@ static const struct ieee80211_iface_combination if_comb_global = { BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_160), - .beacon_int_min_gcd = 100, }; static const struct ieee80211_iface_combination if_comb_global_7992 = { @@ -47,7 +46,6 @@ static const struct ieee80211_iface_combination if_comb_global_7992 = { BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_160), - .beacon_int_min_gcd = 100, }; static const struct ieee80211_iface_limit if_limits[] = { @@ -463,6 +461,8 @@ mt7996_init_wiphy_band(struct ieee80211_hw *hw, struct mt7996_phy *phy) radio->n_freq_range = 1; radio->iface_combinations = &if_comb; radio->n_iface_combinations = 1; + memcpy(dev->radio_addrs[n_radios].addr, phy->mt76->macaddr, ETH_ALEN); + hw->wiphy->n_addresses++; hw->wiphy->n_radio++; wiphy->available_antennas_rx |= phy->mt76->chainmask; @@ -505,6 +505,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy->n_iface_combinations = 1; wiphy->radio = dev->radios; + wiphy->addresses = dev->radio_addrs; wiphy->reg_notifier = mt7996_regd_notifier; wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH | @@ -524,6 +525,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); if (mt7996_eeprom_has_background_radar(dev) && (!mdev->dev->of_node || @@ -559,6 +561,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band) { u32 mask, set; + if (!mt7996_band_valid(dev, band)) + return; + /* clear estimated value of EIFS for Rx duration & OBSS time */ mt76_wr(dev, MT_WF_RMAC_RSVD0(band), MT_WF_RMAC_RSVD0_EIFS_CLR); @@ -590,6 +595,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band) * MT_AGG_ACR_PPDU_TXS2H (PPDU format) even though ACR bit is set. */ mt76_set(dev, MT_AGG_ACR4(band), MT_AGG_ACR_PPDU_TXS2H); + + if (!is_mt7996(&dev->mt76)) + mt7996_mcu_set_bssid_mapping_addr(&dev->mt76, band); } static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev) @@ -1561,7 +1569,6 @@ mt7996_init_eht_caps(struct mt7996_phy *phy, enum nl80211_band band, struct ieee80211_sta_eht_cap *eht_cap = &data->eht_cap; struct ieee80211_eht_cap_elem_fixed *eht_cap_elem = &eht_cap->eht_cap_elem; struct ieee80211_eht_mcs_nss_supp *eht_nss = &eht_cap->eht_mcs_nss_supp; - enum nl80211_chan_width width = phy->mt76->chandef.width; int nss = hweight8(phy->mt76->antenna_mask); int sts = hweight16(phy->mt76->chainmask); u8 val; @@ -1637,11 +1644,16 @@ mt7996_init_eht_caps(struct mt7996_phy *phy, enum nl80211_band band, u8_encode_bits(u8_get_bits(1, GENMASK(1, 0)), IEEE80211_EHT_PHY_CAP5_MAX_NUM_SUPP_EHT_LTF_MASK); - val = width == NL80211_CHAN_WIDTH_320 ? 0xf : - width == NL80211_CHAN_WIDTH_160 ? 0x7 : - width == NL80211_CHAN_WIDTH_80 ? 0x3 : 0x1; - eht_cap_elem->phy_cap_info[6] = - u8_encode_bits(val, IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_MASK); + eht_cap_elem->phy_cap_info[6] = IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_MASK; + if (band != NL80211_BAND_6GHZ) { + eht_cap_elem->phy_cap_info[6] &= + ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ; + + if (band != NL80211_BAND_5GHZ) + eht_cap_elem->phy_cap_info[6] &= + ~(IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_160MHZ | + IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_80MHZ); + } val = u8_encode_bits(nss, IEEE80211_EHT_MCS_NSS_RX) | u8_encode_bits(nss, IEEE80211_EHT_MCS_NSS_TX); @@ -1799,6 +1811,8 @@ void mt7996_unregister_device(struct mt7996_dev *dev) { cancel_work_sync(&dev->dump_work); cancel_work_sync(&dev->wed_rro.work); + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->rc_work); mt7996_unregister_phy(mt7996_phy3(dev)); mt7996_unregister_phy(mt7996_phy2(dev)); mt7996_unregister_thermal(&dev->phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 0eebc8182ca9..1cba381a78b8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -166,7 +166,7 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink = rcu_dereference(msta->vif->mt76.link[wcid->link_id]); if (mlink) { @@ -294,6 +294,10 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev, dcm = FIELD_GET(MT_PRXV_DCM, v2); bw = FIELD_GET(MT_PRXV_FRAME_MODE, v2); + /* the hardware reports NSTS; report the data NSS for STBC frames */ + if (stbc && nss > 1) + nss >>= 1; + switch (*mode) { case MT_PHY_TYPE_CCK: cck = true; @@ -349,7 +353,7 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev, case IEEE80211_STA_RX_BW_20: break; case IEEE80211_STA_RX_BW_40: - if (*mode & MT_PHY_TYPE_HE_EXT_SU && + if (*mode == MT_PHY_TYPE_HE_EXT_SU && (idx & MT_PRXV_TX_ER_SU_106T)) { status->bw = RATE_INFO_BW_HE_RU; status->he_ru = @@ -441,7 +445,13 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, memset(status, 0, sizeof(*status)); band_idx = FIELD_GET(MT_RXD1_NORMAL_BAND_IDX, rxd1); + if (!mt7996_band_valid(dev, band_idx)) + return -EINVAL; + mphy = dev->mt76.phys[band_idx]; + if (!mphy) + return -EINVAL; + phy = mphy->priv; status->phy_idx = mphy->band_idx; @@ -854,6 +864,33 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, txwi[6] |= cpu_to_le32(MT_TXD6_DIS_MAT); } +/* The WLAN_IDX in the TXD and TXP must belong to the primary or secondary + * link of an MLD station; any other link id can make the firmware spin when + * that link is in powersave. Completion events carry the same index, so the + * wcid used for status tracking and accounting must match it + */ +struct mt76_wcid *mt7996_get_tx_wcid(struct mt76_wcid *wcid) +{ + struct mt7996_sta_link *msta_link; + struct mt7996_sta *msta; + + if (!wcid->sta) + return wcid; + + msta_link = container_of(wcid, struct mt7996_sta_link, wcid); + msta = msta_link->sta; + + if (!msta || wcid->link_id == msta->seclink_id || + wcid->link_id == msta->deflink_id) + return wcid; + + msta_link = mt7996_sta_link(msta, msta->deflink_id); + if (msta_link) + return &msta_link->wcid; + + return wcid; +} + void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, @@ -1030,6 +1067,11 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, IEEE80211_TX_CTRL_MLO_LINK); } + /* non-MLD frames are LINK_UNSPECIFIED; use the wcid's own link */ + if (link_id == IEEE80211_LINK_UNSPECIFIED && + wcid != &dev->mt76.global_wcid) + link_id = wcid->link_id; + if (link_id != wcid->link_id && link_id != IEEE80211_LINK_UNSPECIFIED) { if (msta) { struct mt7996_sta_link *msta_link = @@ -1086,6 +1128,8 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, tx_info->buf[1].len, DMA_TO_DEVICE); } + wcid = mt7996_get_tx_wcid(wcid); + pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb); memset(txwi_ptr, 0, MT_TXD_SIZE); /* Transmit non qos data by 802.11 header and need to fill txd by host*/ @@ -1212,8 +1256,6 @@ mt7996_tx_check_aggr(struct ieee80211_link_sta *link_sta, return; tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; - if (tid >= 6) /* skip VO queue */ - return; if (is_8023) { fc = IEEE80211_FTYPE_DATA | @@ -1239,6 +1281,30 @@ mt7996_tx_check_aggr(struct ieee80211_link_sta *link_sta, clear_bit(tid, &wcid->ampdu_state); } +static void +mt7996_txp_skb_unmap(struct mt76_dev *mdev, struct mt76_txwi_cache *t) +{ + u8 *txwi_ptr = mt76_get_txwi_ptr(mdev, t); + __le32 *txwi = (__le32 *)txwi_ptr; + __le32 *txp; + dma_addr_t addr; + u32 val; + + if (!(le32_to_cpu(txwi[7]) & MT_TXD7_MAC_TXD)) { + mt76_connac_txp_skb_unmap(mdev, t); + return; + } + + txp = (__le32 *)(txwi_ptr + MT_TXD_SIZE); + val = le32_to_cpu(txp[3]); + addr = le32_to_cpu(txp[2]); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + addr |= (dma_addr_t)FIELD_GET(MT_TXP3_DMA_ADDR_H, val) << 32; +#endif + dma_unmap_single(mdev->dma_dev, addr, FIELD_GET(MT_TXP_BUF_LEN, val), + DMA_TO_DEVICE); +} + static void mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t, struct ieee80211_link_sta *link_sta, @@ -1248,7 +1314,7 @@ mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t, __le32 *txwi; u16 wcid_idx; - mt76_connac_txp_skb_unmap(mdev, t); + mt7996_txp_skb_unmap(mdev, t); if (!t->skb) goto out; @@ -2383,6 +2449,7 @@ mt7996_mac_reset_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) rcu_assign_pointer(mvif->link[i], NULL); kfree_rcu(mlink, rcu_head); } + mvif->valid_links = 0; rcu_read_unlock(); } @@ -2396,6 +2463,7 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) dev->recovery.hw_full_reset = true; + set_bit(MT76_MCU_RESET, &dev->mphy.state); wake_up(&dev->mt76.mcu.wait); ieee80211_stop_queues(hw); @@ -2416,12 +2484,14 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) mt7996_for_each_phy(dev, phy) phy->omac_mask = 0; + dev->mld_idx_mask = 0; + dev->mld_remap_idx_mask = 0; ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev); + mt76_reset_device(&dev->mt76); ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER, mt7996_mac_reset_vif_iter, dev); - mt76_reset_device(&dev->mt76); INIT_LIST_HEAD(&dev->sta_rc_list); INIT_LIST_HEAD(&dev->twt_list); @@ -2445,6 +2515,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) mutex_unlock(&dev->mt76.mutex); + ieee80211_wake_queues(mt76_hw(dev)); + ieee80211_restart_hw(mt76_hw(dev)); } @@ -2500,8 +2572,8 @@ void mt7996_mac_reset_work(struct work_struct *work) set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); - mt76_abort_scan(&dev->mt76); wake_up(&dev->mt76.mcu.wait); + mt76_abort_scan(&dev->mt76); cancel_work_sync(&dev->wed_rro.work); mt7996_for_each_phy(dev, phy) { @@ -2510,6 +2582,8 @@ void mt7996_mac_reset_work(struct work_struct *work) cancel_delayed_work_sync(&phy->mt76->mac_work); } + mutex_lock(&dev->mt76.mutex); + mt76_worker_disable(&dev->mt76.tx_worker); mt76_for_each_q_rx(&dev->mt76, i) { if (mtk_wed_device_active(&dev->mt76.mmio.wed) && @@ -2527,8 +2601,6 @@ void mt7996_mac_reset_work(struct work_struct *work) } napi_disable(&dev->mt76.tx_napi); - mutex_lock(&dev->mt76.mutex); - mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED); if (mt7996_wait_reset_state(dev, MT_MCU_CMD_RESET_DONE)) { @@ -3146,6 +3218,12 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw, struct mt7996_twt_flow *flow; u8 flowid, table_id, exp; + /* the firmware does not support iTWT agreements with MLD peers, and + * driver TWT state is only tracked on the default link + */ + if (sta->mlo) + goto out; + if (mt7996_mac_check_twt_req(twt)) goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index afbcc8c7b18b..e218856b0c45 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -372,7 +372,8 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, CONN_STATE_PORT_SECURE, true); rcu_assign_pointer(dev->mt76.wcid[idx], &msta_link->wcid); - ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); + if (!mlink->wcid->offchannel) + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); if (!mlink->wcid->offchannel) { if (vif->txq && @@ -605,6 +606,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw, unsigned int link_id; int i; + mutex_lock(&dev->mt76.mutex); + /* Remove all active links */ for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_vif_link *link; @@ -621,6 +624,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw, mt7996_vif_link_destroy(phy, link, vif, NULL); } + mutex_unlock(&dev->mt76.mutex); + ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter, &rdata); mt76_vif_cleanup(&dev->mt76, vif); @@ -1202,15 +1207,9 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); if (msta_link->wcid.link_valid) { - struct mt7996_phy *phy; - mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); - if (phy) - phy->mt76->num_sta--; - if (msta->deflink_id == link_id) { msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; if (msta->seclink_id == link_id) { @@ -1236,6 +1235,12 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, } if (flush) { + struct mt7996_phy *phy = + __mt7996_phy(dev, msta_link->wcid.phy_idx); + + if (phy) + phy->mt76->num_sta--; + rcu_assign_pointer(msta->link[link_id], NULL); rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], NULL); mt76_wcid_mask_clear(dev->mt76.wcid_mask, msta_link->wcid.idx); @@ -1510,20 +1515,26 @@ static void mt7996_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif = info->control.vif; struct mt7996_vif *mvif = vif ? (void *)vif->drv_priv : NULL; struct mt76_wcid *wcid = &dev->mt76.global_wcid; + u8 deflink_id = IEEE80211_LINK_UNSPECIFIED; u8 link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); rcu_read_lock(); + if (msta) + deflink_id = msta->deflink_id; + else if (mvif) + deflink_id = mvif->mt76.deflink_id; + + /* the primary link is unset until the first link has been added */ + if (deflink_id >= IEEE80211_MLD_MAX_NUM_LINKS) + deflink_id = 0; + /* Use primary link_id if the value from mac80211 is set to * IEEE80211_LINK_UNSPECIFIED. */ - if (link_id == IEEE80211_LINK_UNSPECIFIED) { - if (msta) - link_id = msta->deflink_id; - else if (mvif) - link_id = mvif->mt76.deflink_id; - } + if (link_id == IEEE80211_LINK_UNSPECIFIED) + link_id = deflink_id; if (vif && ieee80211_vif_is_mld(vif)) { struct ieee80211_bss_conf *link_conf; @@ -1533,7 +1544,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, link_sta = rcu_dereference(sta->link[link_id]); if (!link_sta) - link_sta = rcu_dereference(sta->link[msta->deflink_id]); + link_sta = rcu_dereference(sta->link[deflink_id]); if (link_sta) { memcpy(hdr->addr1, link_sta->addr, ETH_ALEN); @@ -1579,7 +1590,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, if (msta_link) wcid = &msta_link->wcid; } - mt76_tx(mphy, control->sta, wcid, skb); + mt76_tx(mphy, control->sta, mt7996_get_tx_wcid(wcid), skb); unlock: rcu_read_unlock(); } @@ -1883,8 +1894,6 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw, sinfo->txrate.flags = txrate->flags; sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); } - sinfo->txrate.flags = txrate->flags; - sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); sinfo->tx_failed = msta_link->wcid.stats.tx_failed; sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED); @@ -2029,6 +2038,14 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, continue; mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link); + + /* HW cannot derive the BSSID from 4-address non-AMSDU frames, + * so PS exit is missed on links other than the setup link. + * Let the firmware wake those links instead. + */ + if (enabled && msta->deflink_id != link_id && + is_mt7996(&dev->mt76)) + mt7996_mcu_ps_leave(dev, link, msta_link); } mutex_unlock(&dev->mt76.mutex); @@ -2436,6 +2453,23 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, return 0; } +#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU) +static int +mt7996_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct net_device *netdev, enum tc_setup_type type, + void *type_data) +{ +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + struct mt7996_dev *dev = mt7996_hw_dev(hw); + + if (mtk_wed_device_active(&dev->mt76.mmio.wed)) + return mt76_wed_net_setup_tc(hw, vif, netdev, type, + type_data); +#endif + return mt76_npu_net_setup_tc(hw, vif, netdev, type, type_data); +} +#endif + static int mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 old_links, u16 new_links, @@ -2460,6 +2494,7 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, idx = get_free_idx(dev->mld_remap_idx_mask, 0, 15) - 1; if (idx < 0) { + dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); ret = -ENOSPC; goto out; } @@ -2562,10 +2597,8 @@ const struct ieee80211_ops mt7996_ops = { #endif .set_radar_background = mt7996_set_radar_background, .net_fill_forward_path = mt7996_net_fill_forward_path, -#ifdef CONFIG_NET_MEDIATEK_SOC_WED - .net_setup_tc = mt76_wed_net_setup_tc, -#elif defined(CONFIG_MT7996_NPU) - .net_setup_tc = mt76_npu_net_setup_tc, +#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU) + .net_setup_tc = mt7996_net_setup_tc, #endif .change_vif_links = mt7996_change_vif_links, .change_sta_links = mt7996_mac_sta_change_links, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 2e83f4b79c87..cd7587ce5b8a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -826,6 +826,89 @@ mt7996_mcu_wed_rro_event(struct mt7996_dev *dev, struct sk_buff *skb) } } +static void +mt7996_mcu_ps_transition(struct mt7996_dev *dev, u16 wcid_idx, bool ps) +{ + struct mt76_wcid *wcid; + + wcid = mt76_wcid_ptr(dev, wcid_idx); + if (!wcid || !wcid_to_sta(wcid)) + return; + + mt76_sta_ps_transition(&dev->mt76, wcid, ps); +} + +static void +mt7996_mcu_rx_ps_sync(struct mt7996_dev *dev, struct sk_buff *skb) +{ + struct mt7996_mcu_ps_sync_event *event = (void *)skb->data; + struct tlv *tlv; + int len; + + if (skb->len < sizeof(*event)) + return; + + skb_pull(skb, sizeof(*event)); + + len = skb->len; + while (len >= sizeof(*tlv)) { + u16 tag, tag_len; + + tlv = (struct tlv *)skb->data; + tag = le16_to_cpu(tlv->tag); + tag_len = le16_to_cpu(tlv->len); + /* a tag_len below the header size would not advance the buffer */ + if (tag_len < sizeof(*tlv) || tag_len > len) + break; + + switch (tag) { + case UNI_PS_CLIENT_INFO: { + struct mt7996_mcu_ps_client_info *info = (void *)tlv; + + if (tag_len < sizeof(*info)) + break; + + mt7996_mcu_ps_transition(dev, + le16_to_cpu(info->wlan_idx), + info->ps_bit); + break; + } + case UNI_PS_MULTI_CLIENT_INFO: { + struct mt7996_mcu_ps_multi_client_info *info = (void *)tlv; + u16 cnt; + int i; + + if (tag_len < sizeof(*info)) + break; + + cnt = min_t(u16, le16_to_cpu(info->sta_cnt), + (tag_len - sizeof(*info)) / sizeof(info->sta_ps_info[0])); + for (i = 0; i < cnt; i++) { + u16 entry = le16_to_cpu(info->sta_ps_info[i]); + + mt7996_mcu_ps_transition(dev, + FIELD_GET(MT7996_PS_MULTI_WCID, entry), + !!(entry & MT7996_PS_MULTI_PS_BIT)); + } + break; + } + case UNI_PS_MULTI_CLIENT_INFO_BITMAP: { + u8 *bitmap = tlv->data; + int bitmap_len = tag_len - sizeof(*tlv); + int i; + + for (i = 0; i < bitmap_len * 8; i++) + mt7996_mcu_ps_transition(dev, i, + !!(bitmap[i / 8] & BIT(i % 8))); + break; + } + } + + skb_pull(skb, tag_len); + len -= tag_len; + } +} + static void mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb) { @@ -847,6 +930,9 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb) case MCU_UNI_EVENT_WED_RRO: mt7996_mcu_wed_rro_event(dev, skb); break; + case MCU_UNI_EVENT_PS_SYNC: + mt7996_mcu_rx_ps_sync(dev, skb); + break; default: break; } @@ -1044,43 +1130,6 @@ mt7996_mcu_bss_sec_tlv(struct sk_buff *skb, struct mt76_vif_link *mlink) sec->cipher = mlink->cipher; } -static int -mt7996_mcu_muar_config(struct mt7996_dev *dev, struct mt76_vif_link *mlink, - const u8 *addr, bool bssid, bool enable) -{ -#define UNI_MUAR_ENTRY 2 - u32 idx = mlink->omac_idx - REPEATER_BSSID_START; - struct { - struct { - u8 band; - u8 __rsv[3]; - } hdr; - - __le16 tag; - __le16 len; - - bool smesh; - u8 bssid; - u8 index; - u8 entry_add; - u8 addr[ETH_ALEN]; - u8 __rsv[2]; - } __packed req = { - .hdr.band = mlink->band_idx, - .tag = cpu_to_le16(UNI_MUAR_ENTRY), - .len = cpu_to_le16(sizeof(req) - sizeof(req.hdr)), - .smesh = false, - .index = idx * 2 + bssid, - .entry_add = true, - }; - - if (enable) - memcpy(req.addr, addr, ETH_ALEN); - - return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(REPT_MUAR), &req, - sizeof(req), true); -} - static void mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct mt7996_phy *phy) { @@ -1210,11 +1259,6 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct mt7996_dev *dev = phy->dev; struct sk_buff *skb; - if (mlink->omac_idx >= REPEATER_BSSID_START) { - mt7996_mcu_muar_config(dev, mlink, link_conf->addr, false, enable); - mt7996_mcu_muar_config(dev, mlink, link_conf->bssid, true, enable); - } - skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, mlink, MT7996_BSS_UPDATE_MAX_SIZE); if (IS_ERR(skb)) @@ -2483,7 +2527,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev, cap |= STA_CAP_VHT_SGI_160; if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC) cap |= STA_CAP_VHT_TX_STBC; - if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1) + if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK) cap |= STA_CAP_VHT_RX_STBC; if ((vif->type != NL80211_IFTYPE_AP || link_conf->vht_ldpc) && (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC)) @@ -2996,9 +3040,6 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, }, }; - if (mlink->omac_idx >= REPEATER_BSSID_START) - return mt7996_mcu_muar_config(dev, mlink, link_conf->addr, false, enable); - memcpy(data.tlv.omac_addr, link_conf->addr, ETH_ALEN); return mt76_mcu_send_msg(&dev->mt76, MCU_WMWA_UNI_CMD(DEV_INFO_UPDATE), &data, sizeof(data), true); @@ -4023,6 +4064,9 @@ int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy, struct mt7996_dev *dev = phy->dev; int err, region, rdd_idx = mt7996_get_rdd_idx(phy, true); + if (rdd_idx < 0) + return -EINVAL; + if (!chandef) { /* disable offchain */ err = mt7996_mcu_rdd_cmd(dev, RDD_STOP, rdd_idx, 0); if (err) @@ -4352,11 +4396,18 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_l event = (struct mt7996_mcu_eeprom_access_event *)skb->data; if (event->valid) { u32 ret_len = le32_to_cpu(event->eeprom.ext_eeprom.data_len); + u32 block = mode == EEPROM_MODE_EXT ? MT7996_EXT_EEPROM_BLOCK_SIZE : + MT7996_EEPROM_BLOCK_SIZE; addr = le32_to_cpu(event->addr); - if (!buf) + if (!buf) { + if (addr > dev->mt76.eeprom.size - block) { + dev_kfree_skb(skb); + return -EINVAL; + } buf = (u8 *)dev->mt76.eeprom.data + addr; + } switch (mode) { case EEPROM_MODE_EFUSE: @@ -4438,21 +4489,31 @@ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap) return ret; /* fixed field */ + if (skb->len < 4) { + dev_kfree_skb(skb); + return -EINVAL; + } skb_pull(skb, 4); buf = skb->data; - while (buf - skb->data < skb->len) { + while (buf - skb->data + sizeof(struct tlv) <= skb->len) { struct tlv *tlv = (struct tlv *)buf; + u16 tlv_len = le16_to_cpu(tlv->len); + + if (tlv_len < sizeof(*tlv) || + tlv_len > skb->len - (buf - skb->data)) + break; switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_CHIP_CONFIG_EFUSE_VERSION: - *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv))); + if (tlv_len >= sizeof(*tlv) + sizeof(__le32)) + *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv))); break; default: break; } - buf += le16_to_cpu(tlv->len); + buf += tlv_len; } dev_kfree_skb(skb); @@ -5029,6 +5090,32 @@ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev, &req, sizeof(req), true); } +int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx) +{ + enum { + BSSID_MAPPING_ADDR1, + BSSID_MAPPING_ADDR2, + BSSID_MAPPING_ADDR3, + }; + struct { + u8 band_idx; + u8 _rsv1[3]; + + __le16 tag; + __le16 len; + u8 addr; + u8 _rsv2[3]; + } __packed req = { + .band_idx = band_idx, + .tag = cpu_to_le16(UNI_BAND_CONFIG_BSSID_MAPPING_ADDR), + .len = cpu_to_le16(sizeof(req) - 4), + .addr = BSSID_MAPPING_ADDR1, + }; + + return mt76_mcu_send_msg(dev, MCU_WM_UNI_CMD(BAND_CONFIG), + &req, sizeof(req), true); +} + int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val) { struct { @@ -5145,6 +5232,24 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } +int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link) +{ + struct sk_buff *skb; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, + &msta_link->wcid, + MT7996_STA_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + mt76_connac_mcu_add_tlv(skb, STA_REC_PS_LEAVE, + sizeof(struct sta_rec_ps_leave)); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); +} + int mt7996_mcu_set_fixed_rate_table(struct mt7996_phy *phy, u8 table_idx, u16 rate_idx, bool beacon) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 8902e16508b7..74b70fb6da3d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -293,6 +293,39 @@ enum { UNI_WED_RRO_BA_SESSION_DELETE, }; +struct mt7996_mcu_ps_sync_event { + struct mt7996_mcu_rxd rxd; + + u8 bss_idx; + u8 __rsv[3]; +} __packed; + +struct mt7996_mcu_ps_client_info { + __le16 tag; + __le16 len; + u8 ps_bit; + u8 __rsv; + __le16 wlan_idx; + u8 buffer_size; + u8 __rsv2[3]; +} __packed; + +struct mt7996_mcu_ps_multi_client_info { + __le16 tag; + __le16 len; + __le16 sta_cnt; + __le16 sta_ps_info[]; +} __packed; + +#define MT7996_PS_MULTI_WCID GENMASK(10, 0) +#define MT7996_PS_MULTI_PS_BIT BIT(15) + +enum { + UNI_PS_CLIENT_INFO = 0, + UNI_PS_MULTI_CLIENT_INFO = 1, + UNI_PS_MULTI_CLIENT_INFO_BITMAP = 2, +}; + struct mt7996_mcu_thermal_notify { struct mt7996_mcu_rxd rxd; @@ -680,6 +713,12 @@ struct sta_rec_hdr_trans { u8 mesh; } __packed; +struct sta_rec_ps_leave { + __le16 tag; + __le16 len; + u8 __rsv[4]; +} __packed; + struct sta_rec_mld_setup { __le16 tag; __le16 len; @@ -917,7 +956,7 @@ enum { #define MT7996_BEACON_UPDATE_SIZE (sizeof(struct bss_req_hdr) + \ sizeof(struct bss_bcn_content_tlv) + \ 4 + MT_TXD_SIZE + \ - sizeof(struct bss_bcn_cntdwn_tlv) + \ + sizeof(struct bss_bcn_cntdwn_tlv) * 2 + \ sizeof(struct bss_bcn_mbss_tlv)) #define MT7996_MAX_BSS_OFFLOAD_SIZE 2048 #define MT7996_MAX_BEACON_SIZE (MT7996_MAX_BSS_OFFLOAD_SIZE - \ @@ -927,6 +966,7 @@ enum { UNI_BAND_CONFIG_RADIO_ENABLE, UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08, UNI_BAND_CONFIG_MAC_ENABLE_CTRL = 0x0c, + UNI_BAND_CONFIG_BSSID_MAPPING_ADDR = 0x12, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index d9780bb425a7..fbfbb96a742f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -17,6 +17,8 @@ static bool wed_enable; module_param(wed_enable, bool, 0644); +#define INVALID_REG_ADDR 0xffffffff + static const struct __base mt7996_reg_base[] = { [WF_AGG_BASE] = { { 0x820e2000, 0x820f2000, 0x830e2000 } }, [WF_ARB_BASE] = { { 0x820e3000, 0x820f3000, 0x830e3000 } }, @@ -54,6 +56,14 @@ static const u32 mt7996_offs[] = { [MIB_BSCR7] = 0x9e8, [MIB_BSCR17] = 0xa10, [MIB_TRDR1] = 0xa28, + [MIB_TSCR0] = 0x6b0, + [MIB_TSCR1] = 0x6b4, + [MIB_TSCR2] = 0x6b8, + [MIB_TSCR3] = 0x6bc, + [MIB_TSCR4] = 0x6c0, + [MIB_TSCR5] = 0x6c4, + [MIB_TSCR6] = 0x6c8, + [MIB_TSCR7] = 0x6d0, [HIF_REMAP_L1] = 0x24, [HIF_REMAP_BASE_L1] = 0x130000, [HIF_REMAP_L2] = 0x1b4, @@ -91,6 +101,14 @@ static const u32 mt7992_offs[] = { [MIB_BSCR7] = 0xae4, [MIB_BSCR17] = 0xb0c, [MIB_TRDR1] = 0xb24, + [MIB_TSCR0] = 0x6b0, + [MIB_TSCR1] = 0x6b4, + [MIB_TSCR2] = 0x6b8, + [MIB_TSCR3] = 0x6bc, + [MIB_TSCR4] = 0x6c0, + [MIB_TSCR5] = 0x6c4, + [MIB_TSCR6] = 0x6c8, + [MIB_TSCR7] = 0x6d0, [HIF_REMAP_L1] = 0x8, [HIF_REMAP_BASE_L1] = 0x40000, [HIF_REMAP_L2] = 0x1b4, @@ -128,6 +146,14 @@ static const u32 mt7990_offs[] = { [MIB_BSCR7] = 0xbd4, [MIB_BSCR17] = 0xbfc, [MIB_TRDR1] = 0xc14, + [MIB_TSCR0] = 0x750, + [MIB_TSCR1] = 0x754, + [MIB_TSCR2] = 0x758, + [MIB_TSCR3] = 0x75c, + [MIB_TSCR4] = 0x760, + [MIB_TSCR5] = 0x764, + [MIB_TSCR6] = 0x768, + [MIB_TSCR7] = 0x770, [HIF_REMAP_L1] = 0x8, [HIF_REMAP_BASE_L1] = 0x40000, [HIF_REMAP_L2] = 0x1b8, @@ -334,7 +360,7 @@ static u32 __mt7996_reg_addr(struct mt7996_dev *dev, u32 addr) return dev->reg.map[i].mapped + ofs; } - return 0; + return INVALID_REG_ADDR; } static u32 __mt7996_reg_remap_addr(struct mt7996_dev *dev, u32 addr) @@ -366,7 +392,7 @@ void mt7996_memcpy_fromio(struct mt7996_dev *dev, void *buf, u32 offset, { u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) { + if (addr != INVALID_REG_ADDR) { memcpy_fromio(buf, dev->mt76.mmio.regs + addr, len); return; } @@ -382,7 +408,7 @@ static u32 mt7996_rr(struct mt76_dev *mdev, u32 offset) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset), val; - if (addr) + if (addr != INVALID_REG_ADDR) return dev->bus_ops->rr(mdev, addr); spin_lock_bh(&dev->reg_lock); @@ -397,7 +423,7 @@ static void mt7996_wr(struct mt76_dev *mdev, u32 offset, u32 val) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) { + if (addr != INVALID_REG_ADDR) { dev->bus_ops->wr(mdev, addr, val); return; } @@ -412,7 +438,7 @@ static u32 mt7996_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) + if (addr != INVALID_REG_ADDR) return dev->bus_ops->rmw(mdev, addr, mask, val); spin_lock_bh(&dev->reg_lock); @@ -464,8 +490,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, if (!wed_enable) return 0; - dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 - : MT76_HWRRO_V3_1; + if (hif2 && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + return 0; hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; @@ -491,23 +517,16 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.wpdma_tx = wed->wlan.phy_base + hif1_ofs + MT_TXQ_RING_BASE(0) + MT7996_TXQ_BAND2 * MT_RING_SIZE; - if (mt7996_has_hwrro(dev)) { - if (is_mt7996(&dev->mt76)) { - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; - wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE2 * MT_RING_SIZE; - } else { - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_BAND1_EXT) - 1; - wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_EXT * MT_RING_SIZE; - } - } else { + if (is_mt7996(&dev->mt76)) { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_TRI * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_TRI) - 1; + MT7996_RXQ_TXFREE2 * MT_RING_SIZE; + } else { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_BAND1_EXT) - 1; + wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_MCU_WA_EXT * MT_RING_SIZE; } wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + hif1_ofs + MT_WFDMA0_GLO_CFG; @@ -518,7 +537,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.id = MT7996_DEVICE_ID_2; wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND2) - 1; } else { - wed->wlan.hw_rro = mt7996_has_hwrro(dev); + wed->wlan.hw_rro = true; wed->wlan.wpdma_int = wed->wlan.phy_base + MT_INT_SOURCE_CSR; wed->wlan.wpdma_mask = wed->wlan.phy_base + MT_INT_MASK_CSR; wed->wlan.wpdma_tx = wed->wlan.phy_base + MT_TXQ_RING_BASE(0) + @@ -571,23 +590,15 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND0) - 1; wed->wlan.tx_tbit[1] = ffs(MT_INT_TX_DONE_BAND1) - 1; if (is_mt7996(&dev->mt76)) { - if (mt7996_has_hwrro(dev)) { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE0 * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; - } else { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; - } + wed->wlan.wpdma_txfree = wed->wlan.phy_base + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_TXFREE0 * MT_RING_SIZE; + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; } else { wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) + MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; } - dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + wed->wlan.rx_npkt; if (dev->hif2 && is_mt7992(&dev->mt76)) wed->wlan.id = 0x7992; @@ -610,9 +621,14 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.reset_complete = mt76_wed_reset_complete; } - if (mtk_wed_device_attach(wed)) { - dev->mt76.hwrro_mode = MT76_HWRRO_OFF; + if (mtk_wed_device_attach(wed)) return 0; + + if (!hif2) { + dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 + : MT76_HWRRO_V3_1; + dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + + wed->wlan.rx_npkt; } *irq = wed->irq; @@ -822,7 +838,8 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev, .link_data_size = sizeof(struct mt7996_vif_link), .drv_flags = MT_DRV_TXWI_NO_FREE | MT_DRV_AMSDU_OFFLOAD | - MT_DRV_HW_MGMT_TXQ, + MT_DRV_HW_MGMT_TXQ | + MT_DRV_HW_PS_BUFFERING, .survey_flags = SURVEY_INFO_TIME_TX | SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_BSS_RX, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 0d6488522ba7..2a0cdb56f822 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -411,6 +411,7 @@ struct mt7996_dev { struct mt7996_phy *radio_phy[MT7996_MAX_RADIOS]; struct wiphy_radio radios[MT7996_MAX_RADIOS]; struct wiphy_radio_freq_range radio_freqs[MT7996_MAX_RADIOS]; + struct mac_address radio_addrs[MT7996_MAX_RADIOS]; struct mt7996_hif *hif2; struct mt7996_reg_desc reg; @@ -771,6 +772,7 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li u8 ht_mode, bool use_cts_prot); int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); +int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx); int mt7996_mcu_get_chan_mib_info(struct mt7996_phy *phy, bool chan_switch); int mt7996_mcu_get_temperature(struct mt7996_phy *phy); int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state); @@ -871,6 +873,7 @@ bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask); void mt7996_mac_reset_counters(struct mt7996_phy *phy); void mt7996_mac_cca_stats_reset(struct mt7996_phy *phy); void mt7996_mac_enable_nf(struct mt7996_dev *dev, u8 band); +struct mt76_wcid *mt7996_get_tx_wcid(struct mt76_wcid *wcid); void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, @@ -925,6 +928,8 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); +int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link); int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode); int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev, struct ieee80211_vif *vif, @@ -941,10 +946,6 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, bool hif2, int *irq); u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id); -#ifdef CONFIG_MTK_DEBUG -int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir); -#endif - int mt7996_dma_rro_init(struct mt7996_dev *dev); void mt7996_dma_rro_start(struct mt7996_dev *dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index c6379933b6c3..8ff78cf6eb04 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -64,6 +64,14 @@ enum offs_rev { MIB_BSCR7, MIB_BSCR17, MIB_TRDR1, + MIB_TSCR0, + MIB_TSCR1, + MIB_TSCR2, + MIB_TSCR3, + MIB_TSCR4, + MIB_TSCR5, + MIB_TSCR6, + MIB_TSCR7, HIF_REMAP_L1, HIF_REMAP_BASE_L1, HIF_REMAP_L2, @@ -250,9 +258,9 @@ enum offs_rev { #define MT_MIB_BSCR7(_band) MT_WF_MIB(_band, __OFFS(MIB_BSCR7)) #define MT_MIB_BSCR17(_band) MT_WF_MIB(_band, __OFFS(MIB_BSCR17)) -#define MT_MIB_TSCR5(_band) MT_WF_MIB(_band, 0x6c4) -#define MT_MIB_TSCR6(_band) MT_WF_MIB(_band, 0x6c8) -#define MT_MIB_TSCR7(_band) MT_WF_MIB(_band, 0x6d0) +#define MT_MIB_TSCR5(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR5)) +#define MT_MIB_TSCR6(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR6)) +#define MT_MIB_TSCR7(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR7)) #define MT_MIB_RSCR1(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR1)) /* rx mpdu counter, full 32 bits */ @@ -268,14 +276,14 @@ enum offs_rev { #define MT_MIB_RSCR36(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR36)) /* tx ampdu cnt, full 32 bits */ -#define MT_MIB_TSCR0(_band) MT_WF_MIB(_band, 0x6b0) -#define MT_MIB_TSCR2(_band) MT_WF_MIB(_band, 0x6b8) +#define MT_MIB_TSCR0(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR0)) +#define MT_MIB_TSCR2(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR2)) /* counts all mpdus in ampdu, regardless of success */ -#define MT_MIB_TSCR3(_band) MT_WF_MIB(_band, 0x6bc) +#define MT_MIB_TSCR3(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR3)) /* counts all successfully tx'd mpdus in ampdu */ -#define MT_MIB_TSCR4(_band) MT_WF_MIB(_band, 0x6c0) +#define MT_MIB_TSCR4(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR4)) /* rx ampdu count, 32-bit */ #define MT_MIB_RSCR27(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR27)) @@ -299,7 +307,7 @@ enum offs_rev { #define MT_MIB_RVSR1(_band) MT_WF_MIB(_band, __OFFS(MIB_RVSR1)) /* rx blockack count, 32 bits */ -#define MT_MIB_TSCR1(_band) MT_WF_MIB(_band, 0x6b4) +#define MT_MIB_TSCR1(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR1)) #define MT_MIB_BTSCR0(_band) MT_WF_MIB(_band, 0x5e0) #define MT_MIB_BTSCR5(_band) MT_WF_MIB(_band, __OFFS(MIB_BTSCR5)) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 7fe1b1fbb699..3594b599662d 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -11,15 +11,24 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) .aborted = abort, }; + lockdep_assert_held(&dev->mutex); + if (!phy) return; clear_bit(MT76_SCANNING, &phy->state); - if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel && + /* Re-program the operating channel even when the scan never left it: + * any channel set during the scan ran with MT76_SCANNING held, which + * left DFS radar detection disabled + */ + if (phy->main_chandef.chan && !test_bit(MT76_MCU_RESET, &dev->phy.state)) { - mt76_set_channel(phy, &phy->main_chandef, false); - mt76_offchannel_notify(phy, false); + bool offchannel = phy->offchannel; + + __mt76_set_channel(phy, &phy->main_chandef, false); + if (offchannel) + mt76_offchannel_notify(phy, false); } mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink); memset(&dev->scan, 0, sizeof(dev->scan)); @@ -34,7 +43,10 @@ void mt76_abort_scan(struct mt76_dev *dev) spin_unlock_bh(&dev->scan_lock); cancel_delayed_work_sync(&dev->scan_work); + + mutex_lock(&dev->mutex); mt76_scan_complete(dev, true); + mutex_unlock(&dev->mutex); } EXPORT_SYMBOL_GPL(mt76_abort_scan); @@ -48,6 +60,7 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) struct mt76_phy *phy = dev->scan.phy; struct ieee80211_tx_info *info; struct sk_buff *skb; + u8 link_id; skb = ieee80211_probereq_get(phy->hw, vif->addr, ssid->ssid, ssid->ssid_len, req->ie_len); @@ -77,6 +90,10 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) info->flags |= IEEE80211_TX_CTL_NO_CCK_RATE; info->control.flags |= IEEE80211_TX_CTRL_DONT_USE_RATE_MASK; + link_id = mvif->wcid ? mvif->wcid->link_id : IEEE80211_LINK_UNSPECIFIED; + info->control.flags &= ~IEEE80211_TX_CTRL_MLO_LINK; + info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); + mt76_tx(phy, NULL, mvif->wcid, skb); out: @@ -127,7 +144,9 @@ void mt76_scan_work(struct work_struct *work) goto probe; if (dev->scan.chan_idx >= req->n_channels) { + mutex_lock(&dev->mutex); mt76_scan_complete(dev, false); + mutex_unlock(&dev->mutex); return; } @@ -206,6 +225,7 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, dev->scan.vif = vif; dev->scan.phy = phy; dev->scan.mlink = mlink; + set_bit(MT76_SCANNING, &phy->state); ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, 0); out: diff --git a/drivers/net/wireless/mediatek/mt76/sdio.c b/drivers/net/wireless/mediatek/mt76/sdio.c index 8bae77c761be..ba5f123f7e39 100644 --- a/drivers/net/wireless/mediatek/mt76/sdio.c +++ b/drivers/net/wireless/mediatek/mt76/sdio.c @@ -519,6 +519,10 @@ mt76s_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, enum mt76_txq_id qid, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_sta *sta) { + struct ieee80211_tx_status status = { + .sta = sta, + }; + struct mt76_tx_info tx_info = { .skb = skb, }; @@ -531,8 +535,13 @@ mt76s_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, skb->prev = skb->next = NULL; err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info); - if (err < 0) + if (err < 0) { + status.skb = tx_info.skb; + spin_lock_bh(&dev->rx_lock); + ieee80211_tx_status_ext(dev->hw, &status); + spin_unlock_bh(&dev->rx_lock); return err; + } q->entry[q->head].skb = tx_info.skb; q->entry[q->head].buf_sz = len; diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index f96d9c471853..3707ee19e4ae 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -267,6 +267,21 @@ void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff * wcid = __mt76_wcid_ptr(dev, wcid_idx); mt76_tx_check_non_aql(dev, wcid, skb); + if (wcid && (dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING) && + test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + struct ieee80211_sta *sta = wcid_to_sta(wcid); + + if (sta) { + struct ieee80211_hw *hw = mt76_phy_hw(dev, wcid->phy_idx); + int i; + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + if (sta->txq[i]) + ieee80211_schedule_txq(hw, sta->txq[i]); + mt76_worker_schedule(&dev->tx_worker); + } + } + #ifdef CONFIG_NL80211_TESTMODE if (mt76_is_testmode_skb(dev, skb, &hw)) { struct mt76_phy *phy = hw->priv; @@ -324,10 +339,6 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (idx < 0 || !sta) return idx; - wcid = (struct mt76_wcid *)sta->drv_priv; - if (!wcid->sta) - return idx; - q->entry[idx].wcid = wcid->idx; if (!non_aql) @@ -401,16 +412,23 @@ mt76_txq_dequeue(struct mt76_phy *phy, struct mt76_txq *mtxq) static void mt76_queue_ps_skb(struct mt76_phy *phy, struct ieee80211_sta *sta, - struct sk_buff *skb, bool last) + struct sk_buff *skb, bool last, + enum ieee80211_frame_release_type reason) { struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; info->control.flags |= IEEE80211_TX_CTRL_PS_RESPONSE; - if (last) + if (last) { info->flags |= IEEE80211_TX_STATUS_EOSP | IEEE80211_TX_CTL_REQ_TX_STATUS; + if (reason == IEEE80211_FRAME_RELEASE_UAPSD && + ieee80211_is_data_qos(hdr->frame_control)) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_EOSP; + } + mt76_skb_set_moredata(skb, !last); __mt76_tx_queue_skb(phy, MT_TXQ_PSD, skb, wcid, sta, NULL); } @@ -443,14 +461,15 @@ mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, nframes--; if (last_skb) - mt76_queue_ps_skb(phy, sta, last_skb, false); + mt76_queue_ps_skb(phy, sta, last_skb, false, + reason); last_skb = skb; } while (nframes); } if (last_skb) { - mt76_queue_ps_skb(phy, sta, last_skb, true); + mt76_queue_ps_skb(phy, sta, last_skb, true, reason); dev->queue_ops->kick(dev, hwq); } else { ieee80211_sta_eosp(sta); @@ -480,8 +499,12 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, bool stop = false; int idx; - if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) - return 0; + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + if (!(dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING)) + return 0; + if (ieee80211_txq_aql_pending(phy->hw, txq)) + return 0; + } if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) return 0; @@ -501,6 +524,9 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, if (idx < 0) return idx; + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) + goto out; + do { if (test_bit(MT76_RESET, &phy->state) || phy->offchannel) break; @@ -526,6 +552,7 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, n_frames++; } while (1); +out: spin_lock(&q->lock); dev->queue_ops->kick(dev, q); spin_unlock(&q->lock); @@ -552,10 +579,7 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) mtxq = (struct mt76_txq *)txq->drv_priv; wcid = __mt76_wcid_ptr(dev, mtxq->wcid); - if (!wcid || test_bit(MT_WCID_FLAG_PS, &wcid->flags)) - continue; - - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (!wcid) continue; phy = mt76_dev_phy(dev, wcid->phy_idx); @@ -563,6 +587,24 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; q = phy->q_tx[qid]; + + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + if (!(dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING)) + continue; + + if (!mt76_txq_stopped(q)) + n_frames = mt76_txq_send_burst(phy, q, mtxq, wcid); + + ieee80211_return_txq(phy->hw, txq, false); + + if (unlikely(n_frames < 0)) + return n_frames; + ret += n_frames; + continue; + } + + if (atomic_read(&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) { dev->queue_ops->tx_cleanup(dev, q, false); @@ -635,6 +677,7 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, !ieee80211_is_data_present(hdr->frame_control) && (!ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || + ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) qid = MT_TXQ_PSD; @@ -686,8 +729,8 @@ void mt76_txq_schedule_pending(struct mt76_phy *phy) ret = mt76_txq_schedule_pending_wcid(phy, wcid, &wcid->tx_pending); spin_lock(&phy->tx_lock); - if (!skb_queue_empty(&wcid->tx_pending) && - !skb_queue_empty(&wcid->tx_offchannel) && + if ((!skb_queue_empty(&wcid->tx_pending) || + !skb_queue_empty(&wcid->tx_offchannel)) && list_empty(&wcid->tx_list)) list_add_tail(&wcid->tx_list, &phy->tx_list); } @@ -768,6 +811,33 @@ void mt76_stop_tx_queues(struct mt76_phy *phy, struct ieee80211_sta *sta, } EXPORT_SYMBOL_GPL(mt76_stop_tx_queues); +void mt76_sta_ps_transition(struct mt76_dev *dev, struct mt76_wcid *wcid, + bool ps) +{ + struct ieee80211_sta *sta; + struct ieee80211_hw *hw; + int i; + + if (ps) { + set_bit(MT_WCID_FLAG_PS, &wcid->flags); + mt76_worker_schedule(&dev->tx_worker); + return; + } + + clear_bit(MT_WCID_FLAG_PS, &wcid->flags); + + sta = wcid_to_sta(wcid); + if (!sta) + return; + + hw = mt76_phy_hw(dev, wcid->phy_idx); + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + if (sta->txq[i]) + ieee80211_schedule_txq(hw, sta->txq[i]); + mt76_worker_schedule(&dev->tx_worker); +} +EXPORT_SYMBOL_GPL(mt76_sta_ps_transition); + void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) { struct mt76_phy *phy = hw->priv; diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index d9638a9b749b..a9af3aa6b80a 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -30,6 +30,8 @@ int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, for (i = 0; i < MT_VEND_REQ_MAX_RETRY; i++) { if (test_bit(MT76_REMOVED, &dev->phy.state)) return -EIO; + if (dev->usb.ctrl_timeout && atomic_read(&dev->bus_hung)) + return -EIO; ret = usb_control_msg(udev, pipe, req, req_type, val, offset, buf, len, MT_VEND_REQ_TOUT_MS); @@ -42,6 +44,15 @@ int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, dev_err(dev->dev, "vendor request req:%02x off:%04x failed:%d\n", req, offset, ret); + + if (dev->usb.ctrl_timeout) { + atomic_set(&dev->bus_hung, true); + dev_err(dev->dev, "vendor request req:%02x off:%04x timed out, marking bus hung\n", + req, offset); + dev->usb.ctrl_timeout(dev, ret); + return ret; + } + return ret; } EXPORT_SYMBOL_GPL(__mt76u_vendor_request); @@ -580,7 +591,11 @@ static void mt76u_complete_rx(struct urb *urb) q->head = (q->head + 1) % q->ndesc; q->queued++; - mt76_worker_schedule(&dev->usb.rx_worker); + + if (q == &dev->q_rx[MT_RXQ_MAIN]) + napi_schedule(&dev->napi[MT_RXQ_MAIN]); + else + mt76_worker_schedule(&dev->usb.rx_worker); out: spin_unlock_irqrestore(&q->lock, flags); } @@ -618,11 +633,23 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q) } mt76u_submit_rx_buf(dev, qid, urb); } - if (qid == MT_RXQ_MAIN) { - local_bh_disable(); - mt76_rx_poll_complete(dev, MT_RXQ_MAIN, NULL); - local_bh_enable(); - } +} + +/* Threaded NAPI poll for the MAIN RX queue: drain URBs, build skbs, resubmit, + * then deliver through napi_gro_receive() and let napi_complete() flush GRO. + */ +static int mt76u_napi_poll(struct napi_struct *napi, int budget) +{ + struct mt76_dev *dev = mt76_priv(napi->dev); + + rcu_read_lock(); + mt76u_process_rx_queue(dev, &dev->q_rx[MT_RXQ_MAIN]); + mt76_rx_poll_complete(dev, MT_RXQ_MAIN, napi); + rcu_read_unlock(); + + napi_complete(napi); + + return 0; } static void mt76u_rx_worker(struct mt76_worker *w) @@ -632,8 +659,13 @@ static void mt76u_rx_worker(struct mt76_worker *w) int i; rcu_read_lock(); - mt76_for_each_q_rx(dev, i) + mt76_for_each_q_rx(dev, i) { + /* MT_RXQ_MAIN is serviced by the threaded NAPI poll */ + if (i == MT_RXQ_MAIN) + continue; + mt76u_process_rx_queue(dev, &dev->q_rx[i]); + } rcu_read_unlock(); } @@ -731,6 +763,13 @@ void mt76u_stop_rx(struct mt76_dev *dev) for (j = 0; j < q->ndesc; j++) usb_poison_urb(q->entry[j].urb); } + + /* The MAIN queue napi stays enabled for the device lifetime. The URBs + * are now poisoned, so mt76u_complete_rx() can no longer reschedule it; + * just drain any in-flight poll before the caller frees or resets. + */ + if (dev->napi_dev) + napi_synchronize(&dev->napi[MT_RXQ_MAIN]); } EXPORT_SYMBOL_GPL(mt76u_stop_rx); @@ -1051,6 +1090,13 @@ void mt76u_queues_deinit(struct mt76_dev *dev) mt76u_stop_rx(dev); mt76u_stop_tx(dev); + if (dev->napi_dev) { + napi_disable(&dev->napi[MT_RXQ_MAIN]); + netif_napi_del(&dev->napi[MT_RXQ_MAIN]); + free_netdev(dev->napi_dev); + dev->napi_dev = NULL; + } + mt76u_free_rx(dev); mt76u_free_tx(dev); } @@ -1078,6 +1124,7 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, { struct usb_device *udev = interface_to_usbdev(intf); struct mt76_usb *usb = &dev->usb; + struct mt76_dev **priv; int err; INIT_WORK(&usb->stat_work, mt76u_tx_status_data); @@ -1115,6 +1162,20 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, sched_set_fifo_low(usb->rx_worker.task); sched_set_fifo_low(usb->status_worker.task); + /* threaded NAPI on a dummy netdev (reusing mt76_dev's napi_dev/napi[]) + * services the MAIN RX queue and gives the RX path GRO + */ + dev->napi_dev = alloc_netdev_dummy(sizeof(struct mt76_dev *)); + if (!dev->napi_dev) + return -ENOMEM; + + priv = netdev_priv(dev->napi_dev); + *priv = dev; + strscpy(dev->napi_dev->name, "mt76u-rx", sizeof(dev->napi_dev->name)); + dev->napi_dev->threaded = 1; + netif_napi_add(dev->napi_dev, &dev->napi[MT_RXQ_MAIN], mt76u_napi_poll); + napi_enable(&dev->napi[MT_RXQ_MAIN]); + return 0; } EXPORT_SYMBOL_GPL(__mt76u_init); diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c index ed657d952de2..f210a0c57d81 100644 --- a/drivers/net/wireless/mediatek/mt76/wed.c +++ b/drivers/net/wireless/mediatek/mt76/wed.c @@ -33,10 +33,15 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size) { struct mtk_wed_bm_desc *desc = wed->rx_buf_ring.desc; struct mt76_dev *dev = mt76_wed_to_dev(wed); - struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN]; struct mt76_txwi_cache *t = NULL; + struct mt76_queue *q; int i; + if (wed->version == 2 && dev->phy.band_idx) + q = &dev->q_rx[MT_RXQ_BAND1]; + else + q = &dev->q_rx[MT_RXQ_MAIN]; + for (i = 0; i < size; i++) { dma_addr_t addr; u32 offset; diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index 6654fce4ded8..bb2748a19329 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1100,24 +1100,19 @@ static void wilc_wfi_remain_on_channel_expired(struct wilc_vif *vif, u64 cookie) static int remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { int ret = 0; struct wilc_vif *vif = netdev_priv(wdev->netdev); struct wilc_priv *priv = &vif->priv; - u64 id; if (wdev->iftype == NL80211_IFTYPE_AP) { netdev_dbg(vif->ndev, "Required while in AP mode\n"); return ret; } - id = ++priv->inc_roc_cookie; - if (id == 0) - id = ++priv->inc_roc_cookie; - - ret = wilc_remain_on_channel(vif, id, chan->hw_value, + ret = wilc_remain_on_channel(vif, cookie, chan->hw_value, wilc_wfi_remain_on_channel_expired); if (ret) return ret; @@ -1125,12 +1120,11 @@ static int remain_on_channel(struct wiphy *wiphy, vif->wilc->op_ch = chan->hw_value; priv->remain_on_ch_params.listen_ch = chan; - priv->remain_on_ch_params.listen_cookie = id; - *cookie = id; + priv->remain_on_ch_params.listen_cookie = cookie; priv->p2p_listen_state = true; priv->remain_on_ch_params.listen_duration = duration; - cfg80211_ready_on_channel(wdev, *cookie, chan, duration, GFP_KERNEL); + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_KERNEL); mod_timer(&vif->hif_drv->remain_on_ch_timer, jiffies + msecs_to_jiffies(duration + 1000)); @@ -1153,7 +1147,7 @@ static int cancel_remain_on_channel(struct wiphy *wiphy, static int mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { struct ieee80211_channel *chan = params->chan; unsigned int wait = params->wait; @@ -1170,8 +1164,7 @@ static int mgmt_tx(struct wiphy *wiphy, const u8 *vendor_ie; int ret = 0; - *cookie = get_random_u32(); - priv->tx_cookie = *cookie; + priv->tx_cookie = cookie; mgmt = (const struct ieee80211_mgmt *)buf; if (!ieee80211_is_mgmt(mgmt->frame_control)) diff --git a/drivers/net/wireless/microchip/wilc1000/mon.c b/drivers/net/wireless/microchip/wilc1000/mon.c index b5cf6fa7a851..9b8c083b403d 100644 --- a/drivers/net/wireless/microchip/wilc1000/mon.c +++ b/drivers/net/wireless/microchip/wilc1000/mon.c @@ -142,6 +142,9 @@ static int mon_mgmt_tx(struct net_device *dev, const u8 *buf, size_t len) static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ieee80211_radiotap_header_fixed *rtap_hdr; + struct ieee80211_hdr_3addr *hdr; + unsigned int hdr_len; u32 rtap_len, ret = 0; struct wilc_wfi_mon_priv *mon_priv; struct sk_buff *skb2; @@ -153,13 +156,26 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, if (!mon_priv) return -EFAULT; + if (skb->len < sizeof(*rtap_hdr)) + goto drop; + + rtap_hdr = (void *)skb->data; + if (rtap_hdr->it_version) + goto drop; + rtap_len = ieee80211_get_radiotap_len(skb->data); - if (skb->len < rtap_len) - return -1; + if (rtap_len < sizeof(*rtap_hdr) || skb->len < rtap_len) + goto drop; skb_pull(skb, rtap_len); + hdr_len = ieee80211_get_hdrlen_from_skb(skb); + if (hdr_len < sizeof(*hdr)) + goto drop; - if (skb->data[0] == 0xc0 && is_broadcast_ether_addr(&skb->data[4])) { + hdr = (void *)skb->data; + + if (ieee80211_is_deauth(hdr->frame_control) && + is_broadcast_ether_addr(hdr->addr1)) { skb2 = dev_alloc_skb(skb->len + sizeof(*cb_hdr)); if (!skb2) return -ENOMEM; @@ -191,8 +207,8 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, } skb->dev = mon_priv->real_ndev; - ether_addr_copy(srcadd, &skb->data[10]); - ether_addr_copy(bssid, &skb->data[16]); + ether_addr_copy(srcadd, hdr->addr2); + ether_addr_copy(bssid, hdr->addr3); /* * Identify if data or mgmt packet, if source address and bssid * fields are equal send it to mgmt frames handler @@ -207,6 +223,10 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, } return ret; + +drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; } static const struct net_device_ops wilc_wfi_netdev_ops = { diff --git a/drivers/net/wireless/morsemicro/Kconfig b/drivers/net/wireless/morsemicro/Kconfig new file mode 100644 index 000000000000..cb0653c77d87 --- /dev/null +++ b/drivers/net/wireless/morsemicro/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WLAN_VENDOR_MORSEMICRO + bool "Morse Micro devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + questions about these cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_MORSEMICRO +source "drivers/net/wireless/morsemicro/mm81x/Kconfig" +endif # WLAN_VENDOR_MORSEMICRO diff --git a/drivers/net/wireless/morsemicro/Makefile b/drivers/net/wireless/morsemicro/Makefile new file mode 100644 index 000000000000..5b2670f7d171 --- /dev/null +++ b/drivers/net/wireless/morsemicro/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MM81X) += mm81x/ diff --git a/drivers/net/wireless/morsemicro/mm81x/Kconfig b/drivers/net/wireless/morsemicro/mm81x/Kconfig new file mode 100644 index 000000000000..4c4fae77ff67 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 + +config MM81X + tristate "Morse Micro MM81x wireless devices" if COMPILE_TEST + depends on MAC80211 + select FW_LOADER + select CRC7 + help + This module adds support for wireless devices based + on Morse Micro MM81xx chipsets. + +config MM81X_USB + tristate "Morse Micro MM81x USB support" + depends on MAC80211 && USB + select MM81X + help + This module adds support for the USB interface of + devices using the Morse Micro MM81x chipset. + +config MM81X_SDIO + tristate "Morse Micro MM81x SDIO support" + depends on MAC80211 && MMC + select MM81X + help + This module adds support for the SDIO interface of + devices using the Morse Micro MM81x chipset. diff --git a/drivers/net/wireless/morsemicro/mm81x/Makefile b/drivers/net/wireless/morsemicro/mm81x/Makefile new file mode 100644 index 000000000000..0d494fda1412 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_MM81X) += mm81x_core.o + +mm81x_core-y += core.o +mm81x_core-y += mac.o +mm81x_core-y += hw.o +mm81x_core-y += fw.o +mm81x_core-y += command.o +mm81x_core-y += ps.o +mm81x_core-y += skbq.o +mm81x_core-y += yaps_hw.o +mm81x_core-y += yaps.o +mm81x_core-y += rc.o +mm81x_core-y += mmrc.o + +obj-$(CONFIG_MM81X_USB) += mm81x_usb.o +mm81x_usb-y += usb.o + +obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o +mm81x_sdio-y += sdio.o diff --git a/drivers/net/wireless/morsemicro/mm81x/bus.h b/drivers/net/wireless/morsemicro/mm81x/bus.h new file mode 100644 index 000000000000..d2ccabc037fb --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/bus.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_BUS_H_ +#define _MM81X_BUS_H_ + +#include +#include "core.h" + +enum mm81x_bus_type { + MM81X_BUS_TYPE_USB, + MM81X_BUS_TYPE_SDIO, +}; + +struct mm81x_bus_ops { + int (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len); + int (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len); + int (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data); + int (*reg32_write)(struct mm81x *mors, u32 addr, u32 data); + int (*digital_reset)(struct mm81x *mors); + void (*set_bus_enable)(struct mm81x *mors, bool enable); + void (*config_burst_mode)(struct mm81x *mors, bool enable_burst); + void (*claim)(struct mm81x *mors); + void (*set_irq)(struct mm81x *mors, bool enable); + void (*release)(struct mm81x *mors); + unsigned int bulk_alignment; +}; + +/* + * Default TX alignment for buses which don't care. mac80211 will give us + * SKBs aligned to the 2 byte boundary, so 2 is effectively a noop. + */ +#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2) + +/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */ +static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len) +{ + return mors->bus_ops->dm_read(mors, addr, data, len); +} + +static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data, + int len) +{ + return mors->bus_ops->dm_write(mors, addr, data, len); +} + +static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data) +{ + return mors->bus_ops->reg32_read(mors, addr, data); +} + +static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data) +{ + return mors->bus_ops->reg32_write(mors, addr, data); +} + +static inline int mm81x_bus_digital_reset(struct mm81x *mors) +{ + if (mors->bus_ops->digital_reset) + return mors->bus_ops->digital_reset(mors); + + return 0; +} + +static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable) +{ + mors->bus_ops->set_bus_enable(mors, enable); +} + +static inline void mm81x_bus_config_burst_mode(struct mm81x *mors, + bool enable_burst) +{ + if (mors->bus_ops->config_burst_mode) + mors->bus_ops->config_burst_mode(mors, enable_burst); +} + +static inline void mm81x_claim_bus(struct mm81x *mors) +{ + mors->bus_ops->claim(mors); +} + +static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable) +{ + mors->bus_ops->set_irq(mors, enable); +} + +static inline void mm81x_release_bus(struct mm81x *mors) +{ + mors->bus_ops->release(mors); +} + +static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors) +{ + return mors->bus_ops->bulk_alignment; +} + +#endif /* !_MM81X_BUS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c new file mode 100644 index 000000000000..afb7ee9bb236 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#include +#include +#include +#include + +#include "command.h" +#include "mac.h" +#include "ps.h" +#include "hif.h" + +#define MM_MAX_COMMAND_RETRY 2 +#define HOST_CMD_DEFAULT_TIMEOUT_MS 600 +#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000 + +#define INIT_CMD_HDR(_req, _cmd, _vif_id) \ + ((struct host_cmd_header){ \ + .message_id = cpu_to_le16(_cmd), \ + .len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)), \ + .vif_id = cpu_to_le16(_vif_id), \ + }) + +struct host_cmd_resp_cb { + int ret; + u32 length; + struct host_cmd_resp *dest_resp; +}; + +static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp, + struct host_cmd_req *req, u32 length, u32 timeout) +{ + int cmd_len; + int ret = 0; + u16 host_id; + int retry = 0; + unsigned long wait_ret = 0; + struct sk_buff *skb; + struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors); + struct host_cmd_resp_cb *resp_cb; + DECLARE_COMPLETION_ONSTACK(cmd_comp); + + BUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) > + IEEE80211_TX_INFO_DRIVER_DATA_SIZE); + + cmd_len = sizeof(*req) + le16_to_cpu(req->hdr.len); + req->hdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ); + + mutex_lock(&mors->cmd_wait); + mors->cmd_seq++; + if (mors->cmd_seq > HOST_CMD_HOST_ID_SEQ_MAX) + mors->cmd_seq = 1; + host_id = mors->cmd_seq << HOST_CMD_HOST_ID_SEQ_SHIFT; + + mm81x_ps_disable(mors); + + do { + req->hdr.host_id = cpu_to_le16(host_id | retry); + + skb = mm81x_skbq_alloc_skb(cmd_q, cmd_len); + if (!skb) { + ret = -ENOMEM; + break; + } + + memcpy(skb->data, req, cmd_len); + resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb) + ->driver_data; + resp_cb->length = length; + resp_cb->dest_resp = resp; + + dev_dbg(mors->dev, "CMD 0x%04x:%04x", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id)); + + mutex_lock(&mors->cmd_lock); + mors->cmd_comp = &cmd_comp; + if (retry > 0) + reinit_completion(&cmd_comp); + timeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS; + ret = mm81x_skbq_skb_tx(cmd_q, &skb, NULL, + MM81X_SKB_CHAN_COMMAND); + mutex_unlock(&mors->cmd_lock); + + if (ret) { + dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret); + break; + } + + wait_ret = wait_for_completion_timeout( + &cmd_comp, msecs_to_jiffies(timeout)); + mutex_lock(&mors->cmd_lock); + mors->cmd_comp = NULL; + + if (!wait_ret) { + dev_err(mors->dev, + "Try:%d Command %04x:%04x timeout after %u ms", + retry, le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), timeout); + ret = -ETIMEDOUT; + } else { + ret = (length && resp) ? le32_to_cpu(resp->status) : + resp_cb->ret; + if (ret > 0 || ret < -MAX_ERRNO) + ret = -EIO; + + dev_dbg(mors->dev, "Command 0x%04x:%04x status 0x%08x", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret); + if (ret) { + dev_err(mors->dev, + "Command 0x%04x:%04x error %d", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret); + } + } + /* Free the command request */ + spin_lock_bh(&cmd_q->lock); + mm81x_skbq_skb_finish(cmd_q, skb, NULL); + spin_unlock_bh(&cmd_q->lock); + mutex_unlock(&mors->cmd_lock); + + retry++; + } while ((ret == -ETIMEDOUT) && retry < MM_MAX_COMMAND_RETRY); + + mm81x_ps_enable(mors); + mutex_unlock(&mors->cmd_wait); + + if (ret == -ETIMEDOUT) { + dev_err(mors->dev, "Command %02x:%02x timed out", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id)); + } else if (ret != 0) { + dev_err(mors->dev, + "Command %02x:%02x failed with rc %d (0x%x)\n", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret, ret); + } + + return ret; +} + +int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb) +{ + int length, ret = -ESRCH; /* No such process */ + struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors); + struct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb->data); + struct sk_buff *cmd_skb = NULL; + struct host_cmd_resp_cb *resp_cb; + struct host_cmd_resp *dest_resp; + struct host_cmd_req *req; + u16 message_id = 0; + u16 host_id = 0; + u16 resp_message_id = le16_to_cpu(src_resp->hdr.message_id); + u16 resp_host_id = le16_to_cpu(src_resp->hdr.host_id); + bool is_late_response = false; + + dev_dbg(mors->dev, "EVT 0x%04x:0x%04x", resp_message_id, resp_host_id); + + if (!HOST_CMD_IS_RESP(src_resp)) { + ret = mm81x_mac_event_recv(mors, skb); + goto exit_free; + } + + mutex_lock(&mors->cmd_lock); + + cmd_skb = mm81x_skbq_tx_pending(cmd_q); + if (cmd_skb) { + mm81x_skbq_pull_hdr_post_tx(cmd_skb); + req = (struct host_cmd_req *)cmd_skb->data; + message_id = le16_to_cpu(req->hdr.message_id); + host_id = le16_to_cpu(req->hdr.host_id); + } + + /* + * If there is no pending command or the sequence ID does not match, + * this is a late response for a timed out command which has been + * cleaned up, so just free up the response. If a command was retried, + * the response may be from the retry or from the original command + * (late response) but not from both because the firmware will silently + * drop a retry if it received the initial request. So a mismatched + * retry counter is treated as a matched command and response. + */ + if (!cmd_skb || message_id != resp_message_id || + (host_id & HOST_CMD_HOST_ID_SEQ_MASK) != + (resp_host_id & HOST_CMD_HOST_ID_SEQ_MASK)) { + dev_err(mors->dev, + "Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x", + resp_message_id, resp_host_id, message_id, host_id, + mors->cmd_seq); + is_late_response = true; + goto exit; + } + if ((host_id & HOST_CMD_HOST_ID_RETRY_MASK) != + (resp_host_id & HOST_CMD_HOST_ID_RETRY_MASK)) + dev_dbg(mors->dev, + "Command retry mismatch 0x%04x:%04x 0x%04x:%04x", + message_id, host_id, resp_message_id, resp_host_id); + + resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb) + ->driver_data; + length = resp_cb->length; + dest_resp = resp_cb->dest_resp; + if (length >= sizeof(struct host_cmd_resp) && dest_resp) { + ret = 0; + length = min_t(int, length, + le16_to_cpu(src_resp->hdr.len) + + sizeof(struct host_cmd_header)); + memcpy(dest_resp, src_resp, length); + } else { + ret = le32_to_cpu(src_resp->status); + } + + resp_cb->ret = ret; + +exit: + if (cmd_skb && !is_late_response) { + /* Complete if not already timed out */ + if (mors->cmd_comp) + complete(mors->cmd_comp); + } + + mutex_unlock(&mors->cmd_lock); +exit_free: + dev_kfree_skb(skb); + return 0; +} + +int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid, + struct ieee80211_sta *sta, + enum ieee80211_sta_state state) +{ + struct host_cmd_req_set_sta_state req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE, + mors_vif->id), + .aid = cpu_to_le16(aid), + .state = cpu_to_le16(state), + .uapsd_queues = sta->uapsd_queues, + }; + + memcpy(req.sta_addr, sta->addr, sizeof(req.sta_addr)); + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr, + enum nl80211_iftype type) +{ + int ret; + struct host_cmd_req_add_interface req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0), + }; + struct host_cmd_resp_add_interface resp; + + switch (type) { + case NL80211_IFTYPE_STATION: + req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA); + break; + case NL80211_IFTYPE_AP: + req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP); + break; + default: + return -EOPNOTSUPP; + } + + memcpy(req.addr.octet, addr, sizeof(req.addr.octet)); + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *vif_id = le16_to_cpu(resp.hdr.vif_id); + + return ret; +} + +int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id, + struct mm81x_fw_caps *capabilities) +{ + int ret; + int i; + struct host_cmd_req_get_capabilities req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id), + }; + struct host_cmd_resp_get_capabilities rsp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&rsp, + (struct host_cmd_req *)&req, sizeof(rsp), 0); + if (ret) + return ret; + + capabilities->ampdu_mss = rsp.capabilities.ampdu_mss; + capabilities->mm81x_mmss_offset = rsp.morse_mmss_offset; + capabilities->beamformee_sts_capability = + rsp.capabilities.beamformee_sts_capability; + capabilities->maximum_ampdu_length_exponent = + rsp.capabilities.maximum_ampdu_length_exponent; + capabilities->number_sounding_dimensions = + rsp.capabilities.number_sounding_dimensions; + for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) + capabilities->flags[i] = le32_to_cpu(rsp.capabilities.flags[i]); + + return ret; +} + +int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm) +{ + int ret; + struct host_cmd_req_get_max_txpower req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0), + }; + struct host_cmd_resp_get_max_txpower resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params, + bool store) +{ + int ret; + struct host_cmd_req_hw_scan *req; + size_t cmd_size; + u8 *buf; + u32 flags = 0; + + cmd_size = mm81x_hw_scan_h_get_cmd_size(params); + cmd_size = ROUND_BYTES_TO_WORD(cmd_size); + + req = kzalloc(cmd_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + buf = req->variable; + + if (store) + flags = HOST_CMD_HW_SCAN_FLAGS_STORE; + else if (params->operation == MM81X_HW_SCAN_OP_START) + flags |= HOST_CMD_HW_SCAN_FLAGS_START; + else if (params->operation == MM81X_HW_SCAN_OP_STOP) + flags |= HOST_CMD_HW_SCAN_FLAGS_ABORT; + + flags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES; + + if (params->operation == MM81X_HW_SCAN_OP_START) { + req->dwell_time_ms = cpu_to_le32(params->dwell_time_ms); + buf = mm81x_hw_scan_h_insert_tlvs(params, buf); + } + + req->flags = cpu_to_le32(flags); + req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0); + req->hdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req->hdr))); + ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0); + kfree(req); + + return ret; +} + +int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm, + int txpower_mbm) +{ + int ret; + struct host_cmd_req_set_txpower req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0), + .power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)), + }; + struct host_cmd_resp_set_txpower resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz, + s32 *power_mbm) +{ + int ret; + struct host_cmd_req_set_channel req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0), + .op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz), + .op_bw_mhz = op_bw_mhz, + .pri_bw_mhz = pri_bw_mhz, + .pri_1mhz_chan_idx = pri_1mhz_chan_idx, + .dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH, + }; + struct host_cmd_resp_set_channel resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key) +{ + struct host_cmd_req_disable_key req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif->id), + .aid = cpu_to_le32(aid), + .key_idx = key->hw_key_idx, + .key_type = + cpu_to_le32((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + HOST_CMD_TEMPORAL_KEY_TYPE_PTK : + HOST_CMD_TEMPORAL_KEY_TYPE_GTK), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key, + enum host_cmd_key_cipher cipher, + enum host_cmd_aes_key_len length) +{ + int ret; + struct host_cmd_req_install_key req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif->id), + .pn = cpu_to_le64(atomic64_read(&key->tx_pn)), + .aid = cpu_to_le32(aid), + .cipher = cipher, + .key_length = length, + .key_idx = key->keyidx, + .key_type = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + HOST_CMD_TEMPORAL_KEY_TYPE_PTK : + HOST_CMD_TEMPORAL_KEY_TYPE_GTK, + }; + struct host_cmd_resp_install_key resp; + + if (key->keylen > sizeof(req.key)) + return -EINVAL; + + memcpy(req.key, key->key, key->keylen); + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) { + key->hw_key_idx = resp.key_idx; + dev_dbg(mors->dev, "Installed key @ hw index: %d", + resp.key_idx); + } + + return ret; +} + +int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors, + struct mm81x_vif *mors_vif) +{ + struct host_cmd_req_mcast_filter *req; + struct mcast_filter *filter = mors->mcast_filter; + u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count; + u16 alloc_len = filter_list_len + sizeof(*req); + int ret = 0; + + req = kzalloc(alloc_len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif->id); + req->hdr.len = cpu_to_le16(alloc_len - sizeof(req->hdr)); + req->count = filter->count; + memcpy(req->hw_addr, filter->addr_list, filter_list_len); + + ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0); + kfree(req); + return ret; +} + +int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int, + u16 dtim_period, u32 cssid) +{ + struct host_cmd_req_bss_config req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id), + .beacon_interval_tu = cpu_to_le16(beacon_int), + .cssid = cpu_to_le32(cssid), + .dtim_period = cpu_to_le16(dtim_period), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif, + bool enabled) +{ + struct mm81x_vif *vif = mm81x_vif; + struct host_cmd_req_bss_beacon_config req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG, + vif->id), + .enable = enabled, + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled) +{ + struct host_cmd_req_config_ps req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0), + .enabled = (u8)enabled, + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, + HOST_CMD_POWERSAVE_TIMEOUT_MS); +} + +int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params) +{ + struct host_cmd_req_set_qos_params req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0), + .uapsd = params->uapsd, + .queue_idx = params->aci, + .aifs_slot_count = params->aifs, + .contention_window_min = cpu_to_le16(params->cw_min), + .contention_window_max = cpu_to_le16(params->cw_max), + .max_txop_usec = cpu_to_le32(params->txop), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id) +{ + struct host_cmd_req_remove_interface req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold) +{ + struct host_cmd_req_get_set_generic_param req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0), + .param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD), + .action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET), + .value = cpu_to_le32(frag_threshold), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_get_disabled_channels( + struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp, + uint resp_len) +{ + struct host_cmd_req req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0), + }; + + return mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, &req, resp_len, + 0); +} diff --git a/drivers/net/wireless/morsemicro/mm81x/command.h b/drivers/net/wireless/morsemicro/mm81x/command.h new file mode 100644 index 000000000000..0ea796f1d878 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_COMMAND_H_ +#define _MM81X_COMMAND_H_ + +#include +#include +#include "core.h" +#include "command_defs.h" + +#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_REQ) +#define HOST_CMD_IS_RESP(cmd) \ + (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_RESP) +#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_EVT) + +struct mm81x_queue_params; + +enum mm81x_cmd_return_code { + MM81X_RET_SUCCESS = 0, + MM81X_RET_EPERM = -1, + MM81X_RET_ENOMEM = -12, + MM81X_RET_CMD_NOT_HANDLED = -32757, +}; + +#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF +#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F +#define HOST_CMD_HOST_ID_SEQ_SHIFT 4 +#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0 + +struct host_cmd_req { + struct host_cmd_header hdr; + u8 data[]; +} __packed; + +struct host_cmd_resp { + struct host_cmd_header hdr; + __le32 status; + u8 data[]; +} __packed; + +struct host_cmd_event { + struct host_cmd_header hdr; + u8 data[]; +} __packed; + +int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb); +int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr, + enum nl80211_iftype type); +int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id, + struct mm81x_fw_caps *capabilities); +int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params); +int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif, + bool enabled); +int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int, + u16 dtim_period, u32 cssid); +int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz, + s32 *power_mbm); +int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm); +int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm, + int txpower_mbm); +int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params, + bool store); +int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled); +int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors, + struct mm81x_vif *mors_vif); +int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid, + struct ieee80211_sta *sta, + enum ieee80211_sta_state state); +int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key, + enum host_cmd_key_cipher cipher, + enum host_cmd_aes_key_len length); +int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key); +int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id); +int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold); +int mm81x_cmd_get_disabled_channels( + struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp, + uint resp_len); + +#endif /* !_MM81X_COMMAND_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/command_defs.h b/drivers/net/wireless/morsemicro/mm81x/command_defs.h new file mode 100644 index 000000000000..91a4ac09ad80 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command_defs.h @@ -0,0 +1,1658 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#ifndef _MM81X_COMMAND_DEFS_H_ +#define _MM81X_COMMAND_DEFS_H_ + +#include + +#define __sle16 __le16 +#define __sle32 __le32 +#define __sle64 __le64 + +#define HOST_CMD_SEMVER_MAJOR 56 +#define HOST_CMD_SEMVER_MINOR 17 +#define HOST_CMD_SEMVER_PATCH 0 + +#define HOST_CMD_TYPE_REQ BIT(0) +#define HOST_CMD_TYPE_RESP BIT(1) +#define HOST_CMD_TYPE_EVT BIT(2) + +#define HOST_CMD_SSID_MAX_LEN 32 +#define HOST_CMD_MAC_ADDR_LEN 6 + +enum host_cmd_id { + HOST_CMD_ID_SET_CHANNEL = 0x0001, + HOST_CMD_ID_GET_CHANNEL = 0x001D, + HOST_CMD_ID_GET_CHANNEL_FULL = 0x0013, + HOST_CMD_ID_GET_CHANNEL_DTIM = 0x001C, + HOST_CMD_ID_GET_VERSION = 0x0002, + HOST_CMD_ID_SET_TXPOWER = 0x0003, + HOST_CMD_ID_GET_MAX_TXPOWER = 0x0024, + HOST_CMD_ID_ADD_INTERFACE = 0x0004, + HOST_CMD_ID_REMOVE_INTERFACE = 0x0005, + HOST_CMD_ID_BSS_CONFIG = 0x0006, + HOST_CMD_ID_SCAN_CONFIG = 0x0010, + HOST_CMD_ID_SET_QOS_PARAMS = 0x0011, + HOST_CMD_ID_GET_QOS_PARAMS = 0x0012, + HOST_CMD_ID_SET_STA_STATE = 0x0014, + HOST_CMD_ID_SET_BSS_COLOR = 0x0015, + HOST_CMD_ID_CONFIG_PS = 0x0016, + HOST_CMD_ID_HEALTH_CHECK = 0x0019, + HOST_CMD_ID_CTS_SELF_PS = 0x001A, + HOST_CMD_ID_DTIM_CHANNEL_ENABLE = 0x001B, + HOST_CMD_ID_ARP_OFFLOAD = 0x0020, + HOST_CMD_ID_SET_LONG_SLEEP_CONFIG = 0x0021, + HOST_CMD_ID_SET_DUTY_CYCLE = 0x0022, + HOST_CMD_ID_GET_DUTY_CYCLE = 0x0023, + HOST_CMD_ID_GET_CAPABILITIES = 0x0025, + HOST_CMD_ID_TWT_AGREEMENT_INSTALL = 0x0026, + HOST_CMD_ID_TWT_AGREEMENT_VALIDATE = 0x0036, + HOST_CMD_ID_TWT_AGREEMENT_REMOVE = 0x0027, + HOST_CMD_ID_GET_TSF = 0x0028, + HOST_CMD_ID_MAC_ADDR = 0x0029, + HOST_CMD_ID_MPSW_CONFIG = 0x0030, + HOST_CMD_ID_INSTALL_KEY = 0x000A, + HOST_CMD_ID_DISABLE_KEY = 0x000B, + HOST_CMD_ID_DHCP_OFFLOAD = 0x0032, + HOST_CMD_ID_SET_KEEP_ALIVE_OFFLOAD = 0x0033, + HOST_CMD_ID_UPDATE_OUI_FILTER = 0x0034, + HOST_CMD_ID_IBSS_CONFIG = 0x0035, + HOST_CMD_ID_OCS = 0x0038, + HOST_CMD_ID_MESH_CONFIG = 0x0039, + HOST_CMD_ID_SET_OFFSET_TSF = 0x003A, + HOST_CMD_ID_GET_CHANNEL_USAGE = 0x003B, + HOST_CMD_ID_MCAST_FILTER = 0x003C, + HOST_CMD_ID_BSS_BEACON_CONFIG = 0x003D, + HOST_CMD_ID_UAPSD_CONFIG = 0x0040, + HOST_CMD_ID_PAGE_SLICING_CONFIG = 0x0043, + HOST_CMD_ID_HW_SCAN = 0x0044, + HOST_CMD_ID_SET_WHITELIST = 0x0045, + HOST_CMD_ID_ARP_PERIODIC_REFRESH = 0x0046, + HOST_CMD_ID_SET_TCP_KEEPALIVE = 0x0047, + HOST_CMD_ID_FORCE_POWER_MODE = 0x0048, + HOST_CMD_ID_LI_SLEEP = 0x0049, + HOST_CMD_ID_GET_DISABLED_CHANNELS = 0x004A, + HOST_CMD_ID_SET_CQM_RSSI = 0x004F, + HOST_CMD_ID_GET_APF_CAPABILITIES = 0x0050, + HOST_CMD_ID_READ_WRITE_APF = 0x0051, + HOST_CMD_ID_BSSID_SET = 0x0052, + HOST_CMD_ID_BEACON_OFFLOAD = 0x0053, + HOST_CMD_ID_PROBE_RESPONSE_OFFLOAD = 0x0054, + HOST_CMD_ID_HOST_STATS_LOG = 0x2007, + HOST_CMD_ID_HOST_STATS_RESET = 0x2008, + HOST_CMD_ID_MAC_STATS_LOG = 0x200C, + HOST_CMD_ID_MAC_STATS_RESET = 0x200D, + HOST_CMD_ID_UPHY_STATS_LOG = 0x200E, + HOST_CMD_ID_UPHY_STATS_RESET = 0x200F, + HOST_CMD_ID_SET_STA_TYPE = 0xA000, + HOST_CMD_ID_SET_ENC_MODE = 0xA001, + HOST_CMD_ID_TEST_BA = 0xA002, + HOST_CMD_ID_SET_LISTEN_INTERVAL = 0xA003, + HOST_CMD_ID_SET_AMPDU = 0xA004, + HOST_CMD_ID_COREDUMP = 0xA006, + HOST_CMD_ID_SET_S1G_OP_CLASS = 0xA007, + HOST_CMD_ID_SEND_WAKE_ACTION_FRAME = 0xA008, + HOST_CMD_ID_VENDOR_IE_CONFIG = 0xA009, + HOST_CMD_ID_SET_TWT_CONF = 0xA010, + HOST_CMD_ID_GET_AVAILABLE_CHANNELS = 0xA011, + HOST_CMD_ID_SET_ECSA_S1G_INFO = 0xA012, + HOST_CMD_ID_GET_HW_VERSION = 0xA013, + HOST_CMD_ID_CAC = 0xA014, + HOST_CMD_ID_DRIVER_SET_DUTY_CYCLE = 0xA015, + HOST_CMD_ID_OCS_DRIVER = 0xA017, + HOST_CMD_ID_MBSSID = 0xA016, + HOST_CMD_ID_SET_MESH_CONFIG = 0xA018, + HOST_CMD_ID_SET_MCBA_CONF = 0xA019, + HOST_CMD_ID_DYNAMIC_PEERING_CONFIG = 0xA020, + HOST_CMD_ID_CONFIG_RAW = 0xA021, + HOST_CMD_ID_CONFIG_BSS_STATS = 0xA022, + HOST_CMD_ID_GET_RSSI = 0x1002, + HOST_CMD_ID_SET_IFS = 0x1003, + HOST_CMD_ID_SET_FEM_SETTINGS = 0x1005, + HOST_CMD_ID_SET_TXOP = 0x1008, + HOST_CMD_ID_SET_CONTROL_RESPONSE = 0x1009, + HOST_CMD_ID_SET_PERIODIC_CAL = 0x100A, + HOST_CMD_ID_SET_BCN_RSSI_THRESHOLD = 0x100B, + HOST_CMD_ID_SET_TX_PKT_LIFETIME_USECS = 0x100C, + HOST_CMD_ID_SET_PHYSM_WATCHDOG = 0x100D, + HOST_CMD_ID_TX_POLAR = 0x100E, + HOST_CMD_ID_EVT_STA_STATE = 0x4001, + HOST_CMD_ID_EVT_BEACON_LOSS = 0x4002, + HOST_CMD_ID_EVT_SIG_FIELD_ERROR = 0x4003, + HOST_CMD_ID_EVT_UMAC_TRAFFIC_CONTROL = 0x4004, + HOST_CMD_ID_EVT_DHCP_LEASE_UPDATE = 0x4005, + HOST_CMD_ID_EVT_OCS_DONE = 0x4006, + HOST_CMD_ID_EVT_HW_SCAN_DONE = 0x4011, + HOST_CMD_ID_EVT_CHANNEL_USAGE = 0x4012, + HOST_CMD_ID_EVT_CONNECTION_LOSS = 0x4013, + HOST_CMD_ID_EVT_SCHED_SCAN_RESULTS = 0x4014, + HOST_CMD_ID_EVT_CQM_RSSI_NOTIFY = 0x4015, + HOST_CMD_ID_EVT_SCAN_DONE = 0x4007, + HOST_CMD_ID_EVT_SCAN_RESULT = 0x4008, + HOST_CMD_ID_EVT_CONNECTED = 0x4009, + HOST_CMD_ID_EVT_DISCONNECTED = 0x4010, + HOST_CMD_ID_EVT_BEACON_FILTER_MATCH = 0x4016, + HOST_CMD_ID_SET_CAPABILITIES = 0x8118, + HOST_CMD_ID_SET_TRANSMISSION_RATE = 0x8009, + HOST_CMD_ID_FORCE_ASSERT = 0x800E, + HOST_CMD_ID_GET_SET_GENERIC_PARAM = 0x003E, +}; + +struct host_cmd_mac_addr { + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +}; + +enum host_cmd_ocs_subcmd { + HOST_CMD_OCS_SUBCMD_CONFIG = 1, + HOST_CMD_OCS_SUBCMD_STATUS = 2, +}; + +enum host_cmd_headless_cfg_option { + HOST_CMD_HEADLESS_CFG_OPTION_KEEP_IFACES = BIT(0), + HOST_CMD_HEADLESS_CFG_OPTION_BUFFER_RX = BIT(1), + HOST_CMD_HEADLESS_CFG_OPTION_NOTIFY_ON_ANY_RX = BIT(2), +}; + +struct host_cmd_header { + __le16 flags; + __le16 message_id; + __le16 len; + __le16 host_id; + __le16 vif_id; + __le16 pad; +}; + +#define HOST_CMD_CHANNEL_BW_NOT_SET 0xFF +#define HOST_CMD_CHANNEL_IDX_NOT_SET 0xFF +#define HOST_CMD_CHANNEL_FREQ_NOT_SET 0xFFFFFFFF + +enum host_cmd_dot11_proto_mode { + HOST_CMD_DOT11_PROTO_MODE_AH = 0, +}; + +struct host_cmd_req_set_channel { + struct host_cmd_header hdr; + __le32 op_chan_freq_hz; + u8 op_bw_mhz; + u8 pri_bw_mhz; + u8 pri_1mhz_chan_idx; + u8 dot11_mode; + u8 __deprecated_reg_tx_power_set; + u8 is_off_channel; +} __packed; + +struct host_cmd_resp_set_channel { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_req_get_channel { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_channel { + struct host_cmd_header hdr; + __le32 status; + __le32 op_chan_freq_hz; + u8 op_chan_bw_mhz; + u8 pri_chan_bw_mhz; + u8 pri_1mhz_chan_idx; +} __packed; + +#define HOST_CMD_MAX_VERSION_LEN 128 + +struct host_cmd_req_get_version { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_version { + struct host_cmd_header hdr; + __le32 status; + __sle32 length; + u8 version[]; +} __packed; + +struct host_cmd_req_set_txpower { + struct host_cmd_header hdr; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_resp_set_txpower { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_req_get_max_txpower { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_max_txpower { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +enum host_cmd_interface_type { + HOST_CMD_INTERFACE_TYPE_INVALID = 0, + HOST_CMD_INTERFACE_TYPE_STA = 1, + HOST_CMD_INTERFACE_TYPE_AP = 2, + HOST_CMD_INTERFACE_TYPE_MON = 3, + HOST_CMD_INTERFACE_TYPE_ADHOC = 4, + HOST_CMD_INTERFACE_TYPE_MESH = 5, + HOST_CMD_INTERFACE_TYPE_LAST = HOST_CMD_INTERFACE_TYPE_MESH, +}; + +struct host_cmd_req_add_interface { + struct host_cmd_header hdr; + struct host_cmd_mac_addr addr; + __le32 interface_type; +} __packed; + +struct host_cmd_resp_add_interface { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_remove_interface { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_remove_interface { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_bss_config { + struct host_cmd_header hdr; + __le16 beacon_interval_tu; + __le16 dtim_period; + u8 __padding[2]; + __le32 cssid; +} __packed; + +struct host_cmd_resp_bss_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_scan_config { + struct host_cmd_header hdr; + u8 enabled; + u8 is_survey; +} __packed; + +struct host_cmd_resp_scan_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_qos_params { + struct host_cmd_header hdr; + u8 uapsd; + u8 queue_idx; + u8 aifs_slot_count; + __le16 contention_window_min; + __le16 contention_window_max; + __le32 max_txop_usec; +} __packed; + +struct host_cmd_resp_set_qos_params { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_get_qos_params { + struct host_cmd_header hdr; + u8 queue_idx; +} __packed; + +struct host_cmd_resp_get_qos_params { + struct host_cmd_header hdr; + __le32 status; + u8 aifs_slot_count; + __le16 contention_window_min; + __le16 contention_window_max; + __le32 max_txop_usec; +} __packed; + +struct host_cmd_req_set_sta_state { + struct host_cmd_header hdr; + u8 sta_addr[HOST_CMD_MAC_ADDR_LEN]; + __le16 aid; + __le16 state; + u8 uapsd_queues; + __le32 flags; +} __packed; + +struct host_cmd_resp_set_sta_state { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_bss_color { + struct host_cmd_header hdr; + u8 bss_color; +} __packed; + +struct host_cmd_resp_set_bss_color { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_config_ps { + struct host_cmd_header hdr; + u8 enabled; + u8 dynamic_ps_offload; +} __packed; + +struct host_cmd_resp_config_ps { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_health_check { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_health_check { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_cts_self_ps { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_cts_self_ps { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_dtim_channel_enable { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_dtim_channel_enable { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +#define HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES 4 + +struct host_cmd_req_arp_offload { + struct host_cmd_header hdr; + __be32 ip_table[HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES]; +} __packed; + +struct host_cmd_resp_arp_offload { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_long_sleep_config { + struct host_cmd_header hdr; + u8 enabled; +} __packed; + +struct host_cmd_resp_set_long_sleep_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +#define HOST_CMD_DUTY_CYCLE_SET_CFG_DUTY_CYCLE BIT(0) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_OMIT_CONTROL_RESP BIT(1) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_EXT BIT(2) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_BURST_RECORD_UNIT BIT(3) + +enum host_cmd_duty_cycle_mode { + HOST_CMD_DUTY_CYCLE_MODE_SPREAD = 0, + HOST_CMD_DUTY_CYCLE_MODE_BURST = 1, + HOST_CMD_DUTY_CYCLE_MODE_LAST = HOST_CMD_DUTY_CYCLE_MODE_BURST, +}; + +struct host_cmd_duty_cycle_configuration { + u8 omit_control_responses; + __le32 duty_cycle; +} __packed; + +struct host_cmd_duty_cycle_set_configuration_ext { + __le32 burst_record_unit_us; + u8 mode; +} __packed; + +struct host_cmd_duty_cycle_configuration_ext { + __le32 airtime_remaining_us; + __le32 burst_window_duration_us; + struct host_cmd_duty_cycle_set_configuration_ext set; +} __packed; + +struct host_cmd_req_set_duty_cycle { + struct host_cmd_header hdr; + struct host_cmd_duty_cycle_configuration config; + u8 set_cfgs; + struct host_cmd_duty_cycle_set_configuration_ext config_ext; +} __packed; + +struct host_cmd_resp_get_duty_cycle { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_duty_cycle_configuration config; + struct host_cmd_duty_cycle_configuration_ext config_ext; +} __packed; + +#define HOST_CMD_SET_S1G_CAP_FLAGS BIT(0) +#define HOST_CMD_SET_S1G_CAP_AMPDU_MSS BIT(1) +#define HOST_CMD_SET_S1G_CAP_BEAM_STS BIT(2) +#define HOST_CMD_SET_S1G_CAP_NUM_SOUND_DIMS BIT(3) +#define HOST_CMD_SET_S1G_CAP_MAX_AMPDU_LEXP BIT(4) +#define HOST_CMD_SET_MORSE_CAP_MMSS_OFFSET BIT(5) +#define HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH 4 + +struct host_cmd_mm_capabilities { + __le32 flags[HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH]; + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + u8 maximum_ampdu_length_exponent; +} __packed; + +struct host_cmd_req_get_capabilities { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_capabilities { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_mm_capabilities capabilities; + u8 morse_mmss_offset; +} __packed; + +#define HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN 20 + +struct host_cmd_req_twt_agreement_install { + struct host_cmd_header hdr; + u8 flow_id; + u8 agreement_len; + u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN]; +} __packed; + +struct host_cmd_resp_twt_agreement_install { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_twt_agreement_validate { + struct host_cmd_header hdr; + u8 flow_id; + u8 agreement_len; + u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN]; +} __packed; + +struct host_cmd_resp_twt_agreement_validate { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_twt_agreement_remove { + struct host_cmd_header hdr; + u8 flow_id; +} __packed; + +struct host_cmd_req_get_tsf { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_tsf { + struct host_cmd_header hdr; + __le32 status; + __le64 now_tsf; + __le64 now_chip_ts; +} __packed; + +struct host_cmd_req_mac_addr { + struct host_cmd_header hdr; + u8 write; + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +} __packed; + +struct host_cmd_resp_mac_addr { + struct host_cmd_header hdr; + __le32 status; + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +} __packed; + +#define HOST_CMD_SET_MPSW_CFG_AIRTIME_BOUNDS BIT(0) +#define HOST_CMD_SET_MPSW_CFG_PKT_SPC_WIN_LEN BIT(1) +#define HOST_CMD_SET_MPSW_CFG_ENABLED BIT(2) + +struct host_cmd_mpsw_configuration { + __le32 airtime_max_us; + __le32 airtime_min_us; + __le32 packet_space_window_length_us; + u8 enable; +} __packed; + +struct host_cmd_req_mpsw_config { + struct host_cmd_header hdr; + struct host_cmd_mpsw_configuration config; + u8 set_cfgs; +} __packed; + +struct host_cmd_resp_mpsw_config { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_mpsw_configuration config; +} __packed; + +#define HOST_CMD_MAX_KEY_LEN 32 + +enum host_cmd_key_cipher { + HOST_CMD_KEY_CIPHER_INVALID = 0, + HOST_CMD_KEY_CIPHER_AES_CCM = 1, + HOST_CMD_KEY_CIPHER_AES_GCM = 2, + HOST_CMD_KEY_CIPHER_AES_CMAC = 3, + HOST_CMD_KEY_CIPHER_AES_GMAC = 4, + HOST_CMD_KEY_CIPHER_LAST = HOST_CMD_KEY_CIPHER_AES_GMAC, +}; + +enum host_cmd_aes_key_len { + HOST_CMD_AES_KEY_LEN_INVALID = 0, + HOST_CMD_AES_KEY_LEN_LENGTH_128 = 1, + HOST_CMD_AES_KEY_LEN_LENGTH_256 = 2, + HOST_CMD_AES_KEY_LEN_LENGTH_LAST = HOST_CMD_AES_KEY_LEN_LENGTH_256, +}; + +enum host_cmd_temporal_key_type { + HOST_CMD_TEMPORAL_KEY_TYPE_INVALID = 0, + HOST_CMD_TEMPORAL_KEY_TYPE_GTK = 1, + HOST_CMD_TEMPORAL_KEY_TYPE_PTK = 2, + HOST_CMD_TEMPORAL_KEY_TYPE_IGTK = 3, + HOST_CMD_TEMPORAL_KEY_TYPE_LAST = HOST_CMD_TEMPORAL_KEY_TYPE_IGTK, +}; + +struct host_cmd_req_install_key { + struct host_cmd_header hdr; + __le64 pn; + __le32 aid; + u8 key_idx; + u8 cipher; + u8 key_length; + u8 key_type; + u8 __padding[2]; + u8 key[HOST_CMD_MAX_KEY_LEN]; +} __packed; + +struct host_cmd_resp_install_key { + struct host_cmd_header hdr; + __le32 status; + u8 key_idx; +} __packed; + +struct host_cmd_req_disable_key { + struct host_cmd_header hdr; + __le32 key_type; + __le32 aid; + u8 key_idx; +} __packed; + +struct host_cmd_resp_disable_key { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_dhcp_opcode { + HOST_CMD_DHCP_OPCODE_ENABLE = 0, + HOST_CMD_DHCP_OPCODE_DO_DISCOVERY = 1, + HOST_CMD_DHCP_OPCODE_GET_LEASE = 2, + HOST_CMD_DHCP_OPCODE_CLEAR_LEASE = 3, + HOST_CMD_DHCP_OPCODE_RENEW_LEASE = 4, + HOST_CMD_DHCP_OPCODE_REBIND_LEASE = 5, + HOST_CMD_DHCP_OPCODE_SEND_LEASE_UPDATE = 6, +}; + +enum host_cmd_dhcp_retcode { + HOST_CMD_DHCP_RETCODE_SUCCESS = 0, + HOST_CMD_DHCP_RETCODE_NOT_ENABLED = 1, + HOST_CMD_DHCP_RETCODE_ALREADY_ENABLED = 2, + HOST_CMD_DHCP_RETCODE_NO_LEASE = 3, + HOST_CMD_DHCP_RETCODE_HAVE_LEASE = 4, + HOST_CMD_DHCP_RETCODE_BUSY = 5, + HOST_CMD_DHCP_RETCODE_BAD_VIF = 6, +}; + +struct host_cmd_req_dhcp_offload { + struct host_cmd_header hdr; + __le32 opcode; +} __packed; + +struct host_cmd_resp_dhcp_offload { + struct host_cmd_header hdr; + __le32 status; + __le32 retcode; + __le32 my_ip; + __le32 netmask; + __le32 router; + __le32 dns; +} __packed; + +struct host_cmd_req_set_keep_alive_offload { + struct host_cmd_header hdr; + __le16 bss_max_idle_period; + u8 interpret_as_11ah; +} __packed; + +#define HOST_CMD_MAX_OUI_FILTERS 5 +#define HOST_CMD_OUI_SIZE 3 +#define HOST_CMD_MAX_OUI_FILTER_ARRAY_SIZE 15 + +struct host_cmd_req_update_oui_filter { + struct host_cmd_header hdr; + u8 n_ouis; + u8 ouis[HOST_CMD_MAX_OUI_FILTERS][HOST_CMD_OUI_SIZE]; +} __packed; + +enum host_cmd_ibss_config_opcode { + HOST_CMD_IBSS_CONFIG_OPCODE_CREATE = 0, + HOST_CMD_IBSS_CONFIG_OPCODE_JOIN = 1, + HOST_CMD_IBSS_CONFIG_OPCODE_STOP = 2, +}; + +struct host_cmd_req_ibss_config { + struct host_cmd_header hdr; + u8 ibss_bssid[HOST_CMD_MAC_ADDR_LEN]; + u8 ibss_cfg_opcode; + u8 ibss_probe_filtering; +} __packed; + +enum host_cmd_ocs_type { + HOST_CMD_OCS_TYPE_QNULL = 0, + HOST_CMD_OCS_TYPE_RAW = 1, +}; + +struct host_cmd_ocs_config_req { + __le32 op_channel_freq_hz; + u8 op_channel_bw_mhz; + u8 pri_channel_bw_mhz; + u8 pri_1mhz_channel_index; + __le16 aid; + u8 type; +} __packed; + +struct host_cmd_ocs_status_resp { + u8 running; +} __packed; + +struct host_cmd_req_ocs { + struct host_cmd_header hdr; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_config_req config; + }; +} __packed; + +struct host_cmd_resp_ocs { + struct host_cmd_header hdr; + __le32 status; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_status_resp ocs_status; + }; +} __packed; + +enum host_cmd_mesh_config_opcode { + HOST_CMD_MESH_CONFIG_OPCODE_START = 0, + HOST_CMD_MESH_CONFIG_OPCODE_STOP = 1, +}; + +struct host_cmd_req_mesh_config { + struct host_cmd_header hdr; + u8 mesh_cfg_opcode; + u8 enable_beaconing; + u8 mbca_config; + u8 min_beacon_gap_ms; + __le16 mbss_start_scan_duration_ms; + __le16 tbtt_adj_timer_interval_ms; +} __packed; + +struct host_cmd_req_set_offset_tsf { + struct host_cmd_header hdr; + __sle64 offset_tsf; +} __packed; + +struct host_cmd_req_get_channel_usage { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_channel_usage { + struct host_cmd_header hdr; + __le32 status; + __le64 time_listen; + __le64 busy_time; + __le32 freq_hz; + s8 noise; + u8 bw_mhz; +} __packed; + +#define HOST_CMD_MAX_MCAST_FILTERS 12 + +struct host_cmd_req_mcast_filter { + struct host_cmd_header hdr; + u8 count; + __le32 hw_addr[]; +} __packed; + +struct host_cmd_req_bss_beacon_config { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_bss_beacon_config { + struct host_cmd_header hdr; + __le32 status; + __le16 interface_id; +} __packed; + +struct host_cmd_req_uapsd_config { + struct host_cmd_header hdr; + u8 auto_trigger_enabled; + __le32 auto_trigger_timeout; +} __packed; + +struct host_cmd_resp_uapsd_config { + struct host_cmd_header hdr; + __le32 status; + u8 auto_trigger_enabled; +} __packed; + +struct host_cmd_req_page_slicing_config { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +#define HOST_CMD_HW_SCAN_FLAGS_START BIT(0) +#define HOST_CMD_HW_SCAN_FLAGS_ABORT BIT(1) +#define HOST_CMD_HW_SCAN_FLAGS_SURVEY BIT(2) +#define HOST_CMD_HW_SCAN_FLAGS_STORE BIT(3) +#define HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES BIT(4) +#define HOST_CMD_HW_SCAN_FLAGS_SCHED_START BIT(5) +#define HOST_CMD_HW_SCAN_FLAGS_SCHED_STOP BIT(6) +#define HOST_CMD_HW_SCAN_FLAGS_PROBE_ON_DOZE_BEACON BIT(7) + +enum host_cmd_hw_scan_tlv_tag { + HOST_CMD_HW_SCAN_TLV_TAG_PAD = 0, + HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ = 1, + HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST = 2, + HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST = 3, + HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME = 4, + HOST_CMD_HW_SCAN_TLV_TAG_SCHED = 5, + HOST_CMD_HW_SCAN_TLV_TAG_FILTER = 6, + HOST_CMD_HW_SCAN_TLV_TAG_SCHED_PARAMS = 7, +}; + +struct host_cmd_hw_scan_tlv { + __le16 tag; + __le16 len; + u8 value[]; +} __packed; + +struct host_cmd_req_hw_scan { + struct host_cmd_header hdr; + __le32 flags; + __le32 dwell_time_ms; + u8 variable[]; +} __packed; + +#define HOST_CMD_WHITELIST_FLAGS_CLEAR BIT(0) + +struct host_cmd_req_set_whitelist { + struct host_cmd_header hdr; + u8 flags; + u8 ip_protocol; + __be16 llc_protocol; + __be32 src_ip; + __be32 dest_ip; + __be32 netmask; + __be16 src_port; + __be16 dest_port; +} __packed; + +struct host_cmd_arp_periodic_params { + __le32 refresh_period_s; + __le32 destination_ip; + u8 send_as_garp; +} __packed; + +struct host_cmd_req_arp_periodic_refresh { + struct host_cmd_header hdr; + struct host_cmd_arp_periodic_params config; +} __packed; + +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_PERIOD BIT(0) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT BIT(1) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL BIT(2) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR BIT(3) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR BIT(4) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_PORT BIT(5) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_PORT BIT(6) + +struct host_cmd_req_set_tcp_keepalive { + struct host_cmd_header hdr; + u8 enabled; + u8 retry_count; + u8 retry_interval_s; + u8 set_cfgs; + __be32 src_ip; + __be32 dest_ip; + __be16 src_port; + __be16 dest_port; + __le16 period_s; +} __packed; + +enum host_cmd_power_mode { + HOST_CMD_POWER_MODE_SNOOZE = 0, + HOST_CMD_POWER_MODE_DEEP_SLEEP = 1, + HOST_CMD_POWER_MODE_HIBERNATE = 2, +}; + +struct host_cmd_req_force_power_mode { + struct host_cmd_header hdr; + __le32 mode; +} __packed; + +struct host_cmd_req_li_sleep { + struct host_cmd_header hdr; + __le32 listen_interval; +} __packed; + +struct host_cmd_disabled_channel_entry { + __le16 freq_100khz; + u8 bw_mhz; +} __packed; + +struct host_cmd_resp_get_disabled_channels { + struct host_cmd_header hdr; + __le32 status; + __le32 n_channels; + struct host_cmd_disabled_channel_entry channels[]; +} __packed; + +struct host_cmd_req_set_cqm_rssi { + struct host_cmd_header hdr; + __sle32 threshold; + __le32 hysteresis; +} __packed; + +struct host_cmd_req_get_apf_capabilities { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_apf_capabilities { + struct host_cmd_header hdr; + __le32 status; + __le32 max_length; + u8 version; +} __packed; + +struct host_cmd_req_read_write_apf { + struct host_cmd_header hdr; + __le32 offset; + __le16 program_length; + u8 write; + u8 program[]; +} __packed; + +struct host_cmd_resp_read_write_apf { + struct host_cmd_header hdr; + __le32 status; + __le16 program_length; + u8 program[]; +} __packed; + +struct host_cmd_req_bssid_set { + struct host_cmd_header hdr; + struct host_cmd_mac_addr bssid; +} __packed; + +#define HOST_CMD_BEACON_OFFLOAD_FLAGS_START BIT(0) +#define HOST_CMD_BEACON_OFFLOAD_FLAGS_STOP BIT(1) +#define HOST_CMD_BEACON_OFFLOAD_CSSID_LEN 4 + +enum host_cmd_beacon_offload_tlv_tag { + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_DTIM_CNT = 0, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_FRAME_CTRL = 1, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CHANGE_SEQ = 2, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CSSID = 3, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_IES = 4, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_TX_INFO = 5, +}; + +struct host_cmd_beacon_offload_tlv_hdr { + __le16 tag; + __le16 len; +} __packed; + +struct host_cmd_beacon_offload_tlv_generic { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 value[]; +} __packed; + +struct host_cmd_beacon_offload_tlv_dtim_cnt { + struct host_cmd_beacon_offload_tlv_hdr hdr; + __le16 dtim_cnt; +} __packed; + +struct host_cmd_beacon_offload_tlv_frame_ctrl { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 frame_ctrl[2]; +} __packed; + +struct host_cmd_beacon_offload_tlv_change_seq { + struct host_cmd_beacon_offload_tlv_hdr hdr; + __le16 change_seq; +} __packed; + +struct host_cmd_beacon_offload_tlv_tx_info { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 bw_mhz; +} __packed; + +struct host_cmd_beacon_offload_tlv_cssid { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 cssid[HOST_CMD_BEACON_OFFLOAD_CSSID_LEN]; +} __packed; + +struct host_cmd_beacon_offload_tlv_ies { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 buf[]; +} __packed; + +struct host_cmd_req_beacon_offload { + struct host_cmd_header hdr; + __le32 flags; + u8 variable[]; +} __packed; + +struct host_cmd_resp_beacon_offload { + struct host_cmd_header hdr; + __le32 status; + __le16 dtim_count; +} __packed; + +struct host_cmd_req_probe_response_offload { + struct host_cmd_header hdr; + u8 enable; + __le16 probe_resp_len; + u8 probe_resp_buf[]; +} __packed; + +struct host_cmd_resp_probe_response_offload { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_sta_type { + struct host_cmd_header hdr; + u8 sta_type; +} __packed; + +struct host_cmd_req_set_enc_mode { + struct host_cmd_header hdr; + u8 enc_mode; +} __packed; + +struct host_cmd_req_test_ba { + struct host_cmd_header hdr; + u8 addr[HOST_CMD_MAC_ADDR_LEN]; + u8 start; + u8 tx; + __le32 tid; +} __packed; + +struct host_cmd_req_set_listen_interval { + struct host_cmd_header hdr; + __le16 listen_interval; +} __packed; + +struct host_cmd_req_set_ampdu { + struct host_cmd_header hdr; + u8 ampdu_enabled; +} __packed; + +struct host_cmd_req_set_s1g_op_class { + struct host_cmd_header hdr; + u8 opclass; + u8 prim_opclass; +} __packed; + +struct host_cmd_req_send_wake_action_frame { + struct host_cmd_header hdr; + u8 dest_addr[HOST_CMD_MAC_ADDR_LEN]; + __le32 payload_size; + u8 payload[]; +} __packed; + +#define HOST_CMD_MAX_VENDOR_IE_LENGTH 255 +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_BEACON BIT(0) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_REQ BIT(1) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_RESP BIT(2) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_REQ BIT(3) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_RESP BIT(4) + +enum host_cmd_vendor_ie_op { + HOST_CMD_VENDOR_IE_OP_ADD_ELEMENT = 0, + HOST_CMD_VENDOR_IE_OP_CLEAR_ELEMENTS = 1, + HOST_CMD_VENDOR_IE_OP_ADD_FILTER = 2, + HOST_CMD_VENDOR_IE_OP_CLEAR_FILTERS = 3, + HOST_CMD_VENDOR_IE_OP_INVALID = U16_MAX, +}; + +struct host_cmd_req_vendor_ie_config { + struct host_cmd_header hdr; + __le16 opcode; + __le16 mgmt_type_mask; + u8 data[HOST_CMD_MAX_VENDOR_IE_LENGTH]; +} __packed; + +struct host_cmd_resp_vendor_ie_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_twt_conf_op { + HOST_CMD_TWT_CONF_OP_CONFIGURE = 0, + HOST_CMD_TWT_CONF_OP_FORCE_INSTALL_AGREEMENT = 1, + HOST_CMD_TWT_CONF_OP_REMOVE_AGREEMENT = 2, + HOST_CMD_TWT_CONF_OP_CONFIGURE_EXPLICIT = 3, +}; + +struct host_cmd_explicit_twt_wake_interval { + __le16 wake_interval_mantissa; + u8 wake_interval_exponent; + u8 __padding[5]; +} __packed; + +union host_cmd_wake_interval { + __le64 wake_interval_us; + struct host_cmd_explicit_twt_wake_interval explicit_twt; +} __packed; + +struct host_cmd_req_set_twt_conf { + struct host_cmd_header hdr; + u8 opcode; + u8 flow_id; + __le64 target_wake_time; + union host_cmd_wake_interval wake_interval; + __le32 wake_duration_us; + u8 twt_setup_command; + u8 __padding[3]; +} __packed; + +#define HOST_CMD_MAX_AVAILABLE_CHANNELS 255 + +struct host_cmd_channel_info { + __le32 frequency_khz; + u8 channel_5g; + u8 channel_s1g; + u8 bandwidth_mhz; +} __packed; + +struct host_cmd_resp_get_available_channels { + struct host_cmd_header hdr; + __le32 status; + __le32 num_channels; + struct host_cmd_channel_info channels[HOST_CMD_MAX_AVAILABLE_CHANNELS]; +} __packed; + +#define HOST_CMD_S1G_CAP0_S1G_LONG BIT(0) +#define HOST_CMD_S1G_CAP0_SGI_1MHZ BIT(1) +#define HOST_CMD_S1G_CAP0_SGI_2MHZ BIT(2) +#define HOST_CMD_S1G_CAP0_SGI_4MHZ BIT(3) +#define HOST_CMD_S1G_CAP0_SGI_8MHZ BIT(4) +#define HOST_CMD_S1G_CAP0_SGI_16MHZ BIT(5) + +struct host_cmd_req_set_ecsa_s1g_info { + struct host_cmd_header hdr; + __le32 operating_channel_freq_hz; + u8 opclass; + u8 primary_channel_bw_mhz; + u8 prim_1mhz_ch_idx; + u8 operating_channel_bw_mhz; + u8 prim_opclass; + u8 s1g_cap0; + u8 s1g_cap1; + u8 s1g_cap2; + u8 s1g_cap3; +} __packed; + +struct host_cmd_resp_get_hw_version { + struct host_cmd_header hdr; + __le32 status; + u8 hw_version[64]; +} __packed; + +#define HOST_CMD_CAC_CFG_CHANGE_RULE_MAX 8 +#define HOST_CMD_CAC_CFG_ARFS_MAX 99 +#define HOST_CMD_CAC_CFG_CHANGE_MAX 99 +#define HOST_CMD_CAC_CFG_CHANGE_STEP 5 + +enum host_cmd_cac_op { + HOST_CMD_CAC_OP_DISABLE = 0, + HOST_CMD_CAC_OP_ENABLE = 1, + HOST_CMD_CAC_OP_CFG_GET = 2, + HOST_CMD_CAC_OP_CFG_SET = 3, +}; + +struct host_cmd_cac_change_rule { + __le16 arfs; + __sle16 threshold_change; +} __packed; + +struct host_cmd_req_cac { + struct host_cmd_header hdr; + u8 opcode; + u8 rule_tot; + struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX]; +} __packed; + +struct host_cmd_resp_cac { + struct host_cmd_header hdr; + __le32 status; + u8 rule_tot; + struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX]; +} __packed; + +struct host_cmd_ocs_driver_req { + __le32 op_channel_freq_hz; + u8 op_channel_bw_mhz; + u8 pri_channel_bw_mhz; + u8 pri_1mhz_channel_index; +} __packed; + +struct host_cmd_ocs_driver_resp { + u8 running; +} __packed; + +struct host_cmd_req_ocs_driver { + struct host_cmd_header hdr; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_driver_req config; + }; +} __packed; + +struct host_cmd_resp_ocs_driver { + struct host_cmd_header hdr; + __le32 status; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_driver_resp ocs_status; + }; +} __packed; + +#define HOST_CMD_IFNAMSIZ 16 + +struct host_cmd_req_mbssid { + struct host_cmd_header hdr; + u8 max_bssid_indicator; + s8 transmitter_iface[HOST_CMD_IFNAMSIZ]; +} __packed; + +#define HOST_CMD_MESH_ID_LEN_MAX 32 +#define HOST_CMD_MESH_BEACONLESS_MODE_DISABLE 0 +#define HOST_CMD_MESH_BEACONLESS_MODE_ENABLE 1 +#define HOST_CMD_MESH_PEER_LINKS_MIN 0 +#define HOST_CMD_MESH_PEER_LINKS_MAX 10 + +struct host_cmd_req_set_mesh_config { + struct host_cmd_header hdr; + u8 mesh_id_len; + u8 mesh_id[HOST_CMD_MESH_ID_LEN_MAX]; + u8 mesh_beaconless_mode; + u8 max_plinks; +} __packed; + +struct host_cmd_req_set_mcba_conf { + struct host_cmd_header hdr; + u8 mbca_config; + u8 beacon_timing_report_interval; + u8 min_beacon_gap_ms; + __le16 mbss_start_scan_duration_ms; + __le16 tbtt_adj_interval_ms; +} __packed; + +struct host_cmd_req_dynamic_peering_config { + struct host_cmd_header hdr; + u8 enabled; + u8 rssi_margin; + __le32 blacklist_timeout; +} __packed; + +#define HOST_CMD_CFG_RAW_FLAG_ENABLE BIT(0) +#define HOST_CMD_CFG_RAW_FLAG_DELETE BIT(1) +#define HOST_CMD_CFG_RAW_FLAG_UPDATE BIT(2) +#define HOST_CMD_CFG_RAW_FLAG_DYNAMIC BIT(3) +#define HOST_CMD_RAW_RESERVED_AID_DCS 2008 +#define HOST_CMD_RAW_RESERVED_AID_DOWNLINK 2009 + +enum host_cmd_raw_tlv_tag { + HOST_CMD_RAW_TLV_TAG_SLOT_DEF = 0, + HOST_CMD_RAW_TLV_TAG_GROUP = 1, + HOST_CMD_RAW_TLV_TAG_START_TIME = 2, + HOST_CMD_RAW_TLV_TAG_PRAW = 3, + HOST_CMD_RAW_TLV_TAG_BCN_SPREAD = 4, + HOST_CMD_RAW_TLV_TAG_DYN_GLOBAL = 5, + HOST_CMD_RAW_TLV_TAG_DYN_CONFIG = 6, + HOST_CMD_RAW_TLV_TAG_LAST = 7, +}; + +struct host_cmd_raw_tlv_slot_def { + u8 tag; + __le32 raw_duration_us; + u8 num_slots; + u8 cross_slot_bleed; +} __packed; + +struct host_cmd_raw_tlv_group { + u8 tag; + __le16 aid_start; + __le16 aid_end; +} __packed; + +struct host_cmd_raw_tlv_start_time { + u8 tag; + __le32 start_time_us; +} __packed; + +struct host_cmd_raw_tlv_praw { + u8 tag; + u8 periodicity; + u8 validity; + u8 start_offset; + u8 refresh_on_expiry; +} __packed; + +struct host_cmd_raw_tlv_bcn_spread { + u8 tag; + __le16 max_spread; + __le16 nominal_sta_per_bcn; +} __packed; + +struct host_cmd_raw_tlv_dyn_global { + u8 tag; + __le16 num_configs; + __le16 num_bcn_indexes; +} __packed; + +struct host_cmd_raw_tlv_dyn_config { + u8 tag; + __le16 id; + __le16 index; + __le16 len; + u8 variable[]; +} __packed; + +union host_cmd_raw_tlvs { + u8 tag; + struct host_cmd_raw_tlv_slot_def slot_def; + struct host_cmd_raw_tlv_group group; + struct host_cmd_raw_tlv_start_time start_time; + struct host_cmd_raw_tlv_praw praw; + struct host_cmd_raw_tlv_bcn_spread bcn_spread; + struct host_cmd_raw_tlv_dyn_global dyn_global; + struct host_cmd_raw_tlv_dyn_config dyn_config; +} __packed; + +struct host_cmd_req_config_raw { + struct host_cmd_header hdr; + __le32 flags; + __le16 id; + u8 variable[]; +} __packed; + +struct host_cmd_req_config_bss_stats { + struct host_cmd_header hdr; + u8 enable; + __le32 monitor_window_ms; +} __packed; + +struct host_cmd_req_get_rssi { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_rssi { + struct host_cmd_header hdr; + __le32 status; + __sle32 rssi0; + __sle32 rssi1; + __sle32 rssi2; + __sle32 rssi3; + __sle32 rssi4; + __sle32 rssi5; + __sle32 rssi6; + __sle32 rssi7; +} __packed; + +#define HOST_CMD_SET_IFS_MIN_USECS 160 + +struct host_cmd_req_set_ifs { + struct host_cmd_header hdr; + __le32 period_usecs; +} __packed; + +struct host_cmd_resp_set_ifs { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_fem_settings { + struct host_cmd_header hdr; + __le32 tx_antenna; + __le32 rx_antenna; + __le32 lna_enabled; + __le32 pa_enabled; +} __packed; + +struct host_cmd_resp_set_fem_settings { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_txop { + struct host_cmd_header hdr; + u8 min_packet_count; +} __packed; + +struct host_cmd_resp_set_txop { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_control_response { + struct host_cmd_header hdr; + u8 direction; + u8 control_response_1mhz_en; +} __packed; + +struct host_cmd_resp_set_control_response { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_periodic_cal { + struct host_cmd_header hdr; + __le32 periodic_cal_en_mask; +} __packed; + +struct host_cmd_resp_set_periodic_cal { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_bcn_rssi_threshold { + struct host_cmd_header hdr; + u8 threshold_db; +} __packed; + +struct host_cmd_resp_set_bcn_rssi_threshold { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_tx_pkt_lifetime_usecs { + struct host_cmd_header hdr; + __le32 lifetime_usecs; +} __packed; + +struct host_cmd_resp_set_tx_pkt_lifetime_usecs { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_physm_watchdog { + struct host_cmd_header hdr; + u8 physm_watchdog_en; +} __packed; + +struct host_cmd_req_tx_polar { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_evt_sta_state { + struct host_cmd_header hdr; + u8 sta_addr[HOST_CMD_MAC_ADDR_LEN]; + __le16 aid; + __le16 state; +} __packed; + +struct host_cmd_evt_beacon_loss { + struct host_cmd_header hdr; + __le32 num_bcns; +} __packed; + +struct host_cmd_evt_sig_field_error { + struct host_cmd_header hdr; + __le64 start_timestamp; + __le64 end_timestamp; +} __packed; + +#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_TWT BIT(0) +#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_DUTY_CYCLE BIT(1) + +struct host_cmd_evt_umac_traffic_control { + struct host_cmd_header hdr; + u8 pause_data_traffic; + __le32 sources; +} __packed; + +struct host_cmd_evt_dhcp_lease_update { + struct host_cmd_header hdr; + __le32 my_ip; + __le32 netmask; + __le32 router; + __le32 dns; +} __packed; + +struct host_cmd_evt_ocs_done { + struct host_cmd_header hdr; + __le64 time_listen; + __le64 time_rx; + s8 noise; + u8 metric; +} __packed; + +struct host_cmd_evt_hw_scan_done { + struct host_cmd_header hdr; + u8 aborted; +} __packed; + +struct host_cmd_evt_channel_usage { + struct host_cmd_header hdr; + __le64 time_listen; + __le64 busy_time; + __le32 freq_hz; + u8 noise; + u8 bw_mhz; +} __packed; + +enum host_cmd_connection_loss_reason { + HOST_CMD_CONNECTION_LOSS_REASON_TSF_RESET = 0, +}; + +struct host_cmd_evt_connection_loss { + struct host_cmd_header hdr; + __le32 reason; +} __packed; + +struct host_cmd_evt_sched_scan_results { + struct host_cmd_header hdr; +} __packed; + +enum host_cmd_cqm_rssi_threshold_event { + HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_LOW = 0, + HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1, +}; + +struct host_cmd_evt_cqm_rssi_notify { + struct host_cmd_header hdr; + __sle16 rssi; + __le16 event; +} __packed; + +struct host_cmd_evt_scan_done { + struct host_cmd_header hdr; + u8 aborted; +} __packed; + +enum host_cmd_scan_result_frame { + HOST_CMD_SCAN_RESULT_FRAME_UNKNOWN = 0, + HOST_CMD_SCAN_RESULT_FRAME_BEACON = 1, + HOST_CMD_SCAN_RESULT_FRAME_PROBE_RESPONSE = 2, +}; + +struct host_cmd_evt_scan_result { + struct host_cmd_header hdr; + __le32 channel_freq_hz; + u8 bw_mhz; + u8 frame_type; + __sle16 rssi; + u8 bssid[HOST_CMD_MAC_ADDR_LEN]; + __le16 beacon_interval; + __le16 capability_info; + __le64 tsf; + __le16 ies_len; + u8 ies[]; +} __packed; + +struct host_cmd_evt_connected { + struct host_cmd_header hdr; + u8 bssid[HOST_CMD_MAC_ADDR_LEN]; + __sle16 rssi; + u8 padding_0[8]; + __le16 assoc_resp_ies_len; + u8 assoc_resp_ies[]; +} __packed; + +struct host_cmd_evt_beacon_filter_match { + struct host_cmd_header hdr; + u8 padding_0[4]; + __le32 ies_len; + u8 ies[]; +} __packed; + +struct host_cmd_req_set_capabilities { + struct host_cmd_header hdr; + struct host_cmd_mm_capabilities capabilities; + u8 set_caps; + u8 morse_mmss_offset; +} __packed; + +struct host_cmd_resp_set_capabilities { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_transmission_rate { + struct host_cmd_header hdr; + __sle32 mcs_index; + __sle32 bandwidth_mhz; + __sle32 tx_80211ah_format; + s8 use_traveling_pilots; + s8 use_sgi; + u8 enabled; + s8 nss_idx; + s8 use_ldpc; + s8 use_stbc; +} __packed; + +struct host_cmd_resp_set_transmission_rate { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_hart_id { + HOST_CMD_HART_ID_HOST = 0, + HOST_CMD_HART_ID_MAC = 1, + HOST_CMD_HART_ID_UPHY = 2, + HOST_CMD_HART_ID_LPHY = 3, +}; + +struct host_cmd_req_force_assert { + struct host_cmd_header hdr; + __le32 hart_id; +} __packed; + +#define HOST_CMD_HOST_BLOCK_TX_FRAMES BIT(0) +#define HOST_CMD_HOST_BLOCK_TX_CMD BIT(1) + +enum host_cmd_param_action { + HOST_CMD_PARAM_ACTION_SET = 0, + HOST_CMD_PARAM_ACTION_GET = 1, + HOST_CMD_PARAM_ACTION_LAST = 2, +}; + +enum host_cmd_slow_clock_mode { + HOST_CMD_SLOW_CLOCK_MODE_AUTO = 0, + HOST_CMD_SLOW_CLOCK_MODE_INTERNAL = 1, +}; + +enum host_cmd_param_id { + HOST_CMD_PARAM_ID_MAX_TRAFFIC_DELIVERY_WAIT_US = 0, + HOST_CMD_PARAM_ID_EXTRA_ACK_TIMEOUT_ADJUST_US = 1, + HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_WATERMARK = 2, + HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_MIN_AMPDU_SIZE = 3, + HOST_CMD_PARAM_ID_POWERSAVE_TYPE = 4, + HOST_CMD_PARAM_ID_SNOOZE_DURATION_ADJUST_US = 5, + HOST_CMD_PARAM_ID_TX_BLOCK = 6, + HOST_CMD_PARAM_ID_FORCED_SNOOZE_PERIOD_US = 7, + HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO = 8, + HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO_PULSE_MS = 9, + HOST_CMD_PARAM_ID_CONNECTION_MONITOR_GPIO = 10, + HOST_CMD_PARAM_ID_INPUT_TRIGGER_GPIO = 11, + HOST_CMD_PARAM_ID_INPUT_TRIGGER_MODE = 12, + HOST_CMD_PARAM_ID_COUNTRY = 13, + HOST_CMD_PARAM_ID_RTS_THRESHOLD = 14, + HOST_CMD_PARAM_ID_HOST_TX_BLOCK = 15, + HOST_CMD_PARAM_ID_MEM_RETENTION_CODE = 16, + HOST_CMD_PARAM_ID_NON_TIM_MODE = 17, + HOST_CMD_PARAM_ID_DYNAMIC_PS_TIMEOUT_MS = 18, + HOST_CMD_PARAM_ID_HOME_CHANNEL_DWELL_MS = 19, + HOST_CMD_PARAM_ID_SLOW_CLOCK_MODE = 20, + HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD = 21, + HOST_CMD_PARAM_ID_BEACON_LOSS_COUNT = 22, + HOST_CMD_PARAM_ID_AP_POWER_SAVE = 23, + HOST_CMD_PARAM_ID_BEACON_OFFLOAD = 24, + HOST_CMD_PARAM_ID_PROBE_RESP_OFFLOAD = 25, + HOST_CMD_PARAM_ID_BSS_MAX_AWAY_DURATION = 26, + HOST_CMD_PARAM_ID_DEFAULT_ACTIVE_SCAN_DWELL_MS = 27, + HOST_CMD_PARAM_ID_CTS_TO_SELF = 28, + HOST_CMD_PARAM_ID_CHANNELIZATION = 29, + HOST_CMD_PARAM_ID_LAST = 30, +}; + +struct host_cmd_req_get_set_generic_param { + struct host_cmd_header hdr; + __le32 param_id; + __le32 action; + __le32 flags; + __le32 value; +} __packed; + +struct host_cmd_resp_get_set_generic_param { + struct host_cmd_header hdr; + __le32 status; + __le32 flags; + __le32 value; +} __packed; + +#endif diff --git a/drivers/net/wireless/morsemicro/mm81x/core.c b/drivers/net/wireless/morsemicro/mm81x/core.c new file mode 100644 index 000000000000..5c51d69c4fb4 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/core.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include "core.h" +#include "bus.h" +#include "hif.h" +#include "mac.h" + +static int mm81x_core_attach_regs(struct mm81x *mors) +{ + int ret = 0; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, &mors->chip_id); + mm81x_release_bus(mors); + + if (ret < 0) { + dev_err(mors->dev, "failed to read chip id %d", ret); + return ret; + } + + switch (mors->chip_id) { + case (CHIP_ID_MM8108): + mors->regs = &mm8108_regs; + mors->hif.ops = &mm81x_yaps_ops; + break; + default: + return -ENODEV; + } + + return ret; +} + +static void mm81x_core_init_mac_addr(struct mm81x *mors) +{ + int ret = mm81x_hw_otp_get_mac_addr(mors); + + if (ret || !is_valid_ether_addr(mors->macaddr)) + eth_random_addr(mors->macaddr); +} + +char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver) +{ + const char *fw_base; + + switch (chip_id) { + case CHIP_ID_MM8108: + fw_base = MM8108_FW_BASE; + break; + default: + return NULL; + } + + return kasprintf(GFP_KERNEL, MM81X_FW_DIR "/v%u/%s" MM81X_FW_EXT, + fw_ver, fw_base); +} +EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path); + +struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev) +{ + return mm81x_mac_alloc(priv_size, dev); +} +EXPORT_SYMBOL_GPL(mm81x_core_alloc); + +int mm81x_core_init(struct mm81x *mors) +{ + int ret; + + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + set_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags); + + mm81x_core_init_mac_addr(mors); + + ret = mm81x_core_attach_regs(mors); + if (ret) + return ret; + + mors->chip_wq = create_singlethread_workqueue("chip_wq"); + if (!mors->chip_wq) + return -ENOMEM; + + mors->net_wq = create_singlethread_workqueue("net_wq"); + if (!mors->net_wq) { + ret = -ENOMEM; + goto err_chip_wq; + } + + ret = mm81x_hif_init(mors); + if (ret) + goto err_wqs; + + return 0; + +err_wqs: + flush_workqueue(mors->net_wq); + destroy_workqueue(mors->net_wq); + +err_chip_wq: + flush_workqueue(mors->chip_wq); + destroy_workqueue(mors->chip_wq); + + return ret; +} +EXPORT_SYMBOL_GPL(mm81x_core_init); + +int mm81x_core_register(struct mm81x *mors) +{ + return mm81x_mac_register(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_register); + +void mm81x_core_unregister(struct mm81x *mors) +{ + mm81x_mac_unregister(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_unregister); + +void mm81x_core_deinit(struct mm81x *mors) +{ + mm81x_hif_finish(mors); + flush_workqueue(mors->net_wq); + destroy_workqueue(mors->net_wq); + flush_workqueue(mors->chip_wq); + destroy_workqueue(mors->chip_wq); +} +EXPORT_SYMBOL_GPL(mm81x_core_deinit); + +void mm81x_core_free(struct mm81x *mors) +{ + mm81x_mac_free(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_free); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X core"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h new file mode 100644 index 000000000000..2fd4b4786e77 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/core.h @@ -0,0 +1,456 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_CORE_H_ +#define _MM81X_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "yaps.h" +#include "yaps_hw.h" +#include "hw.h" +#include "fw.h" +#include "rc.h" + +#define MM81X_DRIVER_SEMVER_MAJOR 56 +#define MM81X_DRIVER_SEMVER_MINOR 3 +#define MM81X_DRIVER_SEMVER_PATCH 0 + +#define MM81X_SEMVER_GET_MAJOR(x) (((x) >> 22) & 0x3FF) +#define MM81X_SEMVER_GET_MINOR(x) (((x) >> 10) & 0xFFF) +#define MM81X_SEMVER_GET_PATCH(x) ((x) & 0x3FF) + +#define DRV_VERSION __stringify(MM81X_VERSION) + +#define MM8108_FW_BASE "mm8108" + +#define BCF_SIZE_MAX 48 + +#define KHZ100_TO_MHZ(x) ((x) / 10) +#define KHZ100_TO_KHZ(freq) ((freq) * 100) +#define KHZ100_TO_HZ(freq) ((freq) * 100000) + +#define QDBM_TO_MBM(gain) (((gain) * 100) >> 2) +#define MBM_TO_QDBM(gain) (((gain) << 2) / 100) +#define QDBM_TO_DBM(gain) ((gain) / 4) + +#define BPS_TO_KBPS(x) ((x) / 1000) + +#define NSS_IDX_TO_NSS(x) ((x) + 1) +#define NSS_TO_NSS_IDX(x) ((x) - 1) + +#define ROUND_BYTES_TO_WORD(_nbytes) \ + (((_nbytes) + 3) & ~((typeof(_nbytes))0x03)) + +struct mm81x_bus_ops; +struct mm81x_hif_ops; + +#define MM81X_CAPS_MAX_FW_VAL (128) + +/* Max number of interfaces */ +#define MM81X_MAX_IF (2) + +enum mm81x_caps_flags { + MM81X_CAPS_FW_START = 0, + MM81X_CAPS_2MHZ = MM81X_CAPS_FW_START, + MM81X_CAPS_4MHZ, + MM81X_CAPS_8MHZ, + MM81X_CAPS_16MHZ, + MM81X_CAPS_SGI, + MM81X_CAPS_S1G_LONG, + MM81X_CAPS_TRAVELING_PILOT_ONE_STREAM, + MM81X_CAPS_TRAVELING_PILOT_TWO_STREAM, + MM81X_CAPS_MU_BEAMFORMEE, + MM81X_CAPS_MU_BEAMFORMER, + MM81X_CAPS_RD_RESPONDER, + MM81X_CAPS_STA_TYPE_SENSOR, + MM81X_CAPS_STA_TYPE_NON_SENSOR, + MM81X_CAPS_GROUP_AID, + MM81X_CAPS_NON_TIM, + MM81X_CAPS_TIM_ADE, + MM81X_CAPS_BAT, + MM81X_CAPS_DYNAMIC_AID, + MM81X_CAPS_UPLINK_SYNC, + MM81X_CAPS_FLOW_CONTROL, + MM81X_CAPS_AMPDU, + MM81X_CAPS_AMSDU, + MM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE, + MM81X_CAPS_PAGE_SLICING, + MM81X_CAPS_RAW, + MM81X_CAPS_MCS8, + MM81X_CAPS_MCS9, + MM81X_CAPS_ASYMMETRIC_BA_SUPPORT, + MM81X_CAPS_DAC, + MM81X_CAPS_CAC, + MM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK, + MM81X_CAPS_NDP_PSPOLL, + MM81X_CAPS_FRAGMENT_BA, + MM81X_CAPS_OBSS_MITIGATION, + MM81X_CAPS_TMP_PS_MODE_SWITCH, + MM81X_CAPS_SECTOR_TRAINING, + MM81X_CAPS_UNSOLICIT_DYNAMIC_AID, + MM81X_CAPS_NDP_BEAMFORMING_REPORT, + MM81X_CAPS_MCS_NEGOTIATION, + MM81X_CAPS_DUPLICATE_1MHZ, + MM81X_CAPS_TACK_AS_PSPOLL, + MM81X_CAPS_PV1, + MM81X_CAPS_TWT_RESPONDER, + MM81X_CAPS_TWT_REQUESTER, + MM81X_CAPS_BDT, + MM81X_CAPS_TWT_GROUPING, + MM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC, + MM81X_CAPS_LONG_MPDU, + MM81X_CAPS_TXOP_SECTORIZATION, + MM81X_CAPS_GROUP_SECTORIZATION, + MM81X_CAPS_HTC_VHT, + MM81X_CAPS_HTC_VHT_MFB, + MM81X_CAPS_HTC_VHT_MRQ, + MM81X_CAPS_2SS, + MM81X_CAPS_3SS, + MM81X_CAPS_4SS, + MM81X_CAPS_SU_BEAMFORMEE, + MM81X_CAPS_SU_BEAMFORMER, + MM81X_CAPS_RX_STBC, + MM81X_CAPS_TX_STBC, + MM81X_CAPS_RX_LDPC, + MM81X_CAPS_HW_FRAGMENT, + + MM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL, + MM81X_CAPS_LAST = MM81X_CAPS_FW_END, +}; + +struct mm81x_fw_caps { + u32 flags[FW_CAPABILITIES_FLAGS_WIDTH]; + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + u8 maximum_ampdu_length_exponent; + u8 mm81x_mmss_offset; +}; + +#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \ + mm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY) + +static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps, + enum mm81x_caps_flags flag) +{ + const unsigned long *flags_ptr = (unsigned long *)caps->flags; + + return test_bit(flag, flags_ptr); +} + +struct mm81x_ps { + u32 wakers; + bool enable; + bool suspended; + /* PS state lock */ + struct mutex lock; + struct delayed_work delayed_eval_work; +}; + +enum mm81x_page_aci { + MM81X_ACI_BE = 0, + MM81X_ACI_BK = 1, + MM81X_ACI_VI = 2, + MM81X_ACI_VO = 3, +}; + +enum mm81x_qos_tid_up_index { + MM81X_QOS_TID_UP_BK = 1, + MM81X_QOS_TID_UP_XX = 2, + MM81X_QOS_TID_UP_BE = 0, + MM81X_QOS_TID_UP_EE = 3, + MM81X_QOS_TID_UP_CL = 4, + MM81X_QOS_TID_UP_VI = 5, + MM81X_QOS_TID_UP_VO = 6, + MM81X_QOS_TID_UP_NC = 7, + + MM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK, + MM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC +}; + +struct mm81x_sw_version { + u8 major; + u8 minor; + u8 patch; +}; + +struct mm81x_sta { + const struct ieee80211_vif *vif; + u8 addr[ETH_ALEN]; + enum ieee80211_sta_state state; + bool tid_tx[IEEE80211_NUM_TIDS]; + bool tid_start_tx[IEEE80211_NUM_TIDS]; + u8 tid_params[IEEE80211_NUM_TIDS]; + int max_bw_mhz; + struct mm81x_rc_sta rc; + struct mmrc_rate last_sta_tx_rate; + s16 avg_rssi; + bool tx_ps_filter_en; +}; + +struct mm81x_vif { + struct mm81x *mors; + u16 id; + + union { + struct { + bool is_assoc; + } sta; + struct { + u32 num_stas; + struct work_struct beacon_work; + } ap; + } u; +}; + +struct mm81x_stale_tx_status { + /* Stale Tx lock */ + spinlock_t lock; + struct timer_list timer; +}; + +struct mcast_filter { + u8 count; + /* + * Integer representation of the last four bytes of a multicast MAC + * address. The first two bytes are always 0x0100 (IPv4) or 0x3333 + * (IPv6). + */ + __le32 addr_list[]; +}; + +enum mm81x_hw_scan_op { + MM81X_HW_SCAN_OP_START, + MM81X_HW_SCAN_OP_STOP, +}; + +struct mm81x_hw_scan_params { + struct ieee80211_hw *hw; + + /* vif which initiated the scan */ + struct ieee80211_vif *vif; + bool has_directed_ssid; + u32 dwell_time_ms; + u32 dwell_on_home_ms; + enum mm81x_hw_scan_op operation; + bool store; + struct sk_buff *probe_req; + u16 num_chans; + u16 allocated_chans; + + struct { + struct ieee80211_channel *channel; + /* Index into @ref powers_qdbm for the power of this channel */ + u8 power_idx; + } *channels; + + s32 *powers_qdbm; + u8 n_powers; +}; + +enum mm81x_hw_scan_state { + HW_SCAN_STATE_IDLE, + HW_SCAN_STATE_RUNNING, + HW_SCAN_STATE_ABORTING, +}; + +struct mm81x_hw_scan { + enum mm81x_hw_scan_state state; + struct completion scan_done; + struct mm81x_hw_scan_params *params; + struct delayed_work timeout; + u32 home_dwell_ms; +}; + +enum mm81x_hif_event_flags { + MM81X_HIF_EVT_RX_PEND, + MM81X_HIF_EVT_PAGE_RETURN_PEND, + MM81X_HIF_EVT_TX_COMMAND_PEND, + MM81X_HIF_EVT_TX_BEACON_PEND, + MM81X_HIF_EVT_TX_MGMT_PEND, + MM81X_HIF_EVT_TX_DATA_PEND, + MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, + MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, + MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, + MM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE, +}; + +enum mm81x_state_flags { + MM81X_STATE_CHIP_UNRESPONSIVE, + MM81X_STATE_DATA_QS_STOPPED, + MM81X_STATE_DATA_TX_STOPPED, + MM81X_STATE_REGDOM_SET_BY_USER, + MM81X_STATE_REGDOM_SET_BY_OTP, + MM81X_STATE_RELOAD_FW_AFTER_START, + MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED, + MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED, +}; + +#define MM81X_COUNTRY_LEN (3) +#define INVALID_VIF_INDEX 0xFF + +struct mm81x { + u32 chip_id; + u32 host_table_ptr; + + /* Refer to @enum mm81x_bus_type */ + u32 bus_type; + u32 bcf_address; + + /* + * Parsed from the release tag, which should be in the format + * 'rel___'. If the tag is not in this format + * then corresponding version field will be 0. + */ + struct mm81x_sw_version sw_ver; + u8 macaddr[ETH_ALEN]; + u8 country[MM81X_COUNTRY_LEN]; + + /* Mask of type @enum host_table_firmware_flags */ + u32 fw_flags; + u32 fw_major; + struct mm81x_fw_caps fw_caps; + bool started; + bool chip_was_reset; + struct wiphy *wiphy; + struct mm81x_hw_scan hw_scan; + struct ieee80211_hw *hw; + struct device *dev; + + struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF]; + + /* @mm81x_state_flags */ + unsigned long state_flags; + + u16 cmd_seq; + struct completion *cmd_comp; + /* Serialises commands */ + struct mutex cmd_lock; + + /* Serialises command completion */ + struct mutex cmd_wait; + + const struct mm81x_regs *regs; + + struct { + union { + struct mm81x_yaps yaps; + } u; + const struct mm81x_hif_ops *ops; + /* See @enum mm81x_hif_event_flags for values */ + unsigned long event_flags; + bool validate_skb_checksum; + } hif; + + struct workqueue_struct *chip_wq; + struct work_struct hif_work; + struct work_struct usb_irq_work; + struct mm81x_stale_tx_status stale_status; + bool config_ps; + struct mm81x_ps ps; + + /* Tx power in mBm received from the FW before association */ + s32 tx_power_mbm; + s32 tx_max_power_mbm; + + const struct mm81x_bus_ops *bus_ops; + struct mm81x_rc mrc; + int rts_threshold; + struct workqueue_struct *net_wq; + struct work_struct tx_stale_work; + wait_queue_head_t tx_empty_waitq; + + struct cfg80211_chan_def chandef; + struct mcast_filter *mcast_filter; + atomic_t num_bcn_vifs; + unsigned long beacon_irqs_enabled; + u8 drv_priv[] __aligned(sizeof(void *)); +}; + +/* Map from mac80211 queue to Morse ACI value for page metadata */ +static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue) +{ + switch (mac80211queue) { + case IEEE80211_AC_VO: + return MM81X_ACI_VO; + case IEEE80211_AC_VI: + return MM81X_ACI_VI; + case IEEE80211_AC_BK: + return MM81X_ACI_BK; + default: + return MM81X_ACI_BE; + } +} + +static inline enum mm81x_page_aci +dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid) +{ + switch (tid) { + case MM81X_QOS_TID_UP_BK: + case MM81X_QOS_TID_UP_XX: + return MM81X_ACI_BK; + case MM81X_QOS_TID_UP_CL: + case MM81X_QOS_TID_UP_VI: + return MM81X_ACI_VI; + case MM81X_QOS_TID_UP_VO: + case MM81X_QOS_TID_UP_NC: + return MM81X_ACI_VO; + case MM81X_QOS_TID_UP_BE: + case MM81X_QOS_TID_UP_EE: + default: + return MM81X_ACI_BE; + } +} + +static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors) +{ + return !test_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags) && + !test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, + &mors->hif.event_flags); +} + +static inline struct ieee80211_vif * +mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif) +{ + return container_of((void *)mors_vif, struct ieee80211_vif, drv_priv); +} + +static inline struct mm81x_vif * +ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif) +{ + return (struct mm81x_vif *)vif->drv_priv; +} + +static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif) +{ + return mors_vif->mors; +} + +static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len) +{ + return ~crc32(~0, ssid, len); +} + +int mm81x_beacon_init(struct mm81x_vif *mors_vif); +void mm81x_beacon_finish(struct mm81x_vif *mors_vif); +void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status); +char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver); +struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev); +int mm81x_core_init(struct mm81x *mors); +int mm81x_core_register(struct mm81x *mors); +void mm81x_core_unregister(struct mm81x *mors); +void mm81x_core_deinit(struct mm81x *mors); +void mm81x_core_free(struct mm81x *mors); + +#endif /* !_MM81X_MM81X_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c new file mode 100644 index 000000000000..d6d2ad086c32 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/fw.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fw.h" +#include "mac.h" +#include "bus.h" + +/* + * Maximum wait time (microseconds) for firmware to boot (for host table + * pointer to be available) + */ +#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000 +#define HOST_TABLE_PTR_POLL_PERIOD_US 10000 + +/* Number of times to attempt flashing FW */ +#define FW_FLASH_ATTEMPT_COUNT 3 + +static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr) +{ + const struct mm81x_elf32_ehdr *p = + (const struct mm81x_elf32_ehdr *)data; + + /* Magic check */ + if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 || + p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3) + return -EINVAL; + + /* elf32 and little endian */ + if (p->e_ident[EI_DATA] != ELFDATA2LSB || + p->e_ident[EI_CLASS] != ELFCLASS32) + return -EINVAL; + + ehdr->e_phoff = le32_to_cpu(p->e_phoff); + ehdr->e_phentsize = le16_to_cpu(p->e_phentsize); + ehdr->e_phnum = le16_to_cpu(p->e_phnum); + ehdr->e_shoff = le32_to_cpu(p->e_shoff); + ehdr->e_shentsize = le16_to_cpu(p->e_shentsize); + ehdr->e_shnum = le16_to_cpu(p->e_shnum); + ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx); + ehdr->e_entry = le32_to_cpu(p->e_entry); + + return 0; +} + +static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length) +{ + const struct mm81x_fw_info_tlv *tlv = + (const struct mm81x_fw_info_tlv *)data; + + while ((u8 *)tlv < (data + length)) { + switch (le16_to_cpu(tlv->type)) { + case MM81X_FW_INFO_TLV_BCF_ADDR: + mors->bcf_address = get_unaligned_le32(tlv->val); + break; + default: + break; + } + tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv + + le16_to_cpu( + tlv->length) + + sizeof(*tlv)); + } +} + +static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr, + Elf32_Shdr *shdr, int i) +{ + const struct mm81x_elf32_shdr *p = + (void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize)); + + shdr->sh_name = le32_to_cpu(p->sh_name); + shdr->sh_type = le32_to_cpu(p->sh_type); + shdr->sh_offset = le32_to_cpu(p->sh_offset); + shdr->sh_addr = le32_to_cpu(p->sh_addr); + shdr->sh_size = le32_to_cpu(p->sh_size); + shdr->sh_flags = le32_to_cpu(p->sh_flags); + + return 0; +} + +static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr) +{ + int status; + + dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr); + mm81x_claim_bus(mors); + status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr); + mm81x_release_bus(mors); + return status; +} + +static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw) +{ + int i; + int ret = 0; + Elf32_Ehdr ehdr; + Elf32_Phdr phdr; + Elf32_Shdr shdr; + Elf32_Shdr sh_strtab; + const char *sh_strs; + + u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size), + GFP_KERNEL); + + if (!fw_buf) + return -ENOMEM; + + if (mm81x_fw_get_header(fw->data, &ehdr)) { + dev_err(mors->dev, "Wrong file format"); + return -EINVAL; + } + + if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab, + ehdr.e_shstrndx)) { + dev_err(mors->dev, "Invalid firmware. Missing string table"); + return -ENOENT; + } + + sh_strs = (const char *)fw->data + sh_strtab.sh_offset; + + for (i = 0; i < ehdr.e_phnum; i++) { + int status; + int address; + const struct mm81x_elf32_phdr *p = + (void *)(fw->data + ehdr.e_phoff + + i * ehdr.e_phentsize); + + phdr.p_type = le32_to_cpu(p->p_type); + phdr.p_offset = le32_to_cpu(p->p_offset); + phdr.p_paddr = le32_to_cpu(p->p_paddr); + phdr.p_filesz = le32_to_cpu(p->p_filesz); + phdr.p_memsz = le32_to_cpu(p->p_memsz); + + address = phdr.p_paddr; + + if (phdr.p_type != PT_LOAD || !phdr.p_memsz) + continue; + + if (phdr.p_filesz && phdr.p_offset && + (phdr.p_offset + phdr.p_filesz) < fw->size) { + u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz); + + memcpy(fw_buf, fw->data + phdr.p_offset, padded_size); + /* Set padding to 0xff */ + memset(fw_buf + phdr.p_filesz, 0xff, + padded_size - phdr.p_filesz); + mm81x_claim_bus(mors); + status = mm81x_dm_write(mors, address, fw_buf, + padded_size); + mm81x_release_bus(mors); + if (status) { + ret = -EIO; + break; + } + } + } + + for (i = 0; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i)) + continue; + + /* This is the firmware info. Parse it */ + if (!strncmp(sh_strs + shdr.sh_name, ".fw_info", + sizeof(".fw_info"))) + mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset, + shdr.sh_size); + } + + if (ehdr.e_entry) + ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry); + + devm_kfree(mors->dev, fw_buf); + return ret; +} + +static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr, + const void *src, size_t src_len, u8 *scratch, + size_t scratch_cap) +{ + size_t rounded = ROUND_BYTES_TO_WORD(src_len); + int st; + + if (rounded > scratch_cap) + return -EINVAL; + if (rounded > BCF_DATABASE_SIZE) + return -EFBIG; + + memcpy(scratch, src, src_len); + if (rounded > src_len) + memset(scratch + src_len, 0xff, rounded - src_len); + + mm81x_claim_bus(mors); + st = mm81x_dm_write(mors, addr, scratch, rounded); + mm81x_release_bus(mors); + + return st ? -EIO : 0; +} + +static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf, + unsigned int bcf_address) +{ + int i, ret = 0; + size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded; + Elf32_Ehdr ehdr; + Elf32_Shdr shdr, sh_strtab; + const char *sh_strs, *reg_prefix = ".regdom_", *reg_src; + size_t reg_len; + u8 *bcf_buf; + + bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size), + GFP_KERNEL); + if (!bcf_buf) + return -ENOMEM; + + if (mm81x_fw_get_header(bcf->data, &ehdr)) { + dev_err(mors->dev, "Wrong file format"); + ret = -EINVAL; + goto out_free; + } + + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab, + ehdr.e_shstrndx)) { + dev_err(mors->dev, "Invalid BCF - missing string table"); + ret = -ENOENT; + goto out_free; + } + + sh_strs = (const char *)bcf->data + sh_strtab.sh_offset; + reg_prefix_len = strlen(reg_prefix); + + for (i = 0; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i)) + continue; + if (strcmp(sh_strs + shdr.sh_name, ".board_config")) + continue; + + cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size); + dev_dbg(mors->dev, + "Write BCF board_config - addr 0x%x size %zu", + bcf_address, cfg_len_rounded); + + ret = __mm81x_fw_load_bcf(mors, bcf_address, + bcf->data + shdr.sh_offset, + shdr.sh_size, bcf_buf, + ROUND_BYTES_TO_WORD(bcf->size)); + if (ret) + goto out_free; + + bcf_address += cfg_len_rounded; + break; + } + + ret = -EINVAL; + for (; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i)) + continue; + if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len)) + continue; + if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len, + mors->country, 2)) + continue; + + reg_src = bcf->data + shdr.sh_offset; + reg_len = shdr.sh_size; + dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu", + sh_strs + shdr.sh_name, bcf_address, + ROUND_BYTES_TO_WORD(reg_len)); + ret = 0; + break; + } + + if (ret) + goto out_free; + + reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len); + if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) { + ret = -EFBIG; + goto out_free; + } + + ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf, + ROUND_BYTES_TO_WORD(bcf->size)); + +out_free: + devm_kfree(mors->dev, bcf_buf); + return ret; +} + +static void mm81x_fw_clear_aon(struct mm81x *mors) +{ + int idx; + u8 count = MM81X_REG_AON_COUNT(mors); + u32 address = MM81X_REG_AON_ADDR(mors); + + for (idx = 0; idx < count; idx++, address += 4) { + if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0) + /* Keep the USB power domain enabled in AON. */ + mm81x_reg32_write(mors, address, + MM81X_REG_AON_USB_RESET(mors)); + else + /* clear AON */ + mm81x_reg32_write(mors, address, 0x0); + } + + mm81x_hw_toggle_aon_latch(mors); +} + +static void mm81x_fw_trigger(struct mm81x *mors) +{ + const unsigned int wait_after_msi_trigger_ms = 1; + + mm81x_claim_bus(mors); + /* + * If not coming from a full reset, some AON flags may be latched. + * Make sure to clear any hanging AON bits (can affect booting). + */ + mm81x_fw_clear_aon(mors); + + if (MM81X_REG_CLK_CTRL(mors)) + mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors), + MM81X_REG_CLK_CTRL_VALUE(mors)); + + mm81x_reg32_write(mors, MM81X_REG_MSI(mors), + MM81X_REG_MSI_HOST_INT(mors)); + mm81x_release_bus(mors); + + /* Give the chip a chance to boot */ + mdelay(wait_after_msi_trigger_ms); +} + +static int mm81x_fw_verify_magic(struct mm81x *mors) +{ + int ret = 0; + int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors); + + mm81x_claim_bus(mors); + mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, magic_number), + &magic); + + if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) { + dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x", + MM81X_REG_HOST_MAGIC_VALUE(mors), magic); + ret = -EIO; + } + + mm81x_release_bus(mors); + return ret; +} + +static int mm81x_fw_get_flags(struct mm81x *mors) +{ + int ret = 0; + int fw_flags = 0; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, fw_flags), + &fw_flags); + mors->fw_flags = fw_flags; + mm81x_release_bus(mors); + + return ret; +} + +static int mm81x_fw_check_compatibility(struct mm81x *mors) +{ + int ret = 0; + u32 fw_version; + u32 major; + u32 minor; + u32 patch; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, + fw_version_number), + &fw_version); + mm81x_release_bus(mors); + + major = MM81X_SEMVER_GET_MAJOR(fw_version); + minor = MM81X_SEMVER_GET_MINOR(fw_version); + patch = MM81X_SEMVER_GET_PATCH(fw_version); + + /* Firmware on device must match the firmware file we requested */ + if (ret == 0 && major != mors->fw_major) { + dev_err(mors->dev, + "Incompatible FW version: (Requested) v%u, (Chip) %d.%d.%d\n", + mors->fw_major, major, minor, patch); + ret = -EPERM; + } else if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) { + dev_warn( + mors->dev, + "Running FW v%d.%d.%d, driver supports up to v%d, some features might not be supported", + major, minor, patch, HOST_CMD_SEMVER_MAJOR); + } else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) { + dev_warn( + mors->dev, + "FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d", + HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR, + HOST_CMD_SEMVER_PATCH, major, minor, patch); + } + + return ret; +} + +static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors) +{ + int ret; + + mors->host_table_ptr = 0; + mm81x_claim_bus(mors); + ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0); + mm81x_release_bus(mors); + return ret; +} + +static int mm81x_fw_get_host_table_ptr(struct mm81x *mors) +{ + int ret, err; + + mm81x_claim_bus(mors); + ret = read_poll_timeout(mm81x_reg32_read, err, + err || mors->host_table_ptr, + HOST_TABLE_PTR_POLL_PERIOD_US, + HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors, + MM81X_REG_HOST_MANIFEST_PTR(mors), + &mors->host_table_ptr); + mm81x_release_bus(mors); + + return ret ? ret : err; +} + +static int mm81x_fw_read_ext_host_table(struct mm81x *mors, + struct ext_host_tbl **ext_host_table) +{ + int ret = 0; + u32 host_tbl_ptr = mors->host_table_ptr; + u32 ext_host_tbl_ptr; + u32 ext_host_tbl_ptr_addr = + host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr); + u32 ext_host_tbl_len; + u32 ext_host_tbl_len_ptr_addr; + struct ext_host_tbl *host_tbl = NULL; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr); + if (ret) + goto exit; + + if (!ext_host_tbl_ptr) { + ret = -ENXIO; + goto exit; + } + + ext_host_tbl_len_ptr_addr = + ext_host_tbl_ptr + + offsetof(struct ext_host_tbl, ext_host_tbl_length); + + ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr, + &ext_host_tbl_len); + if (ret) + goto exit; + + ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len); + if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) { + ret = -EINVAL; + goto exit; + } + + host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL); + if (!host_tbl) { + ret = -ENOMEM; + goto exit; + } + + ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl, + (int)ext_host_tbl_len); + if (ret) + goto exit; + + mm81x_release_bus(mors); + *ext_host_table = host_tbl; + return ret; + +exit: + mm81x_release_bus(mors); + kfree(host_tbl); + return ret; +} + +static void mm81x_fw_update_capabilities(struct mm81x *mors, + struct ext_host_tbl_s1g_caps *caps) +{ + int i; + + for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) { + mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]); + dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i, + le32_to_cpu(caps->flags[i])); + } + mors->fw_caps.ampdu_mss = caps->ampdu_mss; + mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset; + mors->fw_caps.beamformee_sts_capability = + caps->beamformee_sts_capability; + mors->fw_caps.maximum_ampdu_length_exponent = + caps->maximum_ampdu_length; + mors->fw_caps.number_sounding_dimensions = + caps->number_sounding_dimensions; + + dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u", + caps->ampdu_mss); + dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u", + caps->mm81x_mmss_offset); + dev_dbg(mors->dev, "\tBeamformee STS Capability: %u", + caps->beamformee_sts_capability); + dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u", + caps->number_sounding_dimensions); + dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u", + caps->maximum_ampdu_length); +} + +static void mm81x_fw_update_validate_skb_checksum( + struct mm81x *mors, + struct ext_host_tbl_insert_skb_checksum *validate_checksum) +{ + mors->hif.validate_skb_checksum = + validate_checksum->insert_and_validate_checksum; + dev_dbg(mors->dev, "Validate checksum inserted by fw %s", + str_enabled_disabled(mors->hif.validate_skb_checksum)); +} + +int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors) +{ + int ret; + u8 *head; + u8 *end; + struct ext_host_tbl *ext_host_table = NULL; + + ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table); + if (ret || !ext_host_table) + goto exit; + + /* Parse the TLVs */ + head = ext_host_table->ext_host_table_data_tlvs; + end = ((u8 *)ext_host_table) + + le32_to_cpu(ext_host_table->ext_host_tbl_length); + + while (head < end) { + struct ext_host_tbl_tlv_hdr *hdr = + (struct ext_host_tbl_tlv_hdr *)head; + + switch (le16_to_cpu(hdr->tag)) { + case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES: + mm81x_fw_update_capabilities( + mors, (struct ext_host_tbl_s1g_caps *)hdr); + break; + + case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM: + mm81x_fw_update_validate_skb_checksum( + mors, + (struct ext_host_tbl_insert_skb_checksum *)hdr); + break; + + case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE: + mm81x_yaps_hw_read_table( + mors, &((struct ext_host_tbl_yaps_table *)hdr) + ->yaps_table); + break; + default: + break; + } + + head += le16_to_cpu(hdr->length); + if (!hdr->length) + break; + } + + kfree(ext_host_table); + return ret; +exit: + dev_err(mors->dev, "failed to parse ext host table %d", ret); + return ret; +} + +static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw, + const struct firmware *bcf, bool reset) +{ + int ret; + + if (reset || !mors->chip_was_reset) { + ret = mm81x_hw_digital_reset(mors); + if (ret) + return ret; + } + + mm81x_hw_pre_firmware_ndr_hook(mors); + + ret = mm81x_fw_invalidate_host_ptr(mors); + if (ret) + return ret; + + ret = mm81x_fw_load_fw(mors, fw); + if (ret) + return ret; + + ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address); + if (ret) + return ret; + + mm81x_fw_trigger(mors); + mm81x_hw_post_firmware_ndr_hook(mors); + + ret = mm81x_fw_get_host_table_ptr(mors); + if (ret) + return ret; + + ret = mm81x_fw_verify_magic(mors); + if (ret) + return ret; + + return mm81x_fw_check_compatibility(mors); +} + +static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw, + const struct firmware *bcf, bool reset) +{ + int ret; + int retries = FW_FLASH_ATTEMPT_COUNT; + + while (retries--) { + ret = __mm81x_fw_flash(mors, fw, bcf, reset); + if (!ret) + return 0; + + mors->chip_was_reset = false; + } + + return ret; +} + +static uint32_t binary_crc(const struct firmware *fw) +{ + return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) & + 0xffffffff; +} + +static int mm81x_fw_request(struct mm81x *mors, const struct firmware **fw) +{ + int ret = -ENOENT; + int ver; + char *fw_path; + + for (ver = MM81X_FW_VER_MAX; ver >= MM81X_FW_VER_MIN; ver--) { + fw_path = mm81x_core_get_fw_path(mors->chip_id, ver); + if (!fw_path) + return -ENOMEM; + + ret = firmware_request_nowarn(fw, fw_path, mors->dev); + if (!ret) { + dev_info( + mors->dev, + "Loaded firmware from %s, size %zu, crc32 0x%08x\n", + fw_path, (*fw)->size, binary_crc(*fw)); + mors->fw_major = ver; + } + + kfree(fw_path); + if (!ret) + return 0; + } + + dev_err(mors->dev, "no firmware found (tried v%d down to v%d): %d\n", + MM81X_FW_VER_MAX, MM81X_FW_VER_MIN, ret); + return ret; +} + +int mm81x_fw_init(struct mm81x *mors, bool reset) +{ + int ret; + int board_id; + char *bcf_path = NULL; + const struct firmware *fw = NULL; + const struct firmware *bcf = NULL; + + board_id = mm81x_hw_otp_get_board_type(mors); + + if (!mm81x_hw_otp_valid_board_type(board_id)) { + dev_err(mors->dev, + "OTP not set, unable to determine BCF to use"); + ret = -EINVAL; + goto out; + } + + dev_dbg(mors->dev, "Using board type 0x%04x from OTP", board_id); + + ret = mm81x_fw_request(mors, &fw); + if (ret) + goto out; + + bcf_path = kasprintf(GFP_KERNEL, + MM81X_FW_DIR + "/v%u/bcf_boardtype_%04x" MM81X_FW_EXT, + mors->fw_major, board_id); + if (!bcf_path) { + ret = -ENOMEM; + goto out; + } + + ret = request_firmware(&bcf, bcf_path, mors->dev); + if (ret) { + if (ret == -ENOENT) + dev_err(mors->dev, "BCF %s not found\n", bcf_path); + goto out; + } + + dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n", + bcf_path, bcf->size, binary_crc(bcf)); + + ret = mm81x_fw_flash(mors, fw, bcf, reset); + if (ret) { + dev_err(mors->dev, "failed to flash firmware: %d", ret); + goto out; + } + + ret = mm81x_fw_get_flags(mors); + +out: + release_firmware(fw); + release_firmware(bcf); + kfree(bcf_path); + + if (ret) + dev_err(mors->dev, "failed to init firmware: %d", ret); + else + dev_dbg(mors->dev, "firmware initialised"); + + return ret; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.h b/drivers/net/wireless/morsemicro/mm81x/fw.h new file mode 100644 index 000000000000..b5f4c0e5f998 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/fw.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_FW_H_ +#define _MM81X_FW_H_ + +#include +#include +#include +#include "command_defs.h" +#include "yaps_hw.h" + +#define BCF_DATABASE_SIZE (1024) +#define MM81X_FW_DIR "morsemicro/mm81x" +#define MM81X_FW_EXT ".bin" + +#define MM81X_FW_VER_MAX HOST_CMD_SEMVER_MAJOR +#define MM81X_FW_VER_MIN 56 + +/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */ +#define FW_CAPABILITIES_FLAGS_WIDTH (4) + +struct mm81x_elf32_ehdr { + unsigned char e_ident[EI_NIDENT]; + __le16 e_type; + __le16 e_machine; + __le32 e_version; + __le32 e_entry; + __le32 e_phoff; + __le32 e_shoff; + __le32 e_flags; + __le16 e_ehsize; + __le16 e_phentsize; + __le16 e_phnum; + __le16 e_shentsize; + __le16 e_shnum; + __le16 e_shstrndx; +} __packed; + +struct mm81x_elf32_shdr { + __le32 sh_name; + __le32 sh_type; + __le32 sh_flags; + __le32 sh_addr; + __le32 sh_offset; + __le32 sh_size; + __le32 sh_link; + __le32 sh_info; + __le32 sh_addralign; + __le32 sh_entsize; +} __packed; + +struct mm81x_elf32_phdr { + __le32 p_type; + __le32 p_offset; + __le32 p_vaddr; + __le32 p_paddr; + __le32 p_filesz; + __le32 p_memsz; + __le32 p_flags; + __le32 p_align; +} __packed; + +enum mm81x_fw_info_tlv_type { + MM81X_FW_INFO_TLV_BCF_ADDR = 1, +}; + +struct mm81x_fw_info_tlv { + __le16 type; + __le16 length; + u8 val[]; +} __packed; + +enum mm81x_fw_ext_host_tbl_tag { + /* The S1G capability tag */ + MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0, + MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1, + MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2, + MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3, + MM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4, + MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5, +}; + +struct ext_host_tbl_tlv_hdr { + /* The tag used to identify which capability this represents */ + __le16 tag; + /* The length of the capability structure including this header */ + __le16 length; +} __packed; + +struct ext_host_tbl_s1g_caps { + struct ext_host_tbl_tlv_hdr header; + __le32 flags[FW_CAPABILITIES_FLAGS_WIDTH]; + /* + * The minimum A-MPDU start spacing required by firmware. + * Value | Description + * ------|------------ + * 0 | No restriction + * 1 | 1/4 us + * 2 | 1/2 us + * 3 | 1 us + * 4 | 2 us + * 5 | 4 us + * 6 | 8 us + * 7 | 16 us + */ + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + /* + * The maximum A-MPDU length. This is the exponent value such that + * (2^(13 + exponent) - 1) is the length + */ + u8 maximum_ampdu_length; + /* + * Offset to apply to the specification's MMSS table to signal further + * minimum MPDU start spacing. + */ + u8 mm81x_mmss_offset; +} __packed; + +struct ext_host_tbl_insert_skb_checksum { + struct ext_host_tbl_tlv_hdr header; + u8 insert_and_validate_checksum; +}; + +struct ext_host_tbl_yaps_table { + struct ext_host_tbl_tlv_hdr header; + struct mm81x_yaps_hw_table yaps_table; +} __packed; + +struct ext_host_tbl { + __le32 ext_host_tbl_length; + u8 dev_mac_addr[6]; + u8 ext_host_table_data_tlvs[]; +} __packed; + +int mm81x_fw_init(struct mm81x *mors, bool reset); +int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors); + +#endif /* !_MM81X_FW_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/hif.h b/drivers/net/wireless/morsemicro/mm81x/hif.h new file mode 100644 index 000000000000..e3d23423049a --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hif.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_HIF_H_ +#define _MM81X_HIF_H_ + +#include "core.h" + +struct mm81x_skbq; + +#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15) +#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29) +#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \ + BIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM) +#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \ + BIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM) + +/* Hardware IF interrupt mask. We may use any interrupts in this range */ +#define MM81X_HIF_IRQ_MASK_ALL \ + (GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \ + MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE) + +enum mm81x_hif_flags { + MM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0), + MM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1), + MM81X_HIF_FLAGS_COMMAND = BIT(2), + MM81X_HIF_FLAGS_BEACON = BIT(3), + MM81X_HIF_FLAGS_DATA = BIT(4) +}; + +struct mm81x_hif_ops { + int (*init)(struct mm81x *mors); + void (*flush_tx_data)(struct mm81x *mors); + void (*flush_cmds)(struct mm81x *mors); + void (*finish)(struct mm81x *mors); + void (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs, + int *num_qs); + struct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci); + int (*handle_irq)(struct mm81x *mors, u32 status); + int (*get_tx_buffered_count)(struct mm81x *mors); + int (*get_tx_status_pending_count)(struct mm81x *mors); +}; + +static inline void mm81x_hif_clear_events(struct mm81x *mors) +{ + mors->hif.event_flags = 0; +} + +static inline int mm81x_hif_init(struct mm81x *mors) +{ + return mors->hif.ops->init(mors); +} + +static inline void mm81x_hif_flush_tx_data(struct mm81x *mors) +{ + mors->hif.ops->flush_tx_data(mors); +} + +static inline void mm81x_hif_flush_cmds(struct mm81x *mors) +{ + mors->hif.ops->flush_cmds(mors); +} + +static inline void mm81x_hif_finish(struct mm81x *mors) +{ + mors->hif.ops->finish(mors); +} + +static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors, + struct mm81x_skbq **qs, int *num_qs) +{ + mors->hif.ops->skbq_get_tx_qs(mors, qs, num_qs); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_cmd_queue(mors); +} + +static inline struct mm81x_skbq * +mm81x_hif_get_tx_beacon_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_beacon_queue(mors); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_mgmt_queue(mors); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors, + int aci) +{ + return mors->hif.ops->get_tx_data_queue(mors, aci); +} + +static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status) +{ + return mors->hif.ops->handle_irq(mors, status); +} + +static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_buffered_count(mors); +} + +static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_status_pending_count(mors); +} + +#endif /* _MM81X_HIF_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.c b/drivers/net/wireless/morsemicro/mm81x/hw.c new file mode 100644 index 000000000000..9293f4094db1 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hw.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "mac.h" +#include "bus.h" +#include "core.h" +#include "fw.h" +#include "yaps.h" + +#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF +#define MM8108_REG_RESET_VALUE 0xDEAD + +#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C + +#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9 +#define MM8108_REG_TRGR_BASE 0x00003c00 +#define MM8108_REG_INT_BASE 0x00003c50 +#define MM8108_REG_MSI_ADDRESS 0x00004100 +#define MM8108_REG_MSI_VALUE 0x1 +#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40 +#define MM8108_REG_APPS_BOOT_ADDR 0x00002084 +#define MM8108_REG_RESET 0x000020AC +#define MM8108_REG_AON_COUNT 2 + +#define MM8108_REG_AON_ADDR 0x00002114 +#define MM8108_REG_AON_LATCH_ADDR 0x00405020 +#define MM8108_REG_AON_LATCH_MASK 0x1 +#define MM8108_REG_AON_RESET_USB_VALUE 0x8 +#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000 + +#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020 +#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040 +#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2 + +#define MM8108_RESET_DELAY_TIME_MS 400 + +#define MM8108_REG_OTPCTRL_PLDO 0x00004014 +#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010 +#define MM8108_REG_OTPCTRL_PDSTB 0x00004018 +#define MM8108_REG_OTPCTRL_PTM 0x0000401c +#define MM8108_REG_OTPCTRL_PCE 0x00004020 +#define MM8108_REG_OTPCTRL_PA 0x00004034 +#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048 +#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c +#define MM8108_REG_OTPCTRL_PDOUT 0x00004040 + +#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27 +#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26 +#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16) +#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26 +#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0) + +#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1) + +static void mm81x_hw_otp_power_up(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1); + udelay(2); + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1); + usleep_range(10, 20); + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1); + udelay(3); +} + +static void mm81x_hw_otp_power_down(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0); +} + +static void mm81x_hw_otp_read_enable(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1); + usleep_range(10, 20); +} + +static void mm81x_hw_otp_read_disable(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0); + udelay(1); +} + +static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf, + u8 ignore_ecc) +{ + u32 auto_rd_start_tmp; + u32 auto_rd_start = 1; + int i; + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc); + + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + &auto_rd_start_tmp); + auto_rd_start_tmp &= 0xfffffffe; + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + auto_rd_start | auto_rd_start_tmp); + + /* Attempt reading up to 5 times. */ + for (i = 0; i < 5 && auto_rd_start; i++) { + usleep_range(15, 20); + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + &auto_rd_start_tmp); + auto_rd_start = auto_rd_start_tmp & 0x1; + } + + if (i == 5) + return -EIO; + + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf); + + return 0; +} + +int mm81x_hw_otp_get_board_type(struct mm81x *mors) +{ + int board_type = 0; + u32 otp_word = 0; + int ret; + + mm81x_claim_bus(mors); + mm81x_hw_otp_power_up(mors); + mm81x_hw_otp_read_enable(mors); + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, &otp_word, + 1); + + mm81x_hw_otp_read_disable(mors); + mm81x_hw_otp_power_down(mors); + mm81x_release_bus(mors); + + if (ret) + return -EINVAL; + + board_type = otp_word & MM81X_OTP_BOARD_TYPE_MASK; + + return board_type; +} + +bool mm81x_hw_otp_valid_board_type(u32 board_type) +{ + return board_type > 0 && board_type < MM810X_BOARD_TYPE_MAX_VALUE; +} + +int mm81x_hw_otp_get_mac_addr(struct mm81x *mors) +{ + u32 mac1 = 0; + u32 mac2 = 0; + int ret = 0; + + mm81x_claim_bus(mors); + mm81x_hw_otp_power_up(mors); + mm81x_hw_otp_read_enable(mors); + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, &mac1, 1); + if (ret) + goto exit; + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, &mac2, 1); + if (ret) + goto exit; + + put_unaligned_le16((mac1 & MM81X_OTP_MAC_ADDR_1_MASK) >> 16, + &mors->macaddr[0]); + put_unaligned_le32(mac2, &mors->macaddr[2]); + +exit: + mm81x_hw_otp_read_disable(mors); + mm81x_hw_otp_power_down(mors); + mm81x_release_bus(mors); + + return ret; +} + +void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable) +{ + u32 irq_en, irq_en_addr = irq < 32 ? MM81X_REG_INT1_EN(mors) : + MM81X_REG_INT2_EN(mors); + u32 irq_clr_addr = irq < 32 ? MM81X_REG_INT1_CLR(mors) : + MM81X_REG_INT2_CLR(mors); + u32 mask = irq < 32 ? (1 << irq) : (1 << (irq - 32)); + + mm81x_claim_bus(mors); + mm81x_reg32_read(mors, irq_en_addr, &irq_en); + if (enable) + irq_en |= (mask); + else + irq_en &= ~(mask); + mm81x_reg32_write(mors, irq_clr_addr, mask); + mm81x_reg32_write(mors, irq_en_addr, irq_en); + mm81x_release_bus(mors); +} + +int mm81x_hw_irq_handle(struct mm81x *mors) +{ + u32 status1 = 0; + + mm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), &status1); + + if (status1 & MM81X_HIF_IRQ_MASK_ALL) + mm81x_hif_handle_irq(mors, status1); + + if (status1 & MM81X_INT_BEACON_VIF_MASK_ALL) + mm81x_mac_beacon_irq_handle(mors, status1); + + mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1); + + return status1 ? 1 : 0; +} +EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle); + +void mm81x_hw_irq_clear(struct mm81x *mors) +{ + mm81x_claim_bus(mors); + mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF); + mm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF); + mm81x_release_bus(mors); +} + +void mm81x_hw_toggle_aon_latch(struct mm81x *mors) +{ + u32 address = MM81X_REG_AON_LATCH_ADDR(mors); + u32 mask = MM81X_REG_AON_LATCH_MASK(mors); + u32 latch; + + mm81x_reg32_read(mors, address, &latch); + mm81x_reg32_write(mors, address, latch & ~(mask)); + mdelay(5); + mm81x_reg32_write(mors, address, latch | mask); + mdelay(5); + mm81x_reg32_write(mors, address, latch & ~(mask)); + mdelay(5); +} + +void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable) +{ + mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable); +} + +void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode) +{ + u32 reg32_value; + + mm81x_claim_bus(mors); + if (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, ®32_value)) + goto end; + + reg32_value &= ~(u32)(SDIO_WORD_BURST_MASK + << MM8108_REG_SDIO_DEVICE_BURST_OFFSET); + reg32_value |= (u32)(burst_mode << MM8108_REG_SDIO_DEVICE_BURST_OFFSET); + + dev_dbg(mors->dev, + "Setting Burst mode to %d Writing 0x%08X to the register", + burst_mode, reg32_value); + + if (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value)) + goto end; + +end: + mm81x_release_bus(mors); +} +EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode); + +static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors) +{ + u32 rc_clock_reg_value; + int ret = 0; + + dev_dbg(mors->dev, "Enabling internal slow clock"); + + ret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR, + &rc_clock_reg_value); + if (ret) + goto exit; + + rc_clock_reg_value &= ~MM8108_REG_RC_CLK_POWER_OFF_MASK; + ret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR, + rc_clock_reg_value); + if (ret) + goto exit; + + mm81x_hw_toggle_aon_latch(mors); + + /* Wait for the clock to turn on and settle */ + mdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS); +exit: + return ret; +} + +int mm81x_hw_digital_reset(struct mm81x *mors) +{ + int ret = 0; + + mm81x_claim_bus(mors); + + /* This should be the first step in digital reset, do not reorder */ + ret = mm81x_hw_enable_internal_slow_clock(mors); + if (ret) + goto exit; + + if (mors->bus_type == MM81X_BUS_TYPE_USB) { + ret = mm81x_bus_digital_reset(mors); + goto usb_done; + } + + if (MM81X_REG_RESET(mors) != 0) + ret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors), + MM81X_REG_RESET_VALUE(mors)); + +usb_done: + msleep(MM8108_RESET_DELAY_TIME_MS); +exit: + mm81x_release_bus(mors); + + if (!ret) + mors->chip_was_reset = true; + + return ret; +} + +void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors) +{ + /* We need disable bursting for firmware download/init procedure */ + mm81x_bus_config_burst_mode(mors, false); +} + +void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors) +{ + /* We are safe here to re-enable bursting again, if supported */ + mm81x_bus_config_burst_mode(mors, true); +} + +const struct mm81x_regs mm8108_regs = { + .chip_id_address = MM8108_REG_CHIP_ID, + .irq_base_address = MM8108_REG_INT_BASE, + .trgr_base_address = MM8108_REG_TRGR_BASE, + .cpu_reset_address = MM8108_REG_RESET, + .cpu_reset_value = MM8108_REG_RESET_VALUE, + .manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS, + .msi_address = MM8108_REG_MSI_ADDRESS, + .msi_value = MM8108_REG_MSI_VALUE, + .magic_num_value = MM8108_REG_HOST_MAGIC_VALUE, + .early_clk_ctrl_value = 0, + .pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START, + .aon_latch = MM8108_REG_AON_LATCH_ADDR, + .aon_latch_mask = MM8108_REG_AON_LATCH_MASK, + .aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE, + .aon = MM8108_REG_AON_ADDR, + .aon_count = MM8108_REG_AON_COUNT, + .boot_address = MM8108_REG_APPS_BOOT_ADDR, +}; + +MODULE_FIRMWARE(MM81X_FW_DIR "/v" __stringify(MM81X_FW_VER_MAX) "/" + MM8108_FW_BASE MM81X_FW_EXT); diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.h b/drivers/net/wireless/morsemicro/mm81x/hw.h new file mode 100644 index 000000000000..178db64861d0 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hw.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_HW_H_ +#define _MM81X_HW_H_ + +#include +#include "core.h" +#include "command_defs.h" + +/* This should be at a fixed location for a family of chipset */ +#define MM8108_REG_CHIP_ID 0x00002d20 + +#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000) + +#define MM81X_CONFIG_ACCESS_1BYTE 0 +#define MM81X_CONFIG_ACCESS_2BYTE 1 +#define MM81X_CONFIG_ACCESS_4BYTE 2 + +#define MM81X_REG_TRGR_BASE(mors) ((mors)->regs->trgr_base_address) +#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00) +#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04) +#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08) +#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C) +#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10) +#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14) +#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18) +#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C) + +#define MM81X_REG_INT_BASE(mors) ((mors)->regs->irq_base_address) +#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00) +#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04) +#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08) +#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C) +#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10) +#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14) +#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18) +#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C) + +#define MM81X_REG_CHIP_ID(mors) ((mors)->regs->chip_id_address) + +#define MM81X_REG_MSI(mors) ((mors)->regs->msi_address) +#define MM81X_REG_MSI_HOST_INT(mors) ((mors)->regs->msi_value) + +#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)->regs->magic_num_value) + +#define MM81X_REG_RESET(mors) ((mors)->regs->cpu_reset_address) +#define MM81X_REG_RESET_VALUE(mors) ((mors)->regs->cpu_reset_value) + +#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)->regs->manifest_ptr_address) + +#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \ + ((mors)->regs->early_clk_ctrl_value) + +#define MM81X_REG_CLK_CTRL(mors) ((mors)->regs->clk_ctrl_address) +#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)->regs->clk_ctrl_value) + +#define MM81X_REG_BOOT_ADDR(mors) ((mors)->regs->boot_address) +#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)->regs->boot_value) + +#define MM81X_REG_AON_ADDR(mors) ((mors)->regs->aon) +#define MM81X_REG_AON_COUNT(mors) ((mors)->regs->aon_count) +#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)->regs->aon_latch) +#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)->regs->aon_latch_mask) +#define MM81X_REG_AON_USB_RESET(mors) ((mors)->regs->aon_reset_usb_value) + +/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */ +#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17)) +#define MM81X_INT_BEACON_BASE_NUM (17) + +/* PV0 NDP probe interrupts (VIF 0 and 1). */ +#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25)) +#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25) + +/* Bit 27 Chip to Host stop notify */ +#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27) +#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM) + +/* Chip IDs */ +#define CHIP_ID_MM8108 0x809 + +/* + * Minimum time we must wait between attempting to reload the HW after a + * stop notification + */ +#define HW_RELOAD_AFTER_STOP_WINDOW 5 + +enum host_table_firmware_flags { + MM81X_FW_FLAGS_SUPPORT_S1G = BIT(0), + MM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1), + MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2), + MM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3), + MM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4), +}; + +struct host_table { + __le32 magic_number; + __le32 fw_version_number; + __le32 host_flags; + __le32 fw_flags; + __le32 memcmd_cmd_addr; + __le32 memcmd_resp_addr; + __le32 ext_host_tbl_addr; +} __packed; + +struct mm81x_regs { + u32 chip_id_address; + u32 irq_base_address; + u32 trgr_base_address; + u32 cpu_reset_address; + u32 cpu_reset_value; + u32 msi_address; + u32 msi_value; + u32 manifest_ptr_address; + u32 magic_num_value; + u32 clk_ctrl_address; + u32 clk_ctrl_value; + u32 early_clk_ctrl_value; + u32 boot_address; + u32 boot_value; + u32 pager_base_address; + u32 aon_latch; + u32 aon_latch_mask; + u32 aon_reset_usb_value; + u32 aon; + u8 aon_count; +}; + +int mm81x_hw_otp_get_board_type(struct mm81x *mors); +bool mm81x_hw_otp_valid_board_type(u32 board_type); +int mm81x_hw_otp_get_mac_addr(struct mm81x *mors); + +void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable); +int mm81x_hw_irq_handle(struct mm81x *mors); +void mm81x_hw_irq_clear(struct mm81x *mors); +void mm81x_hw_toggle_aon_latch(struct mm81x *mors); +void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode); +int mm81x_hw_digital_reset(struct mm81x *mors); +void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors); +void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors); + +enum sdio_burst_mode { + SDIO_WORD_BURST_DISABLE = + 0, /* Intentionally duplicate to make it clear it's disabled */ + SDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */ + SDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */ + SDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */ + SDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */ + SDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */ + SDIO_WORD_BURST_MASK = 7, +}; + +extern const struct mm81x_regs mm8108_regs; + +void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable); + +#endif /* !_MM81X_HW_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c new file mode 100644 index 000000000000..08ca116a68b4 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -0,0 +1,2443 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "core.h" +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "mac.h" +#include "bus.h" +#include "ps.h" +#include "rc.h" + +/* + * Arbitrary size limit for the filter command address list, to ensure that + * the command does not exceed page/MTU size. This will be far greater than + * the number of filters supported by the firmware. + */ +#define MCAST_FILTER_COUNT_MAX (1024 / sizeof(filter->addr_list[0])) + +/* Calculate average RSSI for Rx status */ +#define CALC_AVG_RSSI(_avg, _sample) ((((_avg) * 9 + (_sample)) / 10)) + +/* + * When automatically trying MCS0 before MCS10, this is how many + * MCS0 attempts to make + */ +#define MCS0_BEFORE_MCS10_COUNT (1) + +/* Maximum TX power (default) */ +#define MAX_TX_POWER_MBM (2200) + +/* + * Since S1G runs at 1/10th the clockrate of VHT, the worst-case + * transmission time is significantly longer then that of non-S1G + * PHYs. + */ +#define MM81X_FLUSH_TIMEOUT (16 * HZ) + +/* Default queue count */ +#define MM81X_HW_QUEUE_COUNT (4) + +/* Max rates per skb */ +#define MM81X_HW_MAX_RATES (4) + +/* Max reported rates */ +#define MM81X_HW_MAX_REPORT_RATES (4) + +/* Max rate attempts */ +#define MM81X_HW_MAX_RATE_TRIES (1) + +/* Max sk pacing shift */ +#define MM81X_HW_TX_SK_PACING_SHIFT (3) + +/* NSS/MCS map values */ +#define MM81X_NSS_MCS_BYTE_0 0xfe /* 1SS */ +#define MM81X_NSS_MCS_BYTE_1 0x00 +#define MM81X_NSS_MCS_BYTE_2 0xfc /* 1SS */ +#define MM81X_NSS_MCS_BYTE_3 0x01 +#define MM81X_NSS_MCS_BYTE_4 0x00 + +/* HW restart delay time before terminating hardware IF work items */ +#define MM81X_HW_RESTART_DELAY_MS 20 + +/* clang-format off */ + +/* mm81x chips do not support 16MHz */ +#define CHANS1G(channel, frequency, offset, chan_flags) \ +{ \ + .band = NL80211_BAND_S1GHZ, \ + .center_freq = (frequency), \ + .freq_offset = (offset), \ + .hw_value = (channel), \ + .flags = ((chan_flags) | IEEE80211_CHAN_NO_16MHZ), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel mors_s1ghz_channels[] = { + CHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), + CHANS1G(3, 903, 500, 0), + CHANS1G(5, 904, 500, 0), + CHANS1G(7, 905, 500, 0), + CHANS1G(9, 906, 500, 0), + CHANS1G(11, 907, 500, 0), + CHANS1G(13, 908, 500, 0), + CHANS1G(15, 909, 500, 0), + CHANS1G(17, 910, 500, 0), + CHANS1G(19, 911, 500, 0), + CHANS1G(21, 912, 500, 0), + CHANS1G(23, 913, 500, 0), + CHANS1G(25, 914, 500, 0), + CHANS1G(27, 915, 500, 0), + CHANS1G(29, 916, 500, 0), + CHANS1G(31, 917, 500, 0), + CHANS1G(33, 918, 500, 0), + CHANS1G(35, 919, 500, 0), + CHANS1G(37, 920, 500, 0), + CHANS1G(39, 921, 500, 0), + CHANS1G(41, 922, 500, 0), + CHANS1G(43, 923, 500, 0), + CHANS1G(45, 924, 500, 0), + CHANS1G(47, 925, 500, 0), + CHANS1G(49, 926, 500, 0), + CHANS1G(51, 927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), +}; + +/* clang-format on */ + +static struct ieee80211_supported_band mors_band_s1ghz = { + .band = NL80211_BAND_S1GHZ, + .s1g_cap.s1g = true, + .channels = mors_s1ghz_channels, + .n_channels = ARRAY_SIZE(mors_s1ghz_channels), + .bitrates = NULL, + .n_bitrates = 0, + .s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */ +}; + +static struct ieee80211_iface_limit mors_if_limits[] = { + { + .max = MM81X_MAX_IF, + .types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), + }, +}; + +static struct ieee80211_iface_combination mors_if_combs[] = { + { + .limits = mors_if_limits, + .n_limits = ARRAY_SIZE(mors_if_limits), + .max_interfaces = MM81X_MAX_IF, + .num_different_channels = 1, + }, +}; + +/* Convert from a time in time units (1024us) to us */ +#define MM81X_TU_TO_US(x) ((x) * 1024UL) + +/* Convert from a time in time units (1024us) to ms */ +#define MM81X_TU_TO_MS(x) (MM81X_TU_TO_US(x) / 1000UL) + +/* Default time to dwell on a scan channel */ +#define MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS (30) + +/* Default time to dwell on a scan channel for passive scan */ +#define MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS (110) + +/* Default time to dwell on home channel, in between scan channels */ +#define MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS (200) + +/* Typical time it takes to send the probe */ +#define MM81X_HWSCAN_PROBE_DELAY_MS (30) + +/* A margin to account for event/command processing */ +#define MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS (2000) + +/* Scan channel frequency mask */ +#define HW_SCAN_CH_LIST_FREQ_KHZ GENMASK(19, 0) + +/* + * Scan channel bandwidth mask. + * Encoded as: 0 = 1MHz, 1 = 2MHz, 2 = 4MHz, 3 = 8MHz + */ +#define HW_SCAN_CH_LIST_OP_BW GENMASK(21, 20) + +/* + * Scan channel primary channel width. + * Encoded as: 0 = 1MHz, 1 = 2MHz + */ +#define HW_SCAN_CH_LIST_PRIM_CH_WIDTH BIT(22) + +/* Index into power_list for tx power of channel */ +#define HW_SCAN_CH_LIST_PWR_LIST_IDX GENMASK(31, 26) + +struct hw_scan_tlv_hdr { + __le16 tag; + __le16 len; +} __packed; + +struct hw_scan_tlv_channel_list { + struct hw_scan_tlv_hdr hdr; + __le32 channels[]; +} __packed; + +struct hw_scan_tlv_power_list { + struct hw_scan_tlv_hdr hdr; + s32 tx_power_qdbm[]; +} __packed; + +struct hw_scan_tlv_probe_req { + struct hw_scan_tlv_hdr hdr; + /* Probe request frame template (including SSIDs) */ + u8 buf[]; +} __packed; + +struct hw_scan_tlv_dwell_on_home { + struct hw_scan_tlv_hdr hdr; + /* Time to dwell on home between scan channels */ + __le32 home_dwell_time_ms; +} __packed; + +#define DOT11AH_BA_MAX_MPDU_PER_AMPDU (32) + +/* wiphy scan params */ +#define MM81X_MAX_SCAN_IE_LEN 512 +#define MM81X_MAX_SCAN_SSIDS 1 +#define MM81X_MAX_REMAIN_ON_CHAN_DURATION 10000 + +static bool mm81x_reg_h_cc_equal(const char *cc1, const char *cc2) +{ + return (cc1[0] == cc2[0]) && (cc1[1] == cc2[1]); +} + +static bool mm81x_tx_h_pkt_over_rts_threshold(struct mm81x *mors, + struct ieee80211_tx_info *info, + struct sk_buff *skb) +{ + u8 ccmp_len; + + if (!info->control.hw_key) + return ((skb->len + FCS_LEN) > mors->rts_threshold); + + if (info->control.hw_key->keylen == 32) + ccmp_len = + IEEE80211_CCMP_256_HDR_LEN + IEEE80211_CCMP_256_MIC_LEN; + else if (info->control.hw_key->keylen == 16) + ccmp_len = IEEE80211_CCMP_HDR_LEN + IEEE80211_CCMP_MIC_LEN; + else + ccmp_len = 0; + + return ((skb->len + FCS_LEN + ccmp_len) > mors->rts_threshold); +} + +static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mors, + struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct mm81x_sta *mors_sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!sta) + return false; + + mors_sta = (struct mm81x_sta *)sta->drv_priv; + + if (!mors_sta->tx_ps_filter_en) + return false; + + dev_dbg(mors->dev, "Frame for sta[%pM] PS filtered", mors_sta->addr); + + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + ieee80211_tx_status_skb(mors->hw, skb); + return true; +} + +static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) +{ + int ret = 0; + u32 i; + struct mm81x *mors = hw->priv; + struct host_cmd_resp_get_disabled_channels *resp; + u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) * + ARRAY_SIZE(mors_s1ghz_channels) + + sizeof(*resp); + + resp = kzalloc(resp_len, GFP_KERNEL); + if (!resp) { + ret = -ENOMEM; + goto out; + } + + ret = mm81x_cmd_get_disabled_channels(mors, resp, resp_len); + if (ret) + goto out; + + for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) { + struct ieee80211_channel *ch = &mors_s1ghz_channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + ch->flags &= ~IEEE80211_CHAN_S1G_NO_PRIMARY; + } + + for (i = 0; i < le32_to_cpu(resp->n_channels); i++) { + struct ieee80211_channel *ch; + struct host_cmd_disabled_channel_entry *entry = + &resp->channels[i]; + + if (entry->bw_mhz != 1) + continue; + + ch = ieee80211_get_channel_khz( + hw->wiphy, + KHZ100_TO_KHZ(le16_to_cpu(entry->freq_100khz))); + if (!ch) + continue; + + ch->flags |= IEEE80211_CHAN_S1G_NO_PRIMARY; + dev_dbg(mors->dev, "set NO_PRIMARY on %u KHz", + ieee80211_channel_to_khz(ch)); + } + +out: + if (ret) + dev_err(mors->dev, "failed to set disabled primary channels"); + + kfree(resp); +} + +static int mm81x_mac_ops_start(struct ieee80211_hw *hw) +{ + struct mm81x *mors = hw->priv; + + mors->started = true; + return 0; +} + +static int mm81x_tx_h_get_max_bw(struct mm81x *mors) +{ + return MM81X_FW_SUPP(&mors->fw_caps, 8MHZ) ? 8 : + MM81X_FW_SUPP(&mors->fw_caps, 4MHZ) ? 4 : + MM81X_FW_SUPP(&mors->fw_caps, 2MHZ) ? 2 : + 1; +} + +static void mm81x_mac_caps_init(struct mm81x *mors) +{ + struct mm81x_fw_caps *fw_caps = &mors->fw_caps; + struct ieee80211_sta_s1g_cap *s1g = &mors_band_s1ghz.s1g_cap; + +#define __FW_CAP_N(_n, _cap, _bit) \ + do { \ + if (MM81X_FW_SUPP(fw_caps, _cap)) \ + s1g->cap[_n] |= (_bit); \ + } while (0) + +#define FW_CAP0(_cap, _bit) __FW_CAP_N(0, _cap, _bit) +#define FW_CAP3(_cap, _bit) __FW_CAP_N(3, _cap, _bit) +#define FW_CAP5(_cap, _bit) __FW_CAP_N(5, _cap, _bit) +#define FW_CAP6(_cap, _bit) __FW_CAP_N(6, _cap, _bit) +#define FW_CAP7(_cap, _bit) __FW_CAP_N(7, _cap, _bit) +#define FW_CAP8(_cap, _bit) __FW_CAP_N(8, _cap, _bit) +#define FW_CAP9(_cap, _bit) __FW_CAP_N(9, _cap, _bit) + + FW_CAP0(S1G_LONG, S1G_CAP0_S1G_LONG); + + s1g->cap[0] |= S1G_CAP0_SGI_1MHZ; + if (MM81X_FW_SUPP(fw_caps, SGI)) { + FW_CAP0(2MHZ, S1G_CAP0_SGI_2MHZ); + FW_CAP0(4MHZ, S1G_CAP0_SGI_4MHZ); + FW_CAP0(8MHZ, S1G_CAP0_SGI_8MHZ); + } + + if (MM81X_FW_SUPP(fw_caps, 8MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_8; + else if (MM81X_FW_SUPP(fw_caps, 4MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_4; + else if (MM81X_FW_SUPP(fw_caps, 2MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_2; + + FW_CAP3(RD_RESPONDER, S1G_CAP3_RD_RESPONDER); + FW_CAP3(LONG_MPDU, S1G_CAP3_MAX_MPDU_LEN); + + FW_CAP5(AMSDU, S1G_CAP5_AMSDU); + FW_CAP5(AMPDU, S1G_CAP5_AMPDU); + FW_CAP5(ASYMMETRIC_BA_SUPPORT, S1G_CAP5_ASYMMETRIC_BA); + FW_CAP5(FLOW_CONTROL, S1G_CAP5_FLOW_CONTROL); + + FW_CAP6(OBSS_MITIGATION, S1G_CAP6_OBSS_MITIGATION); + FW_CAP6(FRAGMENT_BA, S1G_CAP6_FRAGMENT_BA); + FW_CAP6(NDP_PSPOLL, S1G_CAP6_NDP_PS_POLL); + FW_CAP6(TXOP_SHARING_IMPLICIT_ACK, S1G_CAP6_TXOP_SHARING_IMP_ACK); + FW_CAP6(HTC_VHT_MFB, S1G_CAP6_VHT_LINK_ADAPT); + + FW_CAP7(TACK_AS_PSPOLL, S1G_CAP7_TACK_AS_PS_POLL); + FW_CAP7(DUPLICATE_1MHZ, S1G_CAP7_DUP_1MHZ); + FW_CAP7(MCS_NEGOTIATION, S1G_CAP7_MCS_NEGOTIATION); + FW_CAP7(1MHZ_CONTROL_RESPONSE_PREAMBLE, + S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE); + FW_CAP7(SECTOR_TRAINING, S1G_CAP7_SECTOR_TRAINING_OPERATION); + FW_CAP7(TMP_PS_MODE_SWITCH, S1G_CAP7_TEMP_PS_MODE_SWITCH); + + FW_CAP8(BDT, S1G_CAP8_BDT); + + FW_CAP9(LINK_ADAPTATION_WO_NDP_CMAC, + S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE); + + /* 1SS MCS 9 for Rx / Tx map */ + s1g->nss_mcs[0] = MM81X_NSS_MCS_BYTE_0; + s1g->nss_mcs[1] = MM81X_NSS_MCS_BYTE_1; + s1g->nss_mcs[2] = MM81X_NSS_MCS_BYTE_2; + s1g->nss_mcs[3] = MM81X_NSS_MCS_BYTE_3; + s1g->nss_mcs[4] = MM81X_NSS_MCS_BYTE_4; + +#undef FW_CAP0 +#undef FW_CAP3 +#undef FW_CAP5 +#undef FW_CAP6 +#undef FW_CAP7 +#undef FW_CAP8 +#undef FW_CAP9 +#undef __FW_CAP_N +} + +static void mm81x_mac_beacon_irq_enable(struct mm81x_vif *mors_vif, bool enable) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + u8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mors_vif->id; + + enable ? set_bit(beacon_irq_num, &mors->beacon_irqs_enabled) : + clear_bit(beacon_irq_num, &mors->beacon_irqs_enabled); + + mm81x_hw_irq_enable(mors, beacon_irq_num, enable); +} + +static void mm81x_beacon_h_fill_tx_info(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct mm81x_vif *mors_vif, + int tx_bw_mhz) +{ + enum dot11_bandwidth bw_idx = + mm81x_ratecode_bw_mhz_to_bw_index(tx_bw_mhz); + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); + + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + + tx_info->rates[0].count = 1; + tx_info->rates[1].count = 0; + tx_info->rates[0].mm81x_ratecode = + mm81x_ratecode_init(bw_idx, 0, 0, pream); + + if (mors->fw_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION) + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); +} + +static void mm81x_mac_beacon_work(struct work_struct *work) +{ + struct mm81x_vif *mors_vif = + from_work(mors_vif, work, u.ap.beacon_work); + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + struct mm81x_skbq *mq; + struct sk_buff *beacon; + struct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mors_vif); + struct mm81x_skb_tx_info tx_info = { 0 }; + int num_bcn_vifs = atomic_read(&mors->num_bcn_vifs); + + mq = mm81x_hif_get_tx_beacon_queue(mors); + if (!mq) { + dev_err(mors->dev, "no matching beacon Q found"); + return; + } + + if (mm81x_skbq_count(mq) >= num_bcn_vifs) { + dev_err(mors->dev, + "previous beacon not consumed, dropping req [id:%d]", + mors_vif->id); + return; + } + + beacon = ieee80211_beacon_get(mors->hw, vif, false); + if (!beacon) + return; + + mm81x_beacon_h_fill_tx_info(mors, &tx_info, mors_vif, + cfg80211_chandef_s1g_pri_width(&mors->chandef)); + mm81x_skbq_skb_tx(mq, &beacon, &tx_info, MM81X_SKB_CHAN_BEACON); +} + +void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status) +{ + int vif_id; + unsigned long masked_status = (status & mors->beacon_irqs_enabled) >> + MM81X_INT_BEACON_BASE_NUM; + + guard(rcu)(); + for_each_set_bit(vif_id, &masked_status, MM81X_MAX_IF) { + struct mm81x_vif *mors_vif; + struct ieee80211_vif *vif; + + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); + if (vif) { + mors_vif = ieee80211_vif_to_mors_vif(vif); + queue_work(system_bh_wq, &mors_vif->u.ap.beacon_work); + } + } +} + +static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + + INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); + mm81x_mac_beacon_irq_enable(mors_vif, true); + atomic_inc(&mors->num_bcn_vifs); +} + +static struct hw_scan_tlv_hdr mm81x_hw_scan_h_pack_tlv_hdr(u16 tag, u16 len) +{ + struct hw_scan_tlv_hdr hdr = { .tag = cpu_to_le16(tag), + .len = cpu_to_le16(len) }; + return hdr; +} + +static __le32 mm81x_hw_scan_h_pack_channel(struct ieee80211_channel *chan, + u8 pwr_idx) +{ + __le32 packed = 0; + u32 freq_khz = ieee80211_channel_to_khz(chan); + + packed |= le32_encode_bits(freq_khz, HW_SCAN_CH_LIST_FREQ_KHZ); + packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), + HW_SCAN_CH_LIST_OP_BW); + packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), + HW_SCAN_CH_LIST_PRIM_CH_WIDTH); + packed |= le32_encode_bits(pwr_idx, HW_SCAN_CH_LIST_PWR_LIST_IDX); + + return packed; +} + +static u8 * +mm81x_hw_scan_h_add_channel_list_tlv(u8 *buf, + struct mm81x_hw_scan_params *params) +{ + int i; + struct hw_scan_tlv_channel_list *ch_list = + (struct hw_scan_tlv_channel_list *)buf; + + ch_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST, + params->num_chans * sizeof(ch_list->channels[0])); + + for (i = 0; i < params->num_chans; i++) { + struct ieee80211_channel *chan = params->channels[i].channel; + + ch_list->channels[i] = mm81x_hw_scan_h_pack_channel( + chan, params->channels[i].power_idx); + } + + return (u8 *)&ch_list->channels[i]; +} + +static u8 * +mm81x_hw_scan_h_add_power_list_tlv(u8 *buf, struct mm81x_hw_scan_params *params) +{ + int i; + struct hw_scan_tlv_power_list *pwr_list = + (struct hw_scan_tlv_power_list *)buf; + size_t size = sizeof(pwr_list->tx_power_qdbm[0]) * params->n_powers; + + pwr_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST, size); + + for (i = 0; i < params->n_powers; i++) + pwr_list->tx_power_qdbm[i] = params->powers_qdbm[i]; + + return (u8 *)&pwr_list->tx_power_qdbm[i]; +} + +static u8 * +mm81x_hw_scan_h_add_probe_req_tlv(u8 *buf, struct mm81x_hw_scan_params *params) +{ + struct sk_buff *skb = params->probe_req; + struct hw_scan_tlv_probe_req *probe_req = + (struct hw_scan_tlv_probe_req *)buf; + + probe_req->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ, skb->len); + memcpy(probe_req->buf, skb->data, skb->len); + + return buf + sizeof(*probe_req) + skb->len; +} + +static u8 * +mm81x_hw_scan_h_insert_dwell_time_tlv(u8 *buf, + struct mm81x_hw_scan_params *params) +{ + struct hw_scan_tlv_dwell_on_home *dwell = + (struct hw_scan_tlv_dwell_on_home *)buf; + + dwell->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME, + sizeof(*dwell) - sizeof(dwell->hdr)); + dwell->home_dwell_time_ms = cpu_to_le32(params->dwell_on_home_ms); + + return buf + sizeof(*dwell); +} + +static int __mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, + u8 *ssid, u8 ssid_len, + struct ieee80211_scan_ies *ies) +{ + u8 *pos; + struct sk_buff *probe_req; + struct ieee80211_tx_info *info; + u16 ies_len = ies->len[NL80211_BAND_S1GHZ] + ies->common_ie_len; + + probe_req = ieee80211_probereq_get(params->hw, params->vif->addr, ssid, + ssid_len, ies_len); + if (!probe_req) + return -ENOMEM; + + pos = skb_put(probe_req, ies_len); + memcpy(pos, ies->common_ies, ies->common_ie_len); + pos += ies->common_ie_len; + memcpy(pos, ies->ies[NL80211_BAND_S1GHZ], ies->len[NL80211_BAND_S1GHZ]); + + info = IEEE80211_SKB_CB(probe_req); + info->control.vif = params->vif; + params->probe_req = probe_req; + + return 0; +} + +static void mm81x_hw_scan_h_init_ssid(struct mm81x *mors, + struct cfg80211_ssid *ssids, int n_ssids, + u8 **out_ssid, u8 *out_ssid_len) +{ + *out_ssid = NULL; + *out_ssid_len = 0; + + if (n_ssids > 0) { + if (n_ssids > 1) { + dev_warn( + mors->dev, + "Multiple SSIDs found when only one supported. Using the first only."); + } + *out_ssid_len = ssids[0].ssid_len; + *out_ssid = ssids[0].ssid; + } +} + +static int +mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, + struct ieee80211_scan_request *scan_req) +{ + struct mm81x *mors = params->hw->priv; + struct cfg80211_scan_request *req = &scan_req->req; + struct ieee80211_scan_ies *ies = &scan_req->ies; + u8 ssid_len = 0; + u8 *ssid = NULL; + + mm81x_hw_scan_h_init_ssid(mors, req->ssids, req->n_ssids, &ssid, + &ssid_len); + + return __mm81x_hw_scan_h_init_probe_req(params, ssid, ssid_len, ies); +} + +static bool +mm81x_hw_scan_h_is_chan_present(const struct mm81x_hw_scan_params *params, + const struct ieee80211_channel *chan) +{ + int channel; + + for (channel = 0; channel < params->num_chans; channel++) { + if (params->channels[channel].channel == chan) + return true; + } + + return false; +} + +static int mm81x_hw_scan_h_insert_chan(struct mm81x_hw_scan_params *params, + struct ieee80211_channel *chan) +{ + if (!params->channels) + return -EFAULT; + + if (!chan) + return -EFAULT; + + if (params->num_chans >= params->allocated_chans) + return -ENOMEM; + + if (mm81x_hw_scan_h_is_chan_present(params, chan)) + return 0; + + params->channels[params->num_chans].channel = chan; + params->num_chans++; + return 0; +} + +static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params, + struct ieee80211_channel **chans, + u32 n_channels) +{ + int i, j; + int num_pwrs_coarse = 0; + int last_pwr = INT_MIN; + int chans_to_allocate = 0; + + for (i = 0; i < n_channels; i++) + if (chans[i]) + chans_to_allocate++; + + params->num_chans = 0; + params->allocated_chans = 0; + params->channels = kcalloc(chans_to_allocate, sizeof(*params->channels), + GFP_KERNEL); + if (!params->channels) + return -ENOMEM; + + params->allocated_chans = chans_to_allocate; + + for (i = 0; i < n_channels; i++) + if (chans[i]) + mm81x_hw_scan_h_insert_chan(params, chans[i]); + + /* + * Calculate a rough estimate of number of different channel + * powers required + */ + for (i = 0; i < params->num_chans; i++) { + if (chans[i]->max_reg_power != last_pwr) { + last_pwr = chans[i]->max_reg_power; + num_pwrs_coarse++; + } + } + + params->powers_qdbm = kmalloc_array( + num_pwrs_coarse, sizeof(*params->powers_qdbm), GFP_KERNEL); + if (!params->powers_qdbm) + return -ENOMEM; + + params->n_powers = 0; + + for (i = 0; i < params->num_chans; i++) { + s32 power_qdbm = + MBM_TO_QDBM(DBM_TO_MBM(chans[i]->max_reg_power)); + + /* Try and find the power in the list */ + for (j = 0; j < params->n_powers; j++) + if (params->powers_qdbm[j] == power_qdbm) + break; + + /* Reached the end of the list - add the new power option */ + if (j == params->n_powers) { + params->powers_qdbm[j] = power_qdbm; + params->n_powers++; + if (params->n_powers > num_pwrs_coarse) { + WARN_ON(1); + return -EFAULT; + } + } + + /* Give the index of the power level to the channel */ + params->channels[i].power_idx = j; + } + return 0; +} + +static void mm81x_hw_scan_h_clean_params(struct mm81x_hw_scan_params *params) +{ + if (params->probe_req) + dev_kfree_skb_any(params->probe_req); + kfree(params->channels); + kfree(params->powers_qdbm); + + params->num_chans = 0; + params->allocated_chans = 0; +} + +size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params) +{ + struct hw_scan_tlv_channel_list *ch_list; + struct hw_scan_tlv_power_list *pwr_list; + struct hw_scan_tlv_probe_req *probe_req; + struct hw_scan_tlv_dwell_on_home *dwell; + struct host_cmd_req_hw_scan *req; + size_t cmd_size = sizeof(*req); + + /* No TLVs if simple abort command */ + if (params->operation != MM81X_HW_SCAN_OP_START) + return cmd_size; + + cmd_size += struct_size(ch_list, channels, params->num_chans); + cmd_size += struct_size(pwr_list, tx_power_qdbm, params->n_powers); + + if (params->probe_req) + cmd_size += struct_size(probe_req, buf, params->probe_req->len); + if (params->dwell_on_home_ms) + cmd_size += sizeof(*dwell); + + return cmd_size; +} + +u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf) +{ + buf = mm81x_hw_scan_h_add_channel_list_tlv(buf, params); + buf = mm81x_hw_scan_h_add_power_list_tlv(buf, params); + + if (params->dwell_on_home_ms) + buf = mm81x_hw_scan_h_insert_dwell_time_tlv(buf, params); + if (params->probe_req) + buf = mm81x_hw_scan_h_add_probe_req_tlv(buf, params); + + return buf; +} + +static u32 mm81x_hw_scan_h_get_dwell_on_home(struct mm81x *mors, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_STATION && vif->cfg.assoc) + return mors->hw_scan.home_dwell_ms; + return 0; +} + +static struct mm81x_hw_scan_params * +__mm81x_hw_scan_h_init_params(struct mm81x *mors) +{ + struct mm81x_hw_scan_params *params = mors->hw_scan.params; + + if (!params) { + params = kzalloc_obj(*params, GFP_KERNEL); + if (params) + mors->hw_scan.params = params; + } else { + mm81x_hw_scan_h_clean_params(params); + memset(params, 0, sizeof(*params)); + } + + return params; +} + +static int mm81x_hw_scan_h_init_params(struct mm81x *mors, + struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct mm81x_hw_scan_params *params = mors->hw_scan.params; + + params = __mm81x_hw_scan_h_init_params(mors); + if (!params) { + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + return -ENOMEM; + } + + params->hw = hw; + params->vif = vif; + params->has_directed_ssid = (req->ssids && req->ssids[0].ssid_len > 0); + params->operation = MM81X_HW_SCAN_OP_START; + params->dwell_on_home_ms = mm81x_hw_scan_h_get_dwell_on_home(mors, vif); + + if (req->duration) + params->dwell_time_ms = MM81X_TU_TO_MS(req->duration); + else if (req->n_ssids == 0) + params->dwell_time_ms = + MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS; + else + params->dwell_time_ms = MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS; + + return 0; +} + +static u32 mm81x_hw_scan_h_calc_timeout(struct mm81x_hw_scan_params *params) +{ + u32 ret = 0; + + ret = params->dwell_time_ms + params->dwell_on_home_ms; + if (params->probe_req) + ret += MM81X_HWSCAN_PROBE_DELAY_MS; + + ret *= params->num_chans; + ret += MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS; + + return ret; +} + +static int mm81x_mac_ops_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + int ret = 0; + struct mm81x *mors = hw->priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct mm81x_hw_scan_params *params; + struct ieee80211_channel **chans = hw_req->req.channels; + + dev_dbg(mors->dev, "state %d", mors->hw_scan.state); + + if (!mors->started) { + dev_warn(mors->dev, "device not ready"); + ret = -ENODEV; + goto exit; + } + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + mors->hw_scan.state = HW_SCAN_STATE_RUNNING; + reinit_completion(&mors->hw_scan.scan_done); + break; + case HW_SCAN_STATE_RUNNING: + case HW_SCAN_STATE_ABORTING: + ret = -EBUSY; + goto exit; + } + + ret = mm81x_hw_scan_h_init_params(mors, hw, vif, req); + if (ret) + goto exit; + + params = mors->hw_scan.params; + + ret = mm81x_hw_scan_h_init_chan_list(params, chans, + hw_req->req.n_channels); + if (ret) + goto exit; + + /* Only init the probe request template if this is an active scan */ + if (req->n_ssids > 0) { + ret = mm81x_hw_scan_h_init_probe_req(params, hw_req); + if (ret) { + dev_err(mors->dev, "Failed to init probe req %d", ret); + goto exit; + } + } + + ret = mm81x_cmd_hw_scan(mors, params, false); + if (ret) { + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + goto exit; + } + + ieee80211_queue_delayed_work( + mors->hw, &mors->hw_scan.timeout, + msecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params))); +exit: + return ret; +} + +static void mm81x_hw_scan_abort(struct mm81x *mors) +{ + int ret; + struct mm81x_hw_scan_params params = { 0 }; + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + case HW_SCAN_STATE_ABORTING: + /* scan not running */ + return; + case HW_SCAN_STATE_RUNNING: + mors->hw_scan.state = HW_SCAN_STATE_ABORTING; + break; + } + + params.operation = MM81X_HW_SCAN_OP_STOP; + + ret = mm81x_cmd_hw_scan(mors, ¶ms, false); + + if (ret || !mors->started || + !wait_for_completion_timeout(&mors->hw_scan.scan_done, 1 * HZ)) { + /* + * We may have lost the event on the bus, the chip could be + * wedged, or the cmd failed for another reason. Nevertheless, + * we should call the done event so mac80211 knows to unblock + * itself. + */ + struct cfg80211_scan_info info = { .aborted = true }; + + ieee80211_scan_completed(mors->hw, &info); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + } +} + +static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mm81x *mors = hw->priv; + + cancel_delayed_work_sync(&mors->hw_scan.timeout); + mm81x_hw_scan_abort(mors); +} + +static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw) +{ + struct mm81x *mors = hw->priv; + struct cfg80211_scan_info info = { 0 }; + + dev_dbg(mors->dev, "completing hw scan"); + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + /* Scan has already been stopped. Just continue */ + goto exit; + case HW_SCAN_STATE_RUNNING: + case HW_SCAN_STATE_ABORTING: + info.aborted = (mors->hw_scan.state == HW_SCAN_STATE_ABORTING); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + } + + ieee80211_scan_completed(mors->hw, &info); +exit: + complete(&mors->hw_scan.scan_done); + cancel_delayed_work_sync(&mors->hw_scan.timeout); +} + +static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work) +{ + struct mm81x *mors = + container_of(work, struct mm81x, hw_scan.timeout.work); + + dev_err(mors->dev, "hw scan timed out, aborting"); + mm81x_hw_scan_abort(mors); +} + +static void mm81x_mac_hw_scan_init(struct mm81x *mors) +{ + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + mors->hw_scan.params = NULL; + mors->hw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS; + + init_completion(&mors->hw_scan.scan_done); + INIT_DELAYED_WORK(&mors->hw_scan.timeout, + mm81x_mac_hw_scan_timeout_work); +} + +static void mm81x_mac_hw_scan_destroy(struct mm81x *mors) +{ + cancel_delayed_work_sync(&mors->hw_scan.timeout); + if (mors->hw_scan.params) + mm81x_hw_scan_h_clean_params(mors->hw_scan.params); + kfree(mors->hw_scan.params); + mors->hw_scan.params = NULL; +} + +static void mm81x_mac_hw_scan_finish(struct mm81x *mors) +{ + struct cfg80211_scan_info info = { + .aborted = true, + }; + + if (mors->hw_scan.state == HW_SCAN_STATE_IDLE) + return; + + ieee80211_scan_completed(mors->hw, &info); + complete(&mors->hw_scan.scan_done); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + cancel_delayed_work_sync(&mors->hw_scan.timeout); +} + +int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb) +{ + struct host_cmd_event *event = (struct host_cmd_event *)(skb->data); + u16 event_id = le16_to_cpu(event->hdr.message_id); + u16 event_iid = le16_to_cpu(event->hdr.host_id); + u16 vif_id = le16_to_cpu(event->hdr.vif_id); + struct ieee80211_vif *vif; + + if (!HOST_CMD_IS_EVT(event) || event_iid != 0) + return -EINVAL; + + switch (event_id) { + case HOST_CMD_ID_EVT_HW_SCAN_DONE: + dev_dbg(mors->dev, + "Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received."); + mm81x_mac_hw_scan_done_event(mors->hw); + break; + case HOST_CMD_ID_EVT_BEACON_LOSS: + dev_dbg(mors->dev, + "Event: HOST_CMD_ID_EVT_BEACON_LOSS Received"); + scoped_guard(rcu) { + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); + if (vif) + ieee80211_beacon_loss(vif); + } + break; + default: + break; + } + + return 0; +} + +static void mm81x_tx_h_apply_mcs10(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info) +{ + u8 i; + u8 j; + int mcs0_first_idx = -1; + int mcs0_last_idx = -1; + + /* Find out where our first and last MCS0 entries are. */ + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_index_get( + tx_info->rates[i].mm81x_ratecode); + + if (bw_idx == DOT11_BANDWIDTH_1MHZ) { + mcs0_last_idx = i; + if (mcs0_first_idx == -1) + mcs0_first_idx = i; + } + + /* + * If the count is 0 then we are at the end of the table. + * Break to allow us to reuse i indicating the end of the + * table. + */ + if (tx_info->rates[i].count == 0) + break; + } + + /* If there aren't any MCS0 (at 1MHz) entries we are done. */ + if (mcs0_first_idx < 0) + return; + + /* + * If we are in MCS10_MODE_AUTO add MCS10 counts to the table if they + * will fit. There should be three cases: + * + * - There is one MSC0 entry and the table is full -> do nothing + * - There is one MSC0 entry and the table has space -> adjust MSC0 + * down and add MCS 10 + * - There are multiple MCS0 entries -> replace entries after the first + * with MCS 10 + */ + /* Case 3 - replace additional entries. */ + if (mcs0_last_idx > mcs0_first_idx) { + for (j = mcs0_first_idx + 1; j < i; j++) { + enum dot11_bandwidth bw_idx = + mm81x_ratecode_bw_index_get( + tx_info->rates[j].mm81x_ratecode); + u8 mcs_index = mm81x_ratecode_mcs_index_get( + tx_info->rates[j].mm81x_ratecode); + if (mcs_index == 0 && bw_idx == DOT11_BANDWIDTH_1MHZ) { + mm81x_ratecode_mcs_index_set( + &tx_info->rates[j].mm81x_ratecode, 10); + } + } + /* Case 2 - add additional MCS10 entry. */ + } else if (mcs0_last_idx == mcs0_first_idx && + i < (IEEE80211_TX_MAX_RATES)) { + int pre_mcs10_mcs0_count = + min_t(u8, tx_info->rates[mcs0_last_idx].count, + MCS0_BEFORE_MCS10_COUNT); + int mcs10_count = tx_info->rates[mcs0_last_idx].count - + pre_mcs10_mcs0_count; + + /* + * If there were less retries than our desired minimum MCS0 we + * don't add MCS10 retries. + */ + if (mcs10_count > 0) { + /* Use the same flags for MCS10 as MCS0. */ + tx_info->rates[i].mm81x_ratecode = + tx_info->rates[mcs0_last_idx].mm81x_ratecode; + mm81x_ratecode_mcs_index_set( + &tx_info->rates[i].mm81x_ratecode, 10); + tx_info->rates[mcs0_last_idx].count = + pre_mcs10_mcs0_count; + tx_info->rates[i].count = mcs10_count; + } + } +} + +void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)pubsta->drv_priv; + u8 tid = ieee80211_get_tid(hdr); + + /* we are already aggregating */ + if (mors_sta->tid_tx[tid] || mors_sta->tid_start_tx[tid]) + return; + + if (mors_sta->state < IEEE80211_STA_AUTHORIZED) + return; + + if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO) + return; + + if (unlikely(!ieee80211_is_data_qos(hdr->frame_control))) + return; + + if (unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE))) + return; + + mors_sta->tid_start_tx[tid] = true; + ieee80211_start_tx_ba_session(pubsta, tid, 0); +} + +int mm81x_tx_h_get_attempts(struct mm81x *mors, + struct mm81x_skb_tx_status *tx_sts) +{ + int attempts = 0; + int i; + int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES); + + for (i = 0; i < count; i++) { + if (tx_sts->rates[i].count > 0) + attempts += tx_sts->rates[i].count; + else + break; + } + + return attempts; +} + +static void mm81x_tx_h_fill_info(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_vif *vif, + int tx_bw_mhz, struct ieee80211_sta *sta) +{ + int i; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif); + struct mm81x_sta *mors_sta = NULL; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int op_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; + bool rts_allowed = op_bw_mhz < 8; + + if (sta) + mors_sta = (struct mm81x_sta *)sta->drv_priv; + + rts_allowed &= mm81x_tx_h_pkt_over_rts_threshold(mors, info, skb); + + mm81x_rc_sta_fill_tx_rates(mors, tx_info, skb, sta, tx_bw_mhz, + rts_allowed); + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + if (rts_allowed) + mm81x_ratecode_enable_rts( + &tx_info->rates[i].mm81x_ratecode); + + if (info->control.rates[i].flags & IEEE80211_TX_RC_SHORT_GI) + mm81x_ratecode_enable_sgi( + &tx_info->rates[i].mm81x_ratecode); + } + + /* Apply change of MCS0 to MCS10 if required. */ + mm81x_tx_h_apply_mcs10(mors, tx_info); + + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_CTL_AMPDU); + + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM); + + if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); + + if (info->flags & IEEE80211_TX_STATUS_EOSP) + tx_info->flags |= cpu_to_le32( + MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); + } else if (ieee80211_is_mgmt(hdr->frame_control) && + !ieee80211_is_bufferable_mmpdu(skb)) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); + } + + if (info->control.hw_key) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_HW_ENCRYPT); + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_KEY_IDX_SET( + info->control.hw_key->hw_key_idx)); + } + + tx_info->tid = tid; + if (mors_sta) { + tx_info->tid_params = mors_sta->tid_params[tid]; + + if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) { + if (mors_sta->tx_ps_filter_en) + dev_dbg(mors->dev, + "TX ps filter cleared sta[%pM]", + mors_sta->addr); + mors_sta->tx_ps_filter_en = false; + } + } +} + +static void mm81x_mac_ops_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mm81x *mors = hw->priv; + struct mm81x_skbq *mq = NULL; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = info->control.vif; + struct mm81x_skb_tx_info tx_info = { 0 }; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + bool is_mgmt = ieee80211_is_mgmt(hdr->frame_control); + int tx_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + struct ieee80211_sta *sta = control->sta; + int max_tx_bw = 0, sta_max_bw_mhz = 0; + + if (sta) { + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + + sta_max_bw_mhz = mors_sta->max_bw_mhz; + } + + max_tx_bw = mm81x_tx_h_get_max_bw(mors); + tx_bw_mhz = min(max_tx_bw, tx_bw_mhz); + + if (is_mgmt) + tx_bw_mhz = cfg80211_chandef_s1g_pri_width(&mors->chandef); + if (sta_max_bw_mhz) + tx_bw_mhz = min(tx_bw_mhz, sta_max_bw_mhz); + if (ieee80211_is_probe_resp(hdr->frame_control)) + tx_bw_mhz = 1; + + mm81x_tx_h_fill_info(mors, &tx_info, skb, vif, tx_bw_mhz, sta); + + if (mm81x_tx_h_ps_filtered_for_sta(mors, skb, sta)) + return; + + if (is_mgmt) + mq = mm81x_hif_get_tx_mgmt_queue(mors); + else + mq = mm81x_hif_get_tx_data_queue(mors, + dot11_tid_to_ac(tx_info.tid)); + + mm81x_skbq_skb_tx(mq, &skb, &tx_info, + (is_mgmt) ? MM81X_SKB_CHAN_MGMT : + MM81X_SKB_CHAN_DATA); +} + +static void mm81x_mac_ops_stop(struct ieee80211_hw *hw, bool suspend) +{ + struct mm81x *mors = hw->priv; + + mors->started = false; +} + +static void mm81x_mac_beacon_finish(struct mm81x_vif *mors_vif) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + + mm81x_mac_beacon_irq_enable(mors_vif, false); + cancel_work_sync(&mors_vif->u.ap.beacon_work); + /* + * Side effect of the restarting required when + * reacting to regdom changes... + */ + atomic_add_unless(&mors->num_bcn_vifs, -1, 0); +} + +static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + ret = mm81x_cmd_rm_if(mors, mors_vif->id); + if (ret) + dev_err(mors->dev, "mm81x_cmd_rm_if failed %d", ret); + + RCU_INIT_POINTER(mors->vifs[mors_vif->id], NULL); +} + +static s32 mm81x_mac_get_max_txpower(struct mm81x *mors) +{ + int ret; + s32 power_mbm; + + /* Retrieve maximum TX power the chip can transmit */ + ret = mm81x_cmd_get_max_txpower(mors, &power_mbm); + if (ret) { + dev_err(mors->dev, "using default tx max power %d mBm", + MAX_TX_POWER_MBM); + return MAX_TX_POWER_MBM; + } + + dev_dbg(mors->dev, "Max tx power detected %d mBm", power_mbm); + return power_mbm; +} + +static s32 mm81x_mac_set_txpower(struct mm81x *mors, s32 power_mbm) +{ + int ret; + s32 out_power_mbm; + + if (mors->tx_max_power_mbm == INT_MAX) + mors->tx_max_power_mbm = mm81x_mac_get_max_txpower(mors); + + power_mbm = min(power_mbm, mors->tx_max_power_mbm); + if (power_mbm == mors->tx_power_mbm) + return mors->tx_power_mbm; + + ret = mm81x_cmd_set_txpower(mors, &out_power_mbm, power_mbm); + if (ret) { + dev_err(mors->dev, "failed, power %d mBm ret %d", power_mbm, + ret); + return mors->tx_power_mbm; + } + + if (out_power_mbm != mors->tx_power_mbm) { + dev_dbg(mors->dev, "%d -> %d mBm", mors->tx_power_mbm, + out_power_mbm); + mors->tx_power_mbm = out_power_mbm; + } + + return mors->tx_power_mbm; +} + +static int mm81x_mac_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, + u8 pri_bw_mhz) +{ + int ret; + + ret = mm81x_cmd_set_channel(mors, op_chan_freq_hz, pri_1mhz_chan_idx, + op_bw_mhz, pri_bw_mhz, &mors->tx_power_mbm); + if (ret) { + dev_err(mors->dev, "mm81x_cmd_set_channel() failed, ret %d", + ret); + return ret; + } + + mm81x_mac_set_txpower(mors, mors->tx_power_mbm); + return 0; +} + +static u8 mm81x_mac_pri_chan_to_index(const struct cfg80211_chan_def *chandef) +{ + u32 bw_mhz = cfg80211_chandef_get_width(chandef); + u32 op_center_khz = ieee80211_chandef_to_khz(chandef); + u32 first_1mhz_center_khz = op_center_khz - (bw_mhz * 500) + 500; + u32 pri_1mhz_khz = ieee80211_channel_to_khz(chandef->chan); + + return (pri_1mhz_khz - first_1mhz_center_khz) / 1000; +} + +static int mm81x_mac_ops_change_channel(struct ieee80211_hw *hw, + struct cfg80211_chan_def *chandef) +{ + int ret; + struct mm81x *mors = hw->priv; + u64 freq_hz = KHZ_TO_HZ(ieee80211_chandef_to_khz(chandef)); + u8 op_bw_mhz = cfg80211_chandef_get_width(chandef); + u8 pri_1mhz_idx = mm81x_mac_pri_chan_to_index(chandef); + int pri_chan_width_mhz = cfg80211_chandef_s1g_pri_width(chandef); + + dev_dbg(mors->dev, "ch: freq=%llu Hz bw=%u pri_idx=%d pri_bw=%d", + freq_hz, op_bw_mhz, pri_1mhz_idx, pri_chan_width_mhz); + + ret = mm81x_mac_set_channel(mors, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz, + pri_chan_width_mhz); + if (ret) + return ret; + + memcpy(&mors->chandef, chandef, sizeof(mors->chandef)); + return 0; +} + +static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) +{ + int ret; + struct mm81x *mors = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + + if (!mors->started) + return 0; + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ret = mm81x_mac_ops_change_channel(hw, &conf->chandef); + if (ret < 0) + return ret; + } + + if ((changed & IEEE80211_CONF_CHANGE_POWER) && + !(changed & IEEE80211_CONF_CHANGE_CHANNEL) && + !(conf->flags & IEEE80211_CONF_MONITOR)) { + s32 power_mbm = DBM_TO_MBM(conf->power_level); + + power_mbm = min(DBM_TO_MBM(channel->max_reg_power), power_mbm); + power_mbm = mm81x_mac_set_txpower(mors, power_mbm); + conf->power_level = MBM_TO_DBM(power_mbm); + } + + return 0; +} + +static int mm81x_mac_ops_get_txpower(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, int *dbm) +{ + struct mm81x *mors = hw->priv; + struct ieee80211_chanctx_conf *chanctx_conf; + struct cfg80211_chan_def *chandef = &vif->bss_conf.chanreq.oper; + + scoped_guard(rcu) { + chanctx_conf = rcu_access_pointer(vif->bss_conf.chanctx_conf); + if (!chanctx_conf || + !cfg80211_chandef_identical(chandef, &chanctx_conf->def)) + return -ENODATA; + } + + *dbm = MBM_TO_DBM(mors->tx_power_mbm); + return 0; +} + +static void mm81x_mac_config_ps(struct mm81x *mors, struct ieee80211_vif *vif) +{ + bool en_ps = vif->cfg.ps; + + if (vif->type == NL80211_IFTYPE_AP || !mors->ps.enable) + return; + + if (mors->config_ps == en_ps) + return; + + dev_dbg(mors->dev, "change powersave mode: %d (current %d)", en_ps, + mors->config_ps); + + mors->config_ps = en_ps; + + if (en_ps) { + mm81x_cmd_set_ps(mors, true); + mm81x_ps_enable(mors); + } else { + mm81x_ps_disable(mors); + mm81x_cmd_set_ps(mors, false); + } +} + +static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u64 changed) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + if (changed & BSS_CHANGED_PS) + mm81x_mac_config_ps(mors, vif); + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + mm81x_cmd_config_beacon_timer(mors, mors_vif, + info->enable_beacon); + + if (!info->enable_beacon) + mm81x_mac_beacon_finish(mors_vif); + } + + if (changed & BSS_CHANGED_BEACON_INT || changed & BSS_CHANGED_SSID) { + ret = mm81x_cmd_cfg_bss(mors, mors_vif->id, info->beacon_int, + info->dtim_period, + mm81x_vif_generate_cssid(vif)); + if (ret) + dev_err(mors->dev, "mm81x_cmd_cfg_bss failed %d", ret); + } +} + +static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct mm81x *mors = hw->priv; + struct mcast_filter *filter; + struct netdev_hw_addr *addr; + u16 addr_count = netdev_hw_addr_list_count(mc_list); + u16 len = sizeof(*filter) + addr_count * sizeof(filter->addr_list[0]); + + filter = kzalloc(len, GFP_ATOMIC); + if (!filter) + return 0; + + if (addr_count > MCAST_FILTER_COUNT_MAX) { + dev_warn( + mors->dev, + "Multicast filtering disabled - too many groups (%d) > %u", + addr_count, (u16)MCAST_FILTER_COUNT_MAX); + filter->count = 0; + } else { + netdev_hw_addr_list_for_each(addr, mc_list) { + dev_dbg(mors->dev, "mcast whitelist (%d): %pM", + filter->count, addr->addr); + filter->addr_list[filter->count++] = + mac2le32(addr->addr); + } + } + + return (u64)(unsigned long)filter; +} + +static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct mm81x *mors = hw->priv; + struct mcast_filter *cmd = (void *)(unsigned long)multicast; + struct mm81x_vif *mors_vif = NULL; + struct ieee80211_vif *vif = NULL; + int vif_id = 0; + int ret = 0; + + if (!cmd) + goto out; + + kfree(mors->mcast_filter); + mors->mcast_filter = cmd; + + for (vif_id = 0; vif_id < ARRAY_SIZE(mors->vifs); vif_id++) { + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, false); + if (!vif) + continue; + + mors_vif = ieee80211_vif_to_mors_vif(vif); + + ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif); + if (!ret) + continue; + + dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret); + mors->mcast_filter = NULL; + kfree(cmd); + break; + } + +out: + *total_flags &= 0; +} + +static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, u16 ac, + const struct ieee80211_tx_queue_params *params) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_queue_params mqp; + + mqp.aci = map_mac80211q_2_mm81x_aci(ac); + mqp.aifs = params->aifs; + mqp.cw_max = params->cw_max; + mqp.cw_min = params->cw_min; + mqp.uapsd = params->uapsd; + mqp.txop = params->txop << 5; + + dev_dbg(mors->dev, "queue:%d txop:%d cw_min:%d cw_max:%d aifs:%d", + mqp.aci, mqp.txop, mqp.cw_min, mqp.cw_max, mqp.aifs); + + ret = mm81x_cmd_cfg_qos(mors, &mqp); + if (ret) + dev_dbg(mors->dev, "mm81x_cmd_cfg_qos failed %d", ret); + return ret; +} + +static int mm81x_mac_ops_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + u16 aid; + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + + /* Ignore both NOTEXIST to NONE and NONE to NOTEXIST */ + if ((old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) || + (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST)) + return 0; + + if (vif->type == NL80211_IFTYPE_STATION) + aid = vif->cfg.aid; + else + aid = sta->aid; + + ret = mm81x_cmd_sta_state(mors, mors_vif, aid, sta, new_state); + if (ret < 0) + goto exit; + + ether_addr_copy(mors_sta->addr, sta->addr); + mors_sta->state = new_state; + + if (new_state > old_state && new_state == IEEE80211_STA_ASSOC) { + if (vif->type == NL80211_IFTYPE_AP) + mors_vif->u.ap.num_stas++; + else if (vif->type == NL80211_IFTYPE_STATION) + mors_vif->u.sta.is_assoc = true; + } + + if (new_state < old_state && new_state == IEEE80211_STA_NONE) { + if (vif->type == NL80211_IFTYPE_AP) + mors_vif->u.ap.num_stas--; + else if (vif->type == NL80211_IFTYPE_STATION) + mors_vif->u.sta.is_assoc = false; + } + +exit: + /* + * Always update our mmrc sta state even on failure to ensure + * we don't hold a dangling sta on error + */ + mm81x_rc_sta_state_check(mors, vif, sta, old_state, new_state); + return new_state < old_state ? 0 : ret; +} + +static int mm81x_mac_ops_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + u16 tid = params->tid; + struct mm81x *mors = hw->priv; + struct ieee80211_sta *sta = params->sta; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + u16 buf_size = + min_t(u16, params->buf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU); + + switch (params->action) { + case IEEE80211_AMPDU_TX_START: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX start", mors_sta->addr, + tid); + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX flush", mors_sta->addr, + tid); + mors_sta->tid_start_tx[tid] = false; + mors_sta->tid_tx[tid] = false; + mors_sta->tid_params[tid] = 0; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX oper", mors_sta->addr, + tid); + mors_sta->tid_tx[tid] = true; + if (!buf_size) { + dev_err(mors->dev, "%pM.%d A-MPDU Invalid buf size", + mors_sta->addr, tid); + break; + } + mors_sta->tid_params[tid] = + u8_encode_bits(buf_size - 1, + TX_INFO_TID_PARAMS_MAX_REORDER_BUF) | + u8_encode_bits(1, TX_INFO_TID_PARAMS_AMPDU_ENABLED) | + u8_encode_bits(params->amsdu, + TX_INFO_TID_PARAMS_AMSDU_SUPPORTED); + break; + default: + break; + } + + return 0; +} + +static int mm81x_mac_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + u16 aid; + int ret = -EOPNOTSUPP; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + enum host_cmd_key_cipher cipher; + enum host_cmd_aes_key_len length; + + if (vif->type == NL80211_IFTYPE_STATION) { + aid = vif->cfg.aid; + } else if (sta) { + aid = sta->aid; + } else { + /* Is a group key - AID is unused */ + WARN_ON(key->flags & IEEE80211_KEY_FLAG_PAIRWISE); + aid = 0; + } + + switch (cmd) { + case SET_KEY: { + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_CCMP_256: + cipher = HOST_CMD_KEY_CIPHER_AES_CCM; + break; + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + cipher = HOST_CMD_KEY_CIPHER_AES_GCM; + break; + default: + /* Cipher suite currently not supported */ + ret = -EOPNOTSUPP; + goto exit; + } + + switch (key->keylen) { + case 16: + length = HOST_CMD_AES_KEY_LEN_LENGTH_128; + break; + case 32: + length = HOST_CMD_AES_KEY_LEN_LENGTH_256; + break; + default: + /* Key length not supported */ + ret = -EOPNOTSUPP; + goto exit; + } + + ret = mm81x_cmd_install_key(mors, mors_vif, aid, key, cipher, + length); + break; + } + case DISABLE_KEY: + ret = mm81x_cmd_disable_key(mors, mors_vif, aid, key); + if (ret) { + /* Must return 0 */ + dev_warn(mors->dev, "Failed to remove key"); + ret = 0; + } + break; + default: + WARN_ON(1); + } + + if (ret) { + dev_dbg(mors->dev, "Falling back to software crypto"); + ret = 1; + } + +exit: + return ret; +} + +static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) +{ + struct mm81x *mors = hw->priv; + + return mm81x_cmd_set_frag_threshold(mors, value); +} + +static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode) +{ + enum dot11_bandwidth bw = mm81x_ratecode_bw_index_get(ratecode); + + switch (bw) { + case DOT11_BANDWIDTH_1MHZ: + return RATE_INFO_BW_1; + case DOT11_BANDWIDTH_2MHZ: + return RATE_INFO_BW_2; + case DOT11_BANDWIDTH_4MHZ: + return RATE_INFO_BW_4; + case DOT11_BANDWIDTH_8MHZ: + return RATE_INFO_BW_8; + default: + return RATE_INFO_BW_1; + } +} + +static void mm81x_rx_h_fill_status(struct mm81x *mors, + struct mm81x_skb_rx_status *hdr_rx_status, + struct ieee80211_rx_status *rx_status, + struct sk_buff *skb) +{ + u32 flags = le32_to_cpu(hdr_rx_status->flags); + u16 freq_100khz = le16_to_cpu(hdr_rx_status->freq_100khz); + __le32 ratecode = hdr_rx_status->mm81x_ratecode; + + rx_status->signal = le16_to_cpu(hdr_rx_status->rssi); + rx_status->encoding = RX_ENC_S1G; + rx_status->band = NL80211_BAND_S1GHZ; + rx_status->freq = KHZ100_TO_MHZ(freq_100khz); + rx_status->freq_offset = (freq_100khz % 10) ? 1 : 0; + rx_status->nss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode)); + + if (flags & MM81X_RX_STATUS_FLAGS_DECRYPTED) + rx_status->flag |= RX_FLAG_DECRYPTED; + + rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode); + rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode); + + if (mm81x_ratecode_sgi_get(ratecode)) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; +} + +static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *rx_status) +{ + struct ieee80211_sta *sta; + struct mm81x_sta *msta; + u8 *lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 : + hdr->addr2; + + lockdep_assert_in_rcu_read_lock(); + + sta = ieee80211_find_sta(vif, lookup); + if (!sta) + return; + + msta = (void *)sta->drv_priv; + if (msta->avg_rssi) { + msta->avg_rssi = + CALC_AVG_RSSI(msta->avg_rssi, rx_status->signal); + } else { + msta->avg_rssi = rx_status->signal; + } +} + +static struct ieee80211_vif * +mm81x_rx_h_skb_get_vif(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status) +{ + u8 vif_id = u32_get_bits(le32_to_cpu(hdr_rx_status->flags), + MM81X_RX_STATUS_FLAGS_VIF_ID); + + lockdep_assert_in_rcu_read_lock(); + + if (vif_id == INVALID_VIF_INDEX) + return NULL; + + return mm81x_rcu_dereference_vif_id(mors, vif_id, true); +} + +void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status) +{ + struct ieee80211_vif *vif; + struct ieee80211_hw *hw = mors->hw; + struct ieee80211_rx_status rx_status; + struct ieee80211_hdr *hdr = (void *)skb->data; + + memset(&rx_status, 0, sizeof(rx_status)); + + if (!mors->started || !skb->data || !skb->len) { + dev_kfree_skb_any(skb); + return; + } + + mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb); + + scoped_guard(rcu) { + vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status); + if (!vif) + goto rx; + + mm81x_rx_h_update_sta(vif, hdr, &rx_status); + } + +rx: + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_ni(hw, skb); +} + +static void mm81x_mac_flush_queues(struct mm81x *mors) +{ + /* + * No need to call mm81x_skbq_stop_tx_queues as mac80211 + * has already cancelled each queue prior to calling .flush() + */ + mm81x_skbq_data_traffic_pause(mors); + + flush_work(&mors->hif_work); + flush_work(&mors->tx_stale_work); + + mm81x_hif_clear_events(mors); + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + + /* Re-enable data, not that there will be any */ + mm81x_skbq_data_traffic_resume(mors); +} + +static bool mm81x_mac_has_tx_pending(struct mm81x *mors) +{ + struct mm81x_skbq *mgmt_q = mm81x_hif_get_tx_mgmt_queue(mors); + struct mm81x_skbq *tx_qs; + int num_qs, i; + + mm81x_hif_skbq_get_tx_qs(mors, &tx_qs, &num_qs); + for (i = 0; i < num_qs; i++) + if (mm81x_skbq_count(&tx_qs[i]) || + mm81x_skbq_pending_count(&tx_qs[i])) + return true; + + if (mm81x_skbq_count(mgmt_q) || mm81x_skbq_pending_count(mgmt_q)) + return true; + + return false; +} + +static void mm81x_mac_wait_queues(struct mm81x *mors) +{ + if (!wait_event_timeout(mors->tx_empty_waitq, + !mm81x_mac_has_tx_pending(mors), + MM81X_FLUSH_TIMEOUT)) + dev_warn(mors->dev, "Unable to empty queues before timeout"); +} + +static void mm81x_mac_ops_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 queues, + bool drop) +{ + struct mm81x *mors = hw->priv; + + /* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */ + if (drop) + mm81x_mac_flush_queues(mors); + else + mm81x_mac_wait_queues(mors); +} + +static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) +{ + struct mm81x *mors = hw->priv; + + mors->rts_threshold = value; + return 0; +} + +static void mm81x_mac_ops_sta_statistics(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mm81x *mors = hw->priv; + const struct mmrc_table *tb = msta->rc.tb; + struct mmrc_rate rate; + + if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) { + sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + return; + } + + rate = tb->best_tp; + sinfo->txrate.mcs = rate.rate; + sinfo->txrate.nss = NSS_IDX_TO_NSS(rate.ss); + sinfo->txrate.flags = RATE_INFO_FLAGS_S1G_MCS; + switch (rate.bw) { + case MMRC_BW_1MHZ: + sinfo->txrate.bw = RATE_INFO_BW_1; + break; + case MMRC_BW_2MHZ: + sinfo->txrate.bw = RATE_INFO_BW_2; + break; + case MMRC_BW_4MHZ: + sinfo->txrate.bw = RATE_INFO_BW_4; + break; + case MMRC_BW_8MHZ: + sinfo->txrate.bw = RATE_INFO_BW_8; + break; + default: + break; + } + + if (rate.guard == MMRC_GUARD_SHORT) + sinfo->txrate.flags |= (RATE_INFO_FLAGS_SHORT_GI); + + dev_dbg(mors->dev, "mcs: %d, bw: %d, flag: 0x%x", rate.rate, rate.bw, + sinfo->txrate.flags); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); +} + +static u32 mm81x_get_expected_throughput(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mm81x *mors = hw->priv; + const struct mmrc_table *tb = msta->rc.tb; + struct mmrc_rate rate; + u32 tput; + + if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) + return 0; + + rate = tb->best_tp; + tput = BPS_TO_KBPS(mmrc_calculate_theoretical_throughput(rate)); + dev_dbg(mors->dev, "Throughput: MCS: %d, BW: %d, GI: %d -> %u", + rate.rate, 1 << rate.bw, rate.guard, tput); + + return tput; +} + +static void mm81x_mac_restart_cleanup_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_finish((struct mm81x_vif *)vif->drv_priv); +} + +static void mm81x_mac_restart_cleanup(struct mm81x *mors) +{ + ieee80211_iterate_active_interfaces(mors->hw, + IEEE80211_IFACE_ITER_NORMAL, + mm81x_mac_restart_cleanup_iter, + NULL); + mm81x_mac_hw_scan_finish(mors); +} + +static int mm81x_mac_restart(struct mm81x *mors) +{ + int ret; + u32 chip_id; + + mors->started = false; + mm81x_ps_disable(mors); + mm81x_bus_set_irq(mors, false); + mm81x_hw_irq_clear(mors); + ieee80211_stop_queues(mors->hw); + + set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); + + /* Allow time for in-transit tx/rx packets to settle */ + mdelay(MM81X_HW_RESTART_DELAY_MS); + flush_work(&mors->hif_work); + flush_work(&mors->tx_stale_work); + mm81x_hif_clear_events(mors); + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, MM81X_REG_CHIP_ID(mors), &chip_id); + mm81x_release_bus(mors); + + if (ret < 0) { + dev_err(mors->dev, "Failed to access HW: %d", ret); + goto exit; + } + + mm81x_mac_restart_cleanup(mors); + + ret = mm81x_fw_init(mors, true); + if (ret < 0) { + dev_err(mors->dev, "Failed to init firmware: %d", ret); + goto exit; + } + + mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true); + + ret = mm81x_fw_parse_ext_host_tbl(mors); + if (ret) { + dev_err(mors->dev, "failed to parse extended host table: %d", + ret); + goto exit; + } + + mm81x_mac_caps_init(mors); + + mm81x_bus_set_irq(mors, true); + clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); + clear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + clear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags); + mm81x_mac_check_fw_disabled_chans(mors->hw); + ieee80211_restart_hw(mors->hw); + +exit: + mm81x_ps_enable(mors); + return ret; +} + +static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret = 0; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + if (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags)) { + dev_info(mors->dev, "Restarting chip with regdom: %s", + mors->country); + + ret = mm81x_mac_restart(mors); + if (ret) { + dev_err(mors->dev, "Failed to restart chip"); + return ret; + } + + /* + * mac_restart will trigger ieee80211_hw_restart and + * add_interface will re-enter. just exit here instead. + */ + return 0; + } + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + mors_vif->mors = mors; + + ret = mm81x_cmd_add_if(mors, &mors_vif->id, vif->addr, vif->type); + if (ret) { + dev_err(mors->dev, "mm81x_cmd_add_if failed %d", ret); + return ret; + } + + if (mors_vif->id >= ARRAY_SIZE(mors->vifs)) { + dev_err(mors->dev, "vif_id is too large %u", mors_vif->id); + ret = -EOPNOTSUPP; + return ret; + } + + if (mors_vif->id != (mors_vif->id & MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) { + dev_err(mors->dev, "invalid vif_id %u", mors_vif->id); + ret = -EOPNOTSUPP; + return ret; + } + + rcu_assign_pointer(mors->vifs[mors_vif->id], vif); + + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_init(mors_vif); + + ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps); + if (ret) { + dev_err(mors->dev, + "mm81x_cmd_get_capabilities failed for vif %d", + mors_vif->id); + return ret; + } + + ieee80211_wake_queues(mors->hw); + return ret; +} + +static const struct ieee80211_ops mm81x_ops = { + .start = mm81x_mac_ops_start, + .stop = mm81x_mac_ops_stop, + .config = mm81x_mac_ops_config, + .wake_tx_queue = ieee80211_handle_wake_tx_queue, + .tx = mm81x_mac_ops_tx, + .add_interface = mm81x_mac_ops_add_interface, + .remove_interface = mm81x_mac_ops_remove_interface, + .configure_filter = mm81x_mac_ops_configure_filter, + .sta_state = mm81x_mac_ops_sta_state, + .flush = mm81x_mac_ops_flush, + .set_frag_threshold = mm81x_mac_set_frag_threshold, + .set_rts_threshold = mm81x_mac_ops_set_rts_threshold, + .sta_statistics = mm81x_mac_ops_sta_statistics, + .get_expected_throughput = mm81x_get_expected_throughput, + .hw_scan = mm81x_mac_ops_hw_scan, + .cancel_hw_scan = mm81x_mac_ops_cancel_hw_scan, + .get_txpower = mm81x_mac_ops_get_txpower, + .bss_info_changed = mm81x_mac_ops_bss_info_changed, + .prepare_multicast = mm81x_mac_ops_prepare_multicast, + .conf_tx = mm81x_mac_ops_conf_tx, + .ampdu_action = mm81x_mac_ops_ampdu_action, + .set_key = mm81x_mac_ops_set_key, + .add_chanctx = ieee80211_emulate_add_chanctx, + .remove_chanctx = ieee80211_emulate_remove_chanctx, + .change_chanctx = ieee80211_emulate_change_chanctx, + .switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx, +}; + +static void mm81x_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + int ret; + struct mm81x *mors = wiphy_to_ieee80211_hw(wiphy)->priv; + + if (mm81x_reg_h_cc_equal(request->alpha2, "00") || + mm81x_reg_h_cc_equal(request->alpha2, mors->country)) + return; + + memcpy(mors->country, request->alpha2, sizeof(mors->country)); + + ret = mm81x_mac_restart(mors); + if (ret) + dev_err(mors->dev, "Failed to restart chip: %d", ret); +} + +static void mm81x_mac_config_hw(struct mm81x *mors) +{ + int i; + struct ieee80211_hw *hw = mors->hw; + struct wiphy *wiphy; + + for (i = 0; i < NUM_NL80211_BANDS; i++) + hw->wiphy->bands[i] = NULL; + + hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mors_band_s1ghz; + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION); + hw->wiphy->reg_notifier = mm81x_reg_notifier; + hw->queues = MM81X_HW_QUEUE_COUNT; + hw->max_rates = MM81X_HW_MAX_RATES; + hw->max_report_rates = MM81X_HW_MAX_REPORT_RATES; + hw->max_rate_tries = MM81X_HW_MAX_RATE_TRIES; + hw->tx_sk_pacing_shift = MM81X_HW_TX_SK_PACING_SHIFT; + hw->vif_data_size = sizeof(struct mm81x_vif); + hw->sta_data_size = sizeof(struct mm81x_sta); + hw->extra_tx_headroom = + sizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors); + + mors->wiphy = hw->wiphy; + + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, MFP_CAPABLE); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); + ieee80211_hw_set(hw, PS_NULLFUNC_STACK); + ieee80211_hw_set(hw, SUPPORTS_TX_FRAG); + ieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK); + + SET_IEEE80211_PERM_ADDR(hw, mors->macaddr); + + wiphy = mors->wiphy; + + wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + + if (!mors->ps.enable) + wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_TX_POWER_INSERTION; + + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); + + wiphy->iface_combinations = mors_if_combs; + wiphy->n_iface_combinations = ARRAY_SIZE(mors_if_combs); + wiphy->max_scan_ie_len = MM81X_MAX_SCAN_IE_LEN; + wiphy->max_scan_ssids = MM81X_MAX_SCAN_SSIDS; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->max_remain_on_channel_duration = + MM81X_MAX_REMAIN_ON_CHAN_DURATION; +} + +static void mm81x_stale_tx_status_timer(struct timer_list *t) +{ + struct mm81x *mors = timer_container_of(mors, t, stale_status.timer); + + spin_lock_bh(&mors->stale_status.lock); + if (mm81x_hif_get_tx_status_pending_count(mors)) + queue_work(mors->net_wq, &mors->tx_stale_work); + spin_unlock_bh(&mors->stale_status.lock); +} + +static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors) +{ + timer_shutdown_sync(&mors->stale_status.timer); +} + +static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors) +{ + spin_lock_init(&mors->stale_status.lock); + timer_setup(&mors->stale_status.timer, mm81x_stale_tx_status_timer, 0); +} + +int mm81x_mac_register(struct mm81x *mors) +{ + int ret; + struct ieee80211_hw *hw = mors->hw; + + mors->tx_power_mbm = INT_MAX; + mors->tx_max_power_mbm = INT_MAX; + mors->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + + ret = mm81x_ps_init(mors); + if (ret) + return ret; + + mm81x_mac_config_hw(mors); + mm81x_mac_hw_scan_init(mors); + mm81x_mac_stale_tx_status_timer_init(mors); + + ret = ieee80211_register_hw(hw); + if (ret) { + dev_err(mors->dev, "ieee80211_register_hw failed %d", ret); + mm81x_mac_unregister(mors); + return ret; + } + + mm81x_rc_init(mors); + + /* + * At this stage, we know bus and pager system interrupts are enabled. + * Trigger the receive workqueue to drain any incoming chip-to-host + * pending packets been pushed in the period between the firmware + * initialization and interrupts being enabled. + */ + set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + + return ret; +} + +void mm81x_mac_unregister(struct mm81x *mors) +{ + mm81x_ps_disable(mors); + mm81x_rc_deinit(mors); + mm81x_mac_hw_scan_destroy(mors); + + ieee80211_stop_queues(mors->hw); + ieee80211_unregister_hw(mors->hw); + + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + mm81x_stale_tx_status_timer_finish(mors); + mm81x_ps_finish(mors); + + kfree(mors->mcast_filter); +} + +struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev) +{ + struct ieee80211_hw *hw; + struct mm81x *mors; + + hw = ieee80211_alloc_hw(sizeof(*mors) + priv_size, &mm81x_ops); + if (!hw) { + dev_err(dev, "ieee80211_alloc_hw failed\r\n"); + return NULL; + } + + SET_IEEE80211_DEV(hw, dev); + memset(hw->priv, 0, sizeof(*mors)); + + mors = hw->priv; + mors->hw = hw; + mors->dev = dev; + mutex_init(&mors->cmd_lock); + mutex_init(&mors->cmd_wait); + init_waitqueue_head(&mors->tx_empty_waitq); + + return mors; +} + +void mm81x_mac_free(struct mm81x *mors) +{ + ieee80211_free_hw(mors->hw); +} diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.h b/drivers/net/wireless/morsemicro/mm81x/mac.h new file mode 100644 index 000000000000..c540471f274e --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mac.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_MAC_H_ +#define _MM81X_MAC_H_ + +#include "core.h" +#include "command.h" + +struct mm81x_queue_params { + u8 uapsd; + u8 aci; + u8 aifs; + u16 cw_min; + u16 cw_max; + u32 txop; +}; + +static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif) +{ + return mm81x_generate_cssid(vif->cfg.ssid, vif->cfg.ssid_len); +} + +/* + * Build a little-endian word from the last four octets of a MAC address; + * the first two octets are dropped. + */ +static inline __le32 mac2le32(const unsigned char *addr) +{ + return cpu_to_le32(((u32)(addr[2]) << 24) | ((u32)(addr[3]) << 16) | + ((u32)(addr[4]) << 8) | ((u32)(addr[5]))); +} + +static inline struct ieee80211_vif * +mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu) +{ + if (WARN_ON(vif_id >= ARRAY_SIZE(mors->vifs))) + return NULL; + + if (rcu) + return rcu_dereference(mors->vifs[vif_id]); + + return rcu_dereference_protected(mors->vifs[vif_id], + lockdep_is_held(&mors->hw->wiphy->mtx)); +} + +int mm81x_tx_h_get_attempts(struct mm81x *mors, + struct mm81x_skb_tx_status *tx_sts); +struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev); +int mm81x_mac_register(struct mm81x *mors); +void mm81x_mac_free(struct mm81x *mors); +void mm81x_mac_unregister(struct mm81x *mors); +int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb); +void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status); +void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status); + +u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf); +size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params); +void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb); +#endif /* !_MM81X_MAC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c new file mode 100644 index 000000000000..fe7e4f501d6c --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "mmrc.h" + +/* + * The default packet size in bits used for calculated throughput of a given + * rate + */ +#define DEFAULT_PACKET_SIZE_BITS 9600 + +/* + * The default packet size in bytes used for calculating retries for a given + * rate + */ +#define DEFAULT_PACKET_SIZE_BYTES 1200 + +/* The sample frequencies at different stages */ +#define LOOKAROUND_RATE_INIT 5 +#define LOOKAROUND_RATE_NORMAL 50 +#define LOOKAROUND_RATE_STABLE 100 + +/* The thresholds for stability stages */ +#define STABILITY_CNT_THRESHOLD_INIT 20 +#define STABILITY_CNT_THRESHOLD_NORMAL 50 +#define STABILITY_CNT_THRESHOLD_STABLE 100 + +/* The backoff step size for the counter */ +#define STABILITY_BACKOFF_STEP 2 + +/* + * The packet success threshold for attempting slower lookaround rates + */ +/* + * Force a look around if there haven't been any for this number of cycles + */ +#define LOOKAROUND_MAX_RC_CYCLES 5 + +/* + * Number of attempts for each lookaround rate within at most two RC cycles + * if there are enough packets + */ +#define LOOKAROUND_RATE_ATTEMPTS 4 + +/* + * Limit the number of times we try to pick a theoretically better rate to + * sample. Necessary so we don't stall the CPU, due to constantly picking worse + * rates. + */ +#define LOOKAROUND_FAIL_MAX 200 + +/* + * Initial and reset probability per rate in the table + * Changing this value will have a severe implication on the current heuristic + * It could mean that some rates will have better probability throughput even + * with no edivence and so will cause unexpected changes in the rate table + */ +#define RATE_INIT_PROBABILITY 0 + +/* + * The lowest number of MPDUs within acknowledged AMPDUs that can be used for + * rate stats + */ +#define AMPDU_STATS_MIN 2 + +/* + * The lowest number of stats to be used for processing in NORMAL lookaround + * mode + */ +#define STATS_MIN_NORMAL 2 + +/* + * The lowest number of stats to be used for processing in INIT lookaround + * mode + */ +#define STATS_MIN_INIT 1 + +/* The lowest probability value considered for recognising a dip */ +#define PROBABILITY_DIP_MIN 20 + +/* The lowest probability value for recovering from a dip */ +#define PROBABILITY_DIP_RECOVERY_MIN 40 + +/* + * The time cap on rate allocation for multiple attempts. If a single attempt + * exceeds this window, no additional attempts will be generated + */ +#define MAX_WINDOW_ATTEMPT_TIME 4000 + +/* The time window for all rates in rate table */ +#define RATE_WINDOW_MICROSECONDS 24000 + +/* + * EWMA is the alpha coefficient in the exponential weighting moving average + * filter used for probability updates. + * + * Y[n] = X[n] * (100 - EWMA) + (Y[n-1] * EWMA) + * ------------------------------------- + * 100 + * + */ +#define EWMA 75 + +/* + * Evidence scaling to allow for one decimal place. Needed for low + * throughput, otherwise the history decays in a single cycle. + */ +#define EVIDENCE_SCALE 5 + +/* + * Evidence maximum to ensure history doesn't decay too slowly when + * there is a lot of historical data. + */ +#define EVIDENCE_MAX 100 + +/* + * This fixed point conversion multiplies a value by one and shifts it + * accordingly to account for the fixed point shifting at the return of a + * function + */ +#define FP_8_MULT_1 256 + +/* Fixed point conversion for 2.1 * 2^8 used for 4MHz symbol multiplication */ +#define FP_8_4MHZ 537 + +/* Fixed point conversion for 4.5 * 2^8 used for 8MHz symbol multiplication */ +#define FP_8_8MHZ 1152 + +/* Fixed point conversion for 9.0 * 2^8 used for 16MHz symbol multiplication */ +#define FP_8_16MHZ 2301 + +/* + * Fixed point conversion for 3.6 * 2^8 used for long guard symbol tx time + * multiplication + */ +#define FP_8_LONG_GUARD_SYMBOL_TIME 1024 + +/* + * Fixed point conversion for 4.0 * 2^8 used for short guard symbol tx time + * multiplication + */ +#define FP_8_SHORT_GUARD_SYMBOL_TIME 921 + +/* + * Shift value to shift back our FP conversions + */ +#define FP_8_SHIFT 8 + +/* + * Limit to count of consecutive variations in one direction + */ +#define MAX_VARIATION_DIRECTION 5 + +/* + * Threshold for considering consecutive variation direction as variation + * or not + */ +#define VARIATION_DIRECTION_THRESHOLD 3 + +/* EWMA percentage value for averaging the best rate probability variation */ +#define VARIATION_EWMA 95 + +/* Percentage variation regarded as minor */ +#define MINOR_VARIATION_THRESHOLD 1 + +/* Percentage variation regarded as moderate */ +#define MODERATE_VARIATION_THRESHOLD 3 + +/* Percentage variation regarded as significant */ +#define SIGNIFICANT_VARIATION_THRESHOLD 5 + +/* If the best rate changes twice in this number of cycles, it is unstable */ +#define BEST_RATE_UNSTABLE_THRESHOLD 4 + +/* + * Once the best rate is unchanged for this number of cycles it has + * converged + */ +#define BEST_RATE_CONVERGED_THRESHOLD 10 + +/* RSSI threshold for short range */ +#define MMRC_SHORT_RANGE_RSSI_LIMIT -70 + +/* RSSI threshold for mid range */ +#define MMRC_MID_RANGE_RSSI_LIMIT -85 + +#define MMRC_MAX_BW(bw_caps) \ + (((bw_caps) & BIT(MMRC_BW_16MHZ)) ? MMRC_BW_16MHZ : \ + ((bw_caps) & BIT(MMRC_BW_8MHZ)) ? MMRC_BW_8MHZ : \ + ((bw_caps) & BIT(MMRC_BW_4MHZ)) ? MMRC_BW_4MHZ : \ + ((bw_caps) & BIT(MMRC_BW_2MHZ)) ? MMRC_BW_2MHZ : \ + MMRC_BW_1MHZ) + +/* + * This table stores the number of bits per symbols used for MCS0-MCS9 based + * on 20MHz and 1SS + */ +static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 }; + +/* + * Calculate which bit is the nth bit set in an integer based flag. + */ +static u8 nth_bit(u16 in, u16 index) +{ + u32 i; + u8 count = 0; + + for (i = 0; count != index + 1; i++) { + if (((1u << i) & in) != 0) + count++; + } + + return i - 1; +} + +/* + * Calculate the input bit's index among all the set bits in an integer + * based flag. + */ +static u16 bit_index(u16 in, u32 bit_pos) +{ + u16 i; + u16 index = 0; + + for (i = 0; i != bit_pos + 1; i++) { + if (((1u << i) & in) != 0) + index++; + } + + if (index == 0) { + /* Could not match bit pos to caps */ + return 0; + } + + return index - 1; +} + +static u16 rows_from_sta_caps(struct mmrc_sta_capabilities *caps) +{ + u16 rows = 0; + u8 n_rates = hweight_long(caps->rates); + + /* Taking MCS10 into account as it is relevant for 1 MHz entries */ + if (caps->rates & BIT(MMRC_MCS10)) { + n_rates -= 1; + rows = 2; + } + + rows += (hweight_long(caps->bandwidth) * n_rates * + hweight_long(caps->guard) * + hweight_long(caps->spatial_streams)); + + return rows; +} + +static void rate_update_index(struct mmrc_table *tb, struct mmrc_rate *rate) +{ + u16 index = 0; + /* Information about our rates */ + u16 bw = hweight_long(tb->caps.bandwidth); + u16 streams = hweight_long(tb->caps.spatial_streams); + u16 guard = hweight_long(tb->caps.guard); + u16 rows = rows_from_sta_caps(&tb->caps); + + index = bit_index(tb->caps.guard, rate->guard) + + bit_index(tb->caps.bandwidth, rate->bw) * guard + + bit_index(tb->caps.spatial_streams, rate->ss) * guard * bw + + bit_index(tb->caps.rates, rate->rate) * bw * streams * guard; + + if (index >= rows) + index = 0; + + rate->index = index; +} + +static struct mmrc_rate get_rate_row(struct mmrc_table *tb, u16 index) +{ + struct mmrc_rate rate; + u16 ss_index; + + /* Information about our rates */ + u16 mcs = hweight_long(tb->caps.rates); + u16 bw = hweight_long(tb->caps.bandwidth); + u16 streams = hweight_long(tb->caps.spatial_streams); + u16 guard = hweight_long(tb->caps.guard); + u16 total_caps = mcs * bw * streams * guard; + + /* Find our MCS */ + u16 rows = total_caps / mcs; + u16 mcs_index = index / rows; + u16 mcs_modulo = index % rows; + + mcs = nth_bit(tb->caps.rates, mcs_index); + + /* Find our spatial stream */ + rows = rows / streams; + streams = nth_bit(tb->caps.spatial_streams, mcs_modulo / rows); + + /* Find our bandwidth */ + ss_index = index % rows; + rows = rows / bw; + bw = nth_bit(tb->caps.bandwidth, ss_index / rows); + + /* Find our guard */ + guard = nth_bit(tb->caps.guard, index % guard); + + /* Add range checks to keep scan-build happy */ + if (bw >= MMRC_BW_MAX) + bw = MMRC_BW_1MHZ; + + if (guard >= MMRC_GUARD_MAX) + guard = MMRC_GUARD_LONG; + + /* Validate guard against capability */ + if (guard == MMRC_GUARD_SHORT && + !(tb->caps.sgi_per_bw & SGI_PER_BW(bw))) + guard = MMRC_GUARD_LONG; + + /* Create our rate row and send it */ + rate.bw = MMRC_BW_TO_BITFIELD(bw); + rate.ss = MMRC_SS_TO_BITFIELD(streams); + rate.rate = MMRC_RATE_TO_BITFIELD(mcs); + rate.guard = MMRC_GUARD_TO_BITFIELD(guard); + rate.attempts = 0; + rate.flags = 0; + + /* Update index as bw or guard may have changed */ + rate_update_index(tb, &rate); + + return rate; +} + +size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps) +{ + return sizeof(struct mmrc_table) + + rows_from_sta_caps(caps) * sizeof(struct mmrc_stats_table); +} + +static u32 calculate_bits_per_symbol(struct mmrc_rate *rate) +{ + u32 bps; + + /* If MCS10 is selected we return 2*MCS0 Symbols */ + if (rate->rate == MMRC_MCS10) + return 6; + + /* Confirm that the rate is valid for the sym_table lookup */ + if (rate->rate >= MMRC_MCS_UNUSED) { + pr_err("%s: Invalid MCS rate %d for sym_table lookup\n", + __func__, rate->rate); + return 1; + } + + /* + * Coversion from 20MHz as in sym_table to: + * 40MHz == x 2.1 + * 80MHz == x 4.5 + * 160MHz == x 9.0 + */ + bps = sym_table[rate->rate]; + switch (rate->bw) { + case (MMRC_BW_4MHZ): + bps *= FP_8_4MHZ; + break; + case (MMRC_BW_8MHZ): + bps *= FP_8_8MHZ; + break; + case (MMRC_BW_16MHZ): + bps *= FP_8_16MHZ; + break; + case (MMRC_BW_1MHZ): + bps = sym_table[rate->rate] * 24 / 52; + bps *= FP_8_MULT_1; + break; + case (MMRC_BW_2MHZ): + case (MMRC_BW_MAX): + default: + bps *= FP_8_MULT_1; + break; + } + /* SS + 1 because mmrc_spatial_stream starts at 0 */ + return ((rate->ss + 1) * bps) >> FP_8_SHIFT; +} + +static u32 get_tx_time(struct mmrc_rate *rate) +{ + u32 tx = 0; + u32 n_sym; + u32 avg_bits; + + /* Calculate tx time based on a default packet size */ + avg_bits = DEFAULT_PACKET_SIZE_BITS; + + /* Number of bits per symbol for this rate */ + n_sym = calculate_bits_per_symbol(rate); + + /* In case of bad calcuation/parameter use lowest value */ + n_sym = n_sym == 0 ? sym_table[0] : n_sym; + + /* number of symbols in default packet size */ + n_sym = avg_bits / n_sym; + + /* tx is time to transmit average packet in us */ + switch (rate->guard) { + case (MMRC_GUARD_LONG): + tx = n_sym * FP_8_LONG_GUARD_SYMBOL_TIME; + break; + case (MMRC_GUARD_SHORT): + tx = n_sym * FP_8_SHORT_GUARD_SYMBOL_TIME; + break; + default: + return 0; + } + + return (tx * 10) >> FP_8_SHIFT; +} + +u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate) +{ + static const u32 s1g_tpt_lgi[4][11] = { + { 300, 600, 900, 1200, 1800, 2400, 2700, 3000, 3600, 4000, + 150 }, + { 650, 1300, 1950, 2600, 3900, 5200, 5850, 6500, 7800, 0, 0 }, + { 1350, 2700, 4050, 5400, 8100, 10800, 12150, 13500, 16200, + 18000, 0 }, + { 2925, 5850, 8775, 11700, 17550, 23400, 26325, 29250, 35100, + 39000, 0 }, + }; + + static const u32 s1g_tpt_sgi[4][11] = { + { 333, 666, 1000, 1333, 2000, 2666, 3000, 3333, 4000, 4444, + 166 }, + { 722, 1444, 2166, 2888, 4333, 5777, 6500, 7222, 8666, 0, 0 }, + { 1500, 3000, 4500, 6000, 9000, 12000, 13500, 15000, 18000, + 20000, 0 }, + { 3250, 6500, 9750, 13000, 19500, 26000, 29250, 32500, 39000, + 43333, 0 }, + }; + + if (rate.guard) + return s1g_tpt_sgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1); + + return s1g_tpt_lgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1); +} + +static u32 calculate_throughput(struct mmrc_table *tb, u8 index) +{ + struct mmrc_rate rate = get_rate_row(tb, index); + + /* + * Avoid the overflow (observed for 8MHz MCS9 rate: 43333) by dividing + * first before multiplying. Should not experience any loss of + * precision as the throughput is already multiplied by 1000 in + * mmrc_calculate_theoretical_throughput (returned as bits/sec) + */ + if (tb->table[rate.index].prob < 10) + return 0; + else if (rate.index == tb->best_tp.index && tb->interference_likely) + /* + * Assist the best rate by increasing the probability by the + * averaged variation + */ + return (mmrc_calculate_theoretical_throughput(rate) / 100) * + (tb->table[rate.index].prob + tb->probability_variation); + else + return (mmrc_calculate_theoretical_throughput(rate) / 100) * + tb->table[rate.index].prob; +} + +static bool validate_rate(struct mmrc_table *tb, struct mmrc_rate *rate) +{ + if (rate->rate == MMRC_MCS10 && + (rate->bw != MMRC_BW_1MHZ || rate->ss != MMRC_SPATIAL_STREAM_1)) { + /* + * 802.11ah does not support MCS10 with BW that is not 1MHz or + * not 1 spatial stream. + */ + return false; + } + + if (rate->rate == MMRC_MCS9 && rate->bw == MMRC_BW_2MHZ && + rate->ss != MMRC_SPATIAL_STREAM_3) { + /* + * 802.11ah does not support MCS9 at 2MHz for 1, 2 or 4 spatial + * streams + */ + return false; + } + + if (rate->guard == MMRC_GUARD_SHORT && + !(tb->caps.sgi_per_bw & SGI_PER_BW(rate->bw))) + return false; + + return true; +} + +static u16 find_baseline_index(struct mmrc_table *tb) +{ + u32 i, theoretical_tp, min_theoretical_tp; + u16 row_count = rows_from_sta_caps(&tb->caps); + u16 min_theoretical_tp_index = 0; + struct mmrc_rate rate; + + if (tb->caps.rates & BIT(MMRC_MCS10)) + return 0; + + min_theoretical_tp = + mmrc_calculate_theoretical_throughput(get_rate_row(tb, 0)); + for (i = 0; i < row_count; i++) { + rate = get_rate_row(tb, i); + if (!validate_rate(tb, &rate)) + continue; + + theoretical_tp = mmrc_calculate_theoretical_throughput(rate); + if (min_theoretical_tp > theoretical_tp) { + min_theoretical_tp = theoretical_tp; + min_theoretical_tp_index = rate.index; + } + } + + return min_theoretical_tp_index; +} + +/* + * Fill out the remaining rates to be used once the best rate is selected. + * Normally the retry rates are one MCS lower than the previous, however in + * unconverged mode we limit the 3 respective retry rates to MCS 4, 2 and 0 + * respectively. The last retry rate is always MCS 0 + */ +static void mmrc_fill_retry_rates(struct mmrc_table *tb) +{ + tb->second_tp = tb->best_tp; + if (tb->second_tp.rate != MMRC_MCS0) { + tb->second_tp.rate--; + if (tb->unconverged && tb->second_tp.rate > MMRC_MCS4) + tb->second_tp.rate = MMRC_MCS4; + rate_update_index(tb, &tb->second_tp); + } else if (tb->second_tp.bw > MMRC_BW_1MHZ) { + tb->second_tp.bw--; + rate_update_index(tb, &tb->second_tp); + } + + tb->best_prob = tb->second_tp; + if (tb->best_prob.rate != MMRC_MCS0) { + tb->best_prob.rate--; + if (tb->unconverged && tb->best_prob.rate > MMRC_MCS2) + tb->best_prob.rate = MMRC_MCS2; + rate_update_index(tb, &tb->best_prob); + } else if (tb->best_prob.bw > MMRC_BW_1MHZ) { + tb->best_prob.bw--; + rate_update_index(tb, &tb->best_prob); + } + + tb->baseline = tb->best_prob; + if (tb->baseline.rate != MMRC_MCS0) { + tb->baseline.rate = MMRC_MCS0; + rate_update_index(tb, &tb->baseline); + } else if (tb->baseline.bw > MMRC_BW_1MHZ) { + tb->baseline.bw--; + rate_update_index(tb, &tb->baseline); + } +} + +/* + * Updates the mmrc_table with the appropriate rate priority based on the + * latest update statistics + */ +static void generate_table_priority(struct mmrc_table *tb, u32 new_stats) +{ + u16 i; + u16 best_row = tb->best_tp.index; + u16 prev_best_row = best_row; + u8 prev_best_rate = tb->best_tp.rate; + u16 second_best_row = tb->second_tp.index; + u32 best_tp = calculate_throughput(tb, best_row); + u32 second_best_tp = calculate_throughput(tb, second_best_row); + u32 last_nonzero_prob = 0; + struct mmrc_rate tmp; + u32 tmp_tp; + + /* Use fixed rate if set */ + if (tb->fixed_rate.rate != MMRC_MCS_UNUSED) { + tb->best_tp = tb->fixed_rate; + tb->second_tp = tb->fixed_rate; + tb->best_prob = tb->fixed_rate; + return; + } + + for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) { + tmp = get_rate_row(tb, i); + if (!validate_rate(tb, &tmp)) + continue; + + if (tb->table[tmp.index].evidence == 0) + continue; + + /* + * Besides better throughput, also consider this rate better if + * lower rates had worse probability. That indicates the rate + * itself is not the problem. Only do the probability check for + * rates up to the previous best rate. + */ + tmp_tp = calculate_throughput(tb, tmp.index); + + if (tmp_tp > best_tp || + (tb->table[tmp.index].max_throughput <= + tb->table[prev_best_row].max_throughput && + tb->table[tmp.index].prob >= + PROBABILITY_DIP_RECOVERY_MIN && + tb->table[tmp.index].prob > + tb->table[last_nonzero_prob].prob)) { + second_best_row = best_row; + second_best_tp = best_tp; + + best_tp = tmp_tp; + best_row = tmp.index; + } else if (tmp_tp > second_best_tp && best_row != tmp.index) { + second_best_tp = tmp_tp; + second_best_row = tmp.index; + } + + if (tb->table[tmp.index].prob >= PROBABILITY_DIP_MIN && + tb->table[tmp.index].max_throughput >= + tb->table[last_nonzero_prob].max_throughput) + last_nonzero_prob = tmp.index; + } + + /* Only update rates and stability when there are new statistics */ + if (!new_stats) + return; + + tb->best_tp = get_rate_row(tb, best_row); + if (best_tp == 0 && tb->best_tp.rate > MMRC_MCS0) { + /* Drop one rate, as the best throughput is zero */ + tb->best_tp.rate--; + rate_update_index(tb, &tb->best_tp); + } + tb->second_tp = get_rate_row(tb, second_best_row); + mmrc_fill_retry_rates(tb); + + if (tb->best_tp.rate > MMRC_MCS1 && prev_best_row == best_row) { + /* Increase the counter when the best rate is not changed */ + tb->stability_cnt++; + } else if (tb->stability_cnt > STABILITY_BACKOFF_STEP) { + /* Back off the counter when there is a new best rate */ + tb->stability_cnt -= STABILITY_BACKOFF_STEP; + } else { + tb->stability_cnt = 0; + } + + if (prev_best_row != best_row) { + s8 latest_best_rate_diff = prev_best_rate - tb->best_tp.rate; + u8 total_abs_best_rate_diff = + abs(tb->best_rate_diff[0] + tb->best_rate_diff[1] + + latest_best_rate_diff); + + if (!tb->interference_likely) { + tb->probability_variation = 0; + if (!tb->unconverged && + tb->best_rate_cycle_count <= + BEST_RATE_UNSTABLE_THRESHOLD && + total_abs_best_rate_diff >= 2) { + /* + * Best rate has changed twice in a few cycles + * and moved at least 2 MCSs from where it was + * 3 best rate changes ago + */ + tb->unconverged = true; + tb->newly_unconverged = true; + } + } + if (tb->unconverged && !tb->newly_unconverged && + total_abs_best_rate_diff < 2) { + /* + * Best rate has been relatively stable (not moved more + * than 1 MCS after the last 3 rate changes), go back + * to converged + */ + tb->unconverged = false; + } + tb->probability_variation_direction = 0; + tb->best_rate_cycle_count = 0; + tb->best_rate_diff[0] = tb->best_rate_diff[1]; + tb->best_rate_diff[1] = latest_best_rate_diff; + } else { + tb->best_rate_cycle_count++; + if (tb->unconverged && !tb->newly_unconverged && + tb->best_rate_cycle_count >= + BEST_RATE_CONVERGED_THRESHOLD) { + /* + * Best rate has been stable for a while, go back to + * converged + */ + tb->unconverged = false; + } + } + + if (tb->newly_unconverged) + tb->newly_unconverged = false; +} + +static u32 calculate_attempt_time(struct mmrc_rate *rate, size_t size) +{ + u32 time; + + time = get_tx_time(rate); + + if (size > DEFAULT_PACKET_SIZE_BYTES) + time = (time * ((size * 1000) / DEFAULT_PACKET_SIZE_BYTES)) / + 1000; + else + time = (time * 1000) / + ((DEFAULT_PACKET_SIZE_BYTES * 1000) / size); + + return time; +} + +u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size) +{ + u8 i; + u32 total_time = 0; + + for (i = 0; i < rate->attempts; i++) + total_time += calculate_attempt_time(rate, size); + + return total_time; +} + +/* + * Calculates the appropriate amount of additional attempts to make based on + * packet size and theoretical throughput. + */ +static void calculate_remaining_attempts(struct mmrc_table *tb, + struct mmrc_rate_table *rate, + s32 *rem_time, size_t size) +{ + size_t i; + + if (*rem_time <= 0) + return; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + u32 attempt_time; + u32 attempt; + + if (rate->rates[i].rate == MMRC_MCS_UNUSED) + break; + + /* + * The attempts for these rates were calculated in the initial + * attempt allocation + */ + if (tb->table[rate->rates[i].index].prob < 20) + continue; + + if (i == 0 && (calculate_throughput(tb, rate->rates[i].index) < + calculate_throughput(tb, tb->best_prob.index))) + continue; + + attempt_time = calculate_attempt_time(&rate->rates[i], size); + if (!attempt_time) + continue; + + attempt = (*rem_time / tb->caps.max_rates) / attempt_time; + attempt += rate->rates[i].attempts; + + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD( + attempt > MMRC_MAX_CHAIN_ATTEMPTS ? + MMRC_MAX_CHAIN_ATTEMPTS : + attempt); + } +} + +/* Allocate initial attempts to all rates in a rate table */ +static void allocate_initial_attempts(struct mmrc_rate_table *rate, + s32 *rem_time, size_t size) +{ + u32 i; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + u32 attempt_time; + + if (rate->rates[i].rate == MMRC_MCS_UNUSED) + break; + + attempt_time = calculate_attempt_time(&rate->rates[i], size); + + /* + * if the time for a single attempt is very long, lets just + * try once + */ + if (attempt_time > MAX_WINDOW_ATTEMPT_TIME) { + *rem_time -= attempt_time; + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(1); + } else { + *rem_time -= attempt_time * 2; + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2); + } + } +} + +void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out, + size_t size) +{ + u8 i; + u16 random_index; + struct mmrc_rate random; + struct mmrc_rate lookaround0 = tb->best_tp; + struct mmrc_rate lookaround1 = tb->second_tp; + bool is_lookaround; + int lookaround_index = -1; + int best_index = 0; + int random_tp = 0; + int best_tp; + int lookaround_fail_count; + bool try_current_lookaround = false; + + s32 rem_time = RATE_WINDOW_MICROSECONDS; + + memset(out, 0, sizeof(*out)); + + tb->lookaround_cnt = (tb->lookaround_cnt + 1) % tb->lookaround_wrap; + /* + * Look around if the counter wraps or there has been no look around + * for a number of rate control cycles. + */ + is_lookaround = (tb->fixed_rate.rate == MMRC_MCS_UNUSED) && + ((tb->lookaround_cnt == 0) || + ((tb->last_lookaround_cycle + + LOOKAROUND_MAX_RC_CYCLES) <= tb->cycle_cnt)); + + /* Also skip sampling if we don't yet have data for our best rate */ + if (tb->table[tb->best_tp.index].evidence == 0) + is_lookaround = false; + + if (tb->lookaround_wrap != LOOKAROUND_RATE_STABLE) { + if (tb->stability_cnt >= tb->stability_cnt_threshold) { + tb->lookaround_wrap = LOOKAROUND_RATE_STABLE; + tb->stability_cnt_threshold = + STABILITY_CNT_THRESHOLD_STABLE; + tb->stability_cnt = STABILITY_CNT_THRESHOLD_STABLE * 2; + is_lookaround = false; + } + } else if (tb->stability_cnt < tb->stability_cnt_threshold) { + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_NORMAL; + tb->lookaround_wrap = LOOKAROUND_RATE_NORMAL; + tb->stability_cnt = 0; + } + + /* Look around only when the fixed rate is not set */ + if (is_lookaround) { + tb->total_lookaround++; + tb->forced_lookaround = + (tb->forced_lookaround + 1) % LOOKAROUND_RATE_NORMAL; + tb->last_lookaround_cycle = tb->cycle_cnt; + + if (tb->current_lookaround_rate_attempts < + LOOKAROUND_RATE_ATTEMPTS) + try_current_lookaround = true; + + best_tp = calculate_throughput(tb, tb->best_tp.index); + + for (lookaround_fail_count = 0; + lookaround_fail_count < LOOKAROUND_FAIL_MAX; + lookaround_fail_count++) { + if (try_current_lookaround) { + random_index = + tb->current_lookaround_rate_index; + try_current_lookaround = false; + } else { + random_index = get_random_u32_below( + rows_from_sta_caps(&tb->caps)); + } + random = get_rate_row(tb, random_index); + + if (!validate_rate(tb, &random)) + continue; + + if (random.rate == MMRC_MCS10) + continue; + + if (tb->table[random_index].evidence > 0) + random_tp = + calculate_throughput(tb, random_index); + else + random_tp = + mmrc_calculate_theoretical_throughput( + random); + + /* + * Skip rates that can only be worse than the current + * best + */ + if (random_tp <= best_tp) + continue; + + /* + * Force looking up the rate no more that one MCS. + * It will avoid looking for rates with very low + * success rate. In case of better environment + * conditions MMRC will collect enough statistics to + * climb up the rates one by one. + */ + if (random.rate > tb->best_tp.rate + 1 || + random.bw > tb->best_tp.bw + 1 || + (random.rate > tb->best_tp.rate && + random.bw > tb->best_tp.bw)) + continue; + + if (tb->current_lookaround_rate_index == random_index) { + tb->current_lookaround_rate_attempts++; + } else { + tb->current_lookaround_rate_attempts = 0; + tb->current_lookaround_rate_index = + random_index; + } + + break; + } + + if (lookaround_fail_count >= LOOKAROUND_FAIL_MAX) { + is_lookaround = false; + tb->current_lookaround_rate_index = tb->best_tp.index; + } else { + lookaround0 = random; + lookaround1 = tb->best_tp; + lookaround_index = 0; + best_index = 1; + } + } + + if (tb->caps.max_rates == 1) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1].rate = MMRC_MCS_UNUSED; + out->rates[2].rate = MMRC_MCS_UNUSED; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else if (tb->caps.max_rates == 2) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->best_prob; + out->rates[2].rate = MMRC_MCS_UNUSED; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else if (tb->caps.max_rates == 3) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp; + out->rates[2] = tb->best_prob; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp; + out->rates[2] = tb->best_prob; + out->rates[3] = tb->baseline; + } + + /* For fallback rates, set RTS/CTS */ + for (i = 1; i < MMRC_MAX_CHAIN_LENGTH; i++) + out->rates[i].flags |= BIT(MMRC_FLAGS_CTS_RTS); + + /* Allocate initial attempts for rate */ + allocate_initial_attempts(out, &rem_time, size); + + /* Calculate and allocate remaining attempts */ + calculate_remaining_attempts(tb, out, &rem_time, size); + + /* Enforce limits on each attempts */ + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + if (out->rates[i].rate != MMRC_MCS_UNUSED) { + out->rates[i].attempts = + out->rates[i].attempts == 0 ? + MMRC_ATTEMPTS_TO_BITFIELD( + MMRC_MIN_CHAIN_ATTEMPTS) : + out->rates[i].attempts; + out->rates[i].attempts = + out->rates[i].attempts > + MMRC_MAX_CHAIN_ATTEMPTS ? + MMRC_ATTEMPTS_TO_BITFIELD( + MMRC_MAX_CHAIN_ATTEMPTS) : + out->rates[i].attempts; + if (i == lookaround_index && + tb->lookaround_wrap != LOOKAROUND_RATE_INIT) + out->rates[i].attempts = + MMRC_ATTEMPTS_TO_BITFIELD(1); + } + } + + /* + * Give the best rate at least 2 attempts to keep peak throughput + * unless it is too low + */ + if (out->rates[best_index].attempts == 1 && + out->rates[best_index].rate > MMRC_MCS1) + out->rates[best_index].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2); + else if (out->rates[best_index].rate <= MMRC_MCS1) + out->rates[best_index].attempts = 1; +} + +static u32 calc_ewma_average(u32 avg, u32 latest, u32 weight) +{ + WARN_ON_ONCE(!(weight <= 100)); + + if (avg == 0) + return latest; + + return ((latest * (100 - weight)) + (avg * weight)) / 100; +} + +static void mmrc_process_variation(struct mmrc_table *tb, u16 current_success, + u32 index) +{ + u32 current_variation; + + /* + * Only process probability variation for the best rate. It is likely + * the only rate to have enough data to see the variation and its + * statistics are more affected because they are usually collected over + * the full period. + */ + if (index != tb->best_tp.index) + return; + + if (current_success == 0) { + if (!tb->unconverged) { + /* + * Best rate is failing completely, go to unconverged + * mode + */ + tb->unconverged = true; + tb->newly_unconverged = true; + } + return; + } + + if (tb->table[index].prob == 0) + return; + + /* Don't process variation while converging after association */ + if (tb->lookaround_wrap == LOOKAROUND_RATE_INIT) + return; + + current_variation = abs(current_success - tb->table[index].prob); + + /* Calculate the EWMA of the probability variation */ + tb->probability_variation = calc_ewma_average( + tb->probability_variation, current_variation, VARIATION_EWMA); + + /* + * Process the variation direction to distinguish converged and + * unconverged scenarios + */ + if (tb->probability_variation >= MODERATE_VARIATION_THRESHOLD || + tb->interference_likely) { + if ((current_success - tb->table[index].prob) * + tb->probability_variation_direction < + 0) + tb->probability_variation_direction = 0; + else if (current_success > tb->table[index].prob) + tb->probability_variation_direction = + min(tb->probability_variation_direction + 1, + MAX_VARIATION_DIRECTION); + else if (current_success < tb->table[index].prob) + tb->probability_variation_direction = + max(tb->probability_variation_direction - 1, + -MAX_VARIATION_DIRECTION); + } + + if (tb->best_rate_cycle_count > VARIATION_DIRECTION_THRESHOLD && + tb->probability_variation >= SIGNIFICANT_VARIATION_THRESHOLD) { + /* + * Only enter interference mode if the best rate is stable for + * enough cycles to determine the direction is random and not + * in one direction only + */ + if (abs(tb->probability_variation_direction) <= + VARIATION_DIRECTION_THRESHOLD && + !tb->interference_likely) { + tb->interference_likely = true; + } + } else if (tb->interference_likely && + (tb->probability_variation <= MINOR_VARIATION_THRESHOLD || + abs(tb->probability_variation_direction) == + MAX_VARIATION_DIRECTION)) { + /* + * Exit interference mode if the variability drops or the + * direction stops being random + */ + tb->interference_likely = false; + } +} + +void mmrc_update(struct mmrc_table *tb) +{ + u32 i; + u16 this_success; + u32 scale; + u32 scaled_ewma; + u32 new_stats = 0; + u32 attempts_for_stats; + u32 success_for_stats; + u32 min_stats; + u32 throughput; + u32 evidence_sent; + + tb->cycle_cnt++; + + /* Allow less minimum stats when converging */ + if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT) + min_stats = STATS_MIN_NORMAL; + else + min_stats = STATS_MIN_INIT; + + for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) { + /* This algorithm is keeping track of the amount of evidence, + * being packets that have been recently sent at this rate. + * This value is smoothed with an EWMA function over time and + * used to update the probability of a rate succeeding + * dynamically. This method allows MMRC to react timely if a + * new rate is used that hasn't been used recently + */ + + /* Necessary to prevent a divide by 0 */ + if (tb->table[i].evidence == 0) + scale = 0; + else + scale = ((tb->table[i].evidence * 2) * 100) / + ((tb->table[i].sent * EVIDENCE_SCALE) + + tb->table[i].evidence); + + /* Restrict scale to appropriate values */ + if (scale > 100) + scale = 100; + + scaled_ewma = scale * EWMA / 100; + + /* + * Only count new packets for evidence if we will process + * them + */ + evidence_sent = + tb->table[i].sent >= min_stats ? tb->table[i].sent : 0; + tb->table[i].evidence = calc_ewma_average( + tb->table[i].evidence, evidence_sent * EVIDENCE_SCALE, + scaled_ewma); + + if (tb->table[i].evidence > EVIDENCE_MAX) + tb->table[i].evidence = EVIDENCE_MAX; + + /* Try to use statistics from acknowledged AMPDUs first */ + attempts_for_stats = tb->table[i].back_mpdu_success + + tb->table[i].back_mpdu_failure; + success_for_stats = tb->table[i].back_mpdu_success; + + /* + * Use the full statistics if rates are not converged or there + * were no AMPDUs for this rate or the remaining attempts are + * less than half of what we have from AMPDUs. + */ + if (!tb->table[i].have_sent_ampdus || tb->unconverged || + attempts_for_stats < AMPDU_STATS_MIN || + (tb->table[i].sent - attempts_for_stats < + attempts_for_stats / 2)) { + attempts_for_stats = tb->table[i].sent; + success_for_stats = tb->table[i].sent_success; + } + + if (attempts_for_stats >= min_stats || + (attempts_for_stats > 0 && tb->table[i].prob > 0)) { + new_stats = 1; + this_success = + (100 * success_for_stats) / attempts_for_stats; + + if (scaled_ewma) + mmrc_process_variation(tb, this_success, i); + + tb->table[i].prob = calc_ewma_average( + tb->table[i].prob, this_success, scaled_ewma); + + /* Clear our sent statistics and update totals */ + tb->table[i].total_sent += tb->table[i].sent; + tb->table[i].sent = 0; + + tb->table[i].total_success += tb->table[i].sent_success; + tb->table[i].sent_success = 0; + + tb->table[i].back_mpdu_failure = 0; + tb->table[i].back_mpdu_success = 0; + tb->table[i].have_sent_ampdus = false; + } + + throughput = calculate_throughput(tb, i); + if (tb->table[i].max_throughput < throughput) + tb->table[i].max_throughput = throughput; + + /* + * Reset the running average windows if reached collector + * limits + */ + if (tb->table[i].sum_throughput > (0xFFFFFFFF - throughput)) { + tb->table[i].sum_throughput /= + tb->table[i].avg_throughput_counter; + tb->table[i].avg_throughput_counter = 1; + } + /* Update the sum and counter so it will be possible later to + * calculate the running average throughput + */ + tb->table[i].sum_throughput += throughput; + tb->table[i].avg_throughput_counter++; + } + + generate_table_priority(tb, new_stats); + + /* + * Switch to faster lookaround mode if rates drop low at very low + * bandwidth or we are in unconverged mode. Switching at low bandwidth + * and rate is to help recover quickly from rates where we would need + * to fragment standard MTU size packets. + */ + if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT && + (tb->unconverged || (tb->best_tp.bw == MMRC_BW_1MHZ && + tb->best_tp.rate <= MMRC_MCS2))) { + tb->lookaround_cnt = 0; + tb->lookaround_wrap = LOOKAROUND_RATE_INIT; + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT; + } + + /* + * If it is unlikely we can do the lookaround attempts in two RC cycles + * choose a new rate + */ + if (tb->current_lookaround_rate_attempts <= + (LOOKAROUND_RATE_ATTEMPTS / 2)) + tb->current_lookaround_rate_attempts = LOOKAROUND_RATE_ATTEMPTS; +} + +void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates, + s32 retry_count, bool was_aggregated) +{ + s32 ind = retry_count; + u32 i; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + rate_update_index(tb, &rates->rates[i]); + tb->table[rates->rates[i].index].have_sent_ampdus |= + was_aggregated; + + if ((s32)rates->rates[i].attempts < ind) { + ind = ind - rates->rates[i].attempts; + tb->table[rates->rates[i].index].sent += + rates->rates[i].attempts; + if (was_aggregated) { + tb->table[rates->rates[i].index] + .back_mpdu_failure += + rates->rates[i].attempts; + } + } else { + tb->table[rates->rates[i].index].sent += ind; + tb->table[rates->rates[i].index].sent_success += 1; + if (was_aggregated) { + tb->table[rates->rates[i].index] + .back_mpdu_success += 1; + tb->table[rates->rates[i].index] + .back_mpdu_failure += + ind > 1 ? ind - 1 : 0; + } + return; + } + } +} + +/* + * Chooses a reasonable starting rate based on range (gathered from + * RSSI measurements) or bandwidth. Then fills out the 3 retry rates + * so a full set of rates is available. + */ +static void mmrc_init_rates(struct mmrc_table *tb, s8 rssi) +{ + tb->best_tp.bw = MMRC_MAX_BW(tb->caps.bandwidth); + if (tb->caps.sgi_per_bw & SGI_PER_BW(tb->best_tp.bw)) + tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_SHORT); + else + tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_LONG); + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS0); + + if (rssi >= MMRC_SHORT_RANGE_RSSI_LIMIT) + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS7); + else if (rssi < MMRC_SHORT_RANGE_RSSI_LIMIT && + rssi >= MMRC_MID_RANGE_RSSI_LIMIT) + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3); + else if (tb->best_tp.bw == MMRC_BW_1MHZ || + tb->best_tp.bw == MMRC_BW_2MHZ) + /* + * To compensate for slow feedback when running with 1 and 2 + * MHz bandwidth, we start from MCS3 which will correspond to + * reasonable feedback and will avoid resetting the rate table + * evidence. + */ + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3); + + tb->best_tp.ss = MMRC_SS_TO_BITFIELD(MMRC_SPATIAL_STREAM_1); + rate_update_index(tb, &tb->best_tp); + /* Init every rate in case they are needed to set the retry rates */ + tb->second_tp = tb->best_tp; + tb->best_prob = tb->best_tp; + tb->baseline = tb->best_tp; + mmrc_fill_retry_rates(tb); +} + +void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps, + s8 rssi) +{ + u32 i; + u16 row_count = rows_from_sta_caps(caps); + + memset(tb, 0, mmrc_memory_required_for_caps(caps)); + memcpy(&tb->caps, caps, sizeof(tb->caps)); + + for (i = 0; i < row_count; i++) { + tb->table[i].prob = RATE_INIT_PROBABILITY; + tb->table[i].evidence = 0; + tb->table[i].sum_throughput = 0; + tb->table[i].avg_throughput_counter = 0; + tb->table[i].max_throughput = 0; + } + + tb->fixed_rate.rate = MMRC_MCS_UNUSED; + tb->cycle_cnt = 0; + tb->last_lookaround_cycle = 0; + tb->lookaround_cnt = 0; + tb->lookaround_wrap = LOOKAROUND_RATE_INIT; + tb->unconverged = true; + tb->newly_unconverged = true; + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT; + tb->baseline = get_rate_row(tb, find_baseline_index(tb)); + mmrc_init_rates(tb, rssi); +} + +bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate) +{ + bool caps_support_rate = true; + + /* Do not accept rate which does not support the STA capabilities */ + if ((BIT(fixed_rate.rate) & tb->caps.rates) == 0 || + (BIT(fixed_rate.bw) & tb->caps.bandwidth) == 0 || + (BIT(fixed_rate.ss) & tb->caps.spatial_streams) == 0 || + (BIT(fixed_rate.guard) & tb->caps.guard) == 0) + caps_support_rate = false; + + if (validate_rate(tb, &fixed_rate) && caps_support_rate) { + tb->fixed_rate = fixed_rate; + rate_update_index(tb, &tb->fixed_rate); + return true; + } + + return false; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.h b/drivers/net/wireless/morsemicro/mm81x/mmrc.h new file mode 100644 index 000000000000..a4c7d941ad55 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_MMRC_H_ +#define _MM81X_MMRC_H_ + +#include +#include +#include +#include +#include +#include + +/* The max length of a retry chain for a single packet transmission */ +#define MMRC_MAX_CHAIN_LENGTH 4 + +/* Rate minimum allowed attempts */ +#define MMRC_MIN_CHAIN_ATTEMPTS 1 + +/* Rate upper limit for attempts */ +#define MMRC_MAX_CHAIN_ATTEMPTS 2 + +/* The frequency of MMRC stat table updates */ +#define MMRC_UPDATE_FREQUENCY_MS 100 + +enum mmrc_flags { + MMRC_FLAGS_CTS_RTS, +}; + +enum mmrc_mcs_rate { + MMRC_MCS0, + MMRC_MCS1, + MMRC_MCS2, + MMRC_MCS3, + MMRC_MCS4, + MMRC_MCS5, + MMRC_MCS6, + MMRC_MCS7, + MMRC_MCS8, + MMRC_MCS9, + MMRC_MCS10, + MMRC_MCS_UNUSED, +}; + +enum mmrc_bw { + MMRC_BW_1MHZ = 0, + MMRC_BW_2MHZ = 1, + MMRC_BW_4MHZ = 2, + MMRC_BW_8MHZ = 3, + MMRC_BW_16MHZ = 4, + MMRC_BW_MAX = 5, +}; + +enum mmrc_spatial_stream { + MMRC_SPATIAL_STREAM_1 = 0, + MMRC_SPATIAL_STREAM_2 = 1, + MMRC_SPATIAL_STREAM_3 = 2, + MMRC_SPATIAL_STREAM_4 = 3, + MMRC_SPATIAL_STREAM_MAX, +}; + +enum mmrc_guard { + MMRC_GUARD_LONG = 0, + MMRC_GUARD_SHORT = 1, + MMRC_GUARD_MAX, +}; + +#define MMRC_RATE_TO_BITFIELD(x) ((x) & 0xF) +#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) & 0x7) +#define MMRC_GUARD_TO_BITFIELD(x) ((x) & 0x1) +#define MMRC_SS_TO_BITFIELD(x) ((x) & 0x3) +#define MMRC_BW_TO_BITFIELD(x) ((x) & 0x7) +#define MMRC_FLAGS_TO_BITFIELD(x) ((x) & 0x7) + +struct mmrc_rate { + u8 rate : 4; + u8 attempts : 3; + u8 guard : 1; + u8 ss : 2; + u8 bw : 3; + u8 flags : 3; + u16 index; +}; + +struct mmrc_rate_table { + struct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH]; +}; + +#define SGI_PER_BW(bw) (1 << (bw)) + +struct mmrc_sta_capabilities { + u8 max_rates : 3; + u8 max_retries : 3; + u8 bandwidth : 5; + u8 spatial_streams : 4; + u16 rates : 11; + u8 guard : 2; + u8 sta_flags : 4; + u8 sgi_per_bw : 5; +}; + +struct mmrc_stats_table { + u32 avg_throughput_counter; + u32 sum_throughput; + u32 max_throughput; + u16 sent; + u16 sent_success; + u16 back_mpdu_success; + u16 back_mpdu_failure; + u32 total_sent; + u32 total_success; + u16 evidence; + u8 prob; + bool have_sent_ampdus; +}; + +struct mmrc_table { + struct mmrc_sta_capabilities caps; + struct mmrc_rate best_tp; + struct mmrc_rate second_tp; + struct mmrc_rate baseline; + struct mmrc_rate best_prob; + struct mmrc_rate fixed_rate; + u32 cycle_cnt; + u32 last_lookaround_cycle; + u8 lookaround_cnt; + + /* The ratio of using normal rate and sampling */ + u8 lookaround_wrap; + + /* + * A counter that is used to determine when we should force a + * lookaround. Should be a portion of the above lookaround with + * less constraints + */ + u8 forced_lookaround; + + u8 current_lookaround_rate_attempts; + u16 current_lookaround_rate_index; + u32 total_lookaround; + + /* + * A counter to detect if the current best rate is optimal + * and may slow down sample frequency. + */ + u32 stability_cnt; + + u32 stability_cnt_threshold; + u8 probability_variation; + + /* The difference in MCS from each of the last 2 rate changes */ + s8 best_rate_diff[2]; + + /* Indication of random versus consistently one-sided variation */ + s8 probability_variation_direction; + + /* Has rate control detected possible interference */ + bool interference_likely; + + /* Has rate control detected the best rate is no longer converged */ + bool unconverged; + + /* Is rate control just entering unconverged state */ + bool newly_unconverged; + + /* + * Number of rate control cycles the best rate has remained + * unchanged + */ + s32 best_rate_cycle_count; + + /* + * The probability table for the STA. This MUST always be the last + * element in the struct. + */ + struct mmrc_stats_table table[]; +}; + +void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps, + s8 rssi); +size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps); +void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out, + size_t size); +void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates, + s32 retry_count, bool was_aggregated); +void mmrc_update(struct mmrc_table *tb); +bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate); +u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate); +u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size); + +#endif /* _MMRC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.c b/drivers/net/wireless/morsemicro/mm81x/ps.c new file mode 100644 index 000000000000..ab67823452ee --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/ps.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include "hif.h" +#include "skbq.h" +#include "mac.h" +#include "bus.h" +#include "ps.h" + +static void mm81x_ps_wakeup(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + + if (!mps->enable || !mps->suspended) + return; + + mm81x_set_bus_enable(mors, true); + mps->suspended = false; +} + +static void mm81x_ps_sleep(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + + if (!mps->enable || mps->suspended) + return; + + mps->suspended = true; + mm81x_set_bus_enable(mors, false); +} + +static void mm81x_ps_evaluate(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + bool needs_wake = false; + unsigned long flags_on_entry = + (mors->hif.event_flags & + ~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND)); + + if (!mps->enable) + return; + + needs_wake = (mps->wakers > 0); + needs_wake |= (flags_on_entry > 0); + needs_wake |= (mm81x_hif_get_tx_buffered_count(mors) > 0); + + if (needs_wake) { + mm81x_ps_wakeup(mps); + return; + } + + mm81x_ps_sleep(mps); +} + +static void mm81x_ps_evaluate_work(struct work_struct *work) +{ + struct mm81x_ps *mps = + container_of(work, struct mm81x_ps, delayed_eval_work.work); + + if (mps->enable) { + mutex_lock(&mps->lock); + mm81x_ps_evaluate(mps); + mutex_unlock(&mps->lock); + } +} + +void mm81x_ps_enable(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mutex_lock(&mps->lock); + if (mps->wakers == 0) { + WARN_ON_ONCE(1); + } else { + mps->wakers--; + mm81x_ps_evaluate(mps); + } + mutex_unlock(&mps->lock); + } +} + +void mm81x_ps_disable(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mutex_lock(&mps->lock); + mps->wakers++; + mm81x_ps_evaluate(mps); + mutex_unlock(&mps->lock); + } +} + +int mm81x_ps_init(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + mps->enable = (mors->bus_type == MM81X_BUS_TYPE_USB); + mps->suspended = true; + mps->wakers = 1; /* we default to being on */ + mutex_init(&mps->lock); + INIT_DELAYED_WORK(&mps->delayed_eval_work, mm81x_ps_evaluate_work); + + return 0; +} + +void mm81x_ps_finish(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mps->enable = false; + cancel_delayed_work_sync(&mps->delayed_eval_work); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.h b/drivers/net/wireless/morsemicro/mm81x/ps.h new file mode 100644 index 000000000000..0b59bb4145ab --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/ps.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_PS_H_ +#define _MM81X_PS_H_ + +#include "core.h" + +/* This should be nominally <= the dynamic ps timeout */ +#define NETWORK_BUS_TIMEOUT_MS (90) + +/* The default period of time to wait to re-evaluate powersave */ +#define DEFAULT_BUS_TIMEOUT_MS (50) + +void mm81x_ps_disable(struct mm81x *mors); +void mm81x_ps_enable(struct mm81x *mors); +int mm81x_ps_init(struct mm81x *mors); +void mm81x_ps_finish(struct mm81x *mors); + +#endif /* !_MM81X_PS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/rate_code.h b/drivers/net/wireless/morsemicro/mm81x/rate_code.h new file mode 100644 index 000000000000..c60fcb9447c4 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rate_code.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_RATE_CODE_H_ +#define _MM81X_RATE_CODE_H_ + +#include + +enum dot11_bandwidth { + DOT11_BANDWIDTH_1MHZ = 0, + DOT11_BANDWIDTH_2MHZ = 1, + DOT11_BANDWIDTH_4MHZ = 2, + DOT11_BANDWIDTH_8MHZ = 3, + DOT11_BANDWIDTH_16MHZ = 4, + + DOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ, + DOT11_INVALID_BANDWIDTH = 5 +}; + +enum mm81x_rate_preamble { + /* S1G LONG format (with SIG-A and SIG-B) */ + MM81X_RATE_PREAMBLE_S1G_LONG = 0, + /* This is the most common format used */ + MM81X_RATE_PREAMBLE_S1G_SHORT = 1, + /* S1G 1M format */ + MM81X_RATE_PREAMBLE_S1G_1M = 2, + + MM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M, + MM81X_RATE_INVALID_PREAMBLE = 7 +}; + +typedef __le32 mm81x_rate_code_t; + +#define MM81X_RATECODE_PREAMBLE (0x0000000F) +#define MM81X_RATECODE_MCS_INDEX (0x000000F0) +#define MM81X_RATECODE_NSS_INDEX (0x00000700) +#define MM81X_RATECODE_BW_INDEX (0x00003800) +#define MM81X_RATECODE_RTS_FLAG (0x00010000) +#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000) +#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000) + +static inline enum mm81x_rate_preamble +mm81x_ratecode_preamble_get(mm81x_rate_code_t rc) +{ + return (enum mm81x_rate_preamble)( + le32_get_bits(rc, MM81X_RATECODE_PREAMBLE)); +} + +static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX); +} + +static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX); +} + +static inline enum dot11_bandwidth +mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc) +{ + return (enum dot11_bandwidth)( + le32_get_bits(rc, MM81X_RATECODE_BW_INDEX)); +} + +static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG); +} + +static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG); +} + +static inline enum dot11_bandwidth +mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc) +{ + return (enum dot11_bandwidth)( + le32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX)); +} + +#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble) \ + (le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) | \ + le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \ + le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \ + le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE)) + +static inline mm81x_rate_code_t +mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index, + enum mm81x_rate_preamble preamble) +{ + return MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble); +} + +static inline void +mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc, + enum mm81x_rate_preamble preamble) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) | + le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE); +} + +static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc, + u32 mcs_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) | + le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX); +} + +static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc, + u32 nss_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) | + le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX); +} + +static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc, + enum dot11_bandwidth bw_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) | + le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX); +} + +static inline void +mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc, + enum dot11_bandwidth bw_index) +{ + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + if (bw_index == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + + mm81x_ratecode_preamble_set(rc, pream); + mm81x_ratecode_bw_index_set(rc, bw_index); +} + +static inline void +mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc, + enum dot11_bandwidth dup_bw_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) | + le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX); +} + +static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc) +{ + *rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG); +} + +static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc) +{ + *rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG); +} + +static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz) +{ + return ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ : + (bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ : + (bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ : + (bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ : + DOT11_BANDWIDTH_2MHZ); +} + +static inline u8 +mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx) +{ + return ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 : + (bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 : + (bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 : + (bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 : + 2); +} + +#endif diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.c b/drivers/net/wireless/morsemicro/mm81x/rc.c new file mode 100644 index 000000000000..28dd293df966 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rc.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include "core.h" +#include "mac.h" +#include "bus.h" +#include "rc.h" + +#define MM81X_RC_BW_TO_MMRC_BW(X) \ + (((X) == 1) ? MMRC_BW_1MHZ : \ + ((X) == 2) ? MMRC_BW_2MHZ : \ + ((X) == 4) ? MMRC_BW_4MHZ : \ + ((X) == 8) ? MMRC_BW_8MHZ : \ + MMRC_BW_2MHZ) + +static void mm81x_rc_work(struct work_struct *work) +{ + struct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work); + struct list_head *pos; + + spin_lock_bh(&mrc->lock); + + list_for_each(pos, &mrc->stas) { + struct mm81x_rc_sta *mrc_sta = + container_of(pos, struct mm81x_rc_sta, list); + unsigned long now = jiffies; + + mrc_sta->last_update = now; + + mmrc_update(mrc_sta->tb); + } + + spin_unlock_bh(&mrc->lock); + + mod_timer(&mrc->timer, jiffies + msecs_to_jiffies(100)); +} + +static void mm81x_rc_timer(struct timer_list *t) +{ + struct mm81x_rc *mrc = timer_container_of(mrc, t, timer); + struct mm81x *mors = mrc->mors; + + queue_work(mors->net_wq, &mors->mrc.work); +} + +void mm81x_rc_init(struct mm81x *mors) +{ + INIT_LIST_HEAD(&mors->mrc.stas); + spin_lock_init(&mors->mrc.lock); + + INIT_WORK(&mors->mrc.work, mm81x_rc_work); + timer_setup(&mors->mrc.timer, mm81x_rc_timer, 0); + + mors->mrc.mors = mors; + mod_timer(&mors->mrc.timer, jiffies + msecs_to_jiffies(100)); +} + +void mm81x_rc_deinit(struct mm81x *mors) +{ + timer_shutdown_sync(&mors->mrc.timer); + cancel_work_sync(&mors->mrc.work); +} + +static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta, + struct mmrc_sta_capabilities *caps) +{ + caps->guard = BIT(MMRC_GUARD_LONG); + + if (caps->bandwidth & BIT(MMRC_BW_1MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_2MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_4MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_8MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } +} + +static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors, + struct mmrc_sta_capabilities *caps, + struct ieee80211_sta_s1g_cap *s1g_cap) +{ + int nss_idx = 0; + u8 rx_mcs = s1g_cap->nss_mcs[0] & 0x3; /* 1SS */ + u8 tx_mcs = (s1g_cap->nss_mcs[2] >> 1) & 0x3; /* 1SS */ + u8 mcs = min(rx_mcs, tx_mcs); + + switch (mcs) { + case IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 -> S1G 9 */ + caps->rates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8); + fallthrough; + case IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -> S1G 7 */ + caps->rates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) | + BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3); + fallthrough; + case IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -> S1G 2 */ + caps->rates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) | + BIT(MMRC_MCS0) | BIT(MMRC_MCS10); + caps->spatial_streams |= (BIT(nss_idx) & 0x0F); + break; + + default: + dev_warn(mors->dev, "Invalid MCS encoding 0x%02x for stream %d", + mcs, nss_idx); + } +} + +int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ieee80211_sta_s1g_cap *s1g_cap = &sta->deflink.s1g_cap; + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mmrc_sta_capabilities caps; + int oper_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + size_t table_mem_size; + struct mmrc_table *tb; + + memset(&caps, 0, sizeof(caps)); + + mm81x_rc_sta_add_s1g_sta_caps(mors, &caps, s1g_cap); + + /* Configure STA for support up to 8MHZ */ + while (oper_bw_mhz > 0) { + caps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz)); + oper_bw_mhz >>= 1; + } + + /* Configure STA for short and long guard */ + mm81x_rc_sta_config_guard_per_bw(sta, &caps); + + /* Set max rates */ + if (mors->hw->max_rates > 0 && + mors->hw->max_rates < IEEE80211_TX_MAX_RATES) + caps.max_rates = mors->hw->max_rates; + else + caps.max_rates = IEEE80211_TX_MAX_RATES; + + /* Set max reties */ + if (mors->hw->max_rate_tries >= MMRC_MIN_CHAIN_ATTEMPTS && + mors->hw->max_rate_tries < MMRC_MAX_CHAIN_ATTEMPTS) + caps.max_retries = mors->hw->max_rate_tries; + else + caps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS; + + WARN_ON(msta->rc.tb); + table_mem_size = mmrc_memory_required_for_caps(&caps); + tb = kzalloc(table_mem_size, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + /* Initialise the STA rate control table */ + mmrc_sta_init(tb, &caps, msta->avg_rssi); + + spin_lock_bh(&mors->mrc.lock); + kfree(msta->rc.tb); + msta->rc.tb = tb; + list_add(&msta->rc.list, &mors->mrc.stas); + msta->rc.last_update = jiffies; + spin_unlock_bh(&mors->mrc.lock); + + return 0; +} + +void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + + spin_lock_bh(&mors->mrc.lock); + if (msta->rc.tb) { + list_del_init(&msta->rc.list); + kfree(msta->rc.tb); + msta->rc.tb = NULL; + } + spin_unlock_bh(&mors->mrc.lock); +} + +static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info, + struct ieee80211_tx_info *info, + int tx_bw) +{ + int i; + enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw); + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + mm81x_ratecode_mcs_index_set(&tx_info->rates[0].mm81x_ratecode, 0); + mm81x_ratecode_nss_index_set(&tx_info->rates[0].mm81x_ratecode, + NSS_TO_NSS_IDX(1)); + mm81x_ratecode_bw_index_set(&tx_info->rates[0].mm81x_ratecode, bw_idx); + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + mm81x_ratecode_preamble_set(&tx_info->rates[0].mm81x_ratecode, pream); + tx_info->rates[0].count = 4; + + for (i = 1; i < IEEE80211_TX_MAX_RATES; i++) + tx_info->rates[i].count = 0; + + info->control.rates[0].idx = 0; + info->control.rates[0].count = tx_info->rates[0].count; + info->control.rates[0].flags = 0; + info->control.rates[1].idx = -1; +} + +static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta, + struct mmrc_rate_table *rates, size_t size) +{ + int ret = -ENOENT; + struct list_head *pos; + + spin_lock_bh(&mors->mrc.lock); + list_for_each(pos, &mors->mrc.stas) { + struct mm81x_rc_sta *mrc_sta = + list_entry(pos, struct mm81x_rc_sta, list); + + if (&msta->rc == mrc_sta) { + ret = 0; + mmrc_get_rates(msta->rc.tb, rates, size); + break; + } + } + spin_unlock_bh(&mors->mrc.lock); + + return ret; +} + +static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta, + struct sk_buff *skb, + struct ieee80211_hdr *hdr) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!sta) + return true; + + if (ieee80211_is_qos_nullfunc(hdr->frame_control) || + ieee80211_is_nullfunc(hdr->frame_control)) + return true; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return true; + + /* Use basic rates for EAPOL exchanges or when instructed */ + if (unlikely((skb->protocol == cpu_to_be16(ETH_P_PAE) || + info->flags & IEEE80211_TX_CTL_USE_MINRATE))) + return true; + + return false; +} + +void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_sta *sta, + int tx_bw, bool rts_allowed) +{ + int ret, i; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct mm81x_sta *msta; + struct mmrc_rate_table rates; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + BUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ || + MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ || + MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ || + MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ)); + + memset(&info->control.rates, 0, sizeof(info->control.rates)); + memset(&info->status.rates, 0, sizeof(info->status.rates)); + mm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw); + + /* Use basic rates for non data packets */ + if (mm81x_rc_use_basic_rates(sta, skb, hdr)) + return; + + msta = (struct mm81x_sta *)sta->drv_priv; + if (!msta) + return; + + ret = mm81x_rc_sta_get_rates(mors, msta, &rates, skb->len); + if (ret != 0) + return; + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + info->control.rates[i].flags = 0; + if (rates.rates[i].rate != MMRC_MCS_UNUSED) { + u8 mcs = rates.rates[i].rate; + u8 nss_index = rates.rates[i].ss; + enum dot11_bandwidth bw_idx = + (enum dot11_bandwidth)rates.rates[i].bw; + enum mm81x_rate_preamble pream = + MM81X_RATE_PREAMBLE_S1G_SHORT; + + mm81x_ratecode_bw_index_set( + &tx_info->rates[i].mm81x_ratecode, bw_idx); + mm81x_ratecode_mcs_index_set( + &tx_info->rates[i].mm81x_ratecode, mcs); + mm81x_ratecode_nss_index_set( + &tx_info->rates[i].mm81x_ratecode, nss_index); + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + mm81x_ratecode_preamble_set( + &tx_info->rates[i].mm81x_ratecode, pream); + tx_info->rates[i].count = rates.rates[i].attempts; + + if (rts_allowed && + (rates.rates[i].flags & BIT(MMRC_FLAGS_CTS_RTS))) { + mm81x_ratecode_enable_rts( + &tx_info->rates[i].mm81x_ratecode); + info->control.rates[i].flags |= + IEEE80211_TX_RC_USE_RTS_CTS; + } + + if (rates.rates[i].guard == MMRC_GUARD_SHORT) { + mm81x_ratecode_enable_sgi( + &tx_info->rates[i].mm81x_ratecode); + info->control.rates[i].flags |= + IEEE80211_TX_RC_SHORT_GI; + } + + /* Update skb tx_info */ + info->control.rates[i].idx = rates.rates[i].rate; + info->control.rates[i].count = rates.rates[i].attempts; + } else { + info->control.rates[i].idx = -1; + info->control.rates[i].count = 0; + tx_info->rates[i].count = 0; + } + } +} + +static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta, + struct mmrc_rate_table *rates, int attempts, + bool was_aggregated) +{ + struct list_head *pos; + + spin_lock_bh(&mors->mrc.lock); + list_for_each(pos, &mors->mrc.stas) { + struct mm81x_rc_sta *mrc_sta = + list_entry(pos, struct mm81x_rc_sta, list); + + if (&msta->rc == mrc_sta) { + mmrc_feedback(msta->rc.tb, rates, attempts, + was_aggregated); + break; + } + } + spin_unlock_bh(&mors->mrc.lock); +} + +void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mm81x_skb_tx_status *tx_sts, + int attempts) +{ + int i; + u32 tx_airtime = 0; + struct mmrc_rate_table rates; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *r = &txi->status.rates[0]; + int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES); + struct mm81x_sta *msta = msta = (struct mm81x_sta *)sta->drv_priv; + + /* Don't update rate info if basic rates were used */ + if (mm81x_rc_use_basic_rates(sta, skb, hdr)) + goto exit; + + if (attempts <= 0) + /* Did we really send the packet? */ + goto exit; + + for (i = 0; i < count; i++) { + rates.rates[i].rate = mm81x_ratecode_mcs_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].ss = mm81x_ratecode_nss_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].guard = + mm81x_ratecode_sgi_get(tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].bw = mm81x_ratecode_bw_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].flags = + mm81x_ratecode_rts_get(tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].attempts = tx_sts->rates[i].count; + + tx_airtime += + mmrc_calculate_rate_tx_time(&rates.rates[i], skb->len); + } + + if (msta) { + /* + * Save the rate information. This will be used to update + * station's tx rate stats + */ + msta->last_sta_tx_rate.bw = rates.rates[0].bw; + msta->last_sta_tx_rate.rate = rates.rates[0].rate; + msta->last_sta_tx_rate.ss = rates.rates[0].ss; + msta->last_sta_tx_rate.guard = rates.rates[0].guard; + } + + mm81x_rc_sta_set_rates(mors, msta, &rates, attempts, + !!(le32_to_cpu(tx_sts->flags) & + MM81X_TX_STATUS_WAS_AGGREGATED)); + + ieee80211_sta_register_airtime(sta, tx_sts->tid, tx_airtime, 0); + +exit: + ieee80211_tx_info_clear_status(txi); + + if (!(le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_NO_ACK) && + !(txi->flags & IEEE80211_TX_CTL_NO_ACK)) + txi->flags |= IEEE80211_TX_STAT_ACK; + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED) { + txi->flags |= IEEE80211_TX_STAT_TX_FILTERED; + + /* + * Clear TX CTL AMPDU flag so that this frame gets rescheduled + * in ieee80211_handle_filtered_frame(). This flag will get set + * again by mac80211's tx path on rescheduling. + */ + txi->flags &= ~IEEE80211_TX_CTL_AMPDU; + if (msta) { + if (!msta->tx_ps_filter_en) + dev_dbg(mors->dev, "TX ps filter set sta[%pM]", + msta->addr); + msta->tx_ps_filter_en = true; + } + } + + for (i = 0; i < count; i++) { + if (tx_sts->rates[i].count > 0) { + r[i].count = tx_sts->rates[i].count; + r[i].flags |= IEEE80211_TX_RC_MCS; + } else { + r[i].idx = -1; + } + } + + /* single packet per A-MPDU (for now) */ + if (txi->flags & IEEE80211_TX_CTL_AMPDU) { + txi->flags |= IEEE80211_TX_STAT_AMPDU; + txi->status.ampdu_len = 1; + txi->status.ampdu_ack_len = + txi->flags & IEEE80211_TX_STAT_ACK ? 1 : 0; + } + + /* + * Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is + * over + */ + if (sta && (txi->flags & IEEE80211_TX_STATUS_EOSP)) { + txi->flags &= ~IEEE80211_TX_STATUS_EOSP; + ieee80211_sta_eosp(sta); + } +} + +void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + + /* Add to Morse RC STA list */ + if (old_state < new_state && new_state == IEEE80211_STA_ASSOC) { + /* Newly associated, add to RC */ + mm81x_rc_sta_add(mors, vif, sta); + } else if (old_state > new_state && (old_state == IEEE80211_STA_ASSOC || + old_state == IEEE80211_STA_AUTH)) { + /* Lost or failed association; remove from list */ + mm81x_rc_sta_remove(mors, sta); + } else if (old_state < new_state && old_state == IEEE80211_STA_NONE && + msta->rc.list.prev) { + /* + * Special case for driver warning issue causing a sta to be + * left on the list + */ + dev_dbg(mors->dev, "Remove stale sta from rc list"); + mm81x_rc_sta_remove(mors, sta); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.h b/drivers/net/wireless/morsemicro/mm81x/rc.h new file mode 100644 index 000000000000..53f129024408 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_RC_H_ +#define _MM81X_RC_H_ + +#include +#include +#include "core.h" +#include "mmrc.h" + +struct mm81x_vif; + +#define INIT_MAX_RATES_NUM 4 + +struct mm81x_rc { + /* Serialise rate control queue manipulation and timer functions */ + spinlock_t lock; + struct list_head stas; + struct timer_list timer; + struct work_struct work; + struct mm81x *mors; +}; + +struct mm81x_rc_sta { + struct mmrc_table *tb; + struct list_head list; + unsigned long last_update; +}; + +void mm81x_rc_init(struct mm81x *mors); +void mm81x_rc_deinit(struct mm81x *mors); +int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta); +void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_sta *sta, + int tx_bw, bool rts_allowed); +void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mm81x_skb_tx_status *tx_sts, + int tx_attempts); +void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state); + +#endif /* !_MM81X_RC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/sdio.c b/drivers/net/wireless/morsemicro/mm81x/sdio.c new file mode 100644 index 000000000000..96fce187dd35 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/sdio.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hw.h" +#include "core.h" +#include "bus.h" +#include "mac.h" +#include "fw.h" +#include "hif.h" + +/* + * Value to indicate that the base address for bulk/register + * read/writes has yet to be set + */ +#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF + +#define MM81X_SDIO_ALIGNMENT (8) + +#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000 +#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE +#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1) +#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2) + +struct mm81x_sdio { + bool enabled; + u32 bulk_addr_base; + u32 register_addr_base; + struct sdio_func *func; + const struct sdio_device_id *id; +}; + +static void irq_handler(struct sdio_func *func1) +{ + struct sdio_func *func = func1->card->sdio_func[1]; + struct mm81x *mors = sdio_get_drvdata(func); + + mm81x_hw_irq_handle(mors); +} + +static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio) +{ + int ret; + struct sdio_func *func = sdio->func; + struct sdio_func *func1 = func->card->sdio_func[0]; + struct mm81x *mors = sdio_get_drvdata(func); + + sdio_claim_host(func); + ret = sdio_claim_irq(func1, irq_handler); + if (ret) + dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret); + + sdio_release_host(func); + return ret; +} + +static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio) +{ + struct sdio_func *func = sdio->func; + struct sdio_func *func1 = func->card->sdio_func[0]; + + sdio_claim_host(func); + sdio_release_irq(func1); + sdio_release_host(func); +} + +static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + + if (enable) + mm81x_sdio_enable_irq(sdio); + else + mm81x_sdio_disable_irq(sdio); +} + +static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access) +{ + return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3); +} + +static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio) +{ + sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; + sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; +} + +static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio, + struct sdio_func *func, u32 address, + u8 access) +{ + int ret = 0; + int retries = 0; + static const int max_retries = 3; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + s32 calculated_addr_base = + mm81x_sdio_calculate_base_address(address, access); + u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base : + &sdio->register_addr_base; + + if ((*current_addr_base) == calculated_addr_base && + *current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET) + return ret; + +retry: + sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)), + MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret); + if (ret) + goto err; + + sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)), + MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret); + if (ret) + goto err; + + sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret); + if (ret) + goto err; + + *current_addr_base = calculated_addr_base; + if (retries) + dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__, + retries); + + return ret; +err: + retries++; + if (ret == -ETIMEDOUT && retries <= max_retries) { + dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n", + __func__, ret, retries, max_retries); + goto retry; + } + + *current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; + return ret; +} + +static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int ret; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + + mm81x_sdio_set_func_address_base(sdio, func2, address, + MM81X_CONFIG_ACCESS_4BYTE); + if (unlikely(!IS_ALIGNED((uintptr_t)data, + mors->bus_ops->bulk_alignment))) { + ret = -EBADE; + goto exit; + } + + address &= 0x0000FFFF; /* remove base and keep offset */ + ret = sdio_memcpy_toio(func2, address, data, size); + if (ret) + goto exit; + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int i, ret; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_1BYTE); + + address &= 0x0000FFFF; /* remove base and keep offset */ + for (i = 0; i < size; i++) { + sdio_writeb(func1, data[i], address + i, (int *)&ret); + if (ret) + goto exit; + } + + ret = size; +exit: + return ret; +} + +static void mm81x_sdio_claim_host(struct mm81x *mors) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + + sdio_claim_host(func); +} + +static void mm81x_sdio_release_host(struct mm81x *mors) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + + sdio_release_host(func); +} + +static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int ret; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + + mm81x_sdio_set_func_address_base(sdio, func2, address, + MM81X_CONFIG_ACCESS_4BYTE); + if (unlikely(!IS_ALIGNED((uintptr_t)data, + mors->bus_ops->bulk_alignment))) { + ret = -EBADE; + goto exit; + } + + address &= 0x0000FFFF; /* remove base and keep offset */ + ret = sdio_memcpy_fromio(func2, data, address, size); + if (ret) + goto exit; + + /* + * Observed sometimes that SDIO read repeats the first 4-bytes + * word twice, overwriting second word (hence, tail will be + * overwritten with 'sync' byte). When this happens, reading + * will fetch the correct word. NB: if repeated again, pass it + * anyway and upper layers will handle it + */ + + if (size >= 8 && memcmp(data, data + 4, 4) == 0) + sdio_memcpy_fromio(func2, data, address, 8); + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int i, ret; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_1BYTE); + + address &= 0x0000FFFF; /* remove base and keep offset */ + for (i = 0; i < size; i++) { + data[i] = sdio_readb(func1, address + i, (int *)&ret); + if (ret) + goto exit; + } + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data, + int len) +{ + int ret = 0; + int block_len, byte_len; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + int remaining = len; + int offset = 0; + + if (remaining > 0 && address & 0x3) { + len = 4 - (address & 0x3); + ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len); + if (ret != len) + return -EIO; + + offset += len; + remaining -= len; + } + + while ((remaining) > 0) { + /* + * We can only write up to the end of a single window in + * each write operation. + */ + u32 window_end = (address + offset) | + ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK; + + len = min(remaining, (int)(window_end + 1 - address - offset)); + block_len = len & ~0x3; + byte_len = len & 0x3; + + if (block_len) { + ret = mm81x_sdio_mem_write_block(sdio, address + offset, + (u8 *)(data + offset), + block_len); + if (ret != block_len) + return -EIO; + + offset += block_len; + } + + if (byte_len) { + ret = mm81x_sdio_mem_write_byte(sdio, address + offset, + (u8 *)(data + offset), + byte_len); + if (ret != byte_len) + return -EIO; + + offset += byte_len; + } + + remaining -= len; + } + + return 0; +} + +static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data, + int len) +{ + int ret = 0; + int block_len, byte_len; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + int remaining = len; + int offset = 0; + + if (remaining > 0 && address & 0x3) { + len = 4 - (address & 0x3); + ret = mm81x_sdio_mem_read_byte(sdio, address, data, len); + if (ret != len) + return -EIO; + + offset += len; + remaining -= len; + } + + while (remaining > 0) { + /* + * We can only read up to the end of a single window in + * each read operation. + */ + u32 window_end = (address + offset) | + ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK; + + len = min(remaining, (int)(window_end + 1 - address - offset)); + block_len = len & ~0x3; + byte_len = len & 0x3; + + if (block_len) { + ret = mm81x_sdio_mem_read_block(sdio, address + offset, + data + offset, + block_len); + if (ret != block_len) + return -EIO; + + offset += block_len; + } + + if (byte_len) { + ret = mm81x_sdio_mem_read_byte(sdio, address + offset, + data + offset, byte_len); + if (ret != byte_len) + return -EIO; + + offset += byte_len; + } + + remaining -= len; + } + + return 0; +} + +static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val) +{ + ssize_t ret = 0; + u32 original_address = address; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_4BYTE); + + address &= 0x0000FFFF; + sdio_writel(func1, (__force u32)cpu_to_le32(val), + (__force u32)cpu_to_le32(address), (int *)&ret); + if (ret) + goto error; + + return 0; + +error: + if (original_address == MM81X_REG_RESET(mors) && + val == MM81X_REG_RESET_VALUE(mors)) { + dev_dbg(mors->dev, + "SDIO reset detected, invalidating base addr\n"); + mm81x_sdio_reset_base_address(sdio); + } + + return -EIO; +} + +static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val) +{ + u32 value; + ssize_t ret = 0; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_4BYTE); + + address &= 0x0000FFFF; + value = sdio_readl(func1, (__force u32)cpu_to_le32(address), + (int *)&ret); + if (ret) + return ret; + + *val = le32_to_cpup((__le32 *)&value); + return 0; +} + +static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + struct mmc_host *host = func->card->host; + + sdio_claim_host(func); + + if (enable) { + /* + * No need to do anything special to re-enable the sdio bus. + * This will happen automatically when a read/write is + * attempted and sdio->bulk_addr_base == 0. + */ + sdio->enabled = true; + host->ops->enable_sdio_irq(host, 1); + dev_dbg(mors->dev, "%s: enabling bus\n", __func__); + } else { + host->ops->enable_sdio_irq(host, 0); + mm81x_sdio_reset_base_address(sdio); + sdio->enabled = false; + dev_dbg(mors->dev, "%s: disabling bus\n", __func__); + } + + sdio_release_host(func); +} + +static void mm81x_sdio_reset(struct sdio_func *func) +{ + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + mdelay(20); + + sdio_claim_host(func); + sdio_disable_func(func); + mmc_hw_reset(func->card); + sdio_enable_func(func); + sdio_release_host(func); +} + +static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst) +{ + u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 : + SDIO_WORD_BURST_DISABLE; + + mm81x_hw_enable_burst_mode(mors, burst_mode); +} + +static const struct mm81x_bus_ops mm81x_sdio_ops = { + .dm_read = mm81x_sdio_dm_read, + .dm_write = mm81x_sdio_dm_write, + .reg32_read = mm81x_sdio_reg32_read, + .reg32_write = mm81x_sdio_reg32_write, + .set_bus_enable = mm81x_sdio_bus_enable, + .claim = mm81x_sdio_claim_host, + .release = mm81x_sdio_release_host, + .config_burst_mode = mm81x_sdio_config_burst_mode, + .set_irq = mm81x_sdio_set_irq, + .bulk_alignment = MM81X_SDIO_ALIGNMENT +}; + +static int mm81x_sdio_enable(struct mm81x_sdio *sdio) +{ + int ret; + struct sdio_func *func = sdio->func; + struct mm81x *mors = sdio_get_drvdata(func); + + sdio_claim_host(func); + ret = sdio_enable_func(func); + if (ret) + dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret); + sdio_release_host(func); + return ret; +} + +static void mm81x_sdio_release(struct mm81x_sdio *sdio) +{ + struct sdio_func *func = sdio->func; + + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); +} + +static int mm81x_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret = 0; + struct mm81x *mors = NULL; + struct mm81x_sdio *sdio; + struct device *dev = &func->dev; + + if (func->num == 1) + return 0; + + if (func->num != 2) + return -ENODEV; + + mors = mm81x_core_alloc(sizeof(*sdio), dev); + if (!mors) + return -ENOMEM; + + mors->bus_ops = &mm81x_sdio_ops; + mors->bus_type = MM81X_BUS_TYPE_SDIO; + + sdio = (struct mm81x_sdio *)mors->drv_priv; + sdio->func = func; + sdio->id = id; + sdio->enabled = true; + mm81x_sdio_reset_base_address(sdio); + + sdio_set_drvdata(func, mors); + + ret = mm81x_sdio_enable(sdio); + if (ret) + goto err_core_free; + + mm81x_sdio_config_burst_mode(mors, true); + + ret = mm81x_core_init(mors); + if (ret) + goto err_sdio_release; + + ret = mm81x_sdio_enable_irq(sdio); + if (ret) + goto err_core_deinit; + + ret = mm81x_core_register(mors); + if (ret) + goto err_disable_irq; + + return 0; + +err_disable_irq: + mm81x_sdio_disable_irq(sdio); +err_core_deinit: + mm81x_core_deinit(mors); +err_sdio_release: + mm81x_sdio_release(sdio); +err_core_free: + mm81x_core_free(mors); + return ret; +} + +static void mm81x_sdio_remove(struct sdio_func *func) +{ + struct mm81x *mors = sdio_get_drvdata(func); + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + + if (!mors) + return; + + mm81x_core_unregister(mors); + mm81x_sdio_disable_irq(sdio); + mm81x_core_deinit(mors); + mm81x_sdio_release(sdio); + mm81x_sdio_reset(func); + mm81x_core_free(mors); + sdio_set_drvdata(func, NULL); +} + +static const struct sdio_device_id mm81x_sdio_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO, + SDIO_DEVICE_ID_MORSEMICRO_MM8108) }, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices); + +static struct sdio_driver mm81x_sdio_driver = { + .name = "mm81x_sdio", + .id_table = mm81x_sdio_devices, + .probe = mm81x_sdio_probe, + .remove = mm81x_sdio_remove, +}; + +module_sdio_driver(mm81x_sdio_driver); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.c b/drivers/net/wireless/morsemicro/mm81x/skbq.c new file mode 100644 index 000000000000..25655bd56d14 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/skbq.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "skbq.h" +#include "mac.h" +#include "command.h" +#include "bus.h" + +/* Returns number of bytes needed to word align */ +#define BYTES_NEEDED_TO_WORD_ALIGN(bytes) \ + ((bytes) & 0x3 ? (4 - ((bytes) & 0x3)) : 0) + +/* Rounds down to the nearest word boundary */ +#define ROUND_DOWN_TO_WORD(bytes) \ + (BYTES_NEEDED_TO_WORD_ALIGN(bytes) ? \ + bytes - (4 - BYTES_NEEDED_TO_WORD_ALIGN(bytes)) : \ + bytes) + +#define MM81X_SKBQ_MAX_TXQ_LEN 32 +#define MM81X_SKBQ_TX_QUEUED_LIFETIME_MS 1000 +#define MM81X_SKBQ_TX_STATUS_LIFETIME_MS (15 * 1000) + +/* Returns padding needed to align x up to a 4-byte boundary */ +#define MM81X_PAD4(x) (((x) & 0x3) ? (4 - ((x) & 0x3)) : 0) + +struct mm81x_tx_status_priv { + /* + * Time (jiffies) at which this packet has spent too long the pending + * queue, waiting for status notification from the firmware, and + * should be considered lost. + */ + unsigned long tx_status_expiry; +}; + +static struct mm81x_tx_status_priv * +__mm81x_skbq_tx_status_priv(struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + + BUILD_BUG_ON(sizeof(struct mm81x_tx_status_priv) > + sizeof(tx_info->status.status_driver_data)); + return (struct mm81x_tx_status_priv *)&tx_info->status + .status_driver_data[0]; +} + +static bool __mm81x_skbq_has_pending_tx_skb_timed_out(struct sk_buff *skb) +{ + struct mm81x_tx_status_priv *info = __mm81x_skbq_tx_status_priv(skb); + + /* If our timestamp value is in the past then we have timed out. */ + return time_is_before_jiffies(info->tx_status_expiry); +} + +static u32 __mm81x_skbq_size(const struct mm81x_skbq *mq) +{ + return mq->skbq_size; +} + +static u32 __mm81x_skbq_space(const struct mm81x_skbq *mq) +{ + return MM81X_SKBQ_SIZE - __mm81x_skbq_size(mq); +} + +static bool __mm81x_skbq_over_threshold(struct mm81x_skbq *mq) +{ + return skb_queue_len(&mq->skbq) >= MM81X_SKBQ_MAX_TXQ_LEN; +} + +static bool __mm81x_skbq_under_threshold(struct mm81x_skbq *mq) +{ + return skb_queue_len(&mq->skbq) < (MM81X_SKBQ_MAX_TXQ_LEN - 2); +} + +static void __mm81x_skbq_unlink(struct mm81x_skbq *mq, + struct sk_buff_head *queue, struct sk_buff *skb) +{ + if (queue == &mq->skbq) { + WARN_ON(skb->len > mq->skbq_size); + mq->skbq_size -= min(skb->len, mq->skbq_size); + } + + __skb_unlink(skb, queue); +} + +static int __mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff_head *queue, + struct sk_buff *skb, bool queue_at_head, + struct sk_buff *queue_before) +{ + /* Limit the size of the Tx queue, but not the pending queue */ + if (queue == &mq->skbq) { + if (skb->len > __mm81x_skbq_space(mq)) + return -ENOMEM; + + mq->skbq_size += skb->len; + } + + if (queue_before) + __skb_queue_before(queue, queue_before, skb); + else if (queue_at_head) + __skb_queue_head(queue, skb); + else + __skb_queue_tail(queue, skb); + + return 0; +} + +static void __mm81x_skbq_pkt_id(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + struct mm81x_skb_hdr *hdr = (struct mm81x_skb_hdr *)skb->data; + + hdr->tx_info.pkt_id = cpu_to_le32(mq->pkt_seq++); +} + +static struct mm81x_skbq * +__mm81x_skbq_tx_status_to_skbq(struct mm81x *mors, + const struct mm81x_skb_tx_status *tx_sts) +{ + int aci; + struct mm81x_skbq *mq = NULL; + + switch (tx_sts->channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_DATA_NOACK: + aci = dot11_tid_to_ac(tx_sts->tid); + mq = mm81x_hif_get_tx_data_queue(mors, aci); + break; + case MM81X_SKB_CHAN_MGMT: + mq = mm81x_hif_get_tx_mgmt_queue(mors); + break; + case MM81X_SKB_CHAN_BEACON: + mq = mm81x_hif_get_tx_beacon_queue(mors); + break; + default: + dev_err(mors->dev, + "unexpected channel on reported tx status [%d]", + tx_sts->channel); + } + + return mq; +} + +void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb) +{ + skb_pull(skb, sizeof(struct mm81x_skb_hdr) + + ((struct mm81x_skb_hdr *)skb->data)->offset); +} + +static void mm81x_skbq_insert_pending(struct mm81x_skbq *mq, + struct sk_buff *skb, __le32 insertion_id) +{ + struct sk_buff *pfirst, *pnext; + struct mm81x_skb_hdr *mhdr; + struct sk_buff *tail = skb_peek_tail(&mq->skbq); + + __mm81x_skbq_unlink(mq, &mq->pending, skb); + + if (!tail) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + return; + } + + /* Check if it should just be inserted on to the end */ + mhdr = (struct mm81x_skb_hdr *)tail->data; + WARN_ON(insertion_id == mhdr->tx_info.pkt_id); + if (le32_to_cpu(insertion_id) >= le32_to_cpu(mhdr->tx_info.pkt_id)) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + return; + } + + /* Otherwise, re-insert to correct spot in skbq */ + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + mhdr = (struct mm81x_skb_hdr *)pfirst->data; + + WARN_ON(insertion_id == mhdr->tx_info.pkt_id); + if (le32_to_cpu(insertion_id) <= + le32_to_cpu(mhdr->tx_info.pkt_id)) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, pfirst); + return; + } + } + + WARN_ON_ONCE(1); +} + +static void mm81x_skbq_sta_eosp(struct mm81x *mors, struct sk_buff *skb) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = txi->control.vif; + + mm81x_skbq_pull_hdr_post_tx(skb); + + /* + * If this frame is the last frame in a PS-Poll or u-APSD SP, + * then mac80211 must be informed that the SP is now over. + */ + if (txi->flags & IEEE80211_TX_STATUS_EOSP) { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_sta *sta; + + scoped_guard(rcu) { + sta = ieee80211_find_sta(vif, hdr->addr1); + if (sta) + ieee80211_sta_eosp(sta); + } + } +} + +static void __mm81x_skbq_drop_pending_skb(struct mm81x_skbq *mq, + struct sk_buff *skb) +{ + __mm81x_skbq_unlink(mq, &mq->pending, skb); + mm81x_skbq_sta_eosp(mq->mors, skb); + ieee80211_free_txskb(mq->mors->hw, skb); +} + +static bool mm81x_tx_h_is_ps_filtered(struct mm81x_skbq *mq, + struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = txi->control.vif; + + WARN_ON_ONCE(!(le32_to_cpu(tx_sts->flags) & + MM81X_TX_STATUS_FLAGS_PS_FILTERED)); + + if (vif->type == NL80211_IFTYPE_AP) { + __mm81x_skbq_drop_pending_skb(mq, skb); + return true; + } + + if (vif->type == NL80211_IFTYPE_STATION) { + mm81x_skbq_insert_pending(mq, skb, tx_sts->pkt_id); + return true; + } + + return false; +} + +/* + * Get a pending frame by its ID. This will also drop frames with + * older packet ids that are in the list + */ +static struct sk_buff *__mm81x_skbq_get_pending_by_id(struct mm81x *mors, + struct mm81x_skbq *mq, + u32 pkt_id) +{ + struct sk_buff *pfirst, *pnext; + struct sk_buff *ret = NULL; + + /* Move sent packets to pending list waiting for feedback */ + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + struct mm81x_skb_hdr *hdr = + (struct mm81x_skb_hdr *)pfirst->data; + + if (le32_to_cpu(hdr->tx_info.pkt_id) == pkt_id) { + ret = pfirst; + break; + + } else if (le32_to_cpu(hdr->tx_info.pkt_id) < pkt_id && + __mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) { + __mm81x_skbq_drop_pending_skb(mq, pfirst); + } + } + + return ret; +} + +static void mm81x_skbq_check_tx_empty(struct mm81x *mors, struct mm81x_skbq *mq) +{ + lockdep_assert_held(&mq->lock); + + if (mq->flags & MM81X_HIF_FLAGS_BEACON) + return; + + if (skb_queue_len(&mq->skbq) + skb_queue_len(&mq->pending) == 0) + wake_up(&mq->mors->tx_empty_waitq); +} + +static void __mm81x_skbq_tx_status_process(struct mm81x *mors, + struct mm81x_skbq *mq, + struct mm81x_skb_tx_status *tx_sts) +{ + struct sk_buff *skb; + + lockdep_assert_held(&mq->lock); + + skb = __mm81x_skbq_get_pending_by_id(mors, mq, + le32_to_cpu(tx_sts->pkt_id)); + if (!skb) { + dev_dbg(mors->dev, + "No pending pkt match found [pktid:%d chan:%d]", + tx_sts->pkt_id, tx_sts->channel); + goto out; + } + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_PAGE_INVALID) { + __mm81x_skbq_drop_pending_skb(mq, skb); + goto out; + } + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED && + mm81x_tx_h_is_ps_filtered(mq, skb, tx_sts)) + /* Has been consumed by mm81x_tx_h_is_ps_filtered */ + goto out; + + mm81x_skbq_pull_hdr_post_tx(skb); + mm81x_skbq_skb_finish(mq, skb, tx_sts); + +out: + mm81x_skbq_check_tx_empty(mors, mq); +} + +static void mm81x_skbq_tx_status_process(struct mm81x *mors, + struct sk_buff *skb) +{ + int i; + struct mm81x_skb_tx_status *tx_sts = + (struct mm81x_skb_tx_status *)skb->data; + int count = skb->len / sizeof(*tx_sts); + + for (i = 0; i < count; tx_sts++, i++) { + struct mm81x_skbq *mq = + __mm81x_skbq_tx_status_to_skbq(mors, tx_sts); + + if (mq) { + spin_lock_bh(&mq->lock); + __mm81x_skbq_tx_status_process(mors, mq, tx_sts); + spin_unlock_bh(&mq->lock); + } + } + + if (mors->ps.enable && !mors->ps.suspended && + (mm81x_hif_get_tx_buffered_count(mors) == 0)) { + /* Evaluate ps, check if it was gated on a pending tx status */ + queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work, + 0); + } +} + +static void mm81x_skbq_dispatch_work(struct work_struct *dispatch_work) +{ + struct mm81x_skbq *mq = + container_of(dispatch_work, struct mm81x_skbq, dispatch_work); + struct mm81x *mors = mq->mors; + struct mm81x_skb_hdr *hdr; + struct sk_buff_head skbq; + struct sk_buff *pfirst, *pnext; + u8 channel; + + __skb_queue_head_init(&skbq); + + mm81x_skbq_deq_num_skb(mq, &skbq, mm81x_skbq_count(mq)); + + skb_queue_walk_safe(&skbq, pfirst, pnext) { + __skb_unlink(pfirst, &skbq); + /* Header endianness has already be adjusted */ + hdr = (struct mm81x_skb_hdr *)pfirst->data; + channel = hdr->channel; + /* Remove mm81x header and padding */ + __skb_pull(pfirst, sizeof(*hdr) + hdr->offset); + + switch (channel) { + case MM81X_SKB_CHAN_COMMAND: + mm81x_cmd_resp_process(mors, pfirst); + break; + case MM81X_SKB_CHAN_TX_STATUS: + mm81x_skbq_tx_status_process(mors, pfirst); + dev_kfree_skb_any(pfirst); + break; + default: + mm81x_mac_rx_skb(mors, pfirst, &hdr->rx_status); + break; + } + } + + if (mm81x_skbq_count(mq)) + queue_work(mors->net_wq, &mq->dispatch_work); +} + +int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + int ret; + + spin_lock_bh(&mq->lock); + ret = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + spin_unlock_bh(&mq->lock); + return ret; +} + +static void mm81x_skbq_set_queued_tx_skb_expiry(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + + if (ieee80211_is_probe_req(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_auth(hdr->frame_control)) { + txi->control.enqueue_time = (u32)jiffies; + } else { + txi->control.enqueue_time = 0; + } +} + +static bool mm81x_skbq_has_queued_tx_skb_expired(struct sk_buff *skb) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + + if (txi->control.enqueue_time > 0) { + u32 expiry_time = + txi->control.enqueue_time + + msecs_to_jiffies(MM81X_SKBQ_TX_QUEUED_LIFETIME_MS); + + return (s32)((u32)jiffies - expiry_time) > 0; + } + + return false; +} + +/* + * Drop selected frames (those with an expiry time set) that could not + * be sent within a reasonable timeframe due to congestion. These would + * only be rejected or ignored by the peer, so are only contributing to + * the problem. + */ +void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst; + struct sk_buff *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + if (!mm81x_skbq_has_queued_tx_skb_expired(pfirst)) + break; + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + ieee80211_free_txskb(mors->hw, pfirst); + } + + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + struct sk_buff *skb; + + spin_lock_bh(&mq->lock); + while ((skb = __skb_dequeue(skbq))) + dev_kfree_skb_any(skb); + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + int size; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + size = __mm81x_skbq_space(mq); + skb_queue_walk_safe(skbq, pfirst, pnext) { + if (pfirst->len > size) + break; + __skb_unlink(pfirst, skbq); + __mm81x_skbq_put(mq, &mq->skbq, pfirst, false, NULL); + size -= pfirst->len; + } + + spin_unlock_bh(&mq->lock); +} + +int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq, + int num_skb) +{ + int count = 0; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + if (count >= num_skb) + break; + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + __skb_queue_tail(skbq, pfirst); + ++count; + } + + spin_unlock_bh(&mq->lock); + return count; +} + +void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + int size; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + size = __mm81x_skbq_space(mq); + + /* + * We are doing a reverse walk here to ensure the order remains the + * same. This means the last member of the queue goes in, on top of + * the queue first and gets pushed down as more members get added to + * the top of the queue. + */ + skb_queue_reverse_walk_safe(skbq, pfirst, pnext) { + if (pfirst->len > size) + break; + __skb_unlink(pfirst, skbq); + __mm81x_skbq_put(mq, &mq->skbq, pfirst, true, NULL); + size -= pfirst->len; + } + + spin_unlock_bh(&mq->lock); +} + +static void mm81x_skbq_stop_tx_queues(struct mm81x *mors) +{ + int queue; + + if (!mors->started) + return; + for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++) + ieee80211_stop_queue(mors->hw, queue); + + set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); +} + +/* Wake all Tx queues if all queues are below threshold */ +void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors) +{ + int queue; + struct mm81x_skbq *qs; + int num_qs; + bool could_wake; + + if (!mors->started) + return; + + could_wake = true; + mm81x_hif_skbq_get_tx_qs(mors, &qs, &num_qs); + for (queue = 0; queue < num_qs; queue++) { + struct mm81x_skbq *mq = &qs[queue]; + + if (!could_wake) + break; + + spin_lock_bh(&mq->lock); + could_wake &= (__mm81x_skbq_under_threshold(mq)); + spin_unlock_bh(&mq->lock); + } + + if (!could_wake) + return; + + for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++) + ieee80211_wake_queue(mors->hw, queue); + + clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); +} + +static int mm81x_skbq_tx(struct mm81x_skbq *mq, struct sk_buff *skb, u8 channel) +{ + int rc; + bool mq_over_threshold; + struct mm81x *mors = mq->mors; + + spin_lock_bh(&mq->lock); + rc = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + if (rc) { + dev_err(mors->dev, "skb put chan %d failed (%d)", channel, rc); + if (channel == MM81X_SKB_CHAN_DATA) { + u16 queue = skb_get_queue_mapping(skb); + + dev_err(mors->dev, "skb put queue %d status %d", queue, + ieee80211_queue_stopped(mors->hw, queue)); + } + } + + /* Fill packet ID in TX info */ + __mm81x_skbq_pkt_id(mq, skb); + + mq_over_threshold = __mm81x_skbq_over_threshold(mq); + spin_unlock_bh(&mq->lock); + + /* For data packets stop queues */ + if (channel == MM81X_SKB_CHAN_DATA && mq_over_threshold) + mm81x_skbq_stop_tx_queues(mors); + + switch (channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_DATA_NOACK: + if (mm81x_is_data_tx_allowed(mors)) { + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, + &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + } + break; + case MM81X_SKB_CHAN_MGMT: + set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + case MM81X_SKB_CHAN_BEACON: + set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + case MM81X_SKB_CHAN_COMMAND: + set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + default: + dev_err(mors->dev, "Invalid skb channel: %d", channel); + break; + } + + return rc; +} + +static void __mm81x_skbq_tx_move_to_pending(struct mm81x_skbq *mq, + struct sk_buff *skb) +{ + struct mm81x_tx_status_priv *pend_info = + __mm81x_skbq_tx_status_priv(skb); + + pend_info->tx_status_expiry = + jiffies + msecs_to_jiffies(MM81X_SKBQ_TX_STATUS_LIFETIME_MS); + __mm81x_skbq_put(mq, &mq->pending, skb, false, NULL); +} + +void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + bool skb_awaits_tx_status = false; + struct mm81x *mors = mq->mors; + struct sk_buff *pfirst, *pnext; + struct sk_buff *peek = skb_peek(skbq); + struct mm81x_skb_hdr *hdr; + const bool fw_reports_bcn_tx_status = + mors->fw_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION; + + if (!peek) + return; + + /* Move sent packets to pending list waiting for feedback */ + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(skbq, pfirst, pnext) { + __skb_unlink(pfirst, skbq); + hdr = (struct mm81x_skb_hdr *)pfirst->data; + /* + * If firmware doesn't give status on beacons just free + * them, otherwise queue and wait for response. + */ + switch (hdr->channel) { + case MM81X_SKB_CHAN_BEACON: + if (fw_reports_bcn_tx_status) { + __mm81x_skbq_tx_move_to_pending(mq, pfirst); + skb_awaits_tx_status = true; + break; + } + /* + * If the FW doesn't give statuses on beacon's, + * then mark them as done. + */ + mm81x_skbq_pull_hdr_post_tx(pfirst); + dev_kfree_skb_any(pfirst); + break; + default: + if (le32_to_cpu(hdr->tx_info.flags) & + MM81X_TX_STATUS_FLAGS_NO_REPORT) { + dev_kfree_skb_any(pfirst); + } else { + /* + * skb has been given to the chip. Store the + * time and queue the skb onto the pending + * queue while we wait for the tx_status. + */ + __mm81x_skbq_tx_move_to_pending(mq, pfirst); + skb_awaits_tx_status = true; + } + break; + } + } + spin_unlock_bh(&mq->lock); + + if (skb_awaits_tx_status) { + spin_lock_bh(&mors->stale_status.lock); + mod_timer(&mors->stale_status.timer, + jiffies + msecs_to_jiffies( + MM81X_SKBQ_TX_STATUS_LIFETIME_MS)); + spin_unlock_bh(&mors->stale_status.lock); + } +} + +/* Returns the first skb in the pending list. */ +struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst; + + spin_lock_bh(&mq->lock); + pfirst = skb_peek(&mq->pending); + spin_unlock_bh(&mq->lock); + return pfirst; +} + +int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq) +{ + int flushed = 0; + struct sk_buff *pfirst; + struct sk_buff *pnext; + + if (!skb_queue_len(&mq->pending)) + return 0; + + /* Move sent packets to pending list waiting for feedback */ + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + struct mm81x_skb_hdr *hdr = + (struct mm81x_skb_hdr *)pfirst->data; + + if (__mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) { + dev_dbg(mors->dev, "TX skb timed out [id:%d,chan:%d]", + hdr->tx_info.pkt_id, hdr->channel); + + __mm81x_skbq_drop_pending_skb(mq, pfirst); + flushed++; + } + } + + if (flushed) + mm81x_skbq_check_tx_empty(mors, mq); + + spin_unlock_bh(&mq->lock); + return flushed; +} + +/* Remove commands from pending (or skbq if not sent) */ +static void __skbq_cmd_finish(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + struct mm81x *mors = mq->mors; + + if (skb_queue_len(&mq->pending)) { + __mm81x_skbq_unlink(mq, &mq->pending, skb); + dev_kfree_skb(skb); + } else if (skb_queue_len(&mq->skbq)) { + /* Command was probably timed out before being sent */ + dev_dbg(mors->dev, + "Command pending queue empty. Removing from SKBQ."); + __mm81x_skbq_unlink(mq, &mq->skbq, skb); + dev_kfree_skb(skb); + } else { + dev_dbg(mors->dev, "Command Q not found"); + } +} + +struct mm81x_update_sta_iter_data { + struct mm81x *mors; + struct sk_buff *skb; + struct mm81x_skb_tx_status *tx_sts; + int tx_attempts; + bool updated; +}; + +static void mm81x_tx_h_update_sta_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mm81x_update_sta_iter_data *iter = data; + struct ieee80211_hdr *hdr; + struct ieee80211_sta *sta; + + if (iter->updated || !iter->skb || !iter->skb->data) + return; + + hdr = (struct ieee80211_hdr *)iter->skb->data; + + /* + * Note that each iteration via + * ieee80211_iterate_active_interfaces_atomic is under an RCU critical + * section so there is no need for a local critical section within here + * when looking up the station. + */ + sta = ieee80211_find_sta(vif, hdr->addr1); + if (!sta) + return; + + mm81x_rc_sta_feedback_rates(iter->mors, iter->skb, sta, iter->tx_sts, + iter->tx_attempts); + mm81x_tx_h_check_aggr(sta, iter->skb); + + /* + * In situations with multiple virtual interfaces, finish iteration + * once we have found our STA to prevent further iteration. + */ + iter->updated = true; +} + +/* TX status/Response received remove packet from pending TX finish */ +static void __skbq_data_tx_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + struct mm81x *mors = mq->mors; + struct mm81x_update_sta_iter_data iter = {}; + + __mm81x_skbq_unlink(mq, &mq->pending, skb); + iter.mors = mors; + iter.skb = skb; + iter.tx_sts = tx_sts; + iter.tx_attempts = mm81x_tx_h_get_attempts(mors, tx_sts); + + ieee80211_iterate_active_interfaces_atomic(mors->hw, + IEEE80211_IFACE_ITER_NORMAL, + mm81x_tx_h_update_sta_iter, + &iter); + + ieee80211_tx_status_skb(mors->hw, skb); +} + +void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + if (mq->flags & MM81X_HIF_FLAGS_COMMAND) + __skbq_cmd_finish(mq, skb); + else + __skbq_data_tx_finish(mq, skb, tx_sts); +} + +void mm81x_skbq_tx_flush(struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + __mm81x_skbq_unlink(mq, &mq->pending, pfirst); + ieee80211_free_txskb(mq->mors->hw, pfirst); + } + + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + ieee80211_free_txskb(mq->mors->hw, pfirst); + } + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags) +{ + spin_lock_init(&mq->lock); + __skb_queue_head_init(&mq->skbq); + __skb_queue_head_init(&mq->pending); + mq->mors = mors; + mq->skbq_size = 0; + mq->flags = flags; + mq->pkt_seq = 0; + if (flags & MM81X_HIF_FLAGS_DIR_TO_HOST) + INIT_WORK(&mq->dispatch_work, mm81x_skbq_dispatch_work); +} + +void mm81x_skbq_finish(struct mm81x_skbq *mq) +{ + if (mq->skbq_size > 0) + dev_dbg(mq->mors->dev, + "Purging a non empty MorseQ. Dropping data!"); + + /* Clean up link to hif */ + if (mq->flags & MM81X_HIF_FLAGS_DIR_TO_HOST) + cancel_work_sync(&mq->dispatch_work); + mm81x_skbq_purge(mq, &mq->skbq); + mm81x_skbq_purge(mq, &mq->pending); + mq->skbq_size = 0; +} + +u32 mm81x_skbq_size(struct mm81x_skbq *mq) +{ + u32 count; + + spin_lock_bh(&mq->lock); + count = __mm81x_skbq_size(mq); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_count(struct mm81x_skbq *mq) +{ + u32 count = 0; + + spin_lock_bh(&mq->lock); + count += skb_queue_len(&mq->skbq); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq) +{ + u32 count; + + spin_lock_bh(&mq->lock); + count = skb_queue_len(&mq->pending); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq) +{ + struct mm81x *mors = mq->mors; + + if (!mm81x_is_data_tx_allowed(mors)) + return 0; + + return mm81x_skbq_count(mq); +} + +u32 mm81x_skbq_space(struct mm81x_skbq *mq) +{ + u32 space; + + spin_lock_bh(&mq->lock); + space = __mm81x_skbq_space(mq); + spin_unlock_bh(&mq->lock); + + return space; +} + +struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, unsigned int length) +{ + struct sk_buff *skb; + int tx_headroom = sizeof(struct mm81x_skb_hdr) + + mm81x_bus_get_alignment(mq->mors); + int skb_len = tx_headroom + length + MM81X_PAD4(length); + + skb = dev_alloc_skb(skb_len); + if (!skb) + return NULL; + + skb_reserve(skb, tx_headroom); + skb_put(skb, length); + return skb; +} + +static int mm81x_skb_tx_h_validate_channel(const struct mm81x *mors, u8 channel) +{ + if (channel == MM81X_SKB_CHAN_COMMAND) { + if (test_bit(MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED, + &mors->state_flags)) + return -EPERM; + } else { + if (test_bit(MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED, + &mors->state_flags)) + return -EPERM; + } + + return 0; +} + +int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb_orig, + struct mm81x_skb_tx_info *tx_info, u8 channel) +{ + int ret; + struct mm81x_skb_hdr hdr; + struct mm81x *mors = mq->mors; + size_t end_of_skb_pad; + struct sk_buff *skb = *skb_orig; + u8 *aligned_head, *data; + + if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags)) { + dev_kfree_skb_any(skb); + return -ENODEV; + } + + ret = mm81x_skb_tx_h_validate_channel(mors, channel); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + mm81x_skbq_set_queued_tx_skb_expiry(skb); + + data = skb->data; + aligned_head = PTR_ALIGN_DOWN((data - sizeof(hdr)), + mm81x_bus_get_alignment(mors)); + hdr.sync = MM81X_SKB_HEADER_SYNC; + hdr.channel = channel; + hdr.len = cpu_to_le16(skb->len); + hdr.offset = data - (aligned_head + sizeof(hdr)); + hdr.checksum_upper = 0; + hdr.checksum_lower = 0; + if (tx_info) + memcpy(&hdr.tx_info, tx_info, sizeof(*tx_info)); + else + memset(&hdr.tx_info, 0, sizeof(hdr.tx_info)); + + skb_push(skb, data - aligned_head); + memcpy(skb->data, &hdr, sizeof(hdr)); + + end_of_skb_pad = MM81X_PAD4(skb->len); + if (end_of_skb_pad && skb_pad(skb, end_of_skb_pad)) + return -EINVAL; + + ret = mm81x_skbq_tx(mq, skb, channel); + if (ret) { + dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret); + dev_kfree_skb_any(skb); + } + + return ret; +} + +void mm81x_skbq_data_traffic_pause(struct mm81x *mors) +{ + set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + /* power-save requirements will be re-evaluated by the caller */ +} + +void mm81x_skbq_data_traffic_resume(struct mm81x *mors) +{ + clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + + /* Set the TX_DATA_PEND bit. This will kick the transmission path to + * send any frames pending in the TX buffers, and wake the mac80211 + * data Qs if they were previously stopped. + */ + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, &mors->hif.event_flags); +} + +bool mm81x_skbq_validate_checksum(u8 *data) +{ + int i; + u32 xor = 0; + struct mm81x_skb_hdr *skb_hdr = (struct mm81x_skb_hdr *)data; + struct ieee80211_hdr *hdr = + (struct ieee80211_hdr *)(data + sizeof(*skb_hdr)); + u16 len = le16_to_cpu(skb_hdr->len) + sizeof(*skb_hdr); + u32 *data_to_xor = (u32 *)data; + u32 header_xor = (le16_to_cpu(skb_hdr->checksum_upper) << 8) | + (skb_hdr->checksum_lower); + + /* + * For data frames the calculate the xor for skb header, mac header + * and ccmp header. For all other channel the xor is calculated for + * the full skb. + */ + if (skb_hdr->channel == MM81X_SKB_CHAN_DATA && + (ieee80211_is_data(hdr->frame_control) || + ieee80211_is_data_qos(hdr->frame_control))) { + u16 data_len = sizeof(*skb_hdr) + + sizeof(struct ieee80211_qos_hdr) + + IEEE80211_CCMP_HDR_LEN; + + len = min(len, data_len); + len = ROUND_DOWN_TO_WORD(len); + } + + skb_hdr->checksum_upper = 0; + skb_hdr->checksum_lower = 0; + + for (i = 0; i < len; i += 4) { + xor ^= *data_to_xor; + data_to_xor++; + } + + xor &= 0x00FFFFFF; + + return xor == header_xor; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.h b/drivers/net/wireless/morsemicro/mm81x/skbq.h new file mode 100644 index 000000000000..9930493141cf --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/skbq.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_SKBQ_H_ +#define _MM81X_SKBQ_H_ + +#include +#include +#include "rate_code.h" + +/* Sync value of skb header to indicate a valid skb */ +#define MM81X_SKB_HEADER_SYNC (0xAA) +/* Sync value indicating that the chip owns this skb */ +#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB) + +enum mm81x_tx_status_and_conf_flags { + MM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0), + MM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1), + MM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2), + MM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3), + MM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) | + BIT(8) | BIT(9) | BIT(10) | BIT(11)), + MM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)), + MM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)), + MM81X_TX_CONF_IGNORE_TWT = (BIT(16)), + MM81X_TX_STATUS_PAGE_INVALID = (BIT(17)), + MM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)), + MM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)), + MM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)), + MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)), + MM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)), + MM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24), + MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31)) +}; + +/* Getter and setter macros for vif id */ +#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF) +#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_VIF_ID_MASK) << 4) +#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_VIF_ID) >> 4) + +/* Getter and setter macros for key index */ +#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) & 0x07) << 12) +#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_KEY_IDX) >> 12) + +enum mm81x_rx_status_flags { + MM81X_RX_STATUS_FLAGS_ERROR = BIT(0), + MM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1), + MM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2), + MM81X_RX_STATUS_FLAGS_EOF = BIT(3), + MM81X_RX_STATUS_FLAGS_AMPDU = BIT(4), + MM81X_RX_STATUS_FLAGS_NDP = BIT(7), + MM81X_RX_STATUS_FLAGS_UPLINK = BIT(8), + MM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)), + MM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)), + MM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14), + MM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17), +}; + +/* Getter and Setter macros for vif id */ +#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) << 17) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_VIF_ID) >> 17) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \ + ((x) & ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK << 17)) + +/* Getter macro for guard interval */ +#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_UPLINK) >> 8) + +/* Getter macro for response indication */ +#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) & MM81X_RX_STATUS_FLAGS_RI) >> 9) + +/* Getter macro for NDP type */ +#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_NDP_TYPE) >> 11) + +enum mm81x_skb_channel { + MM81X_SKB_CHAN_DATA = 0x0, + MM81X_SKB_CHAN_NDP_FRAMES = 0x1, + MM81X_SKB_CHAN_DATA_NOACK = 0x2, + MM81X_SKB_CHAN_BEACON = 0x3, + MM81X_SKB_CHAN_MGMT = 0x4, + MM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80, + MM81X_SKB_CHAN_COMMAND = 0xFE, + MM81X_SKB_CHAN_TX_STATUS = 0xFF +}; + +#define MM81X_SKB_MAX_RATES (4) + +struct mm81x_skb_rate_info { + mm81x_rate_code_t mm81x_ratecode; + u8 count; +} __packed; + +struct mm81x_skb_tx_status { + __le32 flags; + __le32 pkt_id; + u8 tid; + u8 channel; + __le16 ampdu_info; + struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES]; +} __packed; + +#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) >> 10) & 0x3F) +#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) >> 5) & 0x1F) +#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) & 0x1F) + +struct mm81x_skb_tx_info { + __le32 flags; + __le32 pkt_id; + u8 tid; + u8 tid_params; + u8 mmss_params; + u8 padding[1]; + struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES]; +} __packed; + +#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f +#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20 +#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40 +#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80 + +/* Bitmap for MMSS (Minimum MPDU start spacing) parameters + * +-----------+-----------+ + * | Morse | MMSS set | + * | MMSS | by S1G cap| + * | offset | IE | + * |-----------|-----------| + * |b7|b6|b5|b4|b3|b2|b1|b0| + */ +#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0) +#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4 +#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4) +#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) & TX_INFO_MMSS_PARAMS_MMSS_MASK) +#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x) \ + (((x) << TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) & \ + TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK) + +struct mm81x_skb_rx_status { + __le32 flags; + mm81x_rate_code_t mm81x_ratecode; + __le16 rssi; + __le16 freq_100khz; + u8 bss_color; + s8 noise_dbm; + /** Padding for word alignment */ + u8 padding[2]; + __le64 rx_timestamp_us; +} __packed; + +struct mm81x_skb_hdr { + u8 sync; + u8 channel; + __le16 len; + u8 offset; + u8 checksum_lower; + __le16 checksum_upper; + union { + struct mm81x_skb_tx_info tx_info; + struct mm81x_skb_tx_status tx_status; + struct mm81x_skb_rx_status rx_status; + }; +} __packed; + +#define MM81X_SKBQ_SIZE (4 * 128 * 1024) + +struct mm81x; + +struct mm81x_skbq { + struct mm81x *mors; + u32 pkt_seq; /* SKB sequence used in tx_status */ + u16 flags; + u32 skbq_size; /* current off loaded size */ + spinlock_t lock; + struct sk_buff_head skbq; + struct sk_buff_head pending; /* packets sent pending feedback */ + struct work_struct dispatch_work; +}; + +void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq); +u32 mm81x_skbq_space(struct mm81x_skbq *mq); +u32 mm81x_skbq_size(struct mm81x_skbq *mq); +int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq, + int num_skb); +struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, + unsigned int length); +int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb, + struct mm81x_skb_tx_info *tx_info, u8 channel); +int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb); +void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq); +void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags); +void mm81x_skbq_finish(struct mm81x_skbq *mq); +void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb); +void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file); +void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts); +void mm81x_skbq_tx_flush(struct mm81x_skbq *mq); +int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq); +void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors); +u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq); +u32 mm81x_skbq_count(struct mm81x_skbq *mq); +u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq); +void mm81x_skbq_data_traffic_pause(struct mm81x *mors); +void mm81x_skbq_data_traffic_resume(struct mm81x *mors); +bool mm81x_skbq_validate_checksum(u8 *data); + +#endif /* !_MM81X_SKBQ_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/usb.c b/drivers/net/wireless/morsemicro/mm81x/usb.c new file mode 100644 index 000000000000..ec94936b157b --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/usb.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include "hif.h" +#include "bus.h" +#include "mac.h" +#include "core.h" + +/* + * URB timeout in milliseconds. If an URB does not complete within this + * time, it will be killed. This timeout needs to account for USB suspendand + * resume occurring before the URB can be transferred, and it also needs to + * account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially + * slow, congested USB Full Speed link. + */ +#define URB_TIMEOUT_MS 250 + +/* High speed USB 2^(4-1) * 125usec = 1msec */ +#define MM81X_USB_INTERRUPT_INTERVAL 4 + +/* Max bytes per USB read/write */ +#define USB_MAX_TRANSFER_SIZE (16 * 1024) + +/* INT EP buffer size */ +#define MM81X_EP_INT_BUFFER_SIZE 8 + +/* Morse vendor IDs*/ +#define MM81X_VENDOR_ID 0x325b +#define MM81X_MM810X_PRODUCT_ID 0x8100 + +/* Power management runtime auto-suspend delay value in milliseconds */ +#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100 + +enum mm81x_usb_endpoints { + MM81X_EP_CMD = 0, + MM81X_EP_INT, + MM81X_EP_MEM_RD, + MM81X_EP_MEM_WR, + MM81X_EP_REG_RD, + MM81X_EP_REG_WR, + MM81X_EP_EP_MAX, +}; + +struct mm81x_usb_endpoint { + unsigned char *buffer; + struct urb *urb; + __u8 addr; + int size; +}; + +enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED }; + +struct mm81x_usb { + struct usb_device *udev; + struct usb_interface *interface; + struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX]; + int errors; + + /* serialise USB device struct */ + struct mutex lock; + + /* serialise USB bus access */ + struct mutex bus_lock; + + bool ongoing_cmd; + bool ongoing_rw; + wait_queue_head_t rw_in_wait; + unsigned long flags; +}; + +enum mm81x_usb_command_direction { + MM81X_USB_WRITE = 0x00, + MM81X_USB_READ = 0x80, + MM81X_USB_RESET = 0x02, +}; + +struct mm81x_usb_command { + __le32 dir; /* Next BULK direction */ + __le32 address; /* Next BULK address */ + __le32 length; /* Next BULK size */ +}; + +static const struct usb_device_id mm81x_usb_table[] = { + { USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, mm81x_usb_table); + +static void mm81x_usb_irq_work(struct work_struct *work) +{ + struct mm81x *mors = container_of(work, struct mm81x, usb_irq_work); + + mm81x_claim_bus(mors); + mm81x_hw_irq_handle(mors); + mm81x_release_bus(mors); +} + +static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb) +{ + return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) || + (urb->status == -ETIME) || (urb->status == -EPIPE)); +} + +static void mm81x_usb_int_handler(struct urb *urb) +{ + int ret; + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return; + + if (urb->status) { + if (mm81x_usb_urb_status_is_disconnect(urb)) { + clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, + &mors->state_flags); + dev_dbg(mors->dev, + "USB sudden disconnect detected in %s", + __func__); + return; + } + + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, "- nonzero read status received: %d", + urb->status); + } + + ret = usb_submit_urb(urb, GFP_ATOMIC); + + /* usb_kill_urb has been called */ + if (ret == -EPERM) + return; + else if (ret) + dev_err(mors->dev, "error: resubmit urb %p err code %d", urb, + ret); + + queue_work(mors->chip_wq, &mors->usb_irq_work); +} + +static int mm81x_usb_int_enable(struct mm81x *mors) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct urb *urb; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto out; + } + + musb->endpoints[MM81X_EP_INT].urb = urb; + + musb->endpoints[MM81X_EP_INT].buffer = + usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + GFP_KERNEL, &urb->transfer_dma); + if (!musb->endpoints[MM81X_EP_INT].buffer) { + dev_err(mors->dev, "couldn't allocate transfer_buffer"); + ret = -ENOMEM; + goto error_set_urb_null; + } + + usb_fill_int_urb( + musb->endpoints[MM81X_EP_INT].urb, musb->udev, + usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr), + musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE, + mm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + goto error; + } + + return 0; + +error: + usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + musb->endpoints[MM81X_EP_INT].buffer, + urb->transfer_dma); +error_set_urb_null: + musb->endpoints[MM81X_EP_INT].urb = NULL; + usb_free_urb(urb); +out: + return ret; +} + +static void mm81x_usb_int_stop(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb); + cancel_work_sync(&mors->usb_irq_work); +} + +static void mm81x_usb_cmd_callback(struct urb *urb) +{ + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, + "nonzero write bulk status received: %d", + urb->status); + + musb->errors = urb->status; + } + + musb->ongoing_cmd = false; + wake_up(&musb->rw_in_wait); +} + +static int mm81x_usb_cmd(struct mm81x_usb *musb, + const struct mm81x_usb_command *cmd) +{ + int retval = 0; + struct mm81x *mors = usb_get_intfdata(musb->interface); + struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD]; + size_t writesize = sizeof(*cmd); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + memcpy(ep->buffer, cmd, writesize); + + usb_fill_bulk_urb(ep->urb, musb->udev, + usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer, + writesize, mm81x_usb_cmd_callback, mors); + ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + musb->ongoing_cmd = true; + + retval = usb_submit_urb(ep->urb, GFP_KERNEL); + if (retval) { + dev_err(mors->dev, "- failed submitting write urb, error %d", + retval); + + goto error; + } + + retval = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_cmd), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (retval < 0) { + dev_err(mors->dev, "error waiting for urb %d", retval); + goto error; + } else if (retval == 0) { + dev_err(mors->dev, "timed out waiting for urb"); + usb_kill_urb(ep->urb); + retval = -ETIMEDOUT; + goto error; + } + + musb->ongoing_cmd = false; + return writesize; + +error: + musb->ongoing_cmd = false; + return retval; +} + +static int mm81x_usb_ndr_reset(struct mm81x *mors) +{ + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_command cmd; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + cmd.dir = cpu_to_le32(MM81X_USB_RESET); + cmd.address = cpu_to_le32(0); + cmd.length = cpu_to_le32(0); + + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) + dev_err(mors->dev, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n", + ret); + else + ret = 0; + + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + return ret; +} + +static void mm81x_usb_mem_rw_callback(struct urb *urb) +{ + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, + "nonzero write bulk status received: %d", + urb->status); + + musb->errors = urb->status; + } + + musb->ongoing_rw = false; + wake_up(&musb->rw_in_wait); +} + +static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data, + ssize_t size) +{ + int ret; + struct mm81x_usb_command cmd; + struct mm81x *mors = usb_get_intfdata(musb->interface); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + /* Send command ahead to prepare for Tokens */ + cmd.dir = cpu_to_le32(MM81X_USB_READ); + cmd.address = cpu_to_le32(address); + cmd.length = cpu_to_le32(size); + + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) { + dev_err(mors->dev, "mm81x_usb_cmd error %d", ret); + goto error; + } + + /* Let's be fast push the next URB, don't wait until command is done */ + usb_fill_bulk_urb( + musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev, + usb_rcvbulkpipe(musb->udev, + musb->endpoints[MM81X_EP_MEM_RD].addr), + musb->endpoints[MM81X_EP_MEM_RD].buffer, size, + mm81x_usb_mem_rw_callback, mors); + + ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(mors->dev, "failed submitting read urb, error %d", ret); + ret = (ret == -ENOMEM) ? ret : -EIO; + goto error; + } + + ret = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_rw), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (ret < 0) { + dev_err(mors->dev, "wait_event_interruptible: error %d", ret); + goto error; + } else if (ret == 0) { + /* Timed out. */ + usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb); + } + + if (musb->errors) { + ret = musb->errors; + dev_err(mors->dev, "mem read error %d", ret); + goto error; + } + + memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size); + ret = size; + +error: + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + + return ret; +} + +static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data, + ssize_t size) +{ + int ret; + struct mm81x_usb_command cmd; + struct mm81x *mors = usb_get_intfdata(musb->interface); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + /* Send command ahead to prepare for Tokens */ + cmd.dir = cpu_to_le32(MM81X_USB_WRITE); + cmd.address = cpu_to_le32(address); + cmd.length = cpu_to_le32(size); + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) { + dev_err(mors->dev, "mm81x_usb_mem_read error %d", ret); + goto error; + } + + memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size); + + /* prepare a read */ + usb_fill_bulk_urb( + musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev, + usb_sndbulkpipe(musb->udev, + musb->endpoints[MM81X_EP_MEM_WR].addr), + musb->endpoints[MM81X_EP_MEM_WR].buffer, size, + mm81x_usb_mem_rw_callback, mors); + + ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(mors->dev, "- failed submitting write urb, error %d", + ret); + ret = (ret == -ENOMEM) ? ret : -EIO; + goto error; + } + + ret = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_rw), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (ret < 0) { + dev_err(mors->dev, "error %d", ret); + goto error; + } else if (ret == 0) { + /* Timed out. */ + usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb); + } + + if (musb->errors) { + ret = musb->errors; + dev_err(mors->dev, "error %d", ret); + goto error; + } + + ret = size; + +error: + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + return ret; +} + +static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len) +{ + ssize_t offset = 0; + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + while (offset < len) { + ret = mm81x_usb_mem_read(musb, address + offset, + (u8 *)(data + offset), + min((ssize_t)(len - offset), + (ssize_t)USB_MAX_TRANSFER_SIZE)); + if (ret < 0) { + dev_err(mors->dev, "%s failed (errno=%d)", __func__, + ret); + return ret; + } + + offset += ret; + } + + return 0; +} + +static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data, + int len) +{ + ssize_t offset = 0; + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + while (offset < len) { + ret = mm81x_usb_mem_write(musb, address + offset, + (u8 *)(data + offset), + min((ssize_t)(len - offset), + (ssize_t)USB_MAX_TRANSFER_SIZE)); + if (ret < 0) { + dev_err(mors->dev, "%s failed (errno=%d)", __func__, + ret); + return ret; + } + + offset += ret; + } + + return 0; +} + +static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val)); + if (ret == sizeof(*val)) { + *val = le32_to_cpup((__le32 *)val); + return 0; + } + + dev_err(mors->dev, "usb reg32 read failed %d", ret); + return ret; +} + +static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + __le32 val_le = cpu_to_le32(val); + + ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le)); + if (ret == sizeof(val_le)) + return 0; + + dev_err(mors->dev, "usb reg32 write failed %d", ret); + return ret; +} + +static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + if (enable) + usb_autopm_get_interface(musb->interface); + else + usb_autopm_put_interface(musb->interface); +} + +static void mm81x_usb_claim_bus(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + mutex_lock(&musb->bus_lock); +} + +static void mm81x_usb_release_bus(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + mutex_unlock(&musb->bus_lock); +} + +static void mm81x_usb_set_irq(struct mm81x *mors, bool enable) +{ +} + +static const struct mm81x_bus_ops mm81x_usb_ops = { + .dm_read = mm81x_usb_dm_read, + .dm_write = mm81x_usb_dm_write, + .reg32_read = mm81x_usb_reg32_read, + .reg32_write = mm81x_usb_reg32_write, + .digital_reset = mm81x_usb_ndr_reset, + .set_bus_enable = mm81x_usb_bus_enable, + .claim = mm81x_usb_claim_bus, + .release = mm81x_usb_release_bus, + .set_irq = mm81x_usb_set_irq, + .bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT, +}; + +static int mm81x_usb_detect_endpoints(struct mm81x *mors, + const struct usb_interface *intf) +{ + int ret; + unsigned int i; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct usb_endpoint_descriptor *ep_desc; + struct usb_host_interface *intf_desc = intf->cur_altsetting; + + for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { + ep_desc = &intf_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep_desc)) { + if (!musb->endpoints[MM81X_EP_MEM_RD].addr) { + musb->endpoints[MM81X_EP_MEM_RD].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_MEM_RD].size = + usb_endpoint_maxp(ep_desc); + } else if (!musb->endpoints[MM81X_EP_REG_RD].addr) { + musb->endpoints[MM81X_EP_REG_RD].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_REG_RD].size = + usb_endpoint_maxp(ep_desc); + } + } else if (usb_endpoint_is_bulk_out(ep_desc)) { + if (!musb->endpoints[MM81X_EP_MEM_WR].addr) { + musb->endpoints[MM81X_EP_MEM_WR].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_MEM_WR].size = + usb_endpoint_maxp(ep_desc); + } else if (!musb->endpoints[MM81X_EP_REG_WR].addr) { + musb->endpoints[MM81X_EP_REG_WR].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_REG_WR].size = + usb_endpoint_maxp(ep_desc); + } + } else if (usb_endpoint_is_int_in(ep_desc)) { + musb->endpoints[MM81X_EP_INT].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_INT].size = + usb_endpoint_maxp(ep_desc); + } + } + + dev_dbg(mors->dev, "\tMemory Endpoint IN %s detected: %u size %u", + musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not", + musb->endpoints[MM81X_EP_MEM_RD].addr, + musb->endpoints[MM81X_EP_MEM_RD].size); + dev_dbg(mors->dev, "\tMemory Endpoint OUT %s detected: %u size %u", + musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not", + musb->endpoints[MM81X_EP_MEM_WR].addr, + musb->endpoints[MM81X_EP_MEM_WR].size); + dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u", + musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not", + musb->endpoints[MM81X_EP_REG_RD].addr); + dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u", + musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not", + musb->endpoints[MM81X_EP_REG_WR].addr); + dev_dbg(mors->dev, "\tStats IN endpoint %s detected: %u", + musb->endpoints[MM81X_EP_INT].addr ? "" : "not", + musb->endpoints[MM81X_EP_INT].addr); + + /* Verify we have an IN and OUT */ + if (!(musb->endpoints[MM81X_EP_MEM_RD].addr && + musb->endpoints[MM81X_EP_MEM_WR].addr)) + return -ENODEV; + + /* Verify the stats MM81X_EP_INT is detected */ + if (!musb->endpoints[MM81X_EP_INT].addr) + return -ENODEV; + + /* Verify minimum interrupt status read */ + if (musb->endpoints[MM81X_EP_INT].size < 8) + return -ENODEV; + + musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_CMD].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_RD].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_WR].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_RD].buffer = + kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_WR].buffer = + kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent( + musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL, + &musb->endpoints[MM81X_EP_CMD].urb->transfer_dma); + + if (!musb->endpoints[MM81X_EP_CMD].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + /* Assign command to memory out end point */ + musb->endpoints[MM81X_EP_CMD].addr = + musb->endpoints[MM81X_EP_MEM_WR].addr; + musb->endpoints[MM81X_EP_CMD].size = + musb->endpoints[MM81X_EP_MEM_WR].size; + + return 0; + +err_ep: + if (musb->endpoints[MM81X_EP_CMD].urb && + musb->endpoints[MM81X_EP_CMD].buffer) + usb_free_coherent( + musb->udev, sizeof(struct mm81x_usb_command), + musb->endpoints[MM81X_EP_CMD].buffer, + musb->endpoints[MM81X_EP_CMD].urb->transfer_dma); + usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb); + usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb); + usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb); + kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer); + kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer); + + return ret; +} + +static void mm81x_urb_cleanup(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD]; + struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR]; + struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD]; + + usb_kill_urb(rd_ep->urb); + usb_kill_urb(wr_ep->urb); + usb_kill_urb(cmd_ep->urb); + + if (int_ep->urb) + usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + int_ep->buffer, int_ep->urb->transfer_dma); + + if (cmd_ep->urb) + usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command), + cmd_ep->buffer, cmd_ep->urb->transfer_dma); + + kfree(wr_ep->buffer); + kfree(rd_ep->buffer); + + usb_free_urb(int_ep->urb); + usb_free_urb(wr_ep->urb); + usb_free_urb(rd_ep->urb); + usb_free_urb(cmd_ep->urb); +} + +static int mm81x_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int ret; + struct mm81x *mors; + struct mm81x_usb *musb; + + mors = mm81x_core_alloc(sizeof(*musb), &interface->dev); + if (!mors) + return -ENOMEM; + + mors->bus_ops = &mm81x_usb_ops; + mors->bus_type = MM81X_BUS_TYPE_USB; + + musb = (struct mm81x_usb *)mors->drv_priv; + musb->udev = usb_get_dev(interface_to_usbdev(interface)); + musb->interface = usb_get_intf(interface); + + mutex_init(&musb->lock); + mutex_init(&musb->bus_lock); + init_waitqueue_head(&musb->rw_in_wait); + usb_set_intfdata(interface, mors); + + ret = mm81x_usb_detect_endpoints(mors, interface); + if (ret < 0) + goto err_core_free; + + set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + + ret = mm81x_core_init(mors); + if (ret) + goto err_urb_cleanup; + + INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work); + + ret = mm81x_usb_int_enable(mors); + if (ret) + goto err_core_deinit; + + ret = mm81x_core_register(mors); + if (ret) + goto err_usb_int_stop; + + /* USB requires remote wakeup functionality for suspend */ + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + musb->interface->needs_remote_wakeup = 1; + usb_enable_autosuspend(musb->udev); + pm_runtime_set_autosuspend_delay(&musb->udev->dev, + PM_RUNTIME_AUTOSUSPEND_DELAY_MS); + + usb_autopm_get_interface(interface); + return 0; + +err_usb_int_stop: + mm81x_usb_int_stop(mors); +err_core_deinit: + mm81x_core_deinit(mors); +err_urb_cleanup: + mm81x_urb_cleanup(mors); +err_core_free: + mm81x_core_free(mors); + usb_put_intf(interface); + usb_put_dev(interface_to_usbdev(interface)); + return ret; +} + +static void mm81x_usb_disconnect(struct usb_interface *interface) +{ + struct mm81x *mors = usb_get_intfdata(interface); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int minor = interface->minor; + struct usb_device *udev = interface_to_usbdev(interface); + + if (udev->state == USB_STATE_NOTATTACHED) { + clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + dev_dbg(mors->dev, "USB suddenly unplugged"); + } + + usb_disable_autosuspend(udev); + + if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) { + dev_dbg(mors->dev, "USB was suspended: release locks"); + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + } + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + + mm81x_core_unregister(mors); + mm81x_usb_int_stop(mors); + mm81x_core_deinit(mors); + mm81x_urb_cleanup(mors); + mm81x_core_free(mors); + + usb_autopm_put_interface(interface); + usb_set_intfdata(interface, NULL); + dev_info(&interface->dev, "USB Morse #%d now disconnected", minor); + usb_put_intf(interface); + usb_put_dev(udev); +} + +static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD]; + struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR]; + struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + usb_kill_urb(int_ep->urb); + usb_kill_urb(rd_ep->urb); + usb_kill_urb(wr_ep->urb); + usb_kill_urb(cmd_ep->urb); + + /* Locking the bus. No USB communication after this point */ + mm81x_usb_claim_bus(mors); + mutex_lock(&musb->lock); + + set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + return 0; +} + +static int mm81x_usb_resume(struct usb_interface *intf) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int ret; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + ret = usb_submit_urb(int_ep->urb, GFP_KERNEL); + if (ret) + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + return 0; +} + +static int mm81x_usb_reset_resume(struct usb_interface *intf) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int ret; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + ret = usb_submit_urb(int_ep->urb, GFP_KERNEL); + if (ret) + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + + return 0; +} + +static int mm81x_usb_pre_reset(struct usb_interface *intf) +{ + return 0; +} + +static int mm81x_usb_post_reset(struct usb_interface *intf) +{ + return 0; +} + +static struct usb_driver mm81x_usb_driver = { + .name = "mm81x_usb", + .probe = mm81x_usb_probe, + .disconnect = mm81x_usb_disconnect, + .suspend = mm81x_usb_suspend, + .resume = mm81x_usb_resume, + .reset_resume = mm81x_usb_reset_resume, + .pre_reset = mm81x_usb_pre_reset, + .post_reset = mm81x_usb_post_reset, + .id_table = mm81x_usb_table, + .supports_autosuspend = 1, + .soft_unbind = 1, +}; + +module_usb_driver(mm81x_usb_driver); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c new file mode 100644 index 000000000000..e98a2a58726f --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include "hif.h" +#include "ps.h" +#include "bus.h" +#include "command.h" +#include "skbq.h" + +/* This is a fail safe timeout */ +#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30 + +/* Defined as the max number of MPDUs per AMPDU */ +#define MAX_PKTS_PER_TX_TXN 16 +#define MAX_PKTS_PER_RX_TXN 32 + +static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps) +{ + yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN, + sizeof(*yaps->hw.to_chip_pkts), + GFP_KERNEL); + if (!yaps->hw.to_chip_pkts) + return -ENOMEM; + + yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN, + sizeof(*yaps->hw.from_chip_pkts), + GFP_KERNEL); + if (!yaps->hw.from_chip_pkts) { + kfree(yaps->hw.to_chip_pkts); + yaps->hw.to_chip_pkts = NULL; + return -ENOMEM; + } + + return 0; +} + +static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps) +{ + kfree(yaps->hw.from_chip_pkts); + yaps->hw.from_chip_pkts = NULL; + kfree(yaps->hw.to_chip_pkts); + yaps->hw.to_chip_pkts = NULL; +} + +static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts, + int *num_pkts_sent) +{ + return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent); +} + +static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts_max, + int *num_pkts_received) +{ + return yaps->ops->read_pkts(yaps, pkts, num_pkts_max, + num_pkts_received); +} + +static int mm81x_yaps_update_status(struct mm81x_yaps *yaps) +{ + return yaps->ops->update_status(yaps); +} + +/* Mappings between sk_buff, skbq and yaps */ +static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci) +{ + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (aci >= ARRAY_SIZE(yaps->data_tx_qs)) + return NULL; + return &yaps->data_tx_qs[aci]; +} + +static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs, + int *num_qs) +{ + *qs = mors->hif.u.yaps.data_tx_qs; + *num_qs = YAPS_TX_SKBQ_MAX; +} + +static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors) +{ + return &mors->hif.u.yaps.beacon_q; +} + +static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors) +{ + return &mors->hif.u.yaps.mgmt_q; +} + +static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors) +{ + return &mors->hif.u.yaps.cmd_q; +} + +static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status) +{ + if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN)) + set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags); + + if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) { + timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer); + set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, + &mors->hif.event_flags); + } + + queue_work(mors->chip_wq, &mors->hif_work); + return 0; +} + +const struct mm81x_hif_ops mm81x_yaps_ops = { + .init = mm81x_yaps_init, + .flush_tx_data = mm81x_yaps_flush_tx_data, + .flush_cmds = mm81x_yaps_flush_cmds, + .get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count, + .get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count, + .finish = mm81x_yaps_finish, + .skbq_get_tx_qs = mm81x_yaps_get_tx_qs, + .get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q, + .get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q, + .get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue, + .get_tx_data_queue = mm81x_yaps_tc_q_from_aci, + .handle_irq = mm81x_yaps_irq_handler +}; + +static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb) +{ + struct mm81x *mors = yaps->mors; + struct sk_buff_head skbq; + struct mm81x_skbq *mq = NULL; + struct mm81x_skb_hdr *hdr; + int skb_bytes_remaining; + int skb_len; + int ret = 0; + + if (!skb) { + ret = -EINVAL; + goto exit_return_page; + } + + __skb_queue_head_init(&skbq); + + hdr = (struct mm81x_skb_hdr *)skb->data; + if (hdr->sync != MM81X_SKB_HEADER_SYNC) { + dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d", + hdr->sync, hdr->len); + ret = -EIO; + goto exit_return_page; + } + + if (yaps->mors->hif.validate_skb_checksum && + !mm81x_skbq_validate_checksum(skb->data)) { + dev_dbg(yaps->mors->dev, + "SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]", + hdr->channel, hdr->sync, hdr->len); + + if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) { + ret = -EIO; + goto exit; + } + } + + switch (hdr->channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_NDP_FRAMES: + case MM81X_SKB_CHAN_TX_STATUS: + case MM81X_SKB_CHAN_DATA_NOACK: + case MM81X_SKB_CHAN_BEACON: + case MM81X_SKB_CHAN_MGMT: + mq = &yaps->data_rx_q; + break; + case MM81X_SKB_CHAN_COMMAND: + mq = &yaps->cmd_resp_q; + break; + default: + dev_err(mors->dev, "channel value error [%d]", hdr->channel); + ret = -EIO; + goto exit_return_page; + } + + skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len); + skb_bytes_remaining = mm81x_skbq_space(mq); + + if (skb_len > skb_bytes_remaining) { + dev_err(mors->dev, + "Page will not fit in SKBQ, dropping - len %d remain %d", + skb_len, skb_bytes_remaining); + ret = -ENOMEM; + /* Queue work to clear backlog */ + queue_work(mors->net_wq, &mq->dispatch_work); + goto exit_return_page; + } + + skb_trim(skb, skb_len); + __skb_queue_tail(&skbq, skb); + + if (skb_queue_len(&skbq)) + mm81x_skbq_enq(mq, &skbq); + + /* push packets up in a different context */ + queue_work(mors->net_wq, &mq->dispatch_work); + + goto exit; + +exit_return_page: + if (ret && mq) { + dev_err(mors->dev, "failed %d", ret); + mm81x_skbq_purge(mq, &skbq); + goto exit; + } + +exit: + if (ret && skb) + dev_kfree_skb(skb); + + return ret; +} + +static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq) +{ + int i; + int ret = 0; + int num_skbs = 0; + int tc_pkt_idx = 0; + int num_pkts_sent = 0; + struct sk_buff *skb; + struct sk_buff_head skbq_to_send; + struct sk_buff_head skbq_sent; + struct sk_buff_head skbq_failed; + struct sk_buff *pfirst, *pnext; + struct mm81x *mors = yaps->mors; + struct mm81x_skb_hdr *hdr; + + /* Check there is something on the queue */ + spin_lock_bh(&mq->lock); + skb = skb_peek(&mq->skbq); + spin_unlock_bh(&mq->lock); + if (!skb) + return 0; + + __skb_queue_head_init(&skbq_to_send); + __skb_queue_head_init(&skbq_sent); + __skb_queue_head_init(&skbq_failed); + + if (mq == &yaps->cmd_q) + /* Purge timed-out commands (this should not happen) */ + mm81x_skbq_purge(mq, &mq->pending); + else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0) + /* + * Purge old mgmt frames that have not been sent due to + * congestion + */ + mm81x_skbq_purge_aged(mors, mq); + + num_skbs = + mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN); + + skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) { + enum mm81x_yaps_to_chip_q tc_queue; + + hdr = (struct mm81x_skb_hdr *)pfirst->data; + switch (hdr->channel) { + case MM81X_SKB_CHAN_COMMAND: + tc_queue = MM81X_YAPS_CMD_Q; + break; + case MM81X_SKB_CHAN_BEACON: + tc_queue = MM81X_YAPS_BEACON_Q; + break; + case MM81X_SKB_CHAN_MGMT: + tc_queue = MM81X_YAPS_MGMT_Q; + break; + default: + tc_queue = MM81X_YAPS_TX_Q; + break; + } + yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue; + yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst; + tc_pkt_idx++; + } + + /* Send queued packets to chip */ + ret = mm81x_yaps_update_status(yaps); + if (ret) + return ret; + + ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx, + &num_pkts_sent); + + /* Move sent packets to done queue */ + for (i = 0; i < num_pkts_sent; ++i) { + pfirst = __skb_dequeue(&skbq_to_send); + __skb_queue_tail(&skbq_sent, pfirst); + } + + for (i = num_pkts_sent; i < num_skbs; ++i) { + pfirst = __skb_dequeue(&skbq_to_send); + __skb_queue_tail(&skbq_failed, pfirst); + } + + if (skb_queue_len(&skbq_failed) > 0) { + mm81x_skbq_enq_prepend(mq, &skbq_failed); + + /* queue full, can't requeue */ + if (skb_queue_len(&skbq_failed) > 0) { + dev_warn(mors->dev, + "can't requeue failed pkts, purging"); + __skb_queue_purge(&skbq_failed); + } + } + + if (skb_queue_len(&skbq_sent) > 0) + mm81x_skbq_tx_complete(mq, &skbq_sent); + + return ret; +} + +/* Returns true if there are TX data pages waiting to be sent */ +static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps) +{ + s16 aci; + u32 count = 0; + struct mm81x *mors = yaps->mors; + + for (aci = MM81X_ACI_VO; aci >= 0; aci--) { + struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci); + + if (!mm81x_is_data_tx_allowed(mors)) + break; + + yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q); + count += mm81x_skbq_count(data_q); + + if (yaps->chip_queue_full.is_full) + break; + + if (aci == MM81X_ACI_BE) + break; + } + + /* + * Data has potentially been transmitted from the data SKBQs. + * If the mac80211 TX data Qs were previously stopped, now would + * be a good time to check if they can be started again. + */ + mm81x_skbq_may_wake_tx_queues(mors); + + return (count > 0) && mm81x_is_data_tx_allowed(mors); +} + +/* Returns true if there are commands waiting to be sent */ +static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *cmd_q = &yaps->cmd_q; + + mm81x_yaps_tx(yaps, cmd_q); + + return mm81x_skbq_count(cmd_q) > 0; +} + +static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *beacon_q = &yaps->beacon_q; + + mm81x_yaps_tx(yaps, beacon_q); + + return mm81x_skbq_count(beacon_q) > 0; +} + +static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *mgmt_q = &yaps->mgmt_q; + + mm81x_yaps_tx(yaps, mgmt_q); + + return mm81x_skbq_count(mgmt_q) > 0; +} + +/* Returns true if there are populated RX pages left in the device */ +static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps) +{ + int ret = 0; + int i; + int num_pks_received; + + ret = mm81x_yaps_update_status(yaps); + if (ret) + goto exit; + + ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts, + MAX_PKTS_PER_RX_TXN, &num_pks_received); + if (ret && ret != -EAGAIN) { + dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret); + goto exit; + } + + for (i = 0; i < num_pks_received; ++i) { + mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb); + yaps->hw.from_chip_pkts[i].skb = NULL; + } + +exit: + if (ret == -ENOMEM || ret == -EAGAIN) + return true; + else + return false; +} + +void mm81x_yaps_stale_tx_work(struct work_struct *work) +{ + int i; + int flushed = 0; + struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work); + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q); + flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + flushed += mm81x_skbq_check_for_stale_tx(mors, + &yaps->data_tx_qs[i]); + + if (!flushed) + return; + + dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed); + + if (mors->ps.enable && !mors->ps.suspended && + (mm81x_yaps_get_tx_buffered_count(mors) == 0)) { + /* Evaluate ps to check if it was gated on a stale tx status */ + queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work, + 0); + } +} + +void mm81x_yaps_work(struct work_struct *work) +{ + struct mm81x *mors = container_of(work, struct mm81x, hif_work); + unsigned long *flags = &mors->hif.event_flags; + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags)) + return; + + if (!*flags) + return; + + /* Disable power save in case it is running */ + mm81x_ps_disable(mors); + mm81x_claim_bus(mors); + + /* + * Handle any populated RX pages from chip first to + * avoid dropping pkts due to full on-chip buffers. + * Check if all pages were removed, set event flags if not. + */ + if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) { + if (mm81x_yaps_rx_handler(yaps)) + set_bit(MM81X_HIF_EVT_RX_PEND, flags); + } + + /* TX any commands before considering data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) { + if (mm81x_yaps_tx_cmd_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags); + } + + /* TX beacons before considering mgmt/data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) { + if (mm81x_yaps_tx_beacon_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags); + } + + /* TX mgmt before considering data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) { + if (mm81x_yaps_tx_mgmt_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags); + } + + /* Pause TX data Qs */ + if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) { + test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, + flags); + mm81x_skbq_data_traffic_pause(mors); + } + + /* Resume TX data Qs */ + if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags)) + mm81x_skbq_data_traffic_resume(mors); + + /* Handle chip queue status */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags)) + yaps->chip_queue_full.is_full = false; + + /* Check to see if the queue is full or + * long enough has past since the queue was full + */ + if (yaps->chip_queue_full.is_full && + time_before(jiffies, yaps->chip_queue_full.retry_expiry)) + goto exit; + + /* Finally TX any data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) { + if (mm81x_yaps_tx_data_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags); + + if (yaps->chip_queue_full.is_full) { + yaps->chip_queue_full.retry_expiry = + jiffies + + msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS); + mod_timer(&yaps->chip_queue_full.timer, + yaps->chip_queue_full.retry_expiry); + } + } + +exit: + + /* Disable power save in case it is running */ + mm81x_release_bus(mors); + mm81x_ps_enable(mors); + + /* Don't requeue work if we are shutting down. */ + if (yaps->finish) + return; + /* + * Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data + * tx queue is full + */ + if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND)) + queue_work(mors->chip_wq, &mors->hif_work); + /* + * if data tx queue is not full and the work hasn't been queued let's + * queue it + */ + else if (!yaps->chip_queue_full.is_full && *flags) + queue_work(mors->chip_wq, &mors->hif_work); +} + +int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors) +{ + int i = 0; + int count = 0; + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + count += skb_queue_len(&yaps->beacon_q.pending); + count += skb_queue_len(&yaps->mgmt_q.pending); + count += skb_queue_len(&yaps->cmd_q.pending); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + count += skb_queue_len(&yaps->data_tx_qs[i].pending); + + return count; +} + +int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors) +{ + int i = 0; + int count = 0; + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + count += skb_queue_len(&yaps->beacon_q.skbq) + + skb_queue_len(&yaps->beacon_q.pending); + count += skb_queue_len(&yaps->mgmt_q.skbq) + + skb_queue_len(&yaps->mgmt_q.pending); + count += skb_queue_len(&yaps->cmd_q.skbq) + + skb_queue_len(&yaps->cmd_q.pending); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) + + skb_queue_len(&yaps->data_tx_qs[i].pending); + + return count; +} + +static void mm81x_yaps_tx_q_full_timer(struct timer_list *t) +{ + struct mm81x_yaps *yaps = + timer_container_of(yaps, t, chip_queue_full.timer); + + queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work); +} + +static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps) +{ + timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer, + 0); +} + +static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps) +{ + timer_shutdown_sync(&yaps->chip_queue_full.timer); +} + +int mm81x_yaps_init(struct mm81x *mors) +{ + int i, ret; + struct mm81x_yaps *yaps; + + ret = mm81x_yaps_hw_init(mors); + if (ret) { + dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret); + return ret; + } + + yaps = &mors->hif.u.yaps; + yaps->mors = mors; + + mm81x_claim_bus(mors); + + ret = mm81x_yaps_alloc_pkt_buffers(yaps); + if (ret) { + dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d", + ret); + mm81x_yaps_hw_finish(mors); + mm81x_release_bus(mors); + return ret; + } + + /* YAPS is bi-directional */ + mm81x_skbq_init(mors, &yaps->data_rx_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + mm81x_skbq_init(mors, &yaps->beacon_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + mm81x_skbq_init(mors, &yaps->mgmt_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) { + mm81x_skbq_init(mors, &yaps->data_tx_qs[i], + MM81X_HIF_FLAGS_DATA | + MM81X_HIF_FLAGS_DIR_TO_CHIP); + } + + mm81x_skbq_init(mors, &yaps->cmd_q, + MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP); + mm81x_skbq_init(mors, &yaps->cmd_resp_q, + MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST); + + mm81x_yaps_q_chip_full_timer_init(yaps); + INIT_WORK(&mors->hif_work, mm81x_yaps_work); + INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work); + mm81x_release_bus(mors); + mm81x_hw_enable_stop_notifications(mors, true); + return 0; +} + +void mm81x_yaps_finish(struct mm81x *mors) +{ + int i; + struct mm81x_yaps *yaps; + + mm81x_yaps_hw_enable_irqs(mors, false); + + yaps = &mors->hif.u.yaps; + yaps->finish = true; + + mm81x_skbq_finish(&yaps->data_rx_q); + mm81x_skbq_finish(&yaps->beacon_q); + mm81x_skbq_finish(&yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + mm81x_skbq_finish(&yaps->data_tx_qs[i]); + + mm81x_skbq_finish(&yaps->cmd_q); + mm81x_skbq_finish(&yaps->cmd_resp_q); + + mm81x_yaps_q_chip_full_timer_finish(yaps); + + cancel_work_sync(&mors->hif_work); + cancel_work_sync(&mors->tx_stale_work); + + mm81x_yaps_free_pkt_buffers(yaps); + mm81x_yaps_hw_finish(mors); +} + +void mm81x_yaps_flush_tx_data(struct mm81x *mors) +{ + int i; + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + mm81x_skbq_tx_flush(&yaps->beacon_q); + mm81x_skbq_tx_flush(&yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]); +} + +void mm81x_yaps_flush_cmds(struct mm81x *mors) +{ + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) { + mm81x_skbq_finish(&yaps->cmd_q); + mm81x_skbq_finish(&yaps->cmd_resp_q); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.h b/drivers/net/wireless/morsemicro/mm81x/yaps.h new file mode 100644 index 000000000000..2b2bb5f6e399 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_YAPS_H_ +#define _MM81X_YAPS_H_ + +#include +#include +#include "skbq.h" + +#define YAPS_TX_SKBQ_MAX 4 + +struct mm81x_hif_ops; +extern const struct mm81x_hif_ops mm81x_yaps_ops; + +enum mm81x_yaps_to_chip_q { + MM81X_YAPS_TX_Q = 0, + MM81X_YAPS_CMD_Q, + MM81X_YAPS_BEACON_Q, + MM81X_YAPS_MGMT_Q, + /* Keep this last */ + MM81X_YAPS_NUM_TC_Q +}; + +struct mm81x_yaps_pkt { + struct sk_buff *skb; + enum mm81x_yaps_to_chip_q tc_queue; +}; + +struct mm81x_yaps { + struct mm81x *mors; + struct mm81x_yaps_hw_aux_data *aux_data; + const struct mm81x_yaps_ops *ops; + u8 flags; + struct { + struct mm81x_yaps_pkt *to_chip_pkts; + struct mm81x_yaps_pkt *from_chip_pkts; + } hw; + + /* Chip interface is stopping, new work should not be enqueued. */ + bool finish; + + struct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX]; + struct mm81x_skbq beacon_q; + struct mm81x_skbq mgmt_q; + struct mm81x_skbq data_rx_q; + struct mm81x_skbq cmd_q; + struct mm81x_skbq cmd_resp_q; + + struct { + struct timer_list timer; + unsigned long retry_expiry; + bool is_full; + } chip_queue_full; +}; + +struct mm81x_yaps_ops { + int (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts, + int num_pkts, int *num_pkts_sent); + int (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts, + int num_pkts_max, int *num_pkts_received); + int (*update_status)(struct mm81x_yaps *yaps); +}; + +int mm81x_yaps_init(struct mm81x *mors); +void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file); +void mm81x_yaps_finish(struct mm81x *mors); +void mm81x_yaps_flush_tx_data(struct mm81x *mors); +void mm81x_yaps_flush_cmds(struct mm81x *mors); +void mm81x_yaps_work(struct work_struct *work); +void mm81x_yaps_stale_tx_work(struct work_struct *work); +int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors); +int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors); + +#endif /* !_MM81X_YAPS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c new file mode 100644 index 000000000000..3d641d23c35b --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "yaps_hw.h" +#include "bus.h" +#include "hif.h" +#include "yaps.h" + +#define YAPS_HW_WINDOW_SIZE_BYTES 32768 +#define YAPS_MAX_PKT_SIZE_BYTES 16128 +#define YAPS_METADATA_PAGE_COUNT 1 + +#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1 + +#define YAPS_PAGE_SIZE 256 + +/* Calculate padding required for yaps transaction */ +#define YAPS_CALC_PADDING(_bytes) ((_bytes) & 0x3 ? (4 - ((_bytes) & 0x3)) : 0) + +#define YAPS_RESERVED_PAGE_SIZE 256 + +/* + * Yaps data stream delimiter is a 32 bit word with the following fields: + * + * pkt_size (14 bits) - Packet size not including delimiter or padding + * pool_id (3 bits) - Pool that pages should be allocated from. + * padding (2 bits) - Padding required to bring packet to word (4 byte) + * irq (1 bit ) - Raise a PKT_IRQ on the YDS this is sent to + * reserved (5 bits) - Reserved, must write as 0 + * crc (7 bits) - YAPS CRC + */ + +/* Packet size not including delimiter or padding */ +#define YAPS_DELIM_GET_PKT_SIZE(_delim) \ + (((_delim) & 0x3FFF) - YAPS_RESERVED_PAGE_SIZE) +#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \ + (((_pkt_size) & 0x3FFF) + YAPS_RESERVED_PAGE_SIZE) +#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) & 0x3FFF)) + +/* Pool that pages should be allocated from. */ +#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) & 0x7) << 14) + +/* Padding required to bring packet to word (4 byte) boundary */ +#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) >> 17) & 0x3) +#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) & 0x3) << 17) + +/* Raise a PKT_IRQ on the YDS this is sent to */ +#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) & 0x1) << 19) + +/* YAPS CRC */ +#define YAPS_DELIM_GET_CRC(_delim) (((_delim) >> 25) & 0x7F) +#define YAPS_DELIM_SET_CRC(_crc) (((_crc) & 0x7F) << 25) + +struct mm81x_yaps_status_regs { + /* Allocation pools */ + u32 tc_tx_pool_num_pages; + u32 tc_cmd_pool_num_pages; + u32 tc_beacon_pool_num_pages; + u32 tc_mgmt_pool_num_pages; + u32 fc_rx_pool_num_pages; + u32 fc_resp_pool_num_pages; + u32 fc_tx_sts_pool_num_pages; + u32 fc_aux_pool_num_pages; + u32 tc_tx_num_pkts; + u32 tc_cmd_num_pkts; + u32 tc_beacon_num_pkts; + u32 tc_mgmt_num_pkts; + u32 fc_num_pkts; + u32 fc_done_num_pkts; + u32 fc_rx_bytes_in_queue; + u32 tc_delim_crc_fail_detected; + u32 fc_host_ysl_status; + u32 lock; +} __packed __aligned(8); + +struct mm81x_yaps_hw_status_regs { + __le32 tc_tx_pool_num_pages; + __le32 tc_cmd_pool_num_pages; + __le32 tc_beacon_pool_num_pages; + __le32 tc_mgmt_pool_num_pages; + __le32 fc_rx_pool_num_pages; + __le32 fc_resp_pool_num_pages; + __le32 fc_tx_sts_pool_num_pages; + __le32 fc_aux_pool_num_pages; + __le32 tc_tx_num_pkts; + __le32 tc_cmd_num_pkts; + __le32 tc_beacon_num_pkts; + __le32 tc_mgmt_num_pkts; + __le32 fc_num_pkts; + __le32 fc_done_num_pkts; + __le32 fc_rx_bytes_in_queue; + __le32 tc_delim_crc_fail_detected; + __le32 fc_host_ysl_status; + __le32 lock; +} __packed __aligned(8); + +struct mm81x_yaps_hw_aux_data { + unsigned long access_lock; + + u32 yds_addr; + u32 ysl_addr; + u32 status_regs_addr; + + /* Alloc pool sizes */ + u16 tc_tx_pool_size; + u16 tc_cmd_pool_size; + u8 tc_beacon_pool_size; + u8 tc_mgmt_pool_size; + u8 fc_rx_pool_size; + u8 fc_resp_pool_size; + u8 fc_tx_sts_pool_size; + u8 fc_aux_pool_size; + + /* To chip/from chip queue sizes */ + u8 tc_tx_q_size; + u8 tc_cmd_q_size; + u8 tc_beacon_q_size; + u8 tc_mgmt_q_size; + u8 fc_q_size; + u8 fc_done_q_size; + + u16 reserved_yaps_page_size; + + /* Buffers to/from chip to support large contiguous reads/writes */ + char *to_chip_buffer; + char *from_chip_buffer; + + /* status registers in host endian */ + struct mm81x_yaps_status_regs status_regs; + + /* DMA target buffer in firmware endian */ + struct mm81x_yaps_hw_status_regs hw_status_regs; +}; + +static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps) +{ + if (test_and_set_bit_lock(0, &yaps->aux_data->access_lock)) + return -1; + return 0; +} + +static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps) +{ + clear_bit_unlock(0, &yaps->aux_data->access_lock); +} + +static void +mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a, + struct mm81x_yaps_hw_table *t) +{ + a->ysl_addr = __le32_to_cpu(t->ysl_addr); + a->yds_addr = __le32_to_cpu(t->yds_addr); + a->status_regs_addr = __le32_to_cpu(t->status_regs_addr); + a->tc_tx_pool_size = __le16_to_cpu(t->tc_tx_pool_size); + a->fc_rx_pool_size = __le16_to_cpu(t->fc_rx_pool_size); + a->tc_cmd_pool_size = t->tc_cmd_pool_size; + a->tc_beacon_pool_size = t->tc_beacon_pool_size; + a->tc_mgmt_pool_size = t->tc_mgmt_pool_size; + a->fc_resp_pool_size = t->fc_resp_pool_size; + a->fc_tx_sts_pool_size = t->fc_tx_sts_pool_size; + a->fc_aux_pool_size = t->fc_aux_pool_size; + a->tc_tx_q_size = t->tc_tx_q_size; + a->tc_cmd_q_size = t->tc_cmd_q_size; + a->tc_beacon_q_size = t->tc_beacon_q_size; + a->tc_mgmt_q_size = t->tc_mgmt_q_size; + a->fc_q_size = t->fc_q_size; + a->fc_done_q_size = t->fc_done_q_size; + a->reserved_yaps_page_size = le16_to_cpu(t->yaps_reserved_page_size); +} + +static u8 mm81x_yaps_hw_crc(u32 word) +{ + u8 crc = 0; + u8 byte; + int i; + + /* Mask to look at only non-CRC bits */ + word &= 0x1ffffff; + + for (i = 0; i < 4; i++) { + byte = (word >> 24) & 0xff; + crc = crc7_be(crc, &byte, 1); + word <<= 8; + } + + return crc >> 1; +} + +static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps, + unsigned int size, u8 pool_id, + bool irq) +{ + u32 delim = 0; + + delim |= YAPS_DELIM_SET_PKT_SIZE(size); + delim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size)); + delim |= YAPS_DELIM_SET_POOL_ID(pool_id); + delim |= YAPS_DELIM_SET_IRQ(irq); + delim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim)); + return delim; +} + +void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable) +{ + mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable); + mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN, + enable); +} + +void mm81x_yaps_hw_read_table(struct mm81x *mors, + struct mm81x_yaps_hw_table *tbl_ptr) +{ + mm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors->hif.u.yaps.aux_data, + tbl_ptr); + mm81x_yaps_hw_enable_irqs(mors, true); +} + +static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps, + unsigned int size_bytes) +{ + /* Always account for the first metadata page */ + return DIV_ROUND_UP(size_bytes + + yaps->aux_data->reserved_yaps_page_size, + YAPS_PAGE_SIZE) + + YAPS_METADATA_PAGE_COUNT + + YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE; +} + +/* + * Checks if a single pkt will fit in the chip using the pool/alloc holding + * information from the last status register read. + */ +static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkt, bool update) +{ + bool will_fit = true; + const int pages_required = + mm81x_write_pkts_h_pages_required(yaps, pkt->skb->len); + int *pool_pages_avail = NULL; + int *pkts_in_queue = NULL; + int queue_pkts_avail = 0; + + switch (pkt->tc_queue) { + case MM81X_YAPS_TX_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_tx_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_tx_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_tx_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_CMD_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_cmd_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_cmd_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_cmd_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_BEACON_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_beacon_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_beacon_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_beacon_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_MGMT_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_mgmt_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_mgmt_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_mgmt_q_size - *pkts_in_queue; + break; + default: + dev_err(yaps->mors->dev, "yaps invalid tc queue"); + return false; + } + + WARN_ON(queue_pkts_avail < 0); + + if (pages_required > *pool_pages_avail) + will_fit = false; + + if (queue_pkts_avail == 0) + will_fit = false; + + if (will_fit && update) { + *pool_pages_avail -= pages_required; + *pkts_in_queue += 1; + } + + return will_fit; +} + +static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkt) +{ + if (pkt->skb->len + yaps->aux_data->reserved_yaps_page_size > + YAPS_MAX_PKT_SIZE_BYTES) + return -EMSGSIZE; + if (pkt->tc_queue >= MM81X_YAPS_NUM_TC_Q) + return -EINVAL; + if (!mm81x_write_pkts_h_will_fit(yaps, pkt, true)) + return -EAGAIN; + + return 0; +} + +static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts, + int *num_pkts_sent) +{ + int ret = 0; + int i; + u32 delim = 0; + int tx_len; + int batch_txn_len = 0; + int pkts_pending = 0; + bool delim_irq = false; + char *to_chip_buffer_aligned = + PTR_ALIGN(yaps->aux_data->to_chip_buffer, + mm81x_bus_get_alignment(yaps->mors)); + char *write_buf = to_chip_buffer_aligned; + + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + *num_pkts_sent = 0; + + /* Check packet conditions */ + ret = mm81x_write_pkts_h_err_check(yaps, &pkts[0]); + if (ret) + goto exit; + + /* Batch packets into larger transactions */ + for (i = 0; i < num_pkts; ++i) { + u32 pkt_size = + pkts[i].skb->len + YAPS_CALC_PADDING(pkts[i].skb->len); + tx_len = pkt_size + sizeof(delim); + + /* + * Send when we have reached window size, don't split pkt over + * boundary + */ + if ((batch_txn_len + tx_len) > YAPS_HW_WINDOW_SIZE_BYTES) { + ret = mm81x_dm_write(yaps->mors, + yaps->aux_data->yds_addr, + to_chip_buffer_aligned, + batch_txn_len); + + batch_txn_len = 0; + if (ret) + goto exit; + write_buf = to_chip_buffer_aligned; + *num_pkts_sent += pkts_pending; + pkts_pending = 0; + } + + if ((i + 1) == num_pkts) { + /* The last packet in the queue has IRQ set */ + delim_irq = true; + } else { + /* + * Since this is not the last packet, we can check for + * the next one. In case of errors in the next packet + * set the IRQ + */ + ret = mm81x_write_pkts_h_err_check(yaps, &pkts[i + 1]); + if (ret) + delim_irq = true; + } + + /* Build stream header*/ + delim = mm81x_write_pkts_h_build_delim( + yaps, pkt_size, pkts[i].tc_queue, delim_irq); + *((__le32 *)write_buf) = cpu_to_le32(delim); + memcpy(write_buf + sizeof(delim), pkts[i].skb->data, + pkts[i].skb->len); + + write_buf += tx_len; + batch_txn_len += tx_len; + pkts_pending++; + + if (ret) + goto exit; + } + +exit: + if (batch_txn_len > 0) { + ret = mm81x_dm_write(yaps->mors, yaps->aux_data->yds_addr, + to_chip_buffer_aligned, batch_txn_len); + *num_pkts_sent += pkts_pending; + } + + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static bool mm81x_read_pkts_h_is_valid_delim(u32 delim) +{ + u8 calc_crc = mm81x_yaps_hw_crc(delim); + int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim); + int padding = YAPS_DELIM_GET_PADDING(delim); + + if (calc_crc != YAPS_DELIM_GET_CRC(delim)) + return false; + + if (pkt_size == 0) + return false; + + if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES) + return false; + + /* Pkt length + padding should not require more padding */ + if (YAPS_CALC_PADDING(pkt_size) != padding) + return false; + + return true; +} + +static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps) +{ + u32 bytes_in_queue = yaps->aux_data->status_regs.fc_rx_bytes_in_queue; + u32 delim_overhead = + yaps->aux_data->status_regs.fc_num_pkts * sizeof(u32); + u32 reserved_bytes = yaps->aux_data->status_regs.fc_num_pkts * + yaps->aux_data->reserved_yaps_page_size; + + if (WARN_ON(bytes_in_queue > INT_MAX) || + WARN_ON(delim_overhead > INT_MAX) || + WARN_ON(reserved_bytes > INT_MAX)) + return -EIO; + + return (int)bytes_in_queue; +} + +static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, + int num_pkts_max, int *num_pkts_received) +{ + int ret; + int i = 0; + char *from_chip_buffer_aligned = + PTR_ALIGN(yaps->aux_data->from_chip_buffer, + mm81x_bus_get_alignment(yaps->mors)); + char *read_ptr = from_chip_buffer_aligned; + int bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps); + bool again = false; + + *num_pkts_received = 0; + + if (num_pkts_max == 0 || bytes_remaining == 0) + return 0; + if (bytes_remaining < 0) + return bytes_remaining; + + if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) { + bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES; + again = true; + } + + /* + * This is more coarse-grained than it needs to be - once the data + * is read into a local buffer the lock can be released, however + * access to from_chip_buffer will need to be protected with its + * own lock + */ + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + /* Read all available packets to the buffer */ + ret = mm81x_dm_read(yaps->mors, yaps->aux_data->ysl_addr, + from_chip_buffer_aligned, bytes_remaining); + + if (ret) + goto exit; + + /* Split serialised packets from buffer */ + while (i < num_pkts_max && bytes_remaining > 0) { + u32 delim; + int total_len; + int pkt_size; + + delim = le32_to_cpu(*((__le32 *)read_ptr)); + read_ptr += sizeof(delim); + bytes_remaining -= sizeof(delim); + + /* End of stream */ + if (!delim) + break; + + if (!mm81x_read_pkts_h_is_valid_delim(delim)) { + /* + * This will start a hunt for a valid delimiter. Given + * the CRC is only 7 bit it's possible to find an + * invalid block with a valid delimiter, leading to + * desynchronisation. + */ + dev_warn(yaps->mors->dev, "yaps invalid delim"); + break; + } + + /* Total length in chip */ + pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim); + total_len = pkt_size + YAPS_DELIM_GET_PADDING(delim); + + if (pkts[i].skb) + dev_err(yaps->mors->dev, "yaps packet leak"); + + /* SKB doesn't want padding */ + pkts[i].skb = dev_alloc_skb(pkt_size); + if (!pkts[i].skb) { + ret = -ENOMEM; + dev_err(yaps->mors->dev, "yaps no mem for skb"); + goto exit; + } + skb_put(pkts[i].skb, pkt_size); + + if (total_len <= bytes_remaining) { + memcpy(pkts[i].skb->data, read_ptr, pkt_size); + read_ptr += total_len; + bytes_remaining -= total_len; + } else { + const int read_overhang_len = + total_len - bytes_remaining; + const int pkt_overhang_len = pkt_size - bytes_remaining; + + memcpy(pkts[i].skb->data, read_ptr, bytes_remaining); + read_ptr = from_chip_buffer_aligned; + + ret = mm81x_dm_read( + yaps->mors, + /* Offset by 4 to avoid retry logic */ + yaps->aux_data->ysl_addr + 4, read_ptr, + read_overhang_len); + + if (ret) + goto exit; + + memcpy(pkts[i].skb->data + bytes_remaining, read_ptr, + pkt_overhang_len); + read_ptr += read_overhang_len; + bytes_remaining = 0; + } + + *num_pkts_received += 1; + i++; + } + + if (again) + ret = -EAGAIN; + +exit: + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps) +{ + int ret; + int tc_total_pkt_count; + unsigned long reg_read_timeout; + struct mm81x_yaps_status_regs *r = &yaps->aux_data->status_regs; + struct mm81x_yaps_hw_status_regs *hw_r = &yaps->aux_data->hw_status_regs; + + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + reg_read_timeout = jiffies + msecs_to_jiffies(100); + do { + if (time_after(jiffies, reg_read_timeout)) { + dev_err(yaps->mors->dev, + "timed out reading status registers: %d", ret); + ret = -ETIMEDOUT; + break; + } + + ret = mm81x_dm_read(yaps->mors, + yaps->aux_data->status_regs_addr, + (u8 *)hw_r, sizeof(*hw_r)); + } while (!ret && le32_to_cpu(hw_r->lock)); + + if (ret) { + if (ret != -ENODEV) { + dev_err(yaps->mors->dev, + "error reading yaps status registers: %d", ret); + } + goto exit_unlock; + } + + r->tc_tx_pool_num_pages = le32_to_cpu(hw_r->tc_tx_pool_num_pages); + r->tc_cmd_pool_num_pages = le32_to_cpu(hw_r->tc_cmd_pool_num_pages); + r->tc_beacon_pool_num_pages = le32_to_cpu(hw_r->tc_beacon_pool_num_pages); + r->tc_mgmt_pool_num_pages = le32_to_cpu(hw_r->tc_mgmt_pool_num_pages); + r->fc_rx_pool_num_pages = le32_to_cpu(hw_r->fc_rx_pool_num_pages); + r->fc_resp_pool_num_pages = le32_to_cpu(hw_r->fc_resp_pool_num_pages); + r->fc_tx_sts_pool_num_pages = le32_to_cpu(hw_r->fc_tx_sts_pool_num_pages); + r->fc_aux_pool_num_pages = le32_to_cpu(hw_r->fc_aux_pool_num_pages); + r->tc_tx_num_pkts = le32_to_cpu(hw_r->tc_tx_num_pkts); + r->tc_cmd_num_pkts = le32_to_cpu(hw_r->tc_cmd_num_pkts); + r->tc_beacon_num_pkts = le32_to_cpu(hw_r->tc_beacon_num_pkts); + r->tc_mgmt_num_pkts = le32_to_cpu(hw_r->tc_mgmt_num_pkts); + r->fc_num_pkts = le32_to_cpu(hw_r->fc_num_pkts); + r->fc_done_num_pkts = le32_to_cpu(hw_r->fc_done_num_pkts); + r->fc_rx_bytes_in_queue = le32_to_cpu(hw_r->fc_rx_bytes_in_queue); + r->tc_delim_crc_fail_detected = le32_to_cpu(hw_r->tc_delim_crc_fail_detected); + r->lock = le32_to_cpu(hw_r->lock); + r->fc_host_ysl_status = le32_to_cpu(hw_r->fc_host_ysl_status); + + tc_total_pkt_count = r->tc_tx_num_pkts + r->tc_cmd_num_pkts + + r->tc_beacon_num_pkts + r->tc_mgmt_num_pkts; + + if (r->tc_delim_crc_fail_detected) { + /* + * Host and chip have become desynchronised. This can happen if + * the chip crashes during a YAPS transaction. We cannot + * recover from this. + */ + dev_err(yaps->mors->dev, + "to-chip yaps delimiter CRC fail, pkt_count=%d", + tc_total_pkt_count); + ret = -EIO; + } + + if (mm81x_read_pkts_h_bytes_remaining(yaps)) + set_bit(MM81X_HIF_EVT_RX_PEND, &yaps->mors->hif.event_flags); + +exit_unlock: + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = { + .write_pkts = mm81x_yaps_hw_write_pkts, + .read_pkts = mm81x_yaps_hw_read_pkts, + .update_status = mm81x_yaps_hw_update_status, +}; + +int mm81x_yaps_hw_init(struct mm81x *mors) +{ + int ret = 0; + struct mm81x_yaps *yaps = NULL; + int aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data); + int alignment = mm81x_bus_get_alignment(mors); + + yaps = &mors->hif.u.yaps; + yaps->aux_data = kzalloc(aux_data_len, GFP_KERNEL); + if (!yaps->aux_data) { + ret = -ENOMEM; + goto err_exit; + } + + yaps->aux_data->to_chip_buffer = + kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL); + if (!yaps->aux_data->to_chip_buffer) { + ret = -ENOMEM; + goto err_exit; + } + + yaps->aux_data->from_chip_buffer = + kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL); + if (!yaps->aux_data->from_chip_buffer) { + ret = -ENOMEM; + goto err_exit; + } + + if (!IS_ALIGNED((uintptr_t)&yaps->aux_data->status_regs, alignment)) { + dev_warn(mors->dev, + "Status registers are not aligned to %d bytes", + alignment); + } + + yaps->ops = &mm81x_yaps_hw_ops; + return ret; + +err_exit: + mm81x_yaps_hw_finish(mors); + return ret; +} + +void mm81x_yaps_hw_finish(struct mm81x *mors) +{ + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + if (yaps->aux_data) { + kfree(yaps->aux_data->from_chip_buffer); + yaps->aux_data->from_chip_buffer = NULL; + kfree(yaps->aux_data->to_chip_buffer); + yaps->aux_data->to_chip_buffer = NULL; + kfree(yaps->aux_data); + yaps->aux_data = NULL; + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h new file mode 100644 index 000000000000..89e15375aabc --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_YAPS_HW_H_ +#define _MM81X_YAPS_HW_H_ + +#include +#include + +#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0 +#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1 + +struct mm81x_yaps_hw_table { + /* NOTE: We need these padding bytes for yaps to work */ + u8 padding[4]; + __le32 ysl_addr; + __le32 yds_addr; + __le32 status_regs_addr; + + /* Alloc pool sizes */ + __le16 tc_tx_pool_size; + __le16 fc_rx_pool_size; + u8 tc_cmd_pool_size; + u8 tc_beacon_pool_size; + u8 tc_mgmt_pool_size; + u8 fc_resp_pool_size; + u8 fc_tx_sts_pool_size; + u8 fc_aux_pool_size; + + /* To chip/from chip queue sizes */ + u8 tc_tx_q_size; + u8 tc_cmd_q_size; + u8 tc_beacon_q_size; + u8 tc_mgmt_q_size; + u8 fc_q_size; + u8 fc_done_q_size; + + __le16 yaps_reserved_page_size; + __le16 reserved_unused; +} __packed; + +struct mm81x; + +void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable); +int mm81x_yaps_hw_init(struct mm81x *mors); +void mm81x_yaps_hw_finish(struct mm81x *mors); +void mm81x_yaps_hw_read_table(struct mm81x *mors, + struct mm81x_yaps_hw_table *tbl_ptr); + +#endif /* !_MM81X_YAPS_HW_H_ */ diff --git a/drivers/net/wireless/nxp/Kconfig b/drivers/net/wireless/nxp/Kconfig new file mode 100644 index 000000000000..68b32d4536e5 --- /dev/null +++ b/drivers/net/wireless/nxp/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WLAN_VENDOR_NXP + bool "NXP devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + questions about these cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_NXP + +source "drivers/net/wireless/nxp/nxpwifi/Kconfig" + +endif # WLAN_VENDOR_NXP diff --git a/drivers/net/wireless/nxp/Makefile b/drivers/net/wireless/nxp/Makefile new file mode 100644 index 000000000000..27b41a0afdd2 --- /dev/null +++ b/drivers/net/wireless/nxp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_NXPWIFI) += nxpwifi/ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.c b/drivers/net/wireless/nxp/nxpwifi/11ac.c new file mode 100644 index 000000000000..117d06c35401 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11ac helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ac.h" + +/* Map VHT MCS/NSS to highest data rate (Mbps), long GI. */ +static const u16 max_rate_lgi_80MHZ[8][3] = { + {0x124, 0x15F, 0x186}, /* NSS = 1 */ + {0x249, 0x2BE, 0x30C}, /* NSS = 2 */ + {0x36D, 0x41D, 0x492}, /* NSS = 3 */ + {0x492, 0x57C, 0x618}, /* NSS = 4 */ + {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 6 */ + {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */ + {0x924, 0xAF8, 0xC30} /* NSS = 8 */ +}; + +static const u16 max_rate_lgi_160MHZ[8][3] = { + {0x249, 0x2BE, 0x30C}, /* NSS = 1 */ + {0x492, 0x57C, 0x618}, /* NSS = 2 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 3 */ + {0x924, 0xAF8, 0xC30}, /* NSS = 4 */ + {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */ + {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */ + {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */ + {0x1248, 0x15F0, 0x1860} /* NSS = 8 */ +}; + +/* Convert 2-bit MCS map to highest long-GI VHT data rate. */ +static u16 +nxpwifi_convert_mcsmap_to_maxrate(struct nxpwifi_private *priv, + u16 bands, u16 mcs_map) +{ + u8 i, nss, mcs; + u16 max_rate = 0; + u32 usr_vht_cap_info = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_AAC) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* Find max supported NSS. */ + nss = 1; + for (i = 1; i <= 8; i++) { + mcs = GET_VHTNSSMCS(mcs_map, i); + if (mcs < IEEE80211_VHT_MCS_NOT_SUPPORTED) + nss = i; + } + mcs = GET_VHTNSSMCS(mcs_map, nss); + + /* If not supported, fall back to 0-9. */ + if (mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED) + mcs = IEEE80211_VHT_MCS_SUPPORT_0_9; + + if (u32_get_bits(usr_vht_cap_info, IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK)) { + /* Support 160 MHz. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS6. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs - 1]; + } else { + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS3. */ + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs - 1]; + } + + return max_rate; +} + +static void +nxpwifi_fill_vht_cap_info(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_A) + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a); + else + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg); +} + +void +nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 mcs_map_user, mcs_map_resp, mcs_map_result; + u16 mcs_user, mcs_resp, nss, tmp; + + /* Fill VHT capability info. */ + nxpwifi_fill_vht_cap_info(priv, vht_cap, bands); + + /* RX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.rx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.rx_highest = cpu_to_le16(tmp); + + /* TX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.tx_highest = cpu_to_le16(tmp); +} + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_oper_mode_ntf *oper_ntf; + struct ieee_types_oper_mode_ntf *ieee_oper_ntf; + struct nxpwifi_ie_types_vht_oper *vht_op; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 supp_chwd_set; + u32 usr_vht_cap_info; + int ret_len = 0; + + if (bss_desc->bss_band & BAND_A) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* VHT Capabilities element. */ + if (bss_desc->bcn_vht_cap) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)*buffer; + memset(vht_cap, 0, sizeof(*vht_cap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + memcpy((u8 *)vht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_cap, + le16_to_cpu(vht_cap->header.len)); + + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, + bss_desc->bss_band); + *buffer += sizeof(*vht_cap); + ret_len += sizeof(*vht_cap); + } + + /* VHT Operation element. */ + if (bss_desc->bcn_vht_oper) { + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + vht_op = (struct nxpwifi_ie_types_vht_oper *)*buffer; + memset(vht_op, 0, sizeof(*vht_op)); + vht_op->header.type = + cpu_to_le16(WLAN_EID_VHT_OPERATION); + vht_op->header.len = cpu_to_le16(sizeof(*vht_op) - + sizeof(struct nxpwifi_ie_types_header)); + memcpy((u8 *)vht_op + + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_oper, + le16_to_cpu(vht_op->header.len)); + + /* Negotiate channel width; keep peer's center freq. */ + supp_chwd_set = u32_get_bits(usr_vht_cap_info, + IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK); + + switch (supp_chwd_set) { + case 0: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 1: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 2: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + default: + vht_op->chan_width = + IEEE80211_VHT_CHANWIDTH_USE_HT; + break; + } + + *buffer += sizeof(*vht_op); + ret_len += sizeof(*vht_op); + } + } + + /* Operating Mode Notification element. */ + if (bss_desc->oper_mode) { + ieee_oper_ntf = bss_desc->oper_mode; + oper_ntf = (void *)*buffer; + memset(oper_ntf, 0, sizeof(*oper_ntf)); + oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF); + oper_ntf->header.len = cpu_to_le16(sizeof(u8)); + oper_ntf->oper_mode = ieee_oper_ntf->oper_mode; + *buffer += sizeof(*oper_ntf); + ret_len += sizeof(*oper_ntf); + } + + return ret_len; +} + +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg) +{ + struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_11AC_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) + + S_DS_GEN); + vhtcfg->action = cpu_to_le16(cmd_action); + vhtcfg->band_config = cfg->band_config; + vhtcfg->misc_config = cfg->misc_config; + vhtcfg->cap_info = cpu_to_le32(cfg->cap_info); + vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set); + vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set); + + return 0; +} + +/* Initialize BlockAck parameters for 11ac. */ +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.h b/drivers/net/wireless/nxp/nxpwifi/11ac.h new file mode 100644 index 000000000000..edc01b35d5b8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ac (VHT) definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AC_H_ +#define _NXPWIFI_11AC_H_ + +#define VHT_CFG_2GHZ BIT(0) +#define VHT_CFG_5GHZ BIT(1) + +enum vht_cfg_misc_config { + VHT_CAP_TX_OPERATION = 1, + VHT_CAP_ASSOCIATION, + VHT_CAP_UAP_ONLY +}; + +#define DEFAULT_VHT_MCS_SET 0xfffe +#define DISABLE_VHT_MCS_SET 0xffff + +#define VHT_BW_80_160_80P80 BIT(2) + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg); +void nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands); +#endif /* _NXPWIFI_11AC_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c new file mode 100644 index 000000000000..96540914f3cf --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* nxpwifi: 802.11ax (HE) support + * Copyright (C) 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ax.h" + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap) +{ + struct nxpwifi_private *priv; + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + struct nxpwifi_ie_types_he_cap *user_he_cap = NULL; + u8 header_len = sizeof(struct nxpwifi_ie_types_header); + u16 data_len = le16_to_cpu(hw_he_cap->header.len); + bool he_cap_2g = false; + int i; + + if ((data_len + header_len) > sizeof(adapter->hw_he_cap)) { + nxpwifi_dbg(adapter, ERROR, + "hw_he_cap too big, len=%d\n", + data_len); + return; + } + + he_cap = (struct nxpwifi_ie_types_he_cap *)hw_he_cap; + + if (he_cap->he_phy_cap[0] & + (AX_2G_40MHZ_SUPPORT | AX_2G_20MHZ_SUPPORT)) { + adapter->hw_2g_he_cap_len = data_len + header_len; + memcpy(adapter->hw_2g_he_cap, (u8 *)hw_he_cap, + adapter->hw_2g_he_cap_len); + adapter->fw_bands |= BAND_GAX; + he_cap_2g = true; + nxpwifi_dbg_dump(adapter, CMD_D, "2.4G HE capability element ", + adapter->hw_2g_he_cap, + adapter->hw_2g_he_cap_len); + } else { + adapter->hw_he_cap_len = data_len + header_len; + memcpy(adapter->hw_he_cap, (u8 *)hw_he_cap, + adapter->hw_he_cap_len); + adapter->fw_bands |= BAND_AAX; + nxpwifi_dbg_dump(adapter, CMD_D, "5G HE capability element ", + adapter->hw_he_cap, + adapter->hw_he_cap_len); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + if (he_cap_2g) { + priv->user_2g_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(priv->user_2g_he_cap, adapter->hw_2g_he_cap, + sizeof(adapter->hw_2g_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } else { + priv->user_he_cap_len = adapter->hw_he_cap_len; + memcpy(priv->user_he_cap, adapter->hw_he_cap, + sizeof(adapter->hw_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + else + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + adapter->is_hw_11ax_capable = true; +} + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u16 bss_band = bss_desc->bss_band; + + if (bss_desc->disable_11n) + return false; + + if (bss_band & BAND_G) + return (priv->config_bands & BAND_GAX); + else if (bss_band & BAND_A) + return (priv->config_bands & BAND_AAX); + + return false; +} + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_he_cap *hw_he_cap = NULL; + u16 rx_nss, tx_nss; + u8 nss; + u16 cfg_value; + u16 hw_value; + int ret_len; + + if (bands & BAND_A) { + memcpy(he_cap, priv->user_he_cap, priv->user_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_he_cap; + ret_len = priv->user_he_cap_len; + } else { + memcpy(he_cap, priv->user_2g_he_cap, priv->user_2g_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_2g_he_cap; + ret_len = priv->user_2g_he_cap_len; + } + + if (bands & BAND_A) { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream >> 8); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream >> 8) & 0x0f; + } else { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream) & 0x0f; + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->rx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->rx_mcs_80, nss); + if (rx_nss != 0 && nss > rx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->tx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->tx_mcs_80, nss); + if (tx_nss != 0 && nss > tx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + return ret_len; +} + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + int ret_len; + + if (!bss_desc->bcn_he_cap) + return -EOPNOTSUPP; + + he_cap = (struct nxpwifi_ie_types_he_cap *)*buffer; + ret_len = nxpwifi_fill_he_cap_tlv(priv, he_cap, bss_desc->bss_band); + *buffer += ret_len; + + return ret_len; +} + +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &cmd->params.ax_cfg; + u16 cmd_size; + struct nxpwifi_ie_types_header *header; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CFG); + cmd_size = sizeof(struct host_cmd_11ax_cfg) + S_DS_GEN; + + he_cfg->action = cpu_to_le16(cmd_action); + he_cfg->band_config = ax_cfg->band; + + if (ax_cfg->he_cap_cfg.len && + ax_cfg->he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + header->type = cpu_to_le16(ax_cfg->he_cap_cfg.id); + header->len = cpu_to_le16(ax_cfg->he_cap_cfg.len); + memcpy(he_cfg->tlv + sizeof(*header), + &ax_cfg->he_cap_cfg.ext_id, + ax_cfg->he_cap_cfg.len); + cmd_size += (sizeof(*header) + ax_cfg->he_cap_cfg.len); + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg; + struct nxpwifi_ie_types_header *header; + u16 left_len, tlv_type, tlv_len; + u8 ext_id; + struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg; + + left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN; + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + + while (left_len > sizeof(*header)) { + tlv_type = le16_to_cpu(header->type); + tlv_len = le16_to_cpu(header->len); + + if (tlv_type == TLV_TYPE_EXTENSION_ID) { + ext_id = *((u8 *)header + sizeof(*header) + 1); + if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + he_cap->id = tlv_type; + he_cap->len = tlv_len; + memcpy((u8 *)&he_cap->ext_id, + (u8 *)header + sizeof(*header) + 1, + tlv_len); + if (he_cfg->band_config & BIT(1)) { + memcpy(priv->user_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_he_cap_len = + sizeof(*header) + tlv_len; + } else { + memcpy(priv->user_2g_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_2g_he_cap_len = + sizeof(*header) + tlv_len; + } + } + } + + left_len -= (sizeof(*header) + tlv_len); + header = (struct nxpwifi_ie_types_header *)((u8 *)header + + sizeof(*header) + + tlv_len); + } + + return 0; +} + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &cmd->params.ax_cmd; + u16 cmd_size; + struct nxpwifi_11ax_sr_cmd *sr_cmd; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_11ax_beam_cmd *beam_cmd; + struct nxpwifi_11ax_htc_cmd *htc_cmd; + struct nxpwifi_11ax_txomi_cmd *txmoi_cmd; + struct nxpwifi_11ax_toltime_cmd *toltime_cmd; + struct nxpwifi_11ax_txop_cmd *txop_cmd; + struct nxpwifi_11ax_set_bsrp_cmd *set_bsrp_cmd; + struct nxpwifi_11ax_llde_cmd *llde_cmd; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CMD); + cmd_size = sizeof(struct host_cmd_11ax_cmd) + S_DS_GEN; + + he_cmd->action = cpu_to_le16(cmd_action); + he_cmd->sub_id = cpu_to_le16(ax_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + sr_cmd = (struct nxpwifi_11ax_sr_cmd *)&ax_cmd->param; + + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + tlv->header.type = cpu_to_le16(sr_cmd->type); + tlv->header.len = cpu_to_le16(sr_cmd->len); + memcpy(tlv->data, sr_cmd->param.obss_pd_offset.offset, + sr_cmd->len); + cmd_size += (sizeof(tlv->header) + sr_cmd->len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + beam_cmd = (struct nxpwifi_11ax_beam_cmd *)&ax_cmd->param; + + he_cmd->val[0] = beam_cmd->value; + cmd_size += sizeof(*beam_cmd); + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + htc_cmd = (struct nxpwifi_11ax_htc_cmd *)&ax_cmd->param; + + he_cmd->val[0] = htc_cmd->value; + cmd_size += sizeof(*htc_cmd); + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + txmoi_cmd = (struct nxpwifi_11ax_txomi_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, txmoi_cmd, sizeof(*txmoi_cmd)); + cmd_size += sizeof(*txmoi_cmd); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + toltime_cmd = (struct nxpwifi_11ax_toltime_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &toltime_cmd->tol_time, + sizeof(toltime_cmd->tol_time)); + cmd_size += sizeof(*toltime_cmd); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + txop_cmd = (struct nxpwifi_11ax_txop_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &txop_cmd->rts_thres, + sizeof(txop_cmd->rts_thres)); + cmd_size += sizeof(*txop_cmd); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + set_bsrp_cmd = (struct nxpwifi_11ax_set_bsrp_cmd *)&ax_cmd->param; + + he_cmd->val[0] = set_bsrp_cmd->value; + cmd_size += sizeof(*set_bsrp_cmd); + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + llde_cmd = (struct nxpwifi_11ax_llde_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, llde_cmd, sizeof(*llde_cmd)); + cmd_size += sizeof(*llde_cmd); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &resp->params.ax_cmd; + struct nxpwifi_ie_types_data *tlv; + + ax_cmd->sub_id = le16_to_cpu(he_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + memcpy(ax_cmd->param.sr_cfg.param.obss_pd_offset.offset, + tlv->data, + ax_cmd->param.sr_cfg.len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + ax_cmd->param.beam_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + ax_cmd->param.htc_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + memcpy(&ax_cmd->param.txomi_cfg, + he_cmd->val, sizeof(ax_cmd->param.txomi_cfg)); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + memcpy(&ax_cmd->param.toltime_cfg.tol_time, + he_cmd->val, sizeof(ax_cmd->param.toltime_cfg)); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + memcpy(&ax_cmd->param.txop_cfg.rts_thres, + he_cmd->val, sizeof(ax_cmd->param.txop_cfg)); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + ax_cmd->param.setbsrp_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + memcpy(&ax_cmd->param.llde_cfg, + he_cmd->val, sizeof(ax_cmd->param.llde_cfg)); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + return 0; +} + +static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_desc) +{ + struct element *ext_cap; + + if (!bss_desc->bcn_he_cap) + return false; + if (!(bss_desc->bcn_he_cap->mac_cap_info[0] & HE_MAC_CAP_TWT_RESP_SUPPORT)) + return false; + if (!bss_desc->bcn_ext_cap) + return false; + ext_cap = (struct element *)bss_desc->bcn_ext_cap; + + if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT)) + return false; + return true; +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + struct nxpwifi_ie_types_he_cap *hw_he_cap; + + if (!bss_desc) + return false; + + if (!nxpwifi_is_ap_11ax_twt_supported(bss_desc)) { + nxpwifi_dbg(priv->adapter, MSG, + "AP don't support twt feature\n"); + return false; + } + + if (bss_desc->bss_band & BAND_A) { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } else { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_2g_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } + + if (!(hw_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "FW don't support TWT\n"); + return false; + } + + if (!(user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "USER HE_MAC_CAP don't support TWT\n"); + return false; + } + + return true; +} + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + u8 ret = 0; + + if (ISSUPP_11AXENABLED(priv->adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || priv->config_bands & BAND_AAX)) { + if (priv->config_bands & BAND_AAX) + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_he_cap; + else + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_2g_he_cap; + ret = user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + return ret; +} + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_twt_cfg *twt_cfg_cmd = &cmd->params.twt_cfg; + struct nxpwifi_twt_setup *twt_setup; + struct nxpwifi_twt_teardown *twt_teardown; + struct nxpwifi_twt_report *twt_report; + struct nxpwifi_twt_information *twt_information; + struct nxpwifi_btwt_ap_config *btwt_ap_config; + u8 i; + u16 cmd_size; + + cmd->command = cpu_to_le16(HOST_CMD_TWT_CFG); + cmd_size = sizeof(struct host_cmd_twt_cfg) + S_DS_GEN; + + twt_cfg_cmd->action = cpu_to_le16(cmd_action); + twt_cfg_cmd->sub_id = cpu_to_le16(twt_cfg->sub_id); + + switch (twt_cfg->sub_id) { + case NXPWIFI_11AX_TWT_SETUP_SUBID: + twt_setup = (struct nxpwifi_twt_setup *) + twt_cfg_cmd->val; + + memset(twt_setup, 0x00, sizeof(struct nxpwifi_twt_setup)); + twt_setup->implicit = twt_cfg->param.twt_setup.implicit; + twt_setup->announced = twt_cfg->param.twt_setup.announced; + twt_setup->trigger_enabled = twt_cfg->param.twt_setup.trigger_enabled; + twt_setup->twt_info_disabled = twt_cfg->param.twt_setup.twt_info_disabled; + twt_setup->negotiation_type = twt_cfg->param.twt_setup.negotiation_type; + twt_setup->twt_wakeup_duration = + twt_cfg->param.twt_setup.twt_wakeup_duration; + twt_setup->flow_identifier = twt_cfg->param.twt_setup.flow_identifier; + twt_setup->hard_constraint = twt_cfg->param.twt_setup.hard_constraint; + twt_setup->twt_exponent = twt_cfg->param.twt_setup.twt_exponent; + twt_setup->twt_mantissa = twt_cfg->param.twt_setup.twt_mantissa; + twt_setup->twt_request = twt_cfg->param.twt_setup.twt_request; + twt_setup->bcn_miss_threshold = twt_cfg->param.twt_setup.bcn_miss_threshold; + cmd_size += sizeof(struct nxpwifi_twt_setup); + break; + case NXPWIFI_11AX_TWT_TEARDOWN_SUBID: + twt_teardown = (struct nxpwifi_twt_teardown *) + twt_cfg_cmd->val; + memset(twt_teardown, 0x00, + sizeof(struct nxpwifi_twt_teardown)); + twt_teardown->flow_identifier = + twt_cfg->param.twt_teardown.flow_identifier; + twt_teardown->negotiation_type = + twt_cfg->param.twt_teardown.negotiation_type; + twt_teardown->teardown_all_twt = + twt_cfg->param.twt_teardown.teardown_all_twt; + cmd_size += sizeof(struct nxpwifi_twt_teardown); + break; + case NXPWIFI_11AX_TWT_REPORT_SUBID: + twt_report = (struct nxpwifi_twt_report *) + twt_cfg_cmd->val; + memset(twt_report, 0x00, sizeof(struct nxpwifi_twt_report)); + twt_report->type = twt_cfg->param.twt_report.type; + cmd_size += sizeof(struct nxpwifi_twt_report); + break; + case NXPWIFI_11AX_TWT_INFORMATION_SUBID: + twt_information = (struct nxpwifi_twt_information *) + twt_cfg_cmd->val; + memset(twt_information, 0x00, + sizeof(struct nxpwifi_twt_information)); + twt_information->flow_identifier = + twt_cfg->param.twt_information.flow_identifier; + twt_information->suspend_duration = + twt_cfg->param.twt_information.suspend_duration; + cmd_size += sizeof(struct nxpwifi_twt_information); + break; + case NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID: + btwt_ap_config = (struct nxpwifi_btwt_ap_config *) + twt_cfg_cmd->val; + memset(btwt_ap_config, 0x00, + sizeof(struct nxpwifi_btwt_ap_config)); + btwt_ap_config->ap_bcast_bet_sta_wait = + twt_cfg->param.btwt_ap_config.ap_bcast_bet_sta_wait; + btwt_ap_config->ap_bcast_offset = + twt_cfg->param.btwt_ap_config.ap_bcast_offset; + btwt_ap_config->bcast_twtli = + twt_cfg->param.btwt_ap_config.bcast_twtli; + btwt_ap_config->count = + twt_cfg->param.btwt_ap_config.count; + for (i = 0; i < BTWT_AGREEMENT_MAX; i++) { + btwt_ap_config->btwt_sets[i].btwt_id = + twt_cfg->param.btwt_ap_config.btwt_sets[i].btwt_id; + btwt_ap_config->btwt_sets[i].ap_bcast_mantissa = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_mantissa; + btwt_ap_config->btwt_sets[i].ap_bcast_exponent = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_exponent; + btwt_ap_config->btwt_sets[i].nominalwake = + twt_cfg->param.btwt_ap_config.btwt_sets[i].nominalwake; + } + + cmd_size += sizeof(struct nxpwifi_btwt_ap_config); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "Unknown sub id: %d\n", twt_cfg->sub_id); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct host_cmd_twt_cfg *twt_cfg_cmd = &resp->params.twt_cfg; + u16 action; + + action = le16_to_cpu(twt_cfg_cmd->action); + twt_cfg->sub_id = le16_to_cpu(twt_cfg_cmd->sub_id); + + if (action == HOST_ACT_GEN_GET && + twt_cfg->sub_id == NXPWIFI_11AX_TWT_REPORT_SUBID) { + struct nxpwifi_twt_report *twt_report = + (struct nxpwifi_twt_report *)twt_cfg_cmd->val; + + memcpy(&twt_cfg->param.twt_report, twt_report, sizeof(struct nxpwifi_twt_report)); + } + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.h b/drivers/net/wireless/nxp/nxpwifi/11ax.h new file mode 100644 index 000000000000..2eda69f19763 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ax support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AX_H_ +#define _NXPWIFI_11AX_H_ + +/* device support 2.4G 40MHZ */ +#define AX_2G_40MHZ_SUPPORT BIT(1) +/* device support 2.4G 242 tone RUs */ +#define AX_2G_20MHZ_SUPPORT BIT(5) + +/* Get HE MCS map code for n spatial streams (0..3). */ +static inline u16 +nxpwifi_get_he_nss_mcs(__le16 mcs_map_set, int nss) { + return ((le16_to_cpu(mcs_map_set) >> (2 * (nss - 1))) & 0x3); +} + +static inline void +nxpwifi_set_he_nss_mcs(__le16 *mcs_map_set, int nss, int value) { + u16 temp; + + temp = le16_to_cpu(*mcs_map_set); + temp |= ((value & 0x3) << (2 * (nss - 1))); + *mcs_map_set = cpu_to_le16(temp); +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap); + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands); +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg); + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg); + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv); + +#endif /* _NXPWIFI_11AX_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11h.c b/drivers/net/wireless/nxp/nxpwifi/11h.c new file mode 100644 index 000000000000..058c319ff910 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11h.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11h helpers + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "fw.h" +#include "cfg80211.h" + +void nxpwifi_init_11h_params(struct nxpwifi_private *priv) +{ + priv->state_11h.is_11h_enabled = true; + priv->state_11h.is_11h_active = false; +} + +int nxpwifi_is_11h_active(struct nxpwifi_private *priv) +{ + return priv->state_11h.is_11h_active; +} + +/* appends 11h info to a buffer while joining an infrastructure BSS */ +static void +nxpwifi_11h_process_infra_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_header *ie_header; + struct nxpwifi_ie_types_pwr_capability *cap; + struct nxpwifi_ie_types_local_pwr_constraint *constraint; + struct ieee80211_supported_band *sband; + u8 radio_type; + int i; + + if (!buffer || !(*buffer)) + return; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + cap = (struct nxpwifi_ie_types_pwr_capability *)*buffer; + cap->header.type = cpu_to_le16(WLAN_EID_PWR_CAPABILITY); + cap->header.len = cpu_to_le16(2); + cap->min_pwr = 0; + cap->max_pwr = 0; + *buffer += sizeof(*cap); + + constraint = (struct nxpwifi_ie_types_local_pwr_constraint *)*buffer; + constraint->header.type = cpu_to_le16(WLAN_EID_PWR_CONSTRAINT); + constraint->header.len = cpu_to_le16(2); + constraint->chan = bss_desc->channel; + constraint->constraint = bss_desc->local_constraint; + *buffer += sizeof(*constraint); + + ie_header = (struct nxpwifi_ie_types_header *)*buffer; + ie_header->type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header->len = cpu_to_le16(2 * sband->n_channels + 2); + *buffer += sizeof(*ie_header); + *(*buffer)++ = WLAN_EID_SUPPORTED_CHANNELS; + *(*buffer)++ = 2 * sband->n_channels; + for (i = 0; i < sband->n_channels; i++) { + u32 center_freq; + + center_freq = sband->channels[i].center_freq; + *(*buffer)++ = ieee80211_frequency_to_channel(center_freq); + *(*buffer)++ = 1; /* one channel in the subband */ + } +} + +/* Enable or disable the 11h extensions in the firmware */ +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag) +{ + u32 enable = flag; + + /* enable master mode radar detection on AP interface */ + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && enable) + enable |= NXPWIFI_MASTER_RADAR_DET_MASK; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11H_I, &enable, true); +} + +/* + * Process TLV buffer for a pending BSS join. Enable 11h in firmware when the + * network advertises spectrum management, and add required TLVs based on the + * BSS's 11h capability. + */ +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (bss_desc->sensed_11h) { + /* Activate 11h functions in firmware, turns on capability bit */ + nxpwifi_11h_activate(priv, true); + priv->state_11h.is_11h_active = true; + bss_desc->cap_info_bitmap |= WLAN_CAPABILITY_SPECTRUM_MGMT; + nxpwifi_11h_process_infra_join(priv, buffer, bss_desc); + } else { + /* Deactivate 11h functions in the firmware */ + nxpwifi_11h_activate(priv, false); + priv->state_11h.is_11h_active = false; + bss_desc->cap_info_bitmap &= ~WLAN_CAPABILITY_SPECTRUM_MGMT; + } +} + +/* + * DFS CAC work function. This delayed work emits CAC finished event for cfg80211 + * if CAC was started earlier + */ +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_chan_def chandef; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_cac_work); + + chandef = priv->dfs_chandef; + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, MSG, + "CAC timer finished; No radar detected\n"); + cfg80211_cac_event(priv->netdev, &chandef, + NL80211_RADAR_CAC_FINISHED, + GFP_KERNEL, 0); + } +} + +/* prepares channel report request command to FW for starting radar detection */ +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_chan_rpt_req *cr_req = &cmd->params.chan_rpt_req; + struct nxpwifi_radar_params *radar_params = (void *)data_buf; + u16 size; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REPORT_REQUEST); + size = S_DS_GEN; + + cr_req->chan_desc.start_freq = cpu_to_le16(NXPWIFI_A_BAND_START_FREQ); + nxpwifi_convert_chan_to_band_cfg(priv, + &cr_req->chan_desc.band_cfg, + radar_params->chandef); + cr_req->chan_desc.chan_num = radar_params->chandef->chan->hw_value; + cr_req->msec_dwell_time = cpu_to_le32(radar_params->cac_time_ms); + size += sizeof(*cr_req); + + if (radar_params->cac_time_ms) { + struct nxpwifi_ie_types_chan_rpt_data *rpt; + + rpt = (struct nxpwifi_ie_types_chan_rpt_data *)((u8 *)cmd + size); + rpt->header.type = cpu_to_le16(TLV_TYPE_CHANRPT_11H_BASIC); + rpt->header.len = cpu_to_le16(sizeof(u8)); + rpt->meas_rpt_map = 1 << MEAS_RPT_MAP_RADAR_SHIFT_BIT; + size += sizeof(*rpt); + + nxpwifi_dbg(priv->adapter, MSG, + "11h: issuing DFS Radar check for channel=%d\n", + radar_params->chandef->chan->hw_value); + } else { + nxpwifi_dbg(priv->adapter, MSG, "cancelling CAC\n"); + } + + cmd->size = cpu_to_le16(size); + + return 0; +} + +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_radar_params radar_params; + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, + HOST_ACT_GEN_SET, 0, &radar_params, true); +} + +/* Abort ongoing CAC when stopping AP operations or during unload */ +void nxpwifi_abort_cac(struct nxpwifi_private *priv) +{ + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to stop CAC in FW\n"); + nxpwifi_dbg(priv->adapter, MSG, + "Aborting delayed work for CAC.\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } +} + +/* + * handles channel report event from FW during CAC period. If radar is detected + * during CAC, driver indicates the same to cfg80211 and also cancels ongoing + * delayed work + */ +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct host_cmd_ds_chan_rpt_event *rpt_event; + struct nxpwifi_ie_types_chan_rpt_data *rpt; + u16 event_len, tlv_len; + + rpt_event = (void *)(skb->data + sizeof(u32)); + event_len = skb->len - (sizeof(struct host_cmd_ds_chan_rpt_event) + + sizeof(u32)); + + if (le32_to_cpu(rpt_event->result) != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error in channel report event\n"); + return -EINVAL; + } + + while (event_len >= sizeof(struct nxpwifi_ie_types_header)) { + rpt = (void *)&rpt_event->tlvbuf; + tlv_len = le16_to_cpu(rpt->header.len); + + switch (le16_to_cpu(rpt->header.type)) { + case TLV_TYPE_CHANRPT_11H_BASIC: + if (rpt->meas_rpt_map & MEAS_RPT_MAP_RADAR_MASK) { + nxpwifi_dbg(priv->adapter, MSG, + "RADAR Detected on channel %d!\n", + priv->dfs_chandef.chan->hw_value); + + wiphy_delayed_work_cancel(priv->adapter->wiphy, + &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, + &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, + GFP_KERNEL, 0); + cfg80211_radar_event(priv->adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + break; + default: + break; + } + + event_len -= (tlv_len + sizeof(rpt->header)); + } + + return 0; +} + +/* Handler for radar detected event from FW */ +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_radar_det_event *rdr_event; + + rdr_event = (void *)(skb->data + sizeof(u32)); + + nxpwifi_dbg(priv->adapter, MSG, + "radar detected; indicating kernel\n"); + + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } + cfg80211_radar_event(priv->adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + nxpwifi_dbg(priv->adapter, MSG, "regdomain: %d\n", + rdr_event->reg_domain); + nxpwifi_dbg(priv->adapter, MSG, "radar detection type: %d\n", + rdr_event->det_type); + + return 0; +} + +/* + * work function for channel switch handling. takes care of updating new channel + * definitin to bss config structure, restart AP and indicate channel switch + * success to cfg80211 + */ +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_chan_sw_work); + struct nxpwifi_adapter *adapter = priv->adapter; + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + bss_cfg = &priv->bss_cfg; + if (!bss_cfg->beacon_period) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: AP already stopped\n"); + return; + } + + if (nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to stop the BSS\n"); + return; + } + + if (nxpwifi_cfg80211_change_beacon(adapter->wiphy, priv->netdev, + &priv->ap_update_info)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to set beacon\n"); + return; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, priv->dfs_chandef); + + if (nxpwifi_config_start_uap(priv, bss_cfg)) { + nxpwifi_dbg(adapter, ERROR, + "Failed to start AP after channel switch\n"); + return; + } + + nxpwifi_dbg(adapter, MSG, + "indicating channel switch completion to kernel\n"); + + cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef, 0); + + if (priv->uap_stop_tx) { + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + priv->uap_stop_tx = false; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c new file mode 100644 index 000000000000..c2a54d781b42 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11n helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ax.h" + +/* + * Fills HT capability information field, AMPDU Parameters field, HT extended + * capability field, and supported MCS set fields. + * + * HT capability information field, AMPDU Parameters field, supported MCS set + * fields are retrieved from cfg80211 stack + * + * RD responder bit to set to clear in the extended capability header. + */ +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap) +{ + u16 ht_cap_info; + u16 bcn_ht_cap = le16_to_cpu(ht_cap->cap_info); + u16 ht_ext_cap = le16_to_cpu(ht_cap->extended_ht_cap_info); + struct ieee80211_supported_band *sband = + priv->wdev.wiphy->bands[radio_type]; + + if (WARN_ON_ONCE(!sband)) { + nxpwifi_dbg(priv->adapter, ERROR, "Invalid radio type!\n"); + return -EINVAL; + } + + ht_cap->ampdu_params_info = + (AMPDU_FACTOR_64K & IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((priv->adapter->hw_mpdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + + memcpy((u8 *)&ht_cap->mcs, &sband->ht_cap.mcs, + sizeof(sband->ht_cap.mcs)); + + if (priv->bss_mode == NL80211_IFTYPE_STATION || + (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + priv->adapter->sec_chan_offset != IEEE80211_HT_PARAM_CHA_SEC_NONE)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(ht_cap->mcs.rx_mask); + + /* Clear RD responder bit */ + ht_ext_cap &= ~IEEE80211_HT_EXT_CAP_RD_RESPONDER; + + ht_cap_info = sband->ht_cap.cap; + if (bcn_ht_cap) { + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SGI_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SGI_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)) + ht_cap_info &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + } + ht_cap->cap_info = cpu_to_le16(ht_cap_info); + ht_cap->extended_ht_cap_info = cpu_to_le16(ht_ext_cap); + + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + ht_cap->tx_BF_cap_info = cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + + return 0; +} + +/* Return BA stream entry that matches the requested status. */ +static struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_status(struct nxpwifi_private *priv, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl, *found = NULL; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl->ba_status == ba_status) { + found = tx_ba_tsr_tbl; + break; + } + } + return found; +} + +/* Handle DELBA command response (recreate or continue ADDBA as needed). */ +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct host_cmd_ds_11n_delba *del_ba = &resp->params.del_ba; + u16 del_ba_param_set = le16_to_cpu(del_ba->del_ba_param_set); + + tid = del_ba_param_set >> DELBA_TID_POS; + if (del_ba->del_result == BA_RESULT_SUCCESS) { + nxpwifi_del_ba_tbl(priv, tid, del_ba->peer_mac_addr, + TYPE_DELBA_SENT, + INITIATOR_BIT(del_ba_param_set)); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + if (tx_ba_tbl) + nxpwifi_send_addba(priv, tx_ba_tbl->tid, + tx_ba_tbl->ra); + } else { + /* + * In case of failure, recreate the deleted stream in case + * we initiated the DELBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; + + nxpwifi_create_ba_tbl(priv, del_ba->peer_mac_addr, tid, + BA_SETUP_INPROGRESS); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + + if (tx_ba_tbl) + nxpwifi_del_ba_tbl(priv, tx_ba_tbl->tid, tx_ba_tbl->ra, + TYPE_DELBA_SENT, true); + } + + return 0; +} + +/* Handle ADDBA response; delete BA stream on failure. */ +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid, tid_down; + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u16 block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + add_ba_rsp->ssn = cpu_to_le16((le16_to_cpu(add_ba_rsp->ssn)) + & SSN_MASK); + + tid = u16_get_bits(block_ack_param_set, IEEE80211_ADDBA_PARAM_TID_MASK); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, + add_ba_rsp->peer_mac_addr); + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + if (ra_list) { + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + } + nxpwifi_del_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr, + TYPE_DELBA_SENT, true); + if (add_ba_rsp->add_rsp_result != BA_RESULT_TIMEOUT) + priv->aggr_prio_tbl[tid].ampdu_ap = + BA_STREAM_NOT_ALLOWED; + return 0; + } + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr); + if (tx_ba_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, "info: BA stream complete\n"); + tx_ba_tbl->ba_status = BA_SETUP_COMPLETE; + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tx_ba_tbl->amsdu = true; + else + tx_ba_tbl->amsdu = false; + if (ra_list) { + ra_list->amsdu_in_ampdu = tx_ba_tbl->amsdu; + ra_list->ba_status = BA_SETUP_COMPLETE; + } + } else { + nxpwifi_dbg(priv->adapter, ERROR, "BA stream not created\n"); + } + + return 0; +} + +/* + * Reconfigure Tx buffer command. + * Set command ID/action/size; set Tx buffer size on SET; ensure little-endian. + */ +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, int cmd_action, + u16 *buf_size) +{ + struct host_cmd_ds_txbuf_cfg *tx_buf = &cmd->params.tx_buf; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_RECONFIGURE_TX_BUFF); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_txbuf_cfg) + S_DS_GEN); + tx_buf->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + nxpwifi_dbg(priv->adapter, CMD, + "cmd: set tx_buf=%d\n", *buf_size); + tx_buf->buff_size = cpu_to_le16(*buf_size); + break; + case HOST_ACT_GEN_GET: + default: + tx_buf->buff_size = 0; + break; + } + return 0; +} + +/* + * AMSDU aggregation control command. + * Set ID/action/size; set AMSDU params on SET; ensure little-endian. + */ +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl) +{ + struct host_cmd_ds_amsdu_aggr_ctrl *amsdu_ctrl = + &cmd->params.amsdu_aggr_ctrl; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_AMSDU_AGGR_CTRL); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_amsdu_aggr_ctrl) + + S_DS_GEN); + amsdu_ctrl->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + amsdu_ctrl->enable = cpu_to_le16(aa_ctrl->enable); + amsdu_ctrl->curr_buf_size = 0; + break; + case HOST_ACT_GEN_GET: + default: + amsdu_ctrl->curr_buf_size = 0; + break; + } + return 0; +} + +/* + * 11n configuration command. + * Set action, HT Tx capability/info, and misc config when 11ac HW is present. + */ +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg) +{ + struct host_cmd_ds_11n_cfg *htcfg = &cmd->params.htcfg; + + cmd->command = cpu_to_le16(HOST_CMD_11N_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_11n_cfg) + S_DS_GEN); + htcfg->action = cpu_to_le16(cmd_action); + htcfg->ht_tx_cap = cpu_to_le16(txcfg->tx_htcap); + htcfg->ht_tx_info = cpu_to_le16(txcfg->tx_htinfo); + + if (priv->adapter->is_hw_11ac_capable) + htcfg->misc_config = cpu_to_le16(txcfg->misc_config); + + return 0; +} + +/* + * Append 11n TLVs to the caller-owned buffer. + * Caller allocates space; no size checks here. + * May add: HT Cap, HT Operation + channel list, 20/40 BSS Coexistence, + * and Extended Capabilities (HS2/TWT bits when applicable). + */ +int +nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_chan_list_param_set *chan_list; + struct nxpwifi_chan_scan_param_set *chan_param; + struct nxpwifi_ie_types_2040bssco *bss_co_2040; + struct nxpwifi_ie_types_extcap *ext_cap; + int ret_len = 0; + struct ieee80211_supported_band *sband; + struct element *hdr; + u8 radio_type; + + if (!buffer || !*buffer) + return ret_len; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + if (bss_desc->bcn_ht_cap) { + ht_cap = (struct nxpwifi_ie_types_htcap *)*buffer; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + memcpy((u8 *)ht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_ht_cap, + le16_to_cpu(ht_cap->header.len)); + + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + /* Update HT40 capability from current channel. */ + if (bss_desc->bcn_ht_oper) { + u8 ht_param = bss_desc->bcn_ht_oper->ht_param; + u8 radio = + nxpwifi_band_to_radio_type(bss_desc->bss_band); + int freq = + ieee80211_channel_to_frequency(bss_desc->channel, + radio); + struct ieee80211_channel *chan = + ieee80211_get_channel(priv->adapter->wiphy, freq); + + switch (ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + if (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + if (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + } + } + + *buffer += sizeof(struct nxpwifi_ie_types_htcap); + ret_len += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (bss_desc->bcn_ht_oper) { + chan_list = + (struct nxpwifi_ie_types_chan_list_param_set *)*buffer; + chan_param = chan_list->chan_scan_param; + memset(chan_list, 0, struct_size(chan_list, chan_scan_param, 1)); + chan_list->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_list->header.len = cpu_to_le16(sizeof(*chan_param)); + chan_param->chan_number = bss_desc->bcn_ht_oper->primary_chan; + chan_param->band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + bss_desc->bcn_vht_oper && + bss_desc->bcn_vht_oper->chan_width == + IEEE80211_VHT_CHANWIDTH_80MHZ) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_80MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } else if (sband->ht_cap.cap & + IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHAN_WIDTH_ANY) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_40MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } + + *buffer += struct_size(chan_list, chan_scan_param, 1); + ret_len += struct_size(chan_list, chan_scan_param, 1); + } + + if (bss_desc->bcn_bss_co_2040) { + bss_co_2040 = (struct nxpwifi_ie_types_2040bssco *)*buffer; + memset(bss_co_2040, 0, + sizeof(struct nxpwifi_ie_types_2040bssco)); + bss_co_2040->header.type = cpu_to_le16(WLAN_EID_BSS_COEX_2040); + bss_co_2040->header.len = + cpu_to_le16(sizeof(bss_co_2040->bss_co_2040)); + + memcpy((u8 *)bss_co_2040 + + sizeof(struct nxpwifi_ie_types_header), + bss_desc->bcn_bss_co_2040 + + sizeof(struct element), + le16_to_cpu(bss_co_2040->header.len)); + + *buffer += sizeof(struct nxpwifi_ie_types_2040bssco); + ret_len += sizeof(struct nxpwifi_ie_types_2040bssco); + } + + if (bss_desc->bcn_ext_cap) { + u8 *ext_capab; + + hdr = (void *)bss_desc->bcn_ext_cap; + + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, priv->gen_ie_buf, + priv->gen_ie_buf_len); + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)*buffer; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(hdr->datalen); + ext_capab = ext_cap->ext_capab; + *buffer += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + ret_len += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + } + + if (hdr->datalen > 3 && + ext_capab[3] & WLAN_EXT_CAPA4_INTERWORKING_ENABLED) + priv->hs2_enabled = true; + else + priv->hs2_enabled = false; + + if (nxpwifi_is_11ax_twt_supported(priv, bss_desc)) + ext_capab[9] |= + WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + return ret_len; +} + +/* Check if pointer is a valid Tx BA stream entry. */ +static bool +nxpwifi_is_tx_ba_stream_ptr_valid(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl *tx_tbl_ptr) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + bool ret = false; + int tid; + + tid = tx_tbl_ptr->tid; + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl == tx_tbl_ptr) { + ret = true; + break; + } + } + return ret; +} + +/* Delete a Tx BA stream entry (after validating pointer). */ +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)) + return; + + nxpwifi_dbg(priv->adapter, INFO, + "info: tx_ba_tsr_tbl %p\n", tbl); + + list_del_rcu(&tbl->list); + kfree_rcu(tbl, rcu); +} + +/* Delete all entries in Tx BA stream table. */ +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_tx_ba_stream_tbl *del_tbl_ptr, *tmp_node; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_safe(del_tbl_ptr, tmp_node, + &priv->tx_ba_stream_tbl_ptr[i], list) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, del_tbl_ptr); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[i]); + + priv->aggr_prio_tbl[i].ampdu_ap = + priv->aggr_prio_tbl[i].ampdu_user; + } +} + +/* Return BA stream entry for given RA/TID. */ +struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl = NULL; + + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (ether_addr_equal_unaligned(tx_ba_tsr_tbl->ra, ra) && + tx_ba_tsr_tbl->tid == tid) + return tx_ba_tsr_tbl; + } + return NULL; +} + +/* Create Tx BA stream entry for given RA/TID. */ +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *new_node; + struct nxpwifi_ra_list_tbl *ra_list; + int tid_down; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, ra); + + if (!tx_ba_tbl) { + new_node = kzalloc_obj(*new_node, GFP_ATOMIC); + if (!new_node) + return; + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, ra); + if (ra_list) { + ra_list->ba_status = ba_status; + ra_list->amsdu_in_ampdu = false; + } + INIT_LIST_HEAD(&new_node->list); + + new_node->tid = tid; + new_node->ba_status = ba_status; + memcpy(new_node->ra, ra, ETH_ALEN); + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->tx_ba_stream_tbl_ptr[tid]); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Send ADDBA request to the given TID/RA. */ +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac) +{ + struct host_cmd_ds_11n_addba_req add_ba_req; + u32 tx_win_size = priv->add_ba_param.tx_win_size; + static u8 dialog_tok; + u16 block_ack_param_set; + + nxpwifi_dbg(priv->adapter, CMD, "cmd: %s: tid %d\n", __func__, tid); + + memset(&add_ba_req, 0, sizeof(add_ba_req)); + + block_ack_param_set = (u16)((tid << BLOCKACKPARAM_TID_POS) | + tx_win_size << BLOCKACKPARAM_WINSIZE_POS | + IMMEDIATE_BLOCK_ACK); + + /* enable AMSDU inside AMPDU */ + if (priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + block_ack_param_set |= IEEE80211_ADDBA_PARAM_AMSDU_MASK; + + add_ba_req.block_ack_param_set = cpu_to_le16(block_ack_param_set); + add_ba_req.block_ack_tmo = cpu_to_le16((u16)priv->add_ba_param.timeout); + + ++dialog_tok; + + if (dialog_tok == 0) + dialog_tok = 1; + + add_ba_req.dialog_token = dialog_tok; + memcpy(&add_ba_req.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_REQ, + 0, 0, &add_ba_req, false); +} + +/* Send DELBA request to the given TID/RA. */ +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator) +{ + struct host_cmd_ds_11n_delba delba; + u16 del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); + + del_ba_param_set = tid << DELBA_TID_POS; + + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + + delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, + HOST_ACT_GEN_SET, 0, &delba, false); +} + +/* Send DELBA to specific TID. */ +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid) +{ + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 ta[ETH_ALEN]; + bool found = false; + + rcu_read_lock(); + list_for_each_entry_rcu(rx_reor_tbl_ptr, &priv->rx_reorder_tbl_ptr[tid], list) { + if (rx_reor_tbl_ptr->tid == tid) { + memcpy(ta, rx_reor_tbl_ptr->ta, ETH_ALEN); + found = true; + break; + } + } + rcu_read_unlock(); + + if (found) { + nxpwifi_dbg(priv->adapter, INFO, + "Send delba to tid=%d, %pM\n", tid, ta); + nxpwifi_send_delba(priv, tid, ta, 0); + } +} + +/* Handle DELBA event; remove BA stream. */ +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba) +{ + struct host_cmd_ds_11n_delba *cmd_del_ba = + (struct host_cmd_ds_11n_delba *)del_ba; + u16 del_ba_param_set = le16_to_cpu(cmd_del_ba->del_ba_param_set); + int tid; + + tid = del_ba_param_set >> DELBA_TID_POS; + + nxpwifi_del_ba_tbl(priv, tid, cmd_del_ba->peer_mac_addr, + TYPE_DELBA_RECEIVE, INITIATOR_BIT(del_ba_param_set)); +} + +/* Retrieve Rx reordering table. */ +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf) +{ + int i, j; + struct nxpwifi_ds_rx_reorder_tbl *rx_reo_tbl = buf; + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr; + int count = 0; + + guard(rcu)(); + for (j = 0; j < MAX_NUM_TID; j++) { + list_for_each_entry_rcu(rx_reorder_tbl_ptr, + &priv->rx_reorder_tbl_ptr[j], + list) { + rx_reo_tbl->tid = (u16)rx_reorder_tbl_ptr->tid; + memcpy(rx_reo_tbl->ta, rx_reorder_tbl_ptr->ta, ETH_ALEN); + rx_reo_tbl->start_win = rx_reorder_tbl_ptr->start_win; + rx_reo_tbl->win_size = rx_reorder_tbl_ptr->win_size; + for (i = 0; i < rx_reorder_tbl_ptr->win_size; ++i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + rx_reo_tbl->buffer[i] = true; + else + rx_reo_tbl->buffer[i] = false; + } + rx_reo_tbl++; + count++; + + if (count >= NXPWIFI_MAX_RX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Retrieve Tx BA stream table. */ +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + struct nxpwifi_ds_tx_ba_stream_tbl *rx_reo_tbl = buf; + int count = 0; + int i; + + guard(rcu)(); + for (i = 0; i < MAX_NUM_TID; i++) { + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[i], list) { + rx_reo_tbl->tid = (u16)tx_ba_tsr_tbl->tid; + nxpwifi_dbg(priv->adapter, DATA, "data: %s tid=%d\n", + __func__, rx_reo_tbl->tid); + memcpy(rx_reo_tbl->ra, tx_ba_tsr_tbl->ra, ETH_ALEN); + rx_reo_tbl->amsdu = tx_ba_tsr_tbl->amsdu; + rx_reo_tbl++; + count++; + if (count >= NXPWIFI_MAX_TX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Delete Tx BA stream entry by RA. */ +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tbl, *tmp; + int i; + + if (!ra) + return; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_safe(tbl, tmp, + &priv->tx_ba_stream_tbl_ptr[i], list) + if (!memcmp(tbl->ra, ra, ETH_ALEN)) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, tbl); + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + } +} + +/* Initialize BlockAck parameters. */ +void nxpwifi_set_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + } + + priv->add_ba_param.tx_amsdu = true; + priv->add_ba_param.rx_amsdu = true; +} + +u8 nxpwifi_get_sec_chan_offset(int chan) +{ + u8 sec_offset; + + switch (chan) { + case 36: + case 44: + case 52: + case 60: + case 100: + case 108: + case 116: + case 124: + case 132: + case 140: + case 149: + case 157: + case 173: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + break; + case 40: + case 48: + case 56: + case 64: + case 104: + case 112: + case 120: + case 128: + case 136: + case 144: + case 153: + case 161: + case 169: + case 177: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case 165: + default: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + } + + return sec_offset; +} + +/* Send DELBA to entries in the Tx BA stream table. */ +static void +nxpwifi_send_delba_txbastream_tbl(struct nxpwifi_private *priv, u8 tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_stream_tbl_ptr; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_stream_tbl_ptr, + &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_stream_tbl_ptr->ba_status == BA_SETUP_COMPLETE) { + if (tid == tx_ba_stream_tbl_ptr->tid) { + nxpwifi_dbg(adapter, INFO, + "Tx:Send delba to tid=%d, %pM\n", tid, + tx_ba_stream_tbl_ptr->ra); + nxpwifi_send_delba(priv, + tx_ba_stream_tbl_ptr->tid, + tx_ba_stream_tbl_ptr->ra, 1); + break; + } + } + } +} + +/* + * Update tx_win_size for all interfaces and send DELBA when it changes. + */ +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i, j; + u32 tx_win_size; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + tx_win_size = priv->add_ba_param.tx_win_size; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + + if (adapter->coex_win_size) { + if (adapter->coex_tx_win_size) + priv->add_ba_param.tx_win_size = + adapter->coex_tx_win_size; + } + + if (tx_win_size != priv->add_ba_param.tx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_send_delba_txbastream_tbl(priv, j); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.h b/drivers/net/wireless/nxp/nxpwifi/11n.h new file mode 100644 index 000000000000..039c45993f07 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11n support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_H_ +#define _NXPWIFI_11N_H_ + +#include "11n_aggr.h" +#include "11n_rxreorder.h" +#include "wmm.h" + +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg); +int nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap); +int nxpwifi_set_get_11n_htcap_cfg(struct nxpwifi_private *priv, + u16 action, int *htcap_cfg); +void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl + *tx_tbl); +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv); +struct nxpwifi_tx_ba_stream_tbl *nxpwifi_get_ba_tbl(struct nxpwifi_private + *priv, int tid, u8 *ra); +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status); +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac); +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator); +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba); +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf); +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf); +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + int cmd_action, u16 *buf_size); +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl); +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra); +u8 nxpwifi_get_sec_chan_offset(int chan); + +static inline bool +nxpwifi_is_station_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + struct nxpwifi_sta_node *node; + + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ptr->ra); + if (unlikely(!node)) + return false; + + if (node->ampdu_sta[tid] == BA_STREAM_NOT_ALLOWED) + return false; + + return true; +} + +/* Check if AMPDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + if (is_broadcast_ether_addr(ptr->ra)) + return false; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_is_station_ampdu_allowed(priv, ptr, tid); + + return priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED; +} + +/* Check if AMSDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_amsdu_allowed(struct nxpwifi_private *priv, int tid) +{ + bool amsdu_enabled = priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED; + bool rate_ok = priv->is_data_rate_auto || !(priv->bitmap_rates[2] & 0x03); + + return amsdu_enabled && rate_ok; +} + +/* Check if there is available space for a new BA stream. */ +static inline bool +nxpwifi_space_avail_for_new_ba_stream(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + u8 i, j; + size_t ba_stream_num = 0; + size_t ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + ba_stream_max = GETSUPP_TXBASTREAMS(adapter->hw_dot_11n_dev_cap); + if (!ba_stream_max) + ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; j++) + ba_stream_num += list_count_nodes(&priv->tx_ba_stream_tbl_ptr[j]); + } + + return ba_stream_num < ba_stream_max; +} + +/* Find the Tx BA stream to delete and return its TID and RA. */ +static inline bool +nxpwifi_find_stream_to_delete(struct nxpwifi_private *priv, int ptr_tid, + int *ptid, u8 *ra) +{ + int search_tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user; + bool found = false; + struct nxpwifi_tx_ba_stream_tbl *tx_tbl; + int candidate_tid; + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr[ptr_tid], list) { + candidate_tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user; + + if (search_tid > candidate_tid) { + search_tid = candidate_tid; + *ptid = tx_tbl->tid; + memcpy(ra, tx_tbl->ra, ETH_ALEN); + found = true; + } + } + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + return found; +} + +/* Check whether the associated station is 11n enabled. */ +static inline int nxpwifi_is_sta_11n_enabled(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node) +{ + if (!node || (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && + !priv->ap_11n_enabled)) + return 0; + + return node->is_11n_enabled; +} + +#endif /* !_NXPWIFI_11N_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c new file mode 100644 index 000000000000..be7080f2a6ce --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11n_aggr.h" + +/* + * Build an AMSDU subframe for aggregation, with fields + * (DA | SA | Length | SNAP header | MSDU), and compute padding + * to align the subframe to a 4-byte boundary. + */ +static int nxpwifi_11n_form_amsdu_pkt(struct sk_buff *skb_aggr, + struct sk_buff *skb_src, int *pad) + +{ + int dt_offset; + struct rfc_1042_hdr snap = { + 0xaa, /* LLC DSAP */ + 0xaa, /* LLC SSAP */ + 0x03, /* LLC CTRL */ + {0x00, 0x00, 0x00}, /* SNAP OUI */ + 0x0000 /* SNAP type */ + /* This field will be overwritten later with ethertype */ + }; + struct tx_packet_hdr *tx_header; + + tx_header = skb_put(skb_aggr, sizeof(*tx_header)); + + /* Copy DA and SA */ + dt_offset = 2 * ETH_ALEN; + memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset); + + /* Copy SNAP header */ + snap.snap_type = ((struct ethhdr *)skb_src->data)->h_proto; + + dt_offset += sizeof(__be16); + + memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr)); + + skb_pull(skb_src, dt_offset); + + /* Update Length field */ + tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN); + + /* Add payload */ + skb_put_data(skb_aggr, skb_src->data, skb_src->len); + + /* Add padding for new MSDU to start from 4 byte boundary */ + *pad = (4 - ((unsigned long)skb_aggr->tail & 0x3)) % 4; + + return skb_aggr->len + *pad; +} + +/* + * Adds TxPD to AMSDU header. Each AMSDU packet will contain one TxPD at the + * beginning, followed by multiple AMSDU subframes + */ +static void +nxpwifi_11n_form_amsdu_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct txpd *local_tx_pd; + + skb_push(skb, sizeof(*local_tx_pd)); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + + /* Original priority has been overwritten */ + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + /* Always zero as the data is followed by struct txpd */ + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU); + local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len - + sizeof(*local_tx_pd)); + + if (local_tx_pd->tx_control == 0) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + priv->adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + priv->adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } +} + +/* + * Build an aggregated MSDU packet by encapsulating buffers from the RA + * list as AMSDU subframes and concatenating them. A TxPD is prepended + * before transmission to form the final AMSDU packet. + */ +int +nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *pra_list, + int ptrindex) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb_aggr, *skb_src; + struct nxpwifi_txinfo *tx_info_aggr, *tx_info_src; + int pad = 0, aggr_num = 0, ret; + struct nxpwifi_tx_param tx_param; + struct txpd *ptx_pd = NULL; + int headroom = adapter->intf_hdr_len; + + skb_src = skb_peek(&pra_list->skb_head); + if (!skb_src) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return 0; + } + + tx_info_src = NXPWIFI_SKB_TXCB(skb_src); + skb_aggr = nxpwifi_alloc_dma_align_buf(adapter->tx_buf_size, + GFP_ATOMIC); + if (!skb_aggr) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOMEM; + } + + /* + * skb_aggr->data already 64 byte align, just reserve bus interface + * header and txpd. + */ + skb_reserve(skb_aggr, headroom + sizeof(struct txpd)); + tx_info_aggr = NXPWIFI_SKB_TXCB(skb_aggr); + + memset(tx_info_aggr, 0, sizeof(*tx_info_aggr)); + tx_info_aggr->bss_type = tx_info_src->bss_type; + tx_info_aggr->bss_num = tx_info_src->bss_num; + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_AGGR_PKT; + skb_aggr->priority = skb_src->priority; + skb_aggr->tstamp = skb_src->tstamp; + + do { + /* Check if AMSDU can accommodate this MSDU */ + if ((skb_aggr->len + skb_src->len + LLC_SNAP_LEN) > + adapter->tx_buf_size) + break; + + skb_src = skb_dequeue(&pra_list->skb_head); + pra_list->total_pkt_count--; + atomic_dec(&priv->wmm.tx_pkts_queued); + aggr_num++; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_11n_form_amsdu_pkt(skb_aggr, skb_src, &pad); + + nxpwifi_write_data_complete(adapter, skb_src, 0, 0); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOENT; + } + + if (skb_tailroom(skb_aggr) < pad) { + pad = 0; + break; + } + skb_put(skb_aggr, pad); + + skb_src = skb_peek(&pra_list->skb_head); + + } while (skb_src); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + /* Last AMSDU packet does not need padding */ + skb_trim(skb_aggr, skb_aggr->len - pad); + + /* Form AMSDU */ + nxpwifi_11n_form_amsdu_txpd(priv, skb_aggr); + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + ptx_pd = (struct txpd *)skb_aggr->data; + + skb_push(skb_aggr, headroom); + tx_info_aggr->aggr_num = aggr_num * 2; + if (adapter->data_sent || adapter->tx_lock_flag) { + atomic_add(aggr_num * 2, &adapter->tx_queued); + skb_queue_tail(&adapter->tx_data_q, skb_aggr); + return 0; + } + + if (skb_src) + tx_param.next_pkt_len = skb_src->len + sizeof(struct txpd); + else + tx_param.next_pkt_len = 0; + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb_aggr, &tx_param); + + switch (ret) { + case -EBUSY: + spin_lock_bh(&priv->wmm.ra_list_spinlock); + 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 -EINVAL; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (ptx_pd) + ptx_pd->flags = 0; + } + + skb_queue_tail(&pra_list->skb_head, skb_aggr); + + pra_list->total_pkt_count++; + + atomic_inc(&priv->wmm.tx_pkts_queued); + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "%s: host_to_card failed: %#x\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + } + if (ret != -EBUSY) + nxpwifi_rotate_priolists(priv, pra_list, ptrindex); + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h new file mode 100644 index 000000000000..be9f0f8f4e48 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_AGGR_H_ +#define _NXPWIFI_11N_AGGR_H_ + +#define PKT_TYPE_AMSDU 0xE6 +#define MIN_NUM_AMSDU 2 + +int nxpwifi_11n_deaggregate_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int ptr_index) + __releases(&priv->wmm.ra_list_spinlock); + +#endif /* !_NXPWIFI_11N_AGGR_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c new file mode 100644 index 000000000000..65b628411543 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11n_rxreorder.h" +/* Dispatch A-MSDU to stack. */ +static int nxpwifi_11n_dispatch_amsdu_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *local_rx_pd = (struct rxpd *)(skb->data); + int ret; + + if (le16_to_cpu(local_rx_pd->rx_pkt_type) == PKT_TYPE_AMSDU) { + struct sk_buff_head list; + struct sk_buff *rx_skb; + + __skb_queue_head_init(&list); + + skb_pull(skb, le16_to_cpu(local_rx_pd->rx_pkt_offset)); + skb_trim(skb, le16_to_cpu(local_rx_pd->rx_pkt_length)); + + ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr, + priv->wdev.iftype, 0, NULL, NULL, false); + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_uap_recv_packet(priv, rx_skb); + else + ret = nxpwifi_recv_packet(priv, rx_skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Rx of A-MSDU failed"); + } + return 0; + } + + return -EINVAL; +} + +/* Process RX packet and forward to stack. */ +static int nxpwifi_11n_dispatch_pkt(struct nxpwifi_private *priv, + struct sk_buff *payload) +{ + int ret; + + if (!payload) { + nxpwifi_dbg(priv->adapter, INFO, "info: fw drop data\n"); + return 0; + } + + ret = nxpwifi_11n_dispatch_amsdu_pkt(priv, payload); + if (!ret) + return 0; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_handle_uap_rx_forward(priv, payload); + + return nxpwifi_process_rx_packet(priv, payload); +} + +/* Dispatch packets up to start_win. */ +static void +nxpwifi_11n_dispatch_pkt_until_start_win(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl, + int start_win) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int pkt_to_send, i, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + pkt_to_send = (start_win > tbl->start_win) ? + min((start_win - tbl->start_win), tbl->win_size) : + tbl->win_size; + + for (i = 0; i < pkt_to_send; ++i) { + if (tbl->rx_reorder_ptr[i]) { + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + } + + /* Simulate circular buffer via rotation. */ + for (i = 0; i < tbl->win_size - pkt_to_send; ++i) { + tbl->rx_reorder_ptr[i] = tbl->rx_reorder_ptr[pkt_to_send + i]; + tbl->rx_reorder_ptr[pkt_to_send + i] = NULL; + } + + tbl->start_win = start_win; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Dispatch packets until a hole is found. */ +static void +nxpwifi_11n_scan_and_dispatch(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int i, j, xchg, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + for (i = 0; i < tbl->win_size; ++i) { + if (!tbl->rx_reorder_ptr[i]) + break; + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + + /* Simulate circular buffer via rotation. */ + if (i > 0) { + xchg = tbl->win_size - i; + for (j = 0; j < xchg; ++j) { + tbl->rx_reorder_ptr[j] = tbl->rx_reorder_ptr[i + j]; + tbl->rx_reorder_ptr[i + j] = NULL; + } + } + tbl->start_win = (tbl->start_win + i) & (MAX_TID_VALUE - 1); + + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Delete RX reorder entry and flush pending packets. */ +static void +nxpwifi_del_rx_reorder_entry(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + int start_win, tid; + + if (!tbl) + return; + + tid = tbl->tid; + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 1); + flush_workqueue(priv->adapter->rx_workqueue); + + start_win = (tbl->start_win + tbl->win_size) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + + timer_delete_sync(&tbl->timer_context.timer); + tbl->timer_context.timer_is_set = false; + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_del_rcu(&tbl->list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + kfree_rcu(tbl, rcu); + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 0); +} + +/* Lookup RX reorder entry by TID/TA. */ +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *found = NULL; + + guard(rcu)(); + + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[tid], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN) && tbl->tid == tid) { + found = tbl; + break; + } + } + + return found; +} + +/* Delete RX reorder entries by TA. */ +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *tmp; + LIST_HEAD(to_delete); + int i; + + if (!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); + } + } + + list_for_each_entry_safe(tbl, tmp, &to_delete, tmp_list) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + INIT_LIST_HEAD(&to_delete); + } +} + +/* Find last buffered sequence index. */ +static int +nxpwifi_11n_find_last_seq_num(struct reorder_tmr_cnxt *ctx) +{ + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr = ctx->ptr; + int i; + + guard(rcu)(); + for (i = rx_reorder_tbl_ptr->win_size - 1; i >= 0; --i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + return i; + } + + return -EINVAL; +} + +/* Flush and dispatch buffered packets on timer. */ +static void +nxpwifi_flush_data(struct timer_list *t) +{ + struct reorder_tmr_cnxt *ctx = + timer_container_of(ctx, t, timer); + int start_win, seq_num; + + ctx->timer_is_set = false; + seq_num = nxpwifi_11n_find_last_seq_num(ctx); + + if (seq_num < 0) + return; + + nxpwifi_dbg(ctx->priv->adapter, INFO, "info: flush data %d\n", seq_num); + start_win = (ctx->ptr->start_win + seq_num + 1) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(ctx->priv, ctx->ptr, + start_win); +} + +/* Create RX reorder entry (TID/TA, SSN, winsize, timer). */ +static void +nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, + int tid, int win_size, int seq_num) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *new_node; + u16 last_seq = 0; + struct nxpwifi_sta_node *node; + + /* Existing TID/TA: flush and move window to SSN. */ + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (tbl) { + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, seq_num); + return; + } + + if (win_size <= 0) + return; + + /* if !tbl then create one */ + new_node = kzalloc_flex(*new_node, rx_reorder_ptr, win_size); + if (!new_node) + return; + + new_node->win_size = win_size; + INIT_LIST_HEAD(&new_node->list); + new_node->tid = tid; + memcpy(new_node->ta, ta, ETH_ALEN); + new_node->start_win = seq_num; + new_node->init_win = seq_num; + new_node->flags = 0; + + if (nxpwifi_queuing_ra_based(priv)) { + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + } + } else { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + else + last_seq = priv->rx_seq[tid]; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: last_seq=%d start_win=%d\n", + last_seq, new_node->start_win); + + if (last_seq != NXPWIFI_DEF_11N_RX_SEQ_NUM && + last_seq >= new_node->start_win) { + new_node->start_win = last_seq + 1; + new_node->flags |= RXREOR_INIT_WINDOW_SHIFT; + } + + new_node->timer_context.ptr = new_node; + new_node->timer_context.priv = priv; + new_node->timer_context.timer_is_set = false; + + timer_setup(&new_node->timer_context.timer, nxpwifi_flush_data, 0); + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->rx_reorder_tbl_ptr[tid]); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); +} + +static void +nxpwifi_11n_rxreorder_timer_restart(struct nxpwifi_rx_reorder_tbl *tbl) +{ + u32 min_flush_time; + + if (tbl->win_size >= NXPWIFI_BA_WIN_SIZE_32) + min_flush_time = MIN_FLUSH_TIMER_15_MS; + else + min_flush_time = MIN_FLUSH_TIMER_MS; + + mod_timer(&tbl->timer_context.timer, + jiffies + msecs_to_jiffies(min_flush_time * tbl->win_size)); + + tbl->timer_context.timer_is_set = true; +} + +/* Prepare ADDBA request. */ +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_addba_req *add_ba_req = &cmd->params.add_ba_req; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_REQ); + cmd->size = cpu_to_le16(sizeof(*add_ba_req) + S_DS_GEN); + memcpy(add_ba_req, data_buf, sizeof(*add_ba_req)); + + return 0; +} + +/* Prepare ADDBA response and create RX reorder table. */ +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &cmd->params.add_ba_rsp; + u32 rx_win_size = priv->add_ba_param.rx_win_size; + u8 tid; + int win_size; + u16 block_ack_param_set; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_RSP); + cmd->size = cpu_to_le16(sizeof(*add_ba_rsp) + S_DS_GEN); + + memcpy(add_ba_rsp->peer_mac_addr, cmd_addba_req->peer_mac_addr, + ETH_ALEN); + add_ba_rsp->dialog_token = cmd_addba_req->dialog_token; + add_ba_rsp->block_ack_tmo = cmd_addba_req->block_ack_tmo; + add_ba_rsp->ssn = cmd_addba_req->ssn; + + block_ack_param_set = le16_to_cpu(cmd_addba_req->block_ack_param_set); + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + add_ba_rsp->status_code = cpu_to_le16(ADDBA_RSP_STATUS_ACCEPT); + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK; + + /* If we don't support AMSDU inside AMPDU, reset the bit */ + if (!priv->add_ba_param.rx_amsdu || + priv->aggr_prio_tbl[tid].amsdu == BA_STREAM_NOT_ALLOWED) + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_AMSDU_MASK; + block_ack_param_set |= rx_win_size << BLOCKACKPARAM_WINSIZE_POS; + add_ba_rsp->block_ack_param_set = cpu_to_le16(block_ack_param_set); + win_size = (le16_to_cpu(add_ba_rsp->block_ack_param_set) + & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + cmd_addba_req->block_ack_param_set = cpu_to_le16(block_ack_param_set); + + nxpwifi_11n_create_rx_reorder_tbl(priv, cmd_addba_req->peer_mac_addr, + tid, win_size, + le16_to_cpu(cmd_addba_req->ssn)); + return 0; +} + +/* Prepare DELBA command. */ +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_delba *del_ba = &cmd->params.del_ba; + + cmd->command = cpu_to_le16(HOST_CMD_11N_DELBA); + cmd->size = cpu_to_le16(sizeof(*del_ba) + S_DS_GEN); + memcpy(del_ba, data_buf, sizeof(*del_ba)); + + return 0; +} + +/* Decide and perform RX reordering for a packet. */ +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, u16 tid, + u8 *ta, u8 pkt_type, void *payload) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + int prev_start_win, start_win, end_win, win_size; + u16 pkt_index; + bool init_window_shift = false; + int ret = 0; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (!tbl) { + if (pkt_type != PKT_TYPE_BAR) + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + if (pkt_type == PKT_TYPE_AMSDU && !tbl->amsdu) { + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + start_win = tbl->start_win; + prev_start_win = start_win; + win_size = tbl->win_size; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + if (tbl->flags & RXREOR_INIT_WINDOW_SHIFT) { + init_window_shift = true; + tbl->flags &= ~RXREOR_INIT_WINDOW_SHIFT; + } + + if (tbl->flags & RXREOR_FORCE_NO_DROP) { + nxpwifi_dbg(priv->adapter, INFO, + "RXREOR_FORCE_NO_DROP when HS is activated\n"); + tbl->flags &= ~RXREOR_FORCE_NO_DROP; + } else if (init_window_shift && seq_num < start_win && + seq_num >= tbl->init_win) { + nxpwifi_dbg(priv->adapter, INFO, + "Sender TID sequence number reset %d->%d for SSN %d\n", + start_win, seq_num, tbl->init_win); + start_win = seq_num; + tbl->start_win = start_win; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + } else { + /* Drop packet if seq_num < start_win. */ + if ((start_win + TWOPOW11) > (MAX_TID_VALUE - 1)) { + if (seq_num >= ((start_win + TWOPOW11) & + (MAX_TID_VALUE - 1)) && + seq_num < start_win) { + ret = -EINVAL; + goto done; + } + } else if ((seq_num < start_win) || + (seq_num >= (start_win + TWOPOW11))) { + ret = -EINVAL; + goto done; + } + } + + /* Adjust seq_num for BAR (WinStart = seq_num). */ + if (pkt_type == PKT_TYPE_BAR) + seq_num = ((seq_num + win_size) - 1) & (MAX_TID_VALUE - 1); + + if ((end_win < start_win && + seq_num < start_win && seq_num > end_win) || + (end_win > start_win && (seq_num > end_win || + seq_num < start_win))) { + end_win = seq_num; + if (((end_win - win_size) + 1) >= 0) + start_win = (end_win - win_size) + 1; + else + start_win = (MAX_TID_VALUE - (win_size - end_win)) + 1; + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + } + + if (pkt_type != PKT_TYPE_BAR) { + if (seq_num >= start_win) + pkt_index = seq_num - start_win; + else + pkt_index = (seq_num + MAX_TID_VALUE) - start_win; + + if (tbl->rx_reorder_ptr[pkt_index]) { + ret = -EINVAL; + goto done; + } + + tbl->rx_reorder_ptr[pkt_index] = payload; + } + + /* Dispatch sequentially until a hole; update start_win. */ + nxpwifi_11n_scan_and_dispatch(priv, tbl); + +done: + if (!tbl->timer_context.timer_is_set || + prev_start_win != tbl->start_win) + nxpwifi_11n_rxreorder_timer_restart(tbl); + return ret; +} + +/* Delete BA entry for TID/TA. */ +void +nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + u8 type, int initiator) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + struct nxpwifi_tx_ba_stream_tbl *ptx_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u8 cleanup_rx_reorder_tbl; + int tid_down; + + if (type == TYPE_DELBA_RECEIVE) + cleanup_rx_reorder_tbl = (initiator) ? true : false; + else + cleanup_rx_reorder_tbl = (initiator) ? false : true; + + nxpwifi_dbg(priv->adapter, EVENT, "event: DELBA: %pM tid=%d initiator=%d\n", + peer_mac, tid, initiator); + + if (cleanup_rx_reorder_tbl) { + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, peer_mac); + if (!tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, TA not found in table\n"); + return; + } + nxpwifi_del_rx_reorder_entry(priv, tbl); + } else { + guard(rcu)(); + ptx_tbl = nxpwifi_get_ba_tbl(priv, tid, peer_mac); + + if (!ptx_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, RA not found in table\n"); + return; + } + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, peer_mac); + if (ra_list) { + ra_list->amsdu_in_ampdu = false; + ra_list->ba_status = BA_SETUP_NONE; + } + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, ptx_tbl); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Handle ADDBA response. */ +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + int tid, win_size; + struct nxpwifi_rx_reorder_tbl *tbl; + u16 block_ack_param_set; + + block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + /* Check if we had rejected the ADDBA, if yes then do not create the stream */ + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + nxpwifi_dbg(priv->adapter, ERROR, "ADDBA RSP: failed %pM tid=%d)\n", + add_ba_rsp->peer_mac_addr, tid); + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + return 0; + } + + win_size = (block_ack_param_set & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) { + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.rx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tbl->amsdu = true; + else + tbl->amsdu = false; + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: ADDBA RSP: %pM tid=%d ssn=%d win_size=%d\n", + add_ba_rsp->peer_mac_addr, tid, add_ba_rsp->ssn, win_size); + + return 0; +} + +/* Handle BA stream timeout: send DELBA. */ +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event) +{ + struct host_cmd_ds_11n_delba delba; + + memset(&delba, 0, sizeof(struct host_cmd_ds_11n_delba)); + memcpy(delba.peer_mac_addr, event->peer_mac_addr, ETH_ALEN); + + delba.del_ba_param_set |= + cpu_to_le16((u16)event->tid << DELBA_TID_POS); + delba.del_ba_param_set |= + cpu_to_le16((u16)event->origninator << DELBA_INITIATOR_POS); + delba.reason_code = cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT); + nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, 0, 0, &delba, false); +} + +/* Cleanup all RX reorder entries. */ +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_rx_reorder_tbl *del_tbl_ptr, *tmp_node; + LIST_HEAD(to_delete_list); + int i; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[i]); + list_splice_init(&priv->rx_reorder_tbl_ptr[i], &to_delete_list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[i]); + + list_for_each_entry_safe(del_tbl_ptr, tmp_node, &to_delete_list, list) + nxpwifi_del_rx_reorder_entry(priv, del_tbl_ptr); + + INIT_LIST_HEAD(&to_delete_list); + } + + nxpwifi_reset_11n_rx_seq_num(priv); +} + +/* Update flags for all RX reorder tables. */ +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags) +{ + struct nxpwifi_private *priv; + struct nxpwifi_rx_reorder_tbl *tbl; + int i, j; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + for (j = 0; j < MAX_NUM_TID; j++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[j]); + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[j], list) + tbl->flags = flags; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[j]); + } + } +} + +/* Update RX window size based on coex flag. */ +static void nxpwifi_update_ampdu_rxwinsize(struct nxpwifi_adapter *adapter, + bool coex_flag) +{ + u8 i, j; + u32 rx_win_size; + struct nxpwifi_private *priv; + + nxpwifi_dbg(adapter, INFO, "Update rxwinsize %d\n", coex_flag); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rx_win_size = priv->add_ba_param.rx_win_size; + if (coex_flag) { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE; + } else { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } + + if (adapter->coex_win_size && adapter->coex_rx_win_size) + priv->add_ba_param.rx_win_size = + adapter->coex_rx_win_size; + + if (rx_win_size != priv->add_ba_param.rx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_11n_delba(priv, j); + } + } +} + +/* Check coex for RX BA. */ +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i; + struct nxpwifi_private *priv; + u8 count = 0; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (priv->media_connected) + count++; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (priv->bss_started) + count++; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + break; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + nxpwifi_update_ampdu_rxwinsize(adapter, true); + else + nxpwifi_update_ampdu_rxwinsize(adapter, false); +} + +/* Handle RXBA sync event. */ +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len) +{ + struct nxpwifi_ie_types_rxba_sync *tlv_rxba = (void *)event_buf; + u16 tlv_type, tlv_len; + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 i, j; + u16 seq_num, tlv_seq_num, tlv_bitmap_len; + int tlv_buf_left = len; + int ret; + u8 *tmp; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "RXBA_SYNC event:", + event_buf, len); + while (tlv_buf_left > sizeof(*tlv_rxba)) { + tlv_type = le16_to_cpu(tlv_rxba->header.type); + tlv_len = le16_to_cpu(tlv_rxba->header.len); + if (size_add(sizeof(tlv_rxba->header), tlv_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(tlv_rxba->header), tlv_len), + tlv_buf_left); + return; + } + + if (tlv_type != TLV_TYPE_RXBA_SYNC) { + nxpwifi_dbg(priv->adapter, ERROR, + "Wrong TLV id=0x%x\n", tlv_type); + return; + } + + tlv_seq_num = le16_to_cpu(tlv_rxba->seq_num); + tlv_bitmap_len = le16_to_cpu(tlv_rxba->bitmap_len); + if (size_add(sizeof(*tlv_rxba), tlv_bitmap_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(*tlv_rxba), tlv_bitmap_len), + tlv_buf_left); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "%pM tid=%d seq_num=%d bitmap_len=%d\n", + tlv_rxba->mac, tlv_rxba->tid, tlv_seq_num, + tlv_bitmap_len); + + rx_reor_tbl_ptr = + nxpwifi_11n_get_rx_reorder_tbl(priv, tlv_rxba->tid, + tlv_rxba->mac); + if (!rx_reor_tbl_ptr) { + nxpwifi_dbg(priv->adapter, ERROR, + "Can not find rx_reorder_tbl!"); + return; + } + + for (i = 0; i < tlv_bitmap_len; i++) { + for (j = 0 ; j < 8; j++) { + if (tlv_rxba->bitmap[i] & (1 << j)) { + seq_num = (MAX_TID_VALUE - 1) & + (tlv_seq_num + i * 8 + j); + + nxpwifi_dbg(priv->adapter, ERROR, + "drop packet,seq=%d\n", + seq_num); + + ret = nxpwifi_11n_rx_reorder_pkt + (priv, seq_num, tlv_rxba->tid, + tlv_rxba->mac, 0, NULL); + + if (ret) + nxpwifi_dbg(priv->adapter, + ERROR, + "Fail to drop packet"); + } + } + } + + tlv_buf_left -= (sizeof(tlv_rxba->header) + tlv_len); + tmp = (u8 *)tlv_rxba + sizeof(tlv_rxba->header) + tlv_len; + tlv_rxba = (struct nxpwifi_ie_types_rxba_sync *)tmp; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h new file mode 100644 index 000000000000..db95d9db5d1f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_RXREORDER_H_ +#define _NXPWIFI_11N_RXREORDER_H_ + +#define MIN_FLUSH_TIMER_MS 50 +#define MIN_FLUSH_TIMER_15_MS 15 +#define NXPWIFI_BA_WIN_SIZE_32 32 + +#define PKT_TYPE_BAR 0xE7 +#define MAX_TID_VALUE (2 << 11) +#define TWOPOW11 (2 << 10) + +#define BLOCKACKPARAM_TID_POS 2 +#define BLOCKACKPARAM_WINSIZE_POS 6 +#define DELBA_TID_POS 12 +#define DELBA_INITIATOR_POS 11 +#define TYPE_DELBA_SENT 1 +#define TYPE_DELBA_RECEIVE 2 +#define IMMEDIATE_BLOCK_ACK 0x2 + +#define ADDBA_RSP_STATUS_ACCEPT 0 + +#define NXPWIFI_DEF_11N_RX_SEQ_NUM 0xffff +#define BA_SETUP_MAX_PACKET_THRESHOLD 16 +#define BA_SETUP_PACKET_OFFSET 16 + +enum nxpwifi_rxreor_flags { + RXREOR_FORCE_NO_DROP = 1 << 0, + RXREOR_INIT_WINDOW_SHIFT = 1 << 1, +}; + +static inline void nxpwifi_reset_11n_rx_seq_num(struct nxpwifi_private *priv) +{ + memset(priv->rx_seq, 0xff, sizeof(priv->rx_seq)); +} + +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, + u16 tid, u8 *ta, + u8 pkttype, void *payload); +void nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, + u8 *peer_mac, u8 type, int initiator); +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event); +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command + *resp); +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req); +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, + void *data_buf); +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rxreorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta); +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags); +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len); +#endif /* _NXPWIFI_11N_RXREORDER_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/Kconfig b/drivers/net/wireless/nxp/nxpwifi/Kconfig new file mode 100644 index 000000000000..38a8820084dd --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Kconfig @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NXPWIFI + tristate "NXP WiFi Driver" if COMPILE_TEST + depends on CFG80211 + help + This adds support for wireless adapters based on NXP + 802.11n/ac chipsets. + + If you choose to build it as a module, it will be called + nxpwifi. + +config NXPWIFI_SDIO + tristate "NXP WiFi Driver for IW61x" + depends on CFG80211 && MMC + select NXPWIFI + select FW_LOADER + select WANT_DEV_COREDUMP + help + This adds support for wireless adapters based on NXP + IW61x interface. + + If you choose to build it as a module, it will be called + nxpwifi_sdio. diff --git a/drivers/net/wireless/nxp/nxpwifi/Makefile b/drivers/net/wireless/nxp/nxpwifi/Makefile new file mode 100644 index 000000000000..8f581429f28d --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Makefile @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2011-2020 NXP +# + + +nxpwifi-y += main.o +nxpwifi-y += init.o +nxpwifi-y += cfp.o +nxpwifi-y += cmdevt.o +nxpwifi-y += util.o +nxpwifi-y += txrx.o +nxpwifi-y += wmm.o +nxpwifi-y += 11n.o +nxpwifi-y += 11ac.o +nxpwifi-y += 11ax.o +nxpwifi-y += 11n_aggr.o +nxpwifi-y += 11n_rxreorder.o +nxpwifi-y += scan.o +nxpwifi-y += join.o +nxpwifi-y += sta_cfg.o +nxpwifi-y += sta_cmd.o +nxpwifi-y += uap_cmd.o +nxpwifi-y += ie.o +nxpwifi-y += sta_event.o +nxpwifi-y += uap_event.o +nxpwifi-y += sta_tx.o +nxpwifi-y += sta_rx.o +nxpwifi-y += uap_txrx.o +nxpwifi-y += cfg80211.o +nxpwifi-y += ethtool.o +nxpwifi-y += 11h.o +nxpwifi-$(CONFIG_DEBUG_FS) += debugfs.o +obj-$(CONFIG_NXPWIFI) += nxpwifi.o + +nxpwifi_sdio-y += sdio.o +obj-$(CONFIG_NXPWIFI_SDIO) += nxpwifi_sdio.o + +ccflags-y += -D__CHECK_ENDIAN diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg.h b/drivers/net/wireless/nxp/nxpwifi/cfg.h new file mode 100644 index 000000000000..8627a3372978 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg.h @@ -0,0 +1,1019 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: ioctl data structures & APIs + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CFG_H_ +#define _NXPWIFI_CFG_H_ + +#include +#include +#include +#include +#include + +#define NUM_WEP_KEYS 4 + +#define NXPWIFI_BSS_COEX_COUNT 2 +#define NXPWIFI_MAX_BSS_NUM (3) + +#define NXPWIFI_MAX_CSA_COUNTERS 5 + +#define NXPWIFI_DMA_ALIGN_SZ 64 +#define NXPWIFI_RX_HEADROOM 64 +#define MAX_TXPD_SZ 32 +#define INTF_HDR_ALIGN 4 +/* special FW 4 address management header */ +#define NXPWIFI_MIN_DATA_HEADER_LEN (NXPWIFI_DMA_ALIGN_SZ + INTF_HDR_ALIGN + \ + MAX_TXPD_SZ) + +#define NXPWIFI_MGMT_FRAME_HEADER_SIZE 8 /* sizeof(pkt_type) + * + sizeof(tx_control) + */ + +#define FRMCTL_LEN 2 +#define DURATION_LEN 2 +#define SEQCTL_LEN 2 +#define NXPWIFI_MGMT_HEADER_LEN (FRMCTL_LEN + FRMCTL_LEN + ETH_ALEN + \ + ETH_ALEN + ETH_ALEN + SEQCTL_LEN + ETH_ALEN) + +#define AUTH_ALG_LEN 2 +#define AUTH_TRANSACTION_LEN 2 +#define AUTH_STATUS_LEN 2 +#define NXPWIFI_AUTH_BODY_LEN (AUTH_ALG_LEN + AUTH_TRANSACTION_LEN + \ + AUTH_STATUS_LEN) + +#define HOST_MLME_AUTH_PENDING BIT(0) +#define HOST_MLME_AUTH_DONE BIT(1) + +#define HOST_MLME_MGMT_MASK (BIT(IEEE80211_STYPE_AUTH >> 4) | \ + BIT(IEEE80211_STYPE_DEAUTH >> 4) | \ + BIT(IEEE80211_STYPE_DISASSOC >> 4)) + +#define AUTH_TX_DEFAULT_WAIT_TIME 2400 + +#define WLAN_AUTH_NONE 0xFFFF + +#define NXPWIFI_MAX_TX_BASTREAM_SUPPORTED 2 +#define NXPWIFI_MAX_RX_BASTREAM_SUPPORTED 16 + +#define NXPWIFI_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE 32 + +#define NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE 16 +#define NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE 64 + +#define NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT 0xffff + +#define NXPWIFI_RATE_BITMAP_MCS0 32 + +#define NXPWIFI_RX_DATA_BUF_SIZE (4 * 1024) +#define NXPWIFI_RX_CMD_BUF_SIZE (2 * 1024) + +#define NXPWIFI_BEACON_PERIOD_MAX (4000) +#define NXPWIFI_BEACON_PERIOD_MIN (50) +#define NXPWIFI_INVALID_BEACON_PERIOD (NXPWIFI_BEACON_PERIOD_MAX + 1) +#define NXPWIFI_MAX_DTIM_PERIOD (100) +#define NXPWIFI_MIN_DTIM_PERIOD (1) +#define NXPWIFI_INVALID_DTIM_PERIOD (NXPWIFI_MAX_DTIM_PERIOD + 1) +#define NXPWIFI_RTS_THRESHOLD_MIN (0) +#define NXPWIFI_RTS_THRESHOLD_MAX (2347) +#define NXPWIFI_INVALID_RTS (NXPWIFI_RTS_THRESHOLD_MAX + 1) +#define NXPWIFI_FRAG_THRESHOLD_MIN (256) +#define NXPWIFI_FRAG_THRESHOLD_MAX (2346) +#define NXPWIFI_INVALID_FRAG (NXPWIFI_FRAG_THRESHOLD_MAX + 1) +#define NXPWIFI_RETRY_LIMIT_MAX 14 +#define NXPWIFI_INVALID_RETRY_LIMI (NXPWIFI_RETRY_LIMIT_MAX + 1) + +enum nxpwifi_bcast_ssid_ctl { + /* Hide SSID in beacons (SSID length = 0) */ + NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO = 0, + /* Do not hide SSID (normal broadcast) */ + NXPWIFI_BCAST_SSID_VISIBLE, + /* Hide SSID, clear SSID content (ASCII 0), + * but keep the original SSID length + */ + NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN, +}; + +enum nxpwifi_radio_ctl { + NXPWIFI_RADIO_CTL_DISABLE = 0, + NXPWIFI_RADIO_CTL_ENABLE, + __NXPWIFI_RADIO_CTL_MAX, +}; + +static inline bool nxpwifi_radio_ctl_valid(enum nxpwifi_radio_ctl v) +{ + return v < __NXPWIFI_RADIO_CTL_MAX; +} + +#define NXPWIFI_WMM_VERSION 0x01 +#define NXPWIFI_WMM_SUBTYPE 0x01 + +#define NXPWIFI_SDIO_BLOCK_SIZE 256 + +#define NXPWIFI_BUF_FLAG_REQUEUED_PKT BIT(0) +#define NXPWIFI_BUF_FLAG_BRIDGED_PKT BIT(1) +#define NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS BIT(3) +#define NXPWIFI_BUF_FLAG_ACTION_TX_STATUS BIT(4) +#define NXPWIFI_BUF_FLAG_AGGR_PKT BIT(5) + +#define NXPWIFI_BRIDGED_PKTS_THR_HIGH 1024 +#define NXPWIFI_BRIDGED_PKTS_THR_LOW 128 + +/* 54M rates, index from 0 to 11 */ +#define NXPWIFI_RATE_INDEX_MCS0 12 +/* 12-27=MCS0-15(BW20) */ +#define NXPWIFI_BW20_MCS_NUM 15 + +/* Rate index for OFDM 0 */ +#define NXPWIFI_RATE_INDEX_OFDM0 4 + +#define NXPWIFI_MAX_STA_NUM 3 +#define NXPWIFI_MAX_UAP_NUM 3 + +#define NXPWIFI_A_BAND_START_FREQ 5000 + +/* SDIO Aggr data packet special info */ +#define SDIO_MAX_AGGR_BUF_SIZE (256 * 255) +#define BLOCK_NUMBER_OFFSET 15 +#define SDIO_HEADER_OFFSET 28 + +#define NXPWIFI_SIZE_4K 0x4000 +#define NXPWIFI_EXT_CAPAB_IE_LEN 10 + +enum nxpwifi_bss_type { + NXPWIFI_BSS_TYPE_STA = 0, + NXPWIFI_BSS_TYPE_UAP = 1, + NXPWIFI_BSS_TYPE_ANY = 0xff, +}; + +enum nxpwifi_bss_role { + NXPWIFI_BSS_ROLE_STA = 0, + NXPWIFI_BSS_ROLE_UAP = 1, + NXPWIFI_BSS_ROLE_ANY = 0xff, +}; + +#define BSS_ROLE_BIT_MASK BIT(0) + +#define GET_BSS_ROLE(priv) ((priv)->bss_role & BSS_ROLE_BIT_MASK) + +enum nxpwifi_data_frame_type { + NXPWIFI_DATA_FRAME_TYPE_ETH_II = 0, + NXPWIFI_DATA_FRAME_TYPE_802_11, +}; + +struct nxpwifi_fw_image { + u8 *helper_buf; + u32 helper_len; + u8 *fw_buf; + u32 fw_len; +}; + +struct nxpwifi_802_11_ssid { + u32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + +struct nxpwifi_wait_queue { + wait_queue_head_t wait; + int status; +}; + +struct nxpwifi_rxinfo { + struct sk_buff *parent; + u8 bss_num; + u8 bss_type; + u8 use_count; + u8 buf_type; + u16 pkt_len; +}; + +struct nxpwifi_txinfo { + u8 flags; + u8 bss_num; + u8 bss_type; + u8 aggr_num; + u32 pkt_len; + u8 ack_frame_id; + u64 cookie; +}; + +enum nxpwifi_wmm_ac_e { + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VO +} __packed; + +struct nxpwifi_types_wmm_info { + u8 oui[4]; + u8 subtype; + u8 version; + u8 qos_info; + u8 reserved; + struct ieee80211_wmm_ac_param ac[IEEE80211_NUM_ACS]; +} __packed; + +struct nxpwifi_arp_eth_header { + struct arphdr hdr; + u8 ar_sha[ETH_ALEN]; + u8 ar_sip[4]; + u8 ar_tha[ETH_ALEN]; + u8 ar_tip[4]; +} __packed; + +struct nxpwifi_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + u16 total_bss; + u16 cca_scan_dur; + u16 cca_busy_dur; +} __packed; + +#define NXPWIFI_HIST_MAX_SAMPLES 1048576 +#define NXPWIFI_MAX_RX_RATES 44 +#define NXPWIFI_MAX_AC_RX_RATES 74 +#define NXPWIFI_MAX_SNR 256 +#define NXPWIFI_MAX_NOISE_FLR 256 +#define NXPWIFI_MAX_SIG_STRENGTH 256 + +struct nxpwifi_histogram_data { + atomic_t rx_rate[NXPWIFI_MAX_AC_RX_RATES]; + atomic_t snr[NXPWIFI_MAX_SNR]; + atomic_t noise_flr[NXPWIFI_MAX_NOISE_FLR]; + atomic_t sig_str[NXPWIFI_MAX_SIG_STRENGTH]; + atomic_t num_samples; +}; + +struct nxpwifi_iface_comb { + u8 sta_intf; + u8 uap_intf; +}; + +struct nxpwifi_radar_params { + struct cfg80211_chan_def *chandef; + u32 cac_time_ms; +} __packed; + +struct nxpwifi_11h_intf_state { + bool is_11h_enabled; + bool is_11h_active; +} __packed; + +#define NXPWIFI_FW_DUMP_IDX 0xff +#define NXPWIFI_FW_DUMP_MAX_MEMSIZE 0x160000 +#define NXPWIFI_DRV_INFO_IDX 20 +#define FW_DUMP_MAX_NAME_LEN 8 +#define FW_DUMP_HOST_READY 0xEE +#define FW_DUMP_DONE 0xFF +#define FW_DUMP_READ_DONE 0xFE + +/* Channel bandwidth */ +#define CHANNEL_BW_20MHZ 0 +#define CHANNEL_BW_40MHZ_ABOVE 1 +#define CHANNEL_BW_40MHZ_BELOW 3 +/* secondary channel is 80MHz bandwidth for 11ac */ +#define CHANNEL_BW_80MHZ 4 +#define CHANNEL_BW_160MHZ 5 + +struct memory_type_mapping { + u8 mem_name[FW_DUMP_MAX_NAME_LEN]; + u8 *mem_ptr; + u32 mem_size; + u8 done_flag; +}; + +enum rdwr_status { + RDWR_STATUS_SUCCESS = 0, + RDWR_STATUS_FAILURE = 1, + RDWR_STATUS_DONE = 2 +}; + +enum nxpwifi_chan_band { + BAND_2GHZ = 0, + BAND_5GHZ, + BAND_6GHZ, + BAND_4GHZ, +}; + +enum nxpwifi_chan_width { + CHAN_BW_20MHZ = 0, + CHAN_BW_10MHZ, + CHAN_BW_40MHZ, + CHAN_BW_80MHZ, + CHAN_BW_8080MHZ, + CHAN_BW_160MHZ, + CHAN_BW_5MHZ, +}; + +enum { + NXPWIFI_SCAN_TYPE_UNCHANGED = 0, + NXPWIFI_SCAN_TYPE_ACTIVE, + NXPWIFI_SCAN_TYPE_PASSIVE +}; + +#define NXPWIFI_PROMISC_MODE 1 +#define NXPWIFI_MULTICAST_MODE 2 +#define NXPWIFI_ALL_MULTI_MODE 4 +#define NXPWIFI_MAX_MULTICAST_LIST_SIZE 32 + +struct nxpwifi_multicast_list { + u32 mode; + u32 num_multicast_addr; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +}; + +struct nxpwifi_chan_freq { + u32 channel; + u32 freq; +}; + +struct nxpwifi_ssid_bssid { + struct cfg80211_ssid ssid; + u8 bssid[ETH_ALEN]; +}; + +enum { + BAND_B = 1, + BAND_G = 2, + BAND_A = 4, + BAND_GN = 8, + BAND_AN = 16, + BAND_GAC = 32, + BAND_AAC = 64, + BAND_GAX = 256, + BAND_AAX = 512, +}; + +#define NXPWIFI_WPA_PASSHPHRASE_LEN 64 +struct wpa_param { + u8 pairwise_cipher_wpa; + u8 pairwise_cipher_wpa2; + u8 group_cipher; + u32 length; + u8 passphrase[NXPWIFI_WPA_PASSHPHRASE_LEN]; +}; + +struct wep_key { + u8 key_index; + u8 is_default; + u16 length; + u8 key[WLAN_KEY_LEN_WEP104]; +}; + +#define KEY_MGMT_ON_HOST 0x03 +#define NXPWIFI_AUTH_MODE_AUTO 0xFF +#define BAND_CONFIG_BG 0x00 +#define BAND_CONFIG_A 0x01 +#define NXPWIFI_SEC_CHAN_BELOW 0x03 +#define NXPWIFI_SEC_CHAN_ABOVE 0x01 +#define NXPWIFI_SUPPORTED_RATES 14 +#define NXPWIFI_SUPPORTED_RATES_EXT 32 +#define NXPWIFI_PRIO_BK 2 +#define NXPWIFI_PRIO_VI 5 +#define NXPWIFI_SUPPORTED_CHANNELS 2 +#define NXPWIFI_OPERATING_CLASSES 16 + +struct nxpwifi_uap_bss_param { + u8 mac_addr[ETH_ALEN]; + u8 channel; + u8 band_cfg; + u16 rts_threshold; + u16 frag_threshold; + u8 retry_limit; + struct nxpwifi_802_11_ssid ssid; + u8 bcast_ssid_ctl; + u8 radio_ctl; + u8 dtim_period; + u16 beacon_period; + u16 auth_mode; + u16 protocol; + u16 key_mgmt; + u16 key_mgmt_operation; + struct wpa_param wpa_cfg; + struct wep_key wep_cfg[NUM_WEP_KEYS]; + struct ieee80211_ht_cap ht_cap; + struct ieee80211_vht_cap vht_cap; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 sta_ao_timer; + u32 ps_sta_ao_timer; + u8 power_constraint; + struct ieee80211_wmm_param_ie wmm_element; +}; + +struct nxpwifi_ds_get_stats { + u32 mcast_tx_frame; + u32 failed; + u32 retry; + u32 multi_retry; + u32 frame_dup; + u32 rts_success; + u32 rts_failure; + u32 ack_failure; + u32 rx_frag; + u32 mcast_rx_frame; + u32 fcs_error; + u32 tx_frame; + u32 wep_icv_error[4]; + u32 bcn_rcv_cnt; + u32 bcn_miss_cnt; +}; + +#define NXPWIFI_MAX_VER_STR_LEN 128 + +struct nxpwifi_ver_ext { + u32 version_str_sel; + char version_str[NXPWIFI_MAX_VER_STR_LEN]; +}; + +struct nxpwifi_bss_info { + u32 bss_mode; + struct cfg80211_ssid ssid; + u32 bss_chan; + u8 country_code[3]; + u32 media_connected; + u32 max_power_level; + u32 min_power_level; + signed int bcn_nf_last; + u32 wep_status; + u32 is_hs_configured; + u32 is_deep_sleep; + u8 bssid[ETH_ALEN]; +}; + +struct nxpwifi_sta_info { + u8 peer_mac[ETH_ALEN]; + struct station_parameters *params; +}; + +#define MAX_NUM_TID 8 + +#define MAX_RX_WINSIZE 64 + +struct nxpwifi_ds_rx_reorder_tbl { + u16 tid; + u8 ta[ETH_ALEN]; + u32 start_win; + u32 win_size; + u32 buffer[MAX_RX_WINSIZE]; +}; + +struct nxpwifi_ds_tx_ba_stream_tbl { + u16 tid; + u8 ra[ETH_ALEN]; + u8 amsdu; +}; + +#define DBG_CMD_NUM 5 +#define NXPWIFI_DBG_SDIO_MP_NUM 10 + +struct nxpwifi_debug_info { + unsigned int debug_mask; + u32 int_counter; + u32 packets_out[MAX_NUM_TID]; + u32 tx_buf_size; + u32 curr_tx_buf_size; + u32 tx_tbl_num; + struct nxpwifi_ds_tx_ba_stream_tbl + tx_tbl[NXPWIFI_MAX_TX_BASTREAM_SUPPORTED]; + u32 rx_tbl_num; + struct nxpwifi_ds_rx_reorder_tbl rx_tbl + [NXPWIFI_MAX_RX_BASTREAM_SUPPORTED]; + u16 ps_mode; + u32 ps_state; + u8 is_deep_sleep; + u8 pm_wakeup_card_req; + u32 pm_wakeup_fw_try; + u8 is_hs_configured; + u8 hs_activated; + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u8 is_cmd_timedout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + u8 event_received; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +#define NXPWIFI_KEY_INDEX_UNICAST 0x40000000 +#define PN_LEN 16 + +struct nxpwifi_ds_encrypt_key { + u32 key_disable; + u32 key_index; + u32 key_len; + u32 key_cipher; + u8 key_material[WLAN_MAX_KEY_LEN]; + u8 mac_addr[ETH_ALEN]; + u8 pn[PN_LEN]; /* packet number */ + u8 pn_len; + u8 is_igtk_key; + u8 is_current_wep_key; + u8 is_rx_seq_valid; + u8 is_igtk_def_key; +}; + +struct nxpwifi_power_cfg { + u32 is_power_auto; + u32 is_power_fixed; + u32 power_level; +}; + +struct nxpwifi_ds_hs_cfg { + u32 is_invoke_hostcmd; + /* + * Bit0: non-unicast data + * Bit1: unicast data + * Bit2: mac events + * Bit3: magic packet + */ + u32 conditions; + u32 gpio; + u32 gap; +}; + +struct nxpwifi_ds_wakeup_reason { + u16 hs_wakeup_reason; +}; + +#define DEEP_SLEEP_ON 1 +#define DEEP_SLEEP_OFF 0 +#define DEEP_SLEEP_IDLE_TIME 100 +#define PS_MODE_AUTO 1 + +struct nxpwifi_ds_auto_ds { + u16 auto_ds; + u16 idle_time; +}; + +struct nxpwifi_ds_pm_cfg { + union { + u32 ps_mode; + struct nxpwifi_ds_hs_cfg hs_cfg; + struct nxpwifi_ds_auto_ds auto_deep_sleep; + u32 sleep_period; + } param; +}; + +struct nxpwifi_11ac_vht_cfg { + u8 band_config; + u8 misc_config; + u32 cap_info; + u32 mcs_tx_set; + u32 mcs_rx_set; +}; + +struct nxpwifi_ds_11n_tx_cfg { + u16 tx_htcap; + u16 tx_htinfo; + u16 misc_config; /* Needed for 802.11AC cards only */ +}; + +struct nxpwifi_ds_11n_amsdu_aggr_ctrl { + u16 enable; + u16 curr_buf_size; +}; + +struct nxpwifi_ds_ant_cfg { + u32 tx_ant; + u32 rx_ant; +}; + +#define NXPWIFI_NUM_OF_CMD_BUFFER 50 +#define NXPWIFI_SIZE_OF_CMD_BUFFER 2048 + +enum { + NXPWIFI_IE_TYPE_GEN_IE = 0, + NXPWIFI_IE_TYPE_ARP_FILTER, +}; + +enum { + NXPWIFI_REG_MAC = 1, + NXPWIFI_REG_BBP, + NXPWIFI_REG_RF, + NXPWIFI_REG_PMIC, + NXPWIFI_REG_CAU, +}; + +struct nxpwifi_ds_reg_rw { + u32 type; + u32 offset; + u32 value; +}; + +#define MAX_EEPROM_DATA 256 + +struct nxpwifi_ds_read_eeprom { + u16 offset; + u16 byte_count; + u8 value[MAX_EEPROM_DATA]; +}; + +struct nxpwifi_ds_mem_rw { + u32 addr; + u32 value; +}; + +#define IEEE_MAX_IE_SIZE 256 + +#define NXPWIFI_IE_HDR_SIZE (sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE) + +struct nxpwifi_ds_misc_gen_ie { + u32 type; + u32 len; + u8 ie_data[IEEE_MAX_IE_SIZE]; +}; + +struct nxpwifi_ds_misc_cmd { + u32 len; + u8 cmd[NXPWIFI_SIZE_OF_CMD_BUFFER]; +}; + +#define BITMASK_BCN_RSSI_LOW BIT(0) +#define BITMASK_BCN_RSSI_HIGH BIT(4) + +enum subsc_evt_rssi_state { + EVENT_HANDLED, + RSSI_LOW_RECVD, + RSSI_HIGH_RECVD +}; + +struct subsc_evt_cfg { + u8 abs_value; + u8 evt_freq; +}; + +struct nxpwifi_ds_misc_subsc_evt { + u16 action; + u16 events; + struct subsc_evt_cfg bcn_l_rssi_cfg; + struct subsc_evt_cfg bcn_h_rssi_cfg; +}; + +#define NXPWIFI_MEF_MAX_BYTESEQ 6 /* non-adjustable */ +#define NXPWIFI_MEF_MAX_FILTERS 10 + +struct nxpwifi_mef_filter { + u16 repeat; + u16 offset; + s8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + u8 filt_type; + u8 filt_action; +}; + +struct nxpwifi_mef_entry { + u8 mode; + u8 action; + struct nxpwifi_mef_filter filter[NXPWIFI_MEF_MAX_FILTERS]; +}; + +struct nxpwifi_ds_mef_cfg { + u32 criteria; + u16 num_entries; + struct nxpwifi_mef_entry *mef_entry; +}; + +#define NXPWIFI_MAX_VSIE_LEN (256) +#define NXPWIFI_MAX_VSIE_NUM (8) +#define NXPWIFI_VSIE_MASK_CLEAR 0x00 +#define NXPWIFI_VSIE_MASK_SCAN 0x01 +#define NXPWIFI_VSIE_MASK_ASSOC 0x02 +#define NXPWIFI_VSIE_MASK_BGSCAN 0x08 + +enum { + NXPWIFI_FUNC_INIT = 1, + NXPWIFI_FUNC_SHUTDOWN, +}; + +enum COALESCE_OPERATION { + RECV_FILTER_MATCH_TYPE_EQ = 0x80, + RECV_FILTER_MATCH_TYPE_NE, +}; + +enum COALESCE_PACKET_TYPE { + PACKET_TYPE_UNICAST = 1, + PACKET_TYPE_MULTICAST = 2, + PACKET_TYPE_BROADCAST = 3 +}; + +#define NXPWIFI_COALESCE_MAX_RULES 8 +#define NXPWIFI_COALESCE_MAX_BYTESEQ 4 /* non-adjustable */ +#define NXPWIFI_COALESCE_MAX_FILTERS 4 +#define NXPWIFI_MAX_COALESCING_DELAY 100 /* in msecs */ + +struct filt_field_param { + u8 operation; + u8 operand_len; + u16 offset; + u8 operand_byte_stream[NXPWIFI_COALESCE_MAX_BYTESEQ]; +}; + +struct nxpwifi_coalesce_rule { + u16 max_coalescing_delay; + u8 num_of_fields; + u8 pkt_type; + struct filt_field_param params[NXPWIFI_COALESCE_MAX_FILTERS]; +}; + +struct nxpwifi_ds_coalesce_cfg { + u16 num_of_rules; + struct nxpwifi_coalesce_rule rule[NXPWIFI_COALESCE_MAX_RULES]; +}; + +struct nxpwifi_11ax_he_cap_cfg { + u16 id; + u16 len; + u8 ext_id; + struct ieee80211_he_cap_elem cap_elem; + u8 he_txrx_mcs_support[4]; + u8 val[28]; +}; + +#define HE_CAP_MAX_SIZE 54 + +struct nxpwifi_11ax_he_cfg { + u8 band; + union { + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 data[HE_CAP_MAX_SIZE]; + }; +}; + +#define NXPWIFI_11AXCMD_CFG_ID_SR_OBSS_PD_OFFSET 1 +#define NXPWIFI_11AXCMD_CFG_ID_SR_ENABLE 2 +#define NXPWIFI_11AXCMD_CFG_ID_BEAM_CHANGE 3 +#define NXPWIFI_11AXCMD_CFG_ID_HTC_ENABLE 4 +#define NXPWIFI_11AXCMD_CFG_ID_TXOP_RTS 5 +#define NXPWIFI_11AXCMD_CFG_ID_TX_OMI 6 +#define NXPWIFI_11AXCMD_CFG_ID_OBSSNBRU_TOLTIME 7 +#define NXPWIFI_11AXCMD_CFG_ID_SET_BSRP 8 +#define NXPWIFI_11AXCMD_CFG_ID_LLDE 9 + +#define NXPWIFI_11AXCMD_SR_SUBID 0x102 +#define NXPWIFI_11AXCMD_BEAM_SUBID 0x103 +#define NXPWIFI_11AXCMD_HTC_SUBID 0x104 +#define NXPWIFI_11AXCMD_TXOMI_SUBID 0x105 +#define NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID 0x106 +#define NXPWIFI_11AXCMD_TXOPRTS_SUBID 0x108 +#define NXPWIFI_11AXCMD_SET_BSRP_SUBID 0x109 +#define NXPWIFI_11AXCMD_LLDE_SUBID 0x110 + +#define NXPWIFI_11AX_TWT_SETUP_SUBID 0x114 +#define NXPWIFI_11AX_TWT_TEARDOWN_SUBID 0x115 +#define NXPWIFI_11AX_TWT_REPORT_SUBID 0x116 +#define NXPWIFI_11AX_TWT_INFORMATION_SUBID 0x119 +#define NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID 0x120 +#define BTWT_AGREEMENT_MAX 5 + +struct nxpwifi_11axcmdcfg_obss_pd_offset { + /* */ + u8 offset[2]; +}; + +struct nxpwifi_11axcmdcfg_sr_control { + /* 1 enable, 0 disable */ + u8 control; +}; + +struct nxpwifi_11ax_sr_cmd { + /* type */ + u16 type; + /* length of TLV */ + u16 len; + /* value */ + union { + struct nxpwifi_11axcmdcfg_obss_pd_offset obss_pd_offset; + struct nxpwifi_11axcmdcfg_sr_control sr_control; + } param; +}; + +struct nxpwifi_11ax_beam_cmd { + /* command value: 1 is disable, 0 is enable */ + u8 value; +}; + +struct nxpwifi_11ax_htc_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_txomi_cmd { + /* 11ax spec 9.2.4.6a.2 OM Control 12 bits. Bit 0 to bit 11 */ + u16 omi; + /* + * tx option + * 0: send OMI in QoS NULL; 1: send OMI in QoS data; 0xFF: set OMI in + * both + */ + u8 tx_option; + /* + * if OMI is sent in QoS data, specify the number of consecutive data + * packets containing the OMI + */ + u8 num_data_pkts; +}; + +struct nxpwifi_11ax_toltime_cmd { + /* OBSS Narrow Bandwidth RU Tolerance Time */ + u32 tol_time; +}; + +struct nxpwifi_11ax_txop_cmd { + /* + * Two byte rts threshold value of which only 10 bits, bit 0 to bit 9 + * are valid + */ + u16 rts_thres; +}; + +struct nxpwifi_11ax_set_bsrp_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_llde_cmd { + /* Uplink LLDE: enable=1,disable=0 */ + u8 llde; + /* operation mode: default=0,carplay=1,gameplay=2 */ + u8 mode; + /* trigger frame rate: auto=0xff */ + u8 fixrate; + /* cap airtime limit index: auto=0xff */ + u8 trigger_limit; + /* cap peak UL rate */ + u8 peak_ul_rate; + /* Downlink LLDE: enable=1,disable=0 */ + u8 dl_llde; + /* Set trigger frame interval(us): auto=0 */ + u16 poll_interval; + /* Set TxOp duration */ + u16 tx_op_duration; + /* for other configurations */ + u16 llde_ctrl; + u16 mu_rts_successcnt; + u16 mu_rts_failcnt; + u16 basic_trigger_successcnt; + u16 basic_trigger_failcnt; + u16 tbppdu_nullcnt; + u16 tbppdu_datacnt; +}; + +struct nxpwifi_11ax_cmd_cfg { + u32 sub_command; + u32 sub_id; + union { + struct nxpwifi_11ax_sr_cmd sr_cfg; + struct nxpwifi_11ax_beam_cmd beam_cfg; + struct nxpwifi_11ax_htc_cmd htc_cfg; + struct nxpwifi_11ax_txomi_cmd txomi_cfg; + struct nxpwifi_11ax_toltime_cmd toltime_cfg; + struct nxpwifi_11ax_txop_cmd txop_cfg; + struct nxpwifi_11ax_set_bsrp_cmd setbsrp_cfg; + struct nxpwifi_11ax_llde_cmd llde_cfg; + } param; +}; + +struct nxpwifi_twt_setup { + /** Implicit, 0: TWT session is explicit, 1: Session is implicit */ + u8 implicit; + /** Announced, 0: Unannounced, 1: Announced TWT */ + u8 announced; + /** Trigger Enabled, 0: Non-Trigger enabled, 1: Trigger enabled TWT */ + u8 trigger_enabled; + /** TWT Information Disabled, 0: TWT info enabled, 1: TWT info disabled */ + u8 twt_info_disabled; + /* + * Negotiation Type, 0: Future Individual TWT SP start time, 1: + * Next Wake TBTT time + */ + u8 negotiation_type; + /* + * TWT Wakeup Duration, time after which the TWT requesting STA can + * transition to doze state + */ + u8 twt_wakeup_duration; + /** Flow Identifier. Range: [0-7]*/ + u8 flow_identifier; + /* + * Hard Constraint, 0: FW can tweak the TWT setup parameters if it is + * rejected by AP. + * 1: Firmware should not tweak any parameters. + */ + u8 hard_constraint; + /** TWT Exponent, Range: [0-63] */ + u8 twt_exponent; + /** TWT Mantissa Range: [0-sizeof(UINT16)] */ + __le16 twt_mantissa; + /** TWT Request Type, 0: REQUEST_TWT, 1: SUGGEST_TWT*/ + u8 twt_request; + /** TWT Setup State. Set to 0 by driver, filled by FW in response*/ + u8 twt_setup_state; + /** TWT link lost timeout threshold */ + __le16 bcn_miss_threshold; +} __packed; + +struct nxpwifi_twt_teardown { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Negotiation Type. 0: Future Individual TWT SP start time, 1: Next + * Wake TBTT time + */ + u8 negotiation_type; + /** Tear down all TWT. 1: To teardown all TWT, 0 otherwise */ + u8 teardown_all_twt; + /** TWT Teardown State. Set to 0 by driver, filled by FW in response */ + u8 twt_teardown_state; + /** Reserved, set to 0. */ + u8 reserved[3]; +} __packed; + +#define NXPWIFI_BTWT_REPORT_LEN 9 +#define NXPWIFI_BTWT_REPORT_MAX_NUM 4 +struct nxpwifi_twt_report { + /** TWT report type, 0: BTWT id */ + u8 type; + /** TWT report length of value in data */ + u8 length; + u8 reserve[2]; + /** TWT report payload for FW response to fill */ + u8 data[NXPWIFI_BTWT_REPORT_LEN * NXPWIFI_BTWT_REPORT_MAX_NUM]; +} __packed; + +struct nxpwifi_twt_information { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Suspend Duration. Range: [0-UINT32_MAX] + * 0:Suspend forever; + * Else:Suspend agreement for specific duration in milli seconds, + * after than resume the agreement and enter SP immediately + */ + __le32 suspend_duration; + /** TWT Information State. Set to 0 by driver, filled by FW in response */ + u8 twt_information_state; +} __packed; + +struct btwt_set { + u8 btwt_id; + __le16 ap_bcast_mantissa; + u8 ap_bcast_exponent; + u8 nominalwake; +} __packed; + +#define BTWT_AGREEMENT_MAX 5 +struct nxpwifi_btwt_ap_config { + u8 ap_bcast_bet_sta_wait; + __le16 ap_bcast_offset; + u8 bcast_twtli; + u8 count; + struct btwt_set btwt_sets[BTWT_AGREEMENT_MAX]; +} __packed; + +struct nxpwifi_twt_cfg { + u16 action; + u16 sub_id; + union { + struct nxpwifi_twt_setup twt_setup; + struct nxpwifi_twt_teardown twt_teardown; + struct nxpwifi_twt_report twt_report; + struct nxpwifi_twt_information twt_information; + struct nxpwifi_btwt_ap_config btwt_ap_config; + } param; +}; +#endif /* !_NXPWIFI_CFG_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c new file mode 100644 index 000000000000..5cc8cdf594d3 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -0,0 +1,3935 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#include "cfg80211.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "wmm.h" + +static const struct ieee80211_iface_limit nxpwifi_ap_sta_limits[] = { + { + .max = NXPWIFI_MAX_BSS_NUM, + .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR), + }, +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40), +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta_vht = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), +}; + +/* Map nl80211 channel types to secondary channel offsets */ +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type) +{ + switch (chan_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + case NL80211_CHAN_HT40PLUS: + return IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + case NL80211_CHAN_HT40MINUS: + return IEEE80211_HT_PARAM_CHA_SEC_BELOW; + default: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + } +} + +/* Map IEEE HT secondary‑channel type to nl80211 channel type */ +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv) +{ + struct nxpwifi_channel_band channel_band; + int ret; + + ret = nxpwifi_get_chan_info(priv, &channel_band); + + if (!ret) { + switch (channel_band.band_config.chan_width) { + case CHAN_BW_20MHZ: + if (IS_11N_ENABLED(priv)) + return NL80211_CHAN_HT20; + else + return NL80211_CHAN_NO_HT; + case CHAN_BW_40MHZ: + if (channel_band.band_config.chan2_offset == + IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + return NL80211_CHAN_HT40PLUS; + else + return NL80211_CHAN_HT40MINUS; + default: + return NL80211_CHAN_HT20; + } + } + + return NL80211_CHAN_HT20; +} + +/* Retrieve the driver private data from the wiphy */ +static void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy) +{ + return (void *)(*(unsigned long *)wiphy_priv(wiphy)); +} + +/* cfg80211 operation handler to delete a network key. */ +static int +nxpwifi_cfg80211_del_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + static const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const u8 *peer_mac = pairwise ? mac_addr : bc_mac; + int ret; + + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, peer_mac, 1); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "crypto keys deleted failed %d\n", ret); + else + nxpwifi_dbg(priv->adapter, INFO, "info: crypto keys deleted\n"); + + return ret; +} + +/* Build an skb containing a management frame */ +static void +nxpwifi_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) +{ + u8 addr[ETH_ALEN]; + u16 pkt_len; + u32 tx_control = 0, pkt_type = PKT_TYPE_MGMT; + + eth_broadcast_addr(addr); + pkt_len = len + ETH_ALEN; + + skb_reserve(skb, NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memcpy(skb_push(skb, sizeof(pkt_len)), &pkt_len, sizeof(pkt_len)); + + memcpy(skb_push(skb, sizeof(tx_control)), + &tx_control, sizeof(tx_control)); + + memcpy(skb_push(skb, sizeof(pkt_type)), &pkt_type, sizeof(pkt_type)); + + /* Add packet data and address4 */ + skb_put_data(skb, buf, sizeof(struct ieee80211_hdr_3addr)); + skb_put_data(skb, addr, ETH_ALEN); + skb_put_data(skb, buf + sizeof(struct ieee80211_hdr_3addr), + len - sizeof(struct ieee80211_hdr_3addr)); + + skb->priority = TC_PRIO_BESTEFFORT; + __net_timestamp(skb); +} + +/* cfg80211 operation handler to transmit a management frame. */ +static int +nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, u64 cookie) +{ + const u8 *buf = params->buf; + size_t len = params->len; + struct sk_buff *skb; + u16 pkt_len; + const struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + + if (!buf || !len) { + nxpwifi_dbg(priv->adapter, ERROR, "invalid buffer and length\n"); + return -EINVAL; + } + + mgmt = (const struct ieee80211_mgmt *)buf; + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA && + ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Offloaded probe responses; skip TX in AP/GO mode */ + nxpwifi_dbg(priv->adapter, INFO, + "info: skip to send probe resp in AP or GO mode\n"); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send auth to %pM\n", mgmt->da); + if (ieee80211_is_deauth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send deauth to %pM\n", mgmt->da); + if (ieee80211_is_disassoc(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send disassoc to %pM\n", mgmt->da); + if (ieee80211_is_assoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send assoc resp to %pM\n", + mgmt->da); + if (ieee80211_is_reassoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send reassoc resp to %pM\n", + mgmt->da); + } + + pkt_len = len + ETH_ALEN; + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + + if (!skb) { + nxpwifi_dbg(priv->adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + nxpwifi_form_mgmt_frame(skb, buf, len); + + if (ieee80211_is_action(mgmt->frame_control)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_ACTION_TX_STATUS, &cookie); + else + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, + GFP_ATOMIC); + + nxpwifi_queue_tx_pkt(priv, skb); + + nxpwifi_dbg(priv->adapter, INFO, "info: management frame transmitted\n"); + return 0; +} + +/* cfg80211 operation handler to register a mgmt frame. */ +static void +nxpwifi_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct mgmt_frame_regs *upd) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + u32 mask = upd->interface_stypes; + + if (mask != priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = mask; + if (priv->host_mlme_reg && + GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + nxpwifi_dbg(priv->adapter, INFO, "info: mgmt frame registered\n"); + } +} + +/* cfg80211 operation handler to remain on channel. */ +static int +nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, u64 cookie, + const u8 *rx_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + if (!chan) { + nxpwifi_dbg(adapter, ERROR, "Invalid parameter for ROC\n"); + return -EINVAL; + } + + if (priv->roc_cfg.cookie) { + nxpwifi_dbg(adapter, INFO, + "info: ongoing ROC, cookie = 0x%llx\n", + priv->roc_cfg.cookie); + return -EBUSY; + } + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, chan, + duration); + + if (!ret) { + priv->roc_cfg.cookie = cookie; + priv->roc_cfg.chan = *chan; + + cfg80211_ready_on_channel(wdev, cookie, chan, + duration, GFP_ATOMIC); + + nxpwifi_dbg(adapter, INFO, + "info: ROC, cookie = 0x%llx\n", cookie); + } + + return ret; +} + +/* cfg80211 operation handler to cancel remain on channel. */ +static int +nxpwifi_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, u64 cookie) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret; + + if (cookie != priv->roc_cfg.cookie) + return -ENOENT; + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + + if (!ret) { + cfg80211_remain_on_channel_expired(wdev, cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + + memset(&priv->roc_cfg, 0, sizeof(struct nxpwifi_roc_cfg)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: cancel ROC, cookie = 0x%llx\n", cookie); + } + + return ret; +} + +/* cfg80211 operation handler to set Tx power. */ +static int +nxpwifi_cfg80211_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_power_cfg power_cfg; + int dbm = MBM_TO_DBM(mbm); + + switch (type) { + case NL80211_TX_POWER_FIXED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 1; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_LIMITED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 0; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_AUTOMATIC: + power_cfg.is_power_auto = 1; + break; + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + return nxpwifi_set_tx_power(priv, &power_cfg); +} + +/* cfg80211 operation handler to get Tx power. */ +static int +nxpwifi_cfg80211_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + unsigned int link_id, + int *dbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = nxpwifi_get_tx_pwr(priv); + + if (ret < 0) + return ret; + + *dbm = priv->tx_power_level; + + return 0; +} + +/* + * cfg80211 handler for setting IEEE 802.11 Power Save mode. + * + * The 'timeout' parameter is not supported and is ignored. + */ +static int +nxpwifi_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 ps_mode; + + if (timeout) + nxpwifi_dbg(priv->adapter, INFO, + "info: ignore timeout value for IEEE Power Save\n"); + + ps_mode = enabled; + + return nxpwifi_drv_set_power(priv, &ps_mode); +} + +/* + * cfg80211 handler for setting the default WEP key. + * + * Ignored if WEP is not enabled. + */ +static int +nxpwifi_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + int link_id, u8 key_index, bool unicast, + bool multicast) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(netdev); + int ret = 0; + + /* Return if WEP key not configured */ + if (!priv->sec_info.wep_enabled) + return 0; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) { + priv->wep_key_curr_index = key_index; + } else { + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, + NULL, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to set default Tx key index\n"); + } + + return ret; +} + +/* cfg80211 handler for adding an 802.11 encryption key. */ +static int +nxpwifi_cfg80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr, struct key_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_wep_key *wep_key; + u8 bc_mac[ETH_ALEN]; + const u8 *peer_mac; + int ret; + + eth_broadcast_addr(bc_mac); + peer_mac = pairwise ? mac_addr : bc_mac; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + (params->cipher == WLAN_CIPHER_SUITE_WEP40 || + params->cipher == WLAN_CIPHER_SUITE_WEP104)) { + if (params->key && params->key_len) { + wep_key = &priv->wep_key[key_index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + memcpy(wep_key->key_material, params->key, + params->key_len); + wep_key->key_index = key_index; + wep_key->key_length = params->key_len; + priv->sec_info.wep_enabled = 1; + } + return 0; + } + + ret = nxpwifi_set_encode(priv, params, params->key, params->key_len, + key_index, peer_mac, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to add crypto keys\n"); + + return ret; +} + +/* cfg80211 handler for setting the default management key. */ +static int +nxpwifi_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + struct wireless_dev *wdev, + int link_id, + u8 key_index) +{ + return 0; +} + +/* + * Sends regulatory domain information to the firmware. + * + * Includes: + * - Country code + * - Sub-band definitions (first channel, channel count, max TX power) + */ +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band) +{ + u8 no_of_triplet = 0; + struct ieee80211_country_ie_triplet *t; + u8 no_of_parsed_chan = 0; + u8 first_chan = 0, next_chan = 0, max_pwr = 0; + u8 i, flag = 0; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_802_11d_domain_reg *domain_info = &adapter->domain_reg; + int ret; + + domain_info->dfs_region = adapter->dfs_region; + + /* Set country code */ + domain_info->country_code[0] = adapter->country_code[0]; + domain_info->country_code[1] = adapter->country_code[1]; + domain_info->country_code[2] = ' '; + + if (!wiphy->bands[band]) { + nxpwifi_dbg(adapter, ERROR, + "11D: setting domain info in FW\n"); + return -EINVAL; + } + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (!flag) { + flag = 1; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + continue; + } + + if (ch->hw_value == next_chan + 1 && + ch->max_power == max_pwr) { + next_chan++; + no_of_parsed_chan++; + } else { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + } + } + + if (flag) { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + } + + domain_info->no_of_triplet = no_of_triplet; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + ret = nxpwifi_apply_regdomain(priv); + + if (ret) + nxpwifi_dbg(adapter, INFO, + "11D: failed to set domain info in FW\n"); + + return ret; +} + +static void nxpwifi_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *chan; + unsigned int i; + + if (!wiphy->bands[NL80211_BAND_5GHZ]) + return; + sband = wiphy->bands[NL80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + chan = &sband->channels[i]; + if ((!(chan->flags & IEEE80211_CHAN_DISABLED)) && + (chan->flags & IEEE80211_CHAN_RADAR)) + chan->flags |= IEEE80211_CHAN_NO_IR; + } +} + +/* + * cfg80211 regulatory domain change callback. + * + * Invoked when the regulatory domain is updated by: + * - the driver + * - the system core + * - the user + * - a received Country IE + */ +static void nxpwifi_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + nxpwifi_dbg(adapter, INFO, + "info: cfg80211 regulatory domain callback for %c%c\n", + request->alpha2[0], request->alpha2[1]); + nxpwifi_reg_apply_radar_flags(wiphy); + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_CORE: + case NL80211_REGDOM_SET_BY_USER: + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unknown regdom initiator: %d\n", + request->initiator); + return; + } + + /* Skip world/unchanged regulatory domains. */ + if (strncmp(request->alpha2, "00", 2) && + strncmp(request->alpha2, adapter->country_code, + sizeof(request->alpha2))) { + memcpy(adapter->country_code, request->alpha2, + sizeof(request->alpha2)); + adapter->dfs_region = request->dfs_region; + nxpwifi_send_domain_info_cmd_fw(wiphy, NL80211_BAND_2GHZ); + if (adapter->fw_bands & BAND_A) + nxpwifi_send_domain_info_cmd_fw(wiphy, + NL80211_BAND_5GHZ); + } +} + +/* + * cfg80211 op: set wiphy parameters. + * Updates RTS/fragmentation thresholds and retry limits based on 'changed' + * flags. + */ +static int +nxpwifi_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 changed) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_uap_bss_param *bss_cfg; + int ret = 0; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + switch (priv->bss_role) { + case NXPWIFI_BSS_ROLE_UAP: + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) { + ret = -ENOMEM; + break; + } + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) + bss_cfg->rts_threshold = wiphy->rts_threshold; + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + bss_cfg->frag_threshold = wiphy->frag_threshold; + if (changed & WIPHY_PARAM_RETRY_LONG) + bss_cfg->retry_limit = wiphy->retry_long; + + ret = nxpwifi_set_uap_sys_cfg(priv, bss_cfg); + + kfree(bss_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Failed to set wiphy phy params\n"); + break; + + case NXPWIFI_BSS_ROLE_STA: + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + ret = nxpwifi_set_rts(priv, + wiphy->rts_threshold); + if (ret) + break; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + ret = nxpwifi_set_frag(priv, + wiphy->frag_threshold); + break; + } + + return ret; +} + +static int nxpwifi_deinit_priv_params(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + + if (priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = 0; + ret = nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + priv->host_mlme_reg = false; + + } + + nxpwifi_deauthenticate(priv, NULL); + + atomic_set(&adapter->iface_changing, 1); + flush_workqueue(adapter->workqueue); + flush_workqueue(adapter->rx_workqueue); + nxpwifi_free_priv(priv); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + + return ret; +} + +static int +nxpwifi_init_new_priv_params(struct nxpwifi_private *priv, + struct net_device *dev, + enum nl80211_iftype type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + nxpwifi_init_priv(priv); + + priv->bss_mode = type; + priv->wdev.iftype = type; + + nxpwifi_init_priv_params(priv, priv->netdev); + priv->bss_started = 0; + + switch (type) { + case NL80211_IFTYPE_STATION: + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + break; + case NL80211_IFTYPE_AP: + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + + ret = nxpwifi_get_unused_bss_num(adapter, priv->bss_type, + &priv->bss_num); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: no unused bss_num for type %d\n", + dev->name, priv->bss_type); + return ret; + } + + flush_workqueue(adapter->workqueue); + atomic_set(&adapter->iface_changing, 0); + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + return 0; +} + +static bool +is_vif_type_change_allowed(struct nxpwifi_adapter *adapter, + enum nl80211_iftype old_iftype, + enum nl80211_iftype new_iftype) +{ + switch (old_iftype) { + case NL80211_IFTYPE_STATION: + switch (new_iftype) { + case NL80211_IFTYPE_AP: + return adapter->curr_iface_comb.uap_intf != + adapter->iface_limit.uap_intf; + default: + return false; + } + + case NL80211_IFTYPE_AP: + switch (new_iftype) { + case NL80211_IFTYPE_STATION: + return adapter->curr_iface_comb.sta_intf != + adapter->iface_limit.sta_intf; + default: + return false; + } + + default: + break; + } + + return false; +} + +static void +update_vif_type_counter(struct nxpwifi_adapter *adapter, + enum nl80211_iftype iftype, + int change) +{ + switch (iftype) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + adapter->curr_iface_comb.sta_intf += change; + break; + case NL80211_IFTYPE_AP: + adapter->curr_iface_comb.uap_intf += change; + break; + case NL80211_IFTYPE_MONITOR: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unsupported iftype passed: %d\n", + __func__, iftype); + break; + } +} + +static int +nxpwifi_change_vif_to_sta(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to station\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +static int +nxpwifi_change_vif_to_ap(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to AP\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +/* cfg80211 operation handler to change interface type. */ +static int +nxpwifi_cfg80211_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + enum nl80211_iftype curr_iftype = dev->ieee80211_ptr->iftype; + + if (priv->scan_request) { + nxpwifi_dbg(priv->adapter, ERROR, + "change virtual interface: scan in process\n"); + return -EBUSY; + } + + if (type == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: no new type specified, keeping old type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (curr_iftype == type) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: interface already is of type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: change from type %d to %d is not allowed\n", + dev->name, curr_iftype, type); + return -EOPNOTSUPP; + } + + switch (curr_iftype) { + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_AP: + return nxpwifi_change_vif_to_ap(dev, curr_iftype, type, + params); + default: + goto errnotsupp; + } + + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_STATION: + return nxpwifi_change_vif_to_sta(dev, curr_iftype, + type, params); + default: + goto errnotsupp; + } + + default: + goto errnotsupp; + } + +errnotsupp: + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported interface type transition: %d to %d\n", + curr_iftype, type); + return -EOPNOTSUPP; +} + +#define RATE_FORMAT_LG 0 +#define RATE_FORMAT_HT 1 +#define RATE_FORMAT_VHT 2 +#define RATE_FORMAT_HE 3 + +static void +nxpwifi_parse_htinfo(struct nxpwifi_private *priv, u8 rateinfo, u8 htinfo, + struct rate_info *rate) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 rate_format; + u8 he_dcm; + u8 stbc; + u8 gi; + u8 bw; + /* Bitrates in multiples of 100kb/s. */ + static const int legacy_rates[] = { + [0] = 10, + [1] = 20, + [2] = 55, + [3] = 110, + [4] = 60, /* NXPWIFI_RATE_INDEX_OFDM0 */ + [5] = 60, + [6] = 90, + [7] = 120, + [8] = 180, + [9] = 240, + [10] = 360, + [11] = 480, + [12] = 540, + }; + + rate_format = htinfo & 0x3; + + switch (rate_format) { + case RATE_FORMAT_LG: + if (rateinfo < ARRAY_SIZE(legacy_rates)) + rate->legacy = legacy_rates[rateinfo]; + break; + case RATE_FORMAT_HT: + rate->mcs = rateinfo; + rate->flags |= RATE_INFO_FLAGS_MCS; + break; + case RATE_FORMAT_VHT: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_VHT_MCS; + break; + case RATE_FORMAT_HE: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_HE_MCS; + he_dcm = 0; /* ToDo: ext_rate_info */ + gi = (htinfo & BIT(4)) >> 4 | + (htinfo & BIT(7)) >> 6; + stbc = (htinfo & BIT(5)) >> 5; + if (gi > 3) { + nxpwifi_dbg(adapter, ERROR, "Invalid gi value\n"); + break; + } + if (gi == 3 && stbc && he_dcm) { + gi = 0; + stbc = 0; + he_dcm = 0; + } + if (gi > 0) + gi -= 1; + rate->he_gi = gi; + rate->he_dcm = he_dcm; + break; + } + + bw = (htinfo & 0xC) >> 2; + + switch (bw) { + case 0: + rate->bw = RATE_INFO_BW_20; + break; + case 1: + rate->bw = RATE_INFO_BW_40; + break; + case 2: + rate->bw = RATE_INFO_BW_80; + break; + case 3: + rate->bw = RATE_INFO_BW_160; + break; + } + + if (rate_format != RATE_FORMAT_HE && (htinfo & BIT(4))) + rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + + if ((rateinfo >> 4) == 1) + rate->nss = 2; + else + rate->nss = 1; +} + +/* + * Dump station statistics into station_info. + * Includes bytes/packets counters, signal level, and TX/RX rates. + */ +static int +nxpwifi_dump_station_info(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node, + struct station_info *sinfo) +{ + u32 rate; + int ret; + + sinfo->filled = BIT_ULL(NL80211_STA_INFO_RX_BYTES) | + BIT_ULL(NL80211_STA_INFO_TX_BYTES) | + BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_BITRATE) | + BIT_ULL(NL80211_STA_INFO_SIGNAL) | + BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (!node) + return -ENOENT; + + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME) | + BIT_ULL(NL80211_STA_INFO_TX_FAILED); + sinfo->inactive_time = + jiffies_to_msecs(jiffies - node->stats.last_rx); + + sinfo->signal = node->stats.rssi; + sinfo->signal_avg = node->stats.rssi; + sinfo->rx_bytes = node->stats.rx_bytes; + sinfo->tx_bytes = node->stats.tx_bytes; + sinfo->rx_packets = node->stats.rx_packets; + sinfo->tx_packets = node->stats.tx_packets; + sinfo->tx_failed = node->stats.tx_failed; + + nxpwifi_parse_htinfo(priv, priv->tx_rate, + node->stats.last_tx_htinfo, + &sinfo->txrate); + sinfo->txrate.legacy = node->stats.last_tx_rate * 5; + + return 0; + } + + /* Get signal information from the firmware */ + ret = nxpwifi_get_rssi_info(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to get signal information\n"); + goto done; + } + + ret = nxpwifi_drv_get_data_rate(priv, &rate); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "getting data rate error\n"); + goto done; + } + + /* Retrieve DTIM period value from firmware. */ + nxpwifi_get_802_11_snmp_mib(priv, DTIM_PERIOD_I, &priv->dtim_period); + + nxpwifi_parse_htinfo(priv, priv->tx_rate, priv->tx_htinfo, + &sinfo->txrate); + + sinfo->signal_avg = priv->bcn_rssi_avg; + sinfo->rx_bytes = priv->stats.rx_bytes; + sinfo->tx_bytes = priv->stats.tx_bytes; + sinfo->rx_packets = priv->stats.rx_packets; + sinfo->tx_packets = priv->stats.tx_packets; + sinfo->signal = priv->bcn_rssi_avg; + /* Convert bitrate from 500 kb/s units to 100 kb/s units. */ + sinfo->txrate.legacy = rate * 5; + + sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE); + nxpwifi_parse_htinfo(priv, priv->rxpd_rate, priv->rxpd_htinfo, + &sinfo->rxrate); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM); + sinfo->bss_param.flags = 0; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_PREAMBLE) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_PREAMBLE; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_SLOT_TIME) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_SLOT_TIME; + sinfo->bss_param.dtim_period = priv->dtim_period; + sinfo->bss_param.beacon_interval = + priv->curr_bss_params.bss_descriptor.beacon_period; + } + +done: + return ret; +} + +/* + * cfg80211 op: get station information. + * Works only when connected and fills station_info with current stats. + */ +static int +nxpwifi_cfg80211_get_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (!priv->media_connected || + memcmp(mac, priv->cfg_bssid, ETH_ALEN)) + return -ENOENT; + node = NULL; + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + } + + return nxpwifi_dump_station_info(priv, node, sinfo); +} + +/* cfg80211 operation handler to dump station information. */ +static int +nxpwifi_cfg80211_dump_station(struct wiphy *wiphy, struct wireless_dev *wdev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + int i; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + ether_addr_copy(mac, priv->cfg_bssid); + return nxpwifi_dump_station_info(priv, NULL, sinfo); + } else if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_ap_get_sta_list(priv); + + i = 0; + rcu_read_lock(); + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (i++ != idx) + continue; + found = node; + break; + } + rcu_read_unlock(); + + if (found) { + ether_addr_copy(mac, node->mac_addr); + return nxpwifi_dump_station_info(priv, node, sinfo); + } + } + + return -ENOENT; +} + +static int +nxpwifi_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *dev, + int idx, struct survey_info *survey) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_chan_stats *pchan_stats = priv->adapter->chan_stats; + enum nl80211_band band; + u8 chan_num; + + nxpwifi_dbg(priv->adapter, DUMP, "dump_survey idx=%d\n", idx); + + memset(survey, 0, sizeof(struct survey_info)); + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + u8 curr_bss_band = priv->curr_bss_params.band; + u32 chan = priv->curr_bss_params.bss_descriptor.channel; + + band = nxpwifi_band_to_radio_type(curr_bss_band); + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan, band)); + + if (priv->bcn_nf_last) { + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->bcn_nf_last; + } + return 0; + } + + if (idx >= priv->adapter->num_in_chan_stats) + return -ENOENT; + + if (!pchan_stats[idx].cca_scan_dur) + return 0; + + band = pchan_stats[idx].bandcfg; + chan_num = pchan_stats[idx].chan_num; + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan_num, band)); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + survey->noise = pchan_stats[idx].noise; + survey->time = pchan_stats[idx].cca_scan_dur; + survey->time_busy = pchan_stats[idx].cca_busy_dur; + + return 0; +} + +/* Supported rates to be advertised to the cfg80211 */ +static struct ieee80211_rate nxpwifi_rates[] = { + {.bitrate = 10, .hw_value = 2, }, + {.bitrate = 20, .hw_value = 4, }, + {.bitrate = 55, .hw_value = 11, }, + {.bitrate = 110, .hw_value = 22, }, + {.bitrate = 60, .hw_value = 12, }, + {.bitrate = 90, .hw_value = 18, }, + {.bitrate = 120, .hw_value = 24, }, + {.bitrate = 180, .hw_value = 36, }, + {.bitrate = 240, .hw_value = 48, }, + {.bitrate = 360, .hw_value = 72, }, + {.bitrate = 480, .hw_value = 96, }, + {.bitrate = 540, .hw_value = 108, }, +}; + +/* Channel definitions to be advertised to cfg80211 */ +static struct ieee80211_channel nxpwifi_channels_2ghz[] = { + {.center_freq = 2412, .hw_value = 1, }, + {.center_freq = 2417, .hw_value = 2, }, + {.center_freq = 2422, .hw_value = 3, }, + {.center_freq = 2427, .hw_value = 4, }, + {.center_freq = 2432, .hw_value = 5, }, + {.center_freq = 2437, .hw_value = 6, }, + {.center_freq = 2442, .hw_value = 7, }, + {.center_freq = 2447, .hw_value = 8, }, + {.center_freq = 2452, .hw_value = 9, }, + {.center_freq = 2457, .hw_value = 10, }, + {.center_freq = 2462, .hw_value = 11, }, + {.center_freq = 2467, .hw_value = 12, }, + {.center_freq = 2472, .hw_value = 13, }, + {.center_freq = 2484, .hw_value = 14, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_2ghz = { + .band = NL80211_BAND_2GHZ, + .channels = nxpwifi_channels_2ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_2ghz), + .bitrates = nxpwifi_rates, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates), +}; + +static struct ieee80211_channel nxpwifi_channels_5ghz[] = { + {.center_freq = 5040, .hw_value = 8, }, + {.center_freq = 5060, .hw_value = 12, }, + {.center_freq = 5080, .hw_value = 16, }, + {.center_freq = 5170, .hw_value = 34, }, + {.center_freq = 5190, .hw_value = 38, }, + {.center_freq = 5210, .hw_value = 42, }, + {.center_freq = 5230, .hw_value = 46, }, + {.center_freq = 5180, .hw_value = 36, }, + {.center_freq = 5200, .hw_value = 40, }, + {.center_freq = 5220, .hw_value = 44, }, + {.center_freq = 5240, .hw_value = 48, }, + {.center_freq = 5260, .hw_value = 52, }, + {.center_freq = 5280, .hw_value = 56, }, + {.center_freq = 5300, .hw_value = 60, }, + {.center_freq = 5320, .hw_value = 64, }, + {.center_freq = 5500, .hw_value = 100, }, + {.center_freq = 5520, .hw_value = 104, }, + {.center_freq = 5540, .hw_value = 108, }, + {.center_freq = 5560, .hw_value = 112, }, + {.center_freq = 5580, .hw_value = 116, }, + {.center_freq = 5600, .hw_value = 120, }, + {.center_freq = 5620, .hw_value = 124, }, + {.center_freq = 5640, .hw_value = 128, }, + {.center_freq = 5660, .hw_value = 132, }, + {.center_freq = 5680, .hw_value = 136, }, + {.center_freq = 5700, .hw_value = 140, }, + {.center_freq = 5745, .hw_value = 149, }, + {.center_freq = 5765, .hw_value = 153, }, + {.center_freq = 5785, .hw_value = 157, }, + {.center_freq = 5805, .hw_value = 161, }, + {.center_freq = 5825, .hw_value = 165, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_5ghz = { + .band = NL80211_BAND_5GHZ, + .channels = nxpwifi_channels_5ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_5ghz), + .bitrates = nxpwifi_rates + 4, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates) - 4, +}; + +/* Supported crypto cipher suits to be advertised to cfg80211 */ +static const u32 nxpwifi_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_SMS4, + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_GCMP_256, + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_BIP_GMAC_256, + WLAN_CIPHER_SUITE_BIP_CMAC_256, +}; + +/* Supported mgmt frame types to be advertised to cfg80211 */ +static const struct ieee80211_txrx_stypes +nxpwifi_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4), + }, +}; + +/* + * cfg80211 op: set bitrate mask. + * Converts cfg80211 bitrate selections into firmware bitmap format. + */ +static int +nxpwifi_cfg80211_set_bitrate_mask(struct wiphy *wiphy, + struct net_device *dev, + unsigned int link_id, + const u8 *peer, + const struct cfg80211_bitrate_mask *mask) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + enum nl80211_band band; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "Can not set Tx data rate in disconnected state\n"); + return -EINVAL; + } + + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + + memset(bitmap_rates, 0, sizeof(bitmap_rates)); + + /* Fill HR/DSSS legacy rates (2.4 GHz only). */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[0] = mask->control[band].legacy & 0x000f; + + /* Fill OFDM legacy rates. */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[1] = (mask->control[band].legacy & 0x0ff0) >> 4; + else + bitmap_rates[1] = mask->control[band].legacy; + + /* Fill HT MCS bitmap (1x1 or 2x2 depending on hardware). */ + bitmap_rates[2] = mask->control[band].ht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[2] |= mask->control[band].ht_mcs[1] << 8; + + /* Fill VHT MCS bitmap if supported by firmware. */ + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + bitmap_rates[10] = mask->control[band].vht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[11] = mask->control[band].vht_mcs[1]; + } + + return nxpwifi_set_tx_rate(priv, bitmap_rates); +} + +/* + * cfg80211 op: configure connection-quality monitoring. + * Subscribes or unsubscribes HIGH_RSSI and LOW_RSSI events to firmware. + */ +static int nxpwifi_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_ds_misc_subsc_evt subsc_evt; + int ret = 0; + + priv->cqm_rssi_thold = rssi_thold; + priv->cqm_rssi_hyst = rssi_hyst; + + memset(&subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + subsc_evt.events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + + /* Subscribe/unsubscribe low and high rssi events */ + if (rssi_thold && rssi_hyst) { + subsc_evt.action = HOST_ACT_BITWISE_SET; + subsc_evt.bcn_l_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_h_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt.bcn_h_rssi_cfg.evt_freq = 1; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } else { + subsc_evt.action = HOST_ACT_BITWISE_CLR; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } + + return ret; +} + +/* + * cfg80211 operation handler for change_beacon. + * Function retrieves and sets modified management IEs to FW. + */ +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_beacon_data *data = ¶ms->beacon; + int ret; + + nxpwifi_cancel_scan(adapter); + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: bss_type mismatched\n", __func__); + return -EINVAL; + } + + ret = nxpwifi_set_mgmt_ies(priv, data); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + + return ret; +} + +/* + * cfg80211 operation handler for del_station. + * Function deauthenticates station which value is provided in mac parameter. + * If mac is NULL/broadcast, all stations in associated station list are + * deauthenticated. If bss is not started or there are no stations in + * associated stations list, no action is taken. + */ +static int +nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, + struct station_del_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *sta_node; + u8 deauth_mac[ETH_ALEN]; + int ret = 0; + + if (!priv->bss_started && priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, INFO, "%s: abort CAC!\n", __func__); + nxpwifi_abort_cac(priv); + } + + if (list_empty(&priv->sta_list) || !priv->bss_started) + return 0; + + if (!params->mac || is_broadcast_ether_addr(params->mac)) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, "%s: mac address %pM\n", + __func__, params->mac); + + eth_zero_addr(deauth_mac); + + sta_node = nxpwifi_get_sta_entry(priv, params->mac); + if (sta_node) + ether_addr_copy(deauth_mac, params->mac); + + if (is_valid_ether_addr(deauth_mac)) { + ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); + } + return ret; +} + +static int +nxpwifi_cfg80211_set_antenna(struct wiphy *wiphy, int radio_idx, u32 tx_ant, u32 rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + struct nxpwifi_ds_ant_cfg ant_cfg; + + if (!tx_ant || !rx_ant) + return -EOPNOTSUPP; + + if (adapter->hw_dev_mcs_support != HT_STREAM_2X2) { + /* + * Not a MIMO chip. User should provide specific antenna number + * for Tx/Rx path or enable all antennas for diversity + */ + if (tx_ant != rx_ant) + return -EOPNOTSUPP; + + if ((tx_ant & (tx_ant - 1)) && + (tx_ant != BIT(adapter->number_of_antenna) - 1)) + return -EOPNOTSUPP; + + if ((tx_ant == BIT(adapter->number_of_antenna) - 1) && + priv->adapter->number_of_antenna > 1) { + tx_ant = RF_ANTENNA_AUTO; + rx_ant = RF_ANTENNA_AUTO; + } + } else { + struct ieee80211_sta_ht_cap *ht_info; + int rx_mcs_supp; + enum nl80211_band band; + + if ((tx_ant == 0x1 && rx_ant == 0x1)) { + adapter->user_dev_mcs_support = HT_STREAM_1X1; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_1X1; + } else { + adapter->user_dev_mcs_support = HT_STREAM_2X2; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_2X2; + } + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (!adapter->wiphy->bands[band]) + continue; + + ht_info = &adapter->wiphy->bands[band]->ht_cap; + rx_mcs_supp = + GET_RXMCSSUPP(adapter->user_dev_mcs_support); + memset(&ht_info->mcs, 0, adapter->number_of_antenna); + memset(&ht_info->mcs, 0xff, rx_mcs_supp); + } + } + + ant_cfg.tx_ant = tx_ant; + ant_cfg.rx_ant = rx_ant; + + return nxpwifi_set_rf_antenna(priv, &ant_cfg); +} + +static int +nxpwifi_cfg80211_get_antenna(struct wiphy *wiphy, int radio_idx, u32 *tx_ant, u32 *rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + int ret; + + ret = nxpwifi_get_rf_antenna(priv, tx_ant, rx_ant); + + return ret; +} + +/* + * cfg80211 op: stop AP. + * Stops the BSS running on the uAP interface. + */ +static int nxpwifi_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + nxpwifi_abort_cac(priv); + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + priv->ap_11n_enabled = 0; + memset(&priv->bss_cfg, 0, sizeof(priv->bss_cfg)); + + ret = nxpwifi_ap_stop_bss(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop the BSS\n"); + goto done; + } + + ret = nxpwifi_ap_sys_reset(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to reset BSS\n"); + goto done; + } + + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + + if (atomic_dec_and_test(&priv->adapter->uap_count)) { + priv->adapter->chandef_valid = false; + memset(&priv->adapter->chandef, 0, sizeof(priv->adapter->chandef)); + } + +done: + return ret; +} + +/* + * cfg80211 op: start AP. + * Applies beacon/DTIM/SSID/security settings to the uAP configuration and + * starts the BSS. + */ +static int nxpwifi_cfg80211_start_ap(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_chan_def use_chandef; + bool is_first_uap = false; + int ret; + + /* + * Adapter is the HW channel owner (single PHY). + * All UAP interfaces on the same adapter must share + * the same RF channel. + */ + use_chandef = params->chandef; + + if (adapter->chandef_valid) { + if (!cfg80211_chandef_identical(&adapter->chandef, + ¶ms->chandef)) { + nxpwifi_dbg(adapter, INFO, + "UAP already running on channel %d, ignore requested channel %d\n", + adapter->chandef.chan->hw_value, + params->chandef.chan->hw_value); + } + use_chandef = adapter->chandef; + } else { + is_first_uap = true; + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + return -EINVAL; + + if (!nxpwifi_is_channel_setting_allowable(priv, params->chandef.chan)) + return -EOPNOTSUPP; + + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) + return -ENOMEM; + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + memcpy(bss_cfg->mac_addr, priv->curr_addr, ETH_ALEN); + + if (params->beacon_interval) + bss_cfg->beacon_period = params->beacon_interval; + if (params->dtim_period) + bss_cfg->dtim_period = params->dtim_period; + + if (params->ssid && params->ssid_len) { + memcpy(bss_cfg->ssid.ssid, params->ssid, params->ssid_len); + bss_cfg->ssid.ssid_len = params->ssid_len; + } + if (params->inactivity_timeout > 0) { + /* sta_ao_timer/ps_sta_ao_timer is in unit of 100ms */ + bss_cfg->sta_ao_timer = 10 * params->inactivity_timeout; + bss_cfg->ps_sta_ao_timer = 10 * params->inactivity_timeout; + } + + /* Default: SSID is visible */ + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + + switch (params->hidden_ssid) { + case NL80211_HIDDEN_SSID_NOT_IN_USE: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + break; + case NL80211_HIDDEN_SSID_ZERO_LEN: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO; + break; + case NL80211_HIDDEN_SSID_ZERO_CONTENTS: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN; + break; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, use_chandef); + nxpwifi_set_uap_rates(bss_cfg, params); + + ret = nxpwifi_set_secure_params(priv, bss_cfg, params); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Failed to parse security parameters!\n"); + goto done; + } + + nxpwifi_set_ht_params(priv, bss_cfg, params); + + if (adapter->is_hw_11ac_capable) { + nxpwifi_set_vht_params(priv, bss_cfg, params); + nxpwifi_set_vht_width(priv, use_chandef.width, + priv->ap_11ac_enabled); + } + + if (priv->ap_11ac_enabled) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + if (adapter->is_hw_11ax_capable) { + priv->ap_11ax_enabled = + nxpwifi_check_11ax_capability(priv, bss_cfg, params); + if (priv->ap_11ax_enabled) + nxpwifi_set_11ax_status(priv, bss_cfg, params); + } + + nxpwifi_set_wmm_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv)) + nxpwifi_set_tpc_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv) && + !cfg80211_chandef_dfs_required(wiphy, ¶ms->chandef, + priv->bss_mode)) { + nxpwifi_dbg(priv->adapter, INFO, + "Disable 11h extensions in FW\n"); + ret = nxpwifi_11h_activate(priv, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to disable 11h extensions!!"); + goto done; + } + priv->state_11h.is_11h_active = false; + } + + nxpwifi_config_uap_11d(priv, ¶ms->beacon); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon); + if (ret) + goto done; + + ret = nxpwifi_config_start_uap(priv, bss_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start AP\n"); + goto done; + } + + /* First UAP records adapter-level HW channel */ + if (is_first_uap) { + adapter->chandef = use_chandef; + adapter->chandef_valid = true; + } + atomic_inc(&adapter->uap_count); + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, priv->adapter); + + memcpy(&priv->bss_cfg, bss_cfg, sizeof(priv->bss_cfg)); + +done: + kfree(bss_cfg); + return ret; +} + +/* + * cfg80211 op: handle scan request. + * Issues a firmware scan using the requested parameters and reports the + * results. + */ +static int +nxpwifi_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset, ret; + struct ieee80211_channel *chan; + struct element *ie; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + u8 mac_addr[ETH_ALEN]; + + nxpwifi_dbg(priv->adapter, CMD, + "info: received scan request on %s\n", dev->name); + + /* + * Block scan requests during active scanning or scan cleanup. + * Prevents new scans when the interface is disabled or teardown is in + * progress. + */ + if (priv->scan_request || priv->scan_aborting) { + nxpwifi_dbg(priv->adapter, WARN, + "cmd: Scan already in process..\n"); + return -EBUSY; + } + + if (!priv->wdev.connected && priv->scan_block) + priv->scan_block = false; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + if (!user_scan_cfg) + return -ENOMEM; + + priv->scan_request = request; + + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + get_random_mask_addr(mac_addr, request->mac_addr, + request->mac_addr_mask); + ether_addr_copy(request->mac_addr, mac_addr); + ether_addr_copy(user_scan_cfg->random_mac, mac_addr); + } + + user_scan_cfg->num_ssids = request->n_ssids; + user_scan_cfg->ssid_list = request->ssids; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_SCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_USER_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + user_scan_cfg->chan_list[i].chan_number = chan->hw_value; + user_scan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + user_scan_cfg->chan_list[i].scan_time = 0; + } + + if (priv->adapter->scan_chan_gap_enabled && + nxpwifi_is_any_intf_active(priv)) + user_scan_cfg->scan_chan_gap = + priv->adapter->scan_chan_gap_time; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "scan failed: %d\n", ret); + priv->scan_aborting = false; + priv->scan_request = NULL; + return ret; + } + + if (request->ie && request->ie_len) { + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask == NXPWIFI_VSIE_MASK_SCAN) { + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_CLEAR; + memset(&priv->vs_ie[i].ie, 0, + NXPWIFI_MAX_VSIE_LEN); + } + } + } + return 0; +} + +/* + * cfg80211 sched_scan_start handler. + * + * Send a bgscan configuration request to the firmware based on the + * scheduled scan parameters. On success, the firmware later issues a + * BGSCAN_REPORT event, after which the driver should query the firmware + * for scan results. + */ +static int +nxpwifi_cfg80211_sched_scan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset; + struct ieee80211_channel *chan; + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + struct element *ie; + int ret; + + if (!request || (!request->n_ssids && !request->n_match_sets)) { + wiphy_err(wiphy, "%s : Invalid Sched_scan parameters", + __func__); + return -EINVAL; + } + + wiphy_info(wiphy, "sched_scan start : n_ssids=%d n_match_sets=%d ", + request->n_ssids, request->n_match_sets); + wiphy_info(wiphy, "n_channels=%d interval=%d ie_len=%d\n", + request->n_channels, request->scan_plans->interval, + (int)request->ie_len); + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + if (priv->scan_request || priv->scan_aborting) + bgscan_cfg->start_later = true; + + bgscan_cfg->num_ssids = request->n_match_sets; + bgscan_cfg->ssid_list = request->match_sets; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_BGSCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + bgscan_cfg->chan_list[i].chan_number = chan->hw_value; + bgscan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + bgscan_cfg->chan_list[i].scan_time = 0; + } + + bgscan_cfg->chan_per_scan = min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); + + /* Minimum scan cycle duration: 15 seconds */ + bgscan_cfg->scan_interval = (request->scan_plans->interval > + NXPWIFI_BGSCAN_INTERVAL) ? + request->scan_plans->interval : + NXPWIFI_BGSCAN_INTERVAL; + + bgscan_cfg->repeat_count = NXPWIFI_BGSCAN_REPEAT_COUNT; + bgscan_cfg->report_condition = NXPWIFI_BGSCAN_SSID_MATCH | + NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE; + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = true; + if (request->min_rssi_thold != NL80211_SCAN_RSSI_THOLD_OFF) { + bgscan_cfg->report_condition |= NXPWIFI_BGSCAN_SSID_RSSI_MATCH; + bgscan_cfg->rssi_threshold = request->min_rssi_thold; + } + + ret = nxpwifi_bg_scan_config(priv, bgscan_cfg); + + if (!ret) + priv->sched_scanning = true; + + kfree(bgscan_cfg); + return ret; +} + +/* + * cfg80211 sched_scan_stop handler. + * + * Send a bgscan configuration command to disable the previous + * background scan settings in the firmware. + */ +static int nxpwifi_cfg80211_sched_scan_stop(struct wiphy *wiphy, + struct net_device *dev, u64 reqid) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + wiphy_info(wiphy, "sched scan stop!"); + return nxpwifi_stop_bg_scan(priv); +} + +/* + * Set default cfg80211 HT capabilities. + */ +static void +nxpwifi_setup_ht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_ht_cap *ht_info) +{ + int rx_mcs_supp; + struct ieee80211_mcs_info mcs_set; + u8 *mcs = (u8 *)&mcs_set; + struct nxpwifi_adapter *adapter = priv->adapter; + + ht_info->ht_supported = true; + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + /* Fill HT capability information */ + if (ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + if (ISSUPP_SHORTGI20(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_20; + + if (ISSUPP_SHORTGI40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_40; + + if (adapter->user_dev_mcs_support == HT_STREAM_2X2) + ht_info->cap |= 2 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + else + ht_info->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + + if (ISSUPP_TXSTBC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + else + ht_info->cap &= ~IEEE80211_HT_CAP_TX_STBC; + + if (ISSUPP_GREENFIELD(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + else + ht_info->cap &= ~IEEE80211_HT_CAP_GRN_FLD; + + if (ISENABLED_40MHZ_INTOLERANT(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_40MHZ_INTOLERANT; + else + ht_info->cap &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + + if (ISSUPP_RXLDPC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + else + ht_info->cap &= ~IEEE80211_HT_CAP_LDPC_CODING; + + ht_info->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU; + ht_info->cap |= IEEE80211_HT_CAP_SM_PS; + + rx_mcs_supp = GET_RXMCSSUPP(adapter->user_dev_mcs_support); + /* Set MCS for 1x1/2x2 */ + memset(mcs, 0xff, rx_mcs_supp); + /* Clear all the other values */ + memset(&mcs[rx_mcs_supp], 0, + sizeof(struct ieee80211_mcs_info) - rx_mcs_supp); + if (priv->bss_mode == NL80211_IFTYPE_STATION || + ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(mcs_set.rx_mask); + + memcpy((u8 *)&ht_info->mcs, mcs, sizeof(struct ieee80211_mcs_info)); + + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void +nxpwifi_setup_vht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_vht_cap *vht_info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + vht_info->vht_supported = true; + + vht_info->cap = adapter->hw_dot_11ac_dev_cap; + /* Update MCS support for VHT */ + vht_info->vht_mcs.rx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support & 0xFFFF); + vht_info->vht_mcs.rx_highest = 0; + vht_info->vht_mcs.tx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support >> 16); + vht_info->vht_mcs.tx_highest = 0; +} + +/* + * 5 GHz HE capability masks for UAP mode. + * + * MAC: TWT requester/respondor, broadcast TWT, OMI control. + * + * PHY: 40/80 MHz width, puncturing, LDPC, NDP 4xLTF, STBC, + * Doppler, DCM, SU BF/BFe, STS, sounding dims, extended + * range, PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_MAC_CAP0_MASK (IEEE80211_HE_MAC_CAP0_TWT_REQ | \ + IEEE80211_HE_MAC_CAP0_TWT_RES) + +#define UAP_HE_MAC_CAP1_MASK 0 +#define UAP_HE_MAC_CAP2_MASK IEEE80211_HE_MAC_CAP2_BCAST_TWT +#define UAP_HE_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_MAC_CAP4_MASK 0 +#define UAP_HE_MAC_CAP5_MASK 0 + +#define UAP_HE_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G +#define UAP_HE_PHY_CAP1_MASK (IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_20MHZ | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_40MHZ) +#define UAP_HE_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_PHY_CAP8_MASK 0 +#define UAP_HE_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_PHY_CAP10_MASK 0 + +/* + * 2.4 GHz HE capability masks for UAP mode. + * + * MAC: HTC HE, OMI control (no UL OFDMA). + * PHY: 40 MHz, LDPC, NDP 4xLTF, STBC, Doppler, DCM, + * SU BF/BFe, STS/sounding dims, extended range, + * PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_2G_MAC_CAP0_MASK 0x00 +#define UAP_HE_2G_MAC_CAP1_MASK 0x00 +#define UAP_HE_2G_MAC_CAP2_MASK 0x00 +#define UAP_HE_2G_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_2G_MAC_CAP4_MASK 0x00 +#define UAP_HE_2G_MAC_CAP5_MASK 0x00 + +#define UAP_HE_2G_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G +#define UAP_HE_2G_PHY_CAP1_MASK IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD +#define UAP_HE_2G_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_2G_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_2G_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_2G_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_2G_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_2G_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_2G_PHY_CAP8_MASK 0x00 +#define UAP_HE_2G_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_2G_PHY_CAP10_MASK 0x00 +#define HE_CAP_FIX_SIZE 22 + +static void +nxpwifi_update_11ax_ie(u8 band, + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg) +{ + if (band == BAND_A) { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_PHY_CAP10_MASK; + } else { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_2G_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_2G_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_2G_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_2G_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_2G_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_2G_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_2G_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_2G_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_2G_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_2G_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_2G_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_2G_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_2G_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_2G_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_2G_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_2G_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_2G_PHY_CAP10_MASK; + } +} + +static void +nxpwifi_setup_he_caps(struct nxpwifi_private *priv, + struct ieee80211_supported_band *band) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct ieee80211_sband_iftype_data *iftype_data; + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 hw_he_cap_len; + u8 extra_mcs_size; + int ppe_threshold_len; + + if (band->band == NL80211_BAND_5GHZ) { + hw_he_cap_len = adapter->hw_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_A, &he_cap_cfg); + } else { + hw_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_2g_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_G, &he_cap_cfg); + } + + if (!hw_he_cap_len) + return; + + iftype_data = kmalloc_obj(*iftype_data, GFP_KERNEL); + if (!iftype_data) + return; + memset(iftype_data, 0, sizeof(*iftype_data)); + + iftype_data->types_mask = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + iftype_data->he_cap.has_he = true; + + memcpy(iftype_data->he_cap.he_cap_elem.mac_cap_info, + he_cap_cfg.cap_elem.mac_cap_info, + sizeof(he_cap_cfg.cap_elem.mac_cap_info)); + memcpy(iftype_data->he_cap.he_cap_elem.phy_cap_info, + he_cap_cfg.cap_elem.phy_cap_info, + sizeof(he_cap_cfg.cap_elem.phy_cap_info)); + memset(&iftype_data->he_cap.he_mcs_nss_supp, + 0xff, + sizeof(iftype_data->he_cap.he_mcs_nss_supp)); + memcpy(&iftype_data->he_cap.he_mcs_nss_supp, + he_cap_cfg.he_txrx_mcs_support, + sizeof(he_cap_cfg.he_txrx_mcs_support)); + + extra_mcs_size = 0; + /* Add 160 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(3)) + extra_mcs_size += 4; + /* Add 80+80 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(4)) + extra_mcs_size += 4; + if (extra_mcs_size) + memcpy((u8 *)&iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_160, + he_cap_cfg.val, extra_mcs_size); + + /* Parse PPE thresholds when present */ + ppe_threshold_len = he_cap_cfg.len - HE_CAP_FIX_SIZE - extra_mcs_size; + if (he_cap_cfg.cap_elem.phy_cap_info[6] & BIT(7) && ppe_threshold_len) { + memcpy(iftype_data->he_cap.ppe_thres, + &he_cap_cfg.val[extra_mcs_size], + ppe_threshold_len); + } else { + iftype_data->he_cap.he_cap_elem.phy_cap_info[6] &= BIT(7); + } + + _ieee80211_set_sband_iftype_data(band, iftype_data, 1); +} + +/* create a new virtual interface with the given name and name assign type */ +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct net_device *dev; + void *mdev_priv; + int ret; + + if (!adapter) + return ERR_PTR(-EFAULT); + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + if (adapter->curr_iface_comb.sta_intf == + adapter->iface_limit.sta_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple sta ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_STA); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_STATION; + + if (type == NL80211_IFTYPE_UNSPECIFIED) + priv->bss_mode = NL80211_IFTYPE_STATION; + else + priv->bss_mode = type; + + priv->bss_type = NXPWIFI_BSS_TYPE_STA; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + + break; + case NL80211_IFTYPE_AP: + if (adapter->curr_iface_comb.uap_intf == + adapter->iface_limit.uap_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple AP ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_AP; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + case NL80211_IFTYPE_MONITOR: + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_MONITOR; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + default: + nxpwifi_dbg(adapter, ERROR, "type not supported\n"); + return ERR_PTR(-EINVAL); + } + + dev = alloc_netdev_mqs(sizeof(struct nxpwifi_private *), name, + name_assign_type, ether_setup, + IEEE80211_NUM_ACS, 1); + if (!dev) { + nxpwifi_dbg(adapter, ERROR, + "no memory available for netdevice\n"); + ret = -ENOMEM; + goto err_alloc_netdev; + } + + nxpwifi_init_priv_params(priv, dev); + + priv->netdev = dev; + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + if (type != NL80211_IFTYPE_MONITOR) { + ret = nxpwifi_set_bss_mode(priv); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: err_set_bss_mode\n", __func__); + goto err_set_bss_mode; + } + } + + ret = nxpwifi_sta_init_cmd(priv, false, false); + if (ret) + goto err_sta_init; + + dev_net_set(dev, wiphy_net(wiphy)); + dev->ieee80211_ptr = &priv->wdev; + dev->ieee80211_ptr->iftype = priv->bss_mode; + SET_NETDEV_DEV(dev, wiphy_dev(wiphy)); + + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->watchdog_timeo = NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT; + dev->needed_headroom = NXPWIFI_MIN_DATA_HEADER_LEN; + dev->ethtool_ops = &nxpwifi_ethtool_ops; + + mdev_priv = netdev_priv(dev); + *((unsigned long *)mdev_priv) = (unsigned long)priv; + + if (type == NL80211_IFTYPE_MONITOR) + dev->type = ARPHRD_IEEE80211_RADIOTAP; + + SET_NETDEV_DEV(dev, adapter->dev); + + wiphy_work_init(&priv->reset_conn_state_work, nxpwifi_reset_conn_state_work); + + wiphy_delayed_work_init(&priv->dfs_cac_work, nxpwifi_dfs_cac_work); + + wiphy_delayed_work_init(&priv->dfs_chan_sw_work, nxpwifi_dfs_chan_sw_work); + + /* Register network device */ + if (cfg80211_register_netdevice(dev)) { + nxpwifi_dbg(adapter, ERROR, "cannot register network device\n"); + ret = -EFAULT; + goto err_reg_netdev; + } + + nxpwifi_dbg(adapter, INFO, + "info: %s: NXP 802.11 Adapter\n", dev->name); + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_init(priv); +#endif + + update_vif_type_counter(adapter, type, 1); + + return &priv->wdev; + +err_reg_netdev: + free_netdev(dev); + priv->netdev = NULL; +err_sta_init: +err_set_bss_mode: +err_alloc_netdev: + memset(&priv->wdev, 0, sizeof(priv->wdev)); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(nxpwifi_add_virtual_intf); + +/* del_virtual_intf: remove the virtual interface determined by dev */ +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_remove(priv); +#endif + if (priv->bss_mode == NL80211_IFTYPE_MONITOR) { + struct nxpwifi_802_11_net_monitor netmon_cfg; + + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + nxpwifi_config_monitor_mode(priv, &netmon_cfg); + } + + if (priv->sched_scanning) + priv->sched_scanning = false; + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + + netif_carrier_off(priv->netdev); + + if (wdev->netdev->reg_state == NETREG_REGISTERED) + cfg80211_unregister_netdevice(wdev->netdev); + + /* Clear the priv in adapter */ + priv->netdev = NULL; + + update_vif_type_counter(adapter, priv->bss_mode, -1); + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + kfree(priv->hist_data); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_del_virtual_intf); + +static bool +nxpwifi_is_pattern_supported(struct cfg80211_pkt_pattern *pat, s8 *byte_seq, + u8 max_byte_seq) +{ + int j, k, valid_byte_cnt = 0; + bool dont_care_byte = false; + + for (j = 0; j < DIV_ROUND_UP(pat->pattern_len, 8); j++) { + for (k = 0; k < 8; k++) { + if (pat->mask[j] & 1 << k) { + memcpy(byte_seq + valid_byte_cnt, + &pat->pattern[j * 8 + k], 1); + valid_byte_cnt++; + if (dont_care_byte) + return false; + } else { + if (valid_byte_cnt) + dont_care_byte = true; + } + + /* wildcard bytes record as the offset before the valid byte */ + if (!valid_byte_cnt && !dont_care_byte) + pat->pkt_offset++; + + if (valid_byte_cnt > max_byte_seq) + return false; + } + } + + byte_seq[max_byte_seq] = valid_byte_cnt; + + return true; +} + +#ifdef CONFIG_PM +static void nxpwifi_set_auto_arp_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry) +{ + int i, filt_num = 0, num_ipv4 = 0; + struct in_device *in_dev; + struct in_ifaddr *ifa; + __be32 ips[NXPWIFI_MAX_SUPPORTED_IPADDR]; + struct nxpwifi_adapter *adapter = priv->adapter; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_AUTO_ARP; + + /* Enable ARP offload feature */ + memset(ips, 0, sizeof(ips)); + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->netdev) { + in_dev = __in_dev_get_rtnl(adapter->priv[i]->netdev); + if (!in_dev) + continue; + ifa = rtnl_dereference(in_dev->ifa_list); + if (!ifa || !ifa->ifa_local) + continue; + ips[i] = ifa->ifa_local; + num_ipv4++; + } + } + + for (i = 0; i < num_ipv4; i++) { + if (!ips[i]) + continue; + mef_entry->filter[filt_num].repeat = 1; + memcpy(mef_entry->filter[filt_num].byte_seq, + (u8 *)&ips[i], sizeof(ips[i])); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + sizeof(ips[i]); + mef_entry->filter[filt_num].offset = 46; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) { + mef_entry->filter[filt_num].filt_action = + TYPE_OR; + } + filt_num++; + } + + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].byte_seq[0] = 0x08; + mef_entry->filter[filt_num].byte_seq[1] = 0x06; + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = 2; + mef_entry->filter[filt_num].offset = 20; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_AND; +} + +static int nxpwifi_set_wowlan_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_ds_mef_cfg *mef_cfg, + struct nxpwifi_mef_entry *mef_entry, + struct cfg80211_wowlan *wowlan) +{ + int i, filt_num = 0, ret = 0; + bool first_pat = true; + u8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_ALLOW_AND_WAKEUP_HOST; + + for (i = 0; i < wowlan->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported + (&wowlan->patterns[i], byte_seq, + NXPWIFI_MEF_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!wowlan->patterns[i].pkt_offset) { + if (is_unicast_ether_addr(byte_seq) && + byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 1) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + continue; + } else if (is_broadcast_ether_addr(byte_seq)) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_BROADCAST; + continue; + } else if ((!memcmp(byte_seq, "\x33\x33", 2) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 2)) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 3))) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_MULTICAST; + continue; + } + } + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].offset = + wowlan->patterns[i].pkt_offset; + memcpy(mef_entry->filter[filt_num].byte_seq, byte_seq, + sizeof(byte_seq)); + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + + if (first_pat) { + first_pat = false; + nxpwifi_dbg(priv->adapter, INFO, "Wake on patterns\n"); + } else { + mef_entry->filter[filt_num].filt_action = TYPE_AND; + } + + filt_num++; + } + + if (wowlan->magic_pkt) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 28; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) + mef_entry->filter[filt_num].filt_action = TYPE_OR; + + filt_num++; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 56; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_OR; + nxpwifi_dbg(priv->adapter, INFO, "Wake on magic packet\n"); + } + return ret; +} + +static int nxpwifi_set_mef_filter(struct nxpwifi_private *priv, + struct cfg80211_wowlan *wowlan) +{ + int ret = 0, num_entries = 1; + struct nxpwifi_ds_mef_cfg mef_cfg; + struct nxpwifi_mef_entry *mef_entry; + + if (wowlan->n_patterns || wowlan->magic_pkt) + num_entries++; + + mef_entry = kzalloc_objs(*mef_entry, num_entries, GFP_KERNEL); + if (!mef_entry) + return -ENOMEM; + + memset(&mef_cfg, 0, sizeof(mef_cfg)); + mef_cfg.criteria |= NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST; + mef_cfg.num_entries = num_entries; + mef_cfg.mef_entry = mef_entry; + + nxpwifi_set_auto_arp_mef_entry(priv, &mef_entry[0]); + + if (wowlan->n_patterns || wowlan->magic_pkt) { + ret = nxpwifi_set_wowlan_mef_entry(priv, &mef_cfg, + &mef_entry[1], wowlan); + if (ret) + goto done; + } + + if (!mef_cfg.criteria) + mef_cfg.criteria = NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST | + NXPWIFI_CRITERIA_MULTICAST; + + ret = nxpwifi_mef_cfg(priv, &mef_cfg); + +done: + kfree(mef_entry); + return ret; +} + +static int nxpwifi_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowlan) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_ds_hs_cfg hs_cfg; + int i, ret = 0, retry_num = 10; + struct nxpwifi_private *priv; + struct nxpwifi_private *sta_priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + adapter->wowlan_enabled = false; + + sta_priv->scan_aborting = true; + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_abort_cac(priv); + } + + nxpwifi_cancel_all_pending_cmd(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_detach(priv->netdev); + } + + for (i = 0; i < retry_num; i++) { + if (!nxpwifi_wmm_lists_empty(adapter) || + !nxpwifi_bypass_txlist_empty(adapter) || + !skb_queue_empty(&adapter->tx_data_q)) + usleep_range(10000, 15000); + else + break; + } + + if (!wowlan) { + nxpwifi_dbg(adapter, INFO, + "None of the WOWLAN triggers enabled\n"); + ret = 0; + goto done; + } + + if (!sta_priv->media_connected && !wowlan->nd_config) { + nxpwifi_dbg(adapter, ERROR, + "Can not configure WOWLAN in disconnected state\n"); + ret = 0; + goto done; + } + + ret = nxpwifi_set_mef_filter(sta_priv, wowlan); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "Failed to set MEF filter\n"); + goto done; + } + + memset(&hs_cfg, 0, sizeof(hs_cfg)); + hs_cfg.conditions = le32_to_cpu(adapter->hs_cfg.conditions); + + if (wowlan->nd_config) { + nxpwifi_dbg(adapter, INFO, "Wake on net detect\n"); + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_cfg80211_sched_scan_start(wiphy, sta_priv->netdev, + wowlan->nd_config); + } + + if (wowlan->disconnect) { + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_dbg(sta_priv->adapter, INFO, "Wake on device disconnect\n"); + } + + hs_cfg.is_invoke_hostcmd = false; + hs_cfg.gpio = adapter->hs_cfg.gpio; + hs_cfg.gap = adapter->hs_cfg.gap; + ret = nxpwifi_set_hs_params(sta_priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hs_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, "Failed to set HS params\n"); + + adapter->wowlan_enabled = true; + +done: + sta_priv->scan_aborting = false; + return ret; +} + +static int nxpwifi_cfg80211_resume(struct wiphy *wiphy) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_ds_wakeup_reason wakeup_reason; + struct cfg80211_wowlan_wakeup wakeup_report; + int i; + bool report_wakeup_reason = true; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_attach(priv->netdev); + } + + if (!wiphy->wowlan_config) + goto done; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + nxpwifi_get_wakeup_reason(priv, HOST_ACT_GEN_GET, NXPWIFI_SYNC_CMD, + &wakeup_reason); + memset(&wakeup_report, 0, sizeof(struct cfg80211_wowlan_wakeup)); + + wakeup_report.pattern_idx = -1; + + switch (wakeup_reason.hs_wakeup_reason) { + case NO_HSWAKEUP_REASON: + break; + case BCAST_DATA_MATCHED: + break; + case MCAST_DATA_MATCHED: + break; + case UCAST_DATA_MATCHED: + break; + case MASKTABLE_EVENT_MATCHED: + break; + case NON_MASKABLE_EVENT_MATCHED: + if (wiphy->wowlan_config->disconnect) + wakeup_report.disconnect = true; + if (wiphy->wowlan_config->nd_config) + wakeup_report.net_detect = adapter->nd_info; + break; + case NON_MASKABLE_CONDITION_MATCHED: + break; + case MAGIC_PATTERN_MATCHED: + if (wiphy->wowlan_config->magic_pkt) + wakeup_report.magic_pkt = true; + if (wiphy->wowlan_config->n_patterns) + wakeup_report.pattern_idx = 1; + break; + case GTK_REKEY_FAILURE: + if (wiphy->wowlan_config->gtk_rekey_failure) + wakeup_report.gtk_rekey_failure = true; + break; + default: + report_wakeup_reason = false; + break; + } + + if (report_wakeup_reason) + cfg80211_report_wowlan_wakeup(&priv->wdev, &wakeup_report, + GFP_KERNEL); + +done: + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + return 0; +} + +static void nxpwifi_cfg80211_set_wakeup(struct wiphy *wiphy, + bool enabled) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + device_set_wakeup_enable(adapter->dev, enabled); +} +#endif + +static int nxpwifi_get_coalesce_pkt_type(u8 *byte_seq) +{ + if ((byte_seq[0] & 0x01) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 1) + return PACKET_TYPE_UNICAST; + else if (is_broadcast_ether_addr(byte_seq)) + return PACKET_TYPE_BROADCAST; + else if ((!memcmp(byte_seq, "\x33\x33", 2) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 2) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 3)) + return PACKET_TYPE_MULTICAST; + + return 0; +} + +static int +nxpwifi_fill_coalesce_rule_info(struct nxpwifi_private *priv, + struct cfg80211_coalesce_rules *crule, + struct nxpwifi_coalesce_rule *mrule) +{ + u8 byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ + 1]; + struct filt_field_param *param; + int i; + + mrule->max_coalescing_delay = crule->delay; + + param = mrule->params; + + for (i = 0; i < crule->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported(&crule->patterns[i], + byte_seq, + NXPWIFI_COALESCE_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!crule->patterns[i].pkt_offset) { + u8 pkt_type; + + pkt_type = nxpwifi_get_coalesce_pkt_type(byte_seq); + if (pkt_type && mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Multiple packet types not allowed\n"); + return -EOPNOTSUPP; + } else if (pkt_type) { + mrule->pkt_type = pkt_type; + continue; + } + } + + if (crule->condition == NL80211_COALESCE_CONDITION_MATCH) + param->operation = RECV_FILTER_MATCH_TYPE_EQ; + else + param->operation = RECV_FILTER_MATCH_TYPE_NE; + + param->operand_len = byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ]; + memcpy(param->operand_byte_stream, byte_seq, + param->operand_len); + param->offset = crule->patterns[i].pkt_offset; + param++; + + mrule->num_of_fields++; + } + + if (!mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Packet type can not be determined\n"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int nxpwifi_cfg80211_set_coalesce(struct wiphy *wiphy, + struct cfg80211_coalesce *coalesce) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + int i, ret; + struct nxpwifi_ds_coalesce_cfg coalesce_cfg; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + memset(&coalesce_cfg, 0, sizeof(coalesce_cfg)); + + if (!coalesce) + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); + + coalesce_cfg.num_of_rules = coalesce->n_rules; + for (i = 0; i < coalesce->n_rules; i++) { + ret = nxpwifi_fill_coalesce_rule_info(priv, &coalesce->rules[i], + &coalesce_cfg.rule[i]); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Recheck the patterns provided for rule %d\n", + i + 1); + return ret; + } + } + + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); +} + +static int +nxpwifi_cfg80211_uap_add_station(struct nxpwifi_private *priv, const u8 *mac, + struct station_parameters *params) +{ + struct nxpwifi_sta_info add_sta; + int ret; + + memcpy(add_sta.peer_mac, mac, ETH_ALEN); + add_sta.params = params; + + ret = nxpwifi_add_new_station(priv, &add_sta); + + return ret; +} + +static int +nxpwifi_cfg80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret = -EOPNOTSUPP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_cfg80211_uap_add_station(priv, mac, params); + + return ret; +} + +static int +nxpwifi_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int chsw_msec; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan in process...\n"); + return -EBUSY; + } + + if (priv->wdev.links[0].cac_started) + return -EBUSY; + + if (cfg80211_chandef_identical(¶ms->chandef, + &priv->dfs_chandef)) + return -EINVAL; + + if (params->block_tx) { + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + priv->uap_stop_tx = true; + } + + ret = nxpwifi_del_mgmt_ies(priv); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon_csa); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + goto done; + } + + memcpy(&priv->dfs_chandef, ¶ms->chandef, sizeof(priv->dfs_chandef)); + memcpy(&priv->ap_update_info.beacon, ¶ms->beacon_after, + sizeof(priv->ap_update_info.beacon)); + + chsw_msec = max(params->count * priv->bss_cfg.beacon_period, 100); + + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_chan_sw_work, + msecs_to_jiffies(chsw_msec)); + +done: + return ret; +} + +static int nxpwifi_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_bssdescriptor *curr_bss; + struct ieee80211_channel *chan; + enum nl80211_channel_type chan_type; + enum nl80211_band band; + int freq; + int ret = -ENODATA; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + cfg80211_chandef_valid(&priv->bss_chandef)) { + *chandef = priv->bss_chandef; + ret = 0; + } else if (priv->media_connected) { + curr_bss = &priv->curr_bss_params.bss_descriptor; + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + freq = ieee80211_channel_to_frequency(curr_bss->channel, band); + chan = ieee80211_get_channel(wiphy, freq); + + if (priv->ht_param_present) { + chan_type = nxpwifi_get_chan_type(priv); + cfg80211_chandef_create(chandef, chan, chan_type); + } else { + cfg80211_chandef_create(chandef, chan, + NL80211_CHAN_NO_HT); + } + ret = 0; + } + + return ret; +} + +#ifdef CONFIG_NL80211_TESTMODE + +enum nxpwifi_tm_attr { + __NXPWIFI_TM_ATTR_INVALID = 0, + NXPWIFI_TM_ATTR_CMD = 1, + NXPWIFI_TM_ATTR_DATA = 2, + + /* keep last */ + __NXPWIFI_TM_ATTR_AFTER_LAST, + NXPWIFI_TM_ATTR_MAX = __NXPWIFI_TM_ATTR_AFTER_LAST - 1, +}; + +static const struct nla_policy nxpwifi_tm_policy[NXPWIFI_TM_ATTR_MAX + 1] = { + [NXPWIFI_TM_ATTR_CMD] = { .type = NLA_U32 }, + [NXPWIFI_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = NXPWIFI_SIZE_OF_CMD_BUFFER }, +}; + +enum nxpwifi_tm_command { + NXPWIFI_TM_CMD_HOSTCMD = 0, +}; + +static int nxpwifi_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, + void *data, int len) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_ds_misc_cmd *hostcmd; + struct nlattr *tb[NXPWIFI_TM_ATTR_MAX + 1]; + struct sk_buff *skb; + int err; + + if (!priv) + return -EINVAL; + + err = nla_parse_deprecated(tb, NXPWIFI_TM_ATTR_MAX, data, len, + nxpwifi_tm_policy, NULL); + if (err) + return err; + + if (!tb[NXPWIFI_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[NXPWIFI_TM_ATTR_CMD])) { + case NXPWIFI_TM_CMD_HOSTCMD: + if (!tb[NXPWIFI_TM_ATTR_DATA]) + return -EINVAL; + + hostcmd = kzalloc_obj(*hostcmd, GFP_KERNEL); + if (!hostcmd) + return -ENOMEM; + + hostcmd->len = nla_len(tb[NXPWIFI_TM_ATTR_DATA]); + memcpy(hostcmd->cmd, nla_data(tb[NXPWIFI_TM_ATTR_DATA]), + hostcmd->len); + + if (nxpwifi_hostcmd(priv, hostcmd)) { + nxpwifi_dbg(priv->adapter, ERROR, "Failed to process hostcmd\n"); + kfree(hostcmd); + return -EFAULT; + } + + /* process hostcmd response*/ + skb = cfg80211_testmode_alloc_reply_skb(wiphy, hostcmd->len); + if (!skb) { + kfree(hostcmd); + return -ENOMEM; + } + err = nla_put(skb, NXPWIFI_TM_ATTR_DATA, + hostcmd->len, hostcmd->cmd); + if (err) { + kfree(hostcmd); + kfree_skb(skb); + return -EMSGSIZE; + } + + err = cfg80211_testmode_reply(skb); + kfree(hostcmd); + return err; + default: + return -EOPNOTSUPP; + } +} +#endif + +static int +nxpwifi_cfg80211_start_radar_detection(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef, + u32 cac_time_ms, int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_radar_params radar_params; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan already in process...\n"); + return -EBUSY; + } + + if (!nxpwifi_is_11h_active(priv)) { + nxpwifi_dbg(priv->adapter, INFO, + "Enable 11h extensions in FW\n"); + if (nxpwifi_11h_activate(priv, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to activate 11h extensions!!"); + return -EPERM; + } + priv->state_11h.is_11h_active = true; + } + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = cac_time_ms; + + memcpy(&priv->dfs_chandef, chandef, sizeof(priv->dfs_chandef)); + + ret = nxpwifi_chan_report_request(priv, &radar_params); + if (!ret) + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_cac_work, + msecs_to_jiffies(cac_time_ms)); + + return ret; +} + +static int +nxpwifi_cfg80211_change_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, + struct station_parameters *params) +{ + return 0; +} + +static int +nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_auth_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb; + u16 pkt_len, auth_alg; + int ret; + struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + u8 trans = 1, status_code = 0; + u8 *varptr = NULL; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(adapter, ERROR, "Interface role is AP\n"); + return -EINVAL; + } + + if (priv->wdev.iftype != NL80211_IFTYPE_STATION) { + nxpwifi_dbg(adapter, ERROR, + "Interface type is not correct (type %d)\n", + priv->wdev.iftype); + return -EINVAL; + } + + if (!nxpwifi_is_channel_setting_allowable(priv, req->bss->channel)) + return -EOPNOTSUPP; + + if (priv->auth_alg != WLAN_AUTH_SAE && + (priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + nxpwifi_dbg(adapter, ERROR, "Pending auth on going\n"); + return -EBUSY; + } + + if (!priv->host_mlme_reg) { + priv->host_mlme_reg = true; + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + } + + switch (req->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + auth_alg = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + auth_alg = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_FT: + auth_alg = WLAN_AUTH_FT; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + auth_alg = WLAN_AUTH_LEAP; + break; + case NL80211_AUTHTYPE_SAE: + auth_alg = WLAN_AUTH_SAE; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unsupported auth type=%d\n", req->auth_type); + return -EOPNOTSUPP; + } + + if (!(priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, + req->bss->channel, + AUTH_TX_DEFAULT_WAIT_TIME); + + if (!ret) { + priv->roc_cfg.cookie = + nxpwifi_roc_cookie(adapter); + priv->roc_cfg.chan = *req->bss->channel; + } else { + return -EPERM; + } + } + + priv->sec_info.authentication_mode = auth_alg; + + nxpwifi_cancel_scan(adapter); + + pkt_len = (u16)req->ie_len + req->auth_data_len + + NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + + if (req->auth_data_len >= 4) + pkt_len -= 4; + + mgmt = kzalloc(pkt_len, GFP_KERNEL); + + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); + memcpy(mgmt->sa, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->bssid, req->bss->bssid, ETH_ALEN); + mgmt->frame_control = + cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_AUTH); + + if (req->auth_data_len >= 4) { + if (req->auth_type == NL80211_AUTHTYPE_SAE) { + __le16 *pos = (__le16 *)req->auth_data; + + trans = le16_to_cpu(pos[0]); + status_code = le16_to_cpu(pos[1]); + } + memcpy((u8 *)(&mgmt->u.auth.variable), req->auth_data + 4, + req->auth_data_len - 4); + varptr = (u8 *)&mgmt->u.auth.variable + + (req->auth_data_len - 4); + } + + mgmt->u.auth.auth_alg = cpu_to_le16(auth_alg); + mgmt->u.auth.auth_transaction = cpu_to_le16(trans); + mgmt->u.auth.status_code = cpu_to_le16(status_code); + + if (req->ie && req->ie_len) { + if (!varptr) + varptr = (u8 *)&mgmt->u.auth.variable; + memcpy((u8 *)varptr, req->ie, req->ie_len); + } + + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + kfree(mgmt); + priv->auth_flag = HOST_MLME_AUTH_PENDING; + priv->auth_alg = auth_alg; + skb->priority = WMM_HIGHEST_PRIORITY; + __net_timestamp(skb); + + nxpwifi_dbg(adapter, MSG, + "auth: send authentication to %pM\n", req->bss->bssid); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +static int +nxpwifi_cfg80211_associate(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_assoc_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct cfg80211_ssid req_ssid; + const u8 *ssid_ie; + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) { + nxpwifi_dbg(adapter, ERROR, + "%s: reject infra assoc request in non-STA role\n", + dev->name); + return -EINVAL; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: Ignore association.\t" + "Card removed or FW in bad state\n", + dev->name); + return -EPERM; + } + + if (priv->auth_alg == WLAN_AUTH_SAE) + priv->auth_flag = HOST_MLME_AUTH_DONE; + + if (priv->auth_flag && !(priv->auth_flag & HOST_MLME_AUTH_DONE)) + return -EBUSY; + + if (priv->roc_cfg.cookie) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + if (!ret) + memset(&priv->roc_cfg, 0, + sizeof(struct nxpwifi_roc_cfg)); + else + return ret; + } + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + memset(&req_ssid, 0, sizeof(struct cfg80211_ssid)); + rcu_read_lock(); + ssid_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID); + + if (!ssid_ie) + goto ssid_err; + + req_ssid.ssid_len = ssid_ie[1]; + if (req_ssid.ssid_len > IEEE80211_MAX_SSID_LEN) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + + memcpy(req_ssid.ssid, ssid_ie + 2, req_ssid.ssid_len); + if (!req_ssid.ssid_len || req_ssid.ssid[0] < 0x20) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + rcu_read_unlock(); + + /* + * As this is new association, clear locally stored + * keys and security related flags + */ + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wep_key_curr_index = 0; + priv->sec_info.encryption_mode = 0; + priv->sec_info.is_authtype_auto = 0; + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, 0, NULL, 1); + + if (req->crypto.n_ciphers_pairwise) + priv->sec_info.encryption_mode = + req->crypto.ciphers_pairwise[0]; + + if (req->crypto.cipher_group) + priv->sec_info.encryption_mode = req->crypto.cipher_group; + + if (req->ie) + ret = nxpwifi_set_gen_ie(priv, req->ie, req->ie_len); + + memcpy(priv->cfg_bssid, req->bss->bssid, ETH_ALEN); + + nxpwifi_dbg(adapter, MSG, + "assoc: send association to %pM\n", req->bss->bssid); + + cfg80211_ref_bss(adapter->wiphy, req->bss); + + ret = nxpwifi_bss_start(priv, req->bss, &req_ssid); + + if (ret) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + eth_zero_addr(priv->cfg_bssid); + } + + if (ret >= 0) { + if (priv->assoc_rsp_size) { + priv->req_bss = req->bss; + adapter->assoc_resp_received = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + ret = 0; + } + + cfg80211_put_bss(priv->adapter->wiphy, req->bss); + + return ret; + +ssid_err: + + rcu_read_unlock(); + return -EINVAL; +} + +static int +nxpwifi_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + ret = nxpwifi_deauthenticate(priv, NULL); + if (!ret) { + eth_zero_addr(priv->cfg_bssid); + priv->hs2_enabled = false; + } + + return ret; +} + +static int +nxpwifi_cfg80211_deauthenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_deauth_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_disassociate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_disassoc_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 cookie) +{ + /* + * hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, + * it requires monitor-mode support (which mwifiex doesn't support). + * Provide fake probe_peer support to work around this. + */ + return -EOPNOTSUPP; +} + +/* station cfg80211 operations */ +static const struct cfg80211_ops nxpwifi_cfg80211_ops = { + .add_virtual_intf = nxpwifi_add_virtual_intf, + .del_virtual_intf = nxpwifi_del_virtual_intf, + .change_virtual_intf = nxpwifi_cfg80211_change_virtual_intf, + .scan = nxpwifi_cfg80211_scan, + .auth = nxpwifi_cfg80211_authenticate, + .assoc = nxpwifi_cfg80211_associate, + .deauth = nxpwifi_cfg80211_deauthenticate, + .disassoc = nxpwifi_cfg80211_disassociate, + .probe_peer = nxpwifi_cfg80211_probe_peer, + .get_station = nxpwifi_cfg80211_get_station, + .dump_station = nxpwifi_cfg80211_dump_station, + .dump_survey = nxpwifi_cfg80211_dump_survey, + .set_wiphy_params = nxpwifi_cfg80211_set_wiphy_params, + .add_key = nxpwifi_cfg80211_add_key, + .del_key = nxpwifi_cfg80211_del_key, + .set_default_mgmt_key = nxpwifi_cfg80211_set_default_mgmt_key, + .mgmt_tx = nxpwifi_cfg80211_mgmt_tx, + .update_mgmt_frame_registrations = + nxpwifi_cfg80211_update_mgmt_frame_registrations, + .remain_on_channel = nxpwifi_cfg80211_remain_on_channel, + .cancel_remain_on_channel = nxpwifi_cfg80211_cancel_remain_on_channel, + .set_default_key = nxpwifi_cfg80211_set_default_key, + .set_power_mgmt = nxpwifi_cfg80211_set_power_mgmt, + .set_tx_power = nxpwifi_cfg80211_set_tx_power, + .get_tx_power = nxpwifi_cfg80211_get_tx_power, + .set_bitrate_mask = nxpwifi_cfg80211_set_bitrate_mask, + .start_ap = nxpwifi_cfg80211_start_ap, + .stop_ap = nxpwifi_cfg80211_stop_ap, + .change_beacon = nxpwifi_cfg80211_change_beacon, + .set_cqm_rssi_config = nxpwifi_cfg80211_set_cqm_rssi_config, + .set_antenna = nxpwifi_cfg80211_set_antenna, + .get_antenna = nxpwifi_cfg80211_get_antenna, + .del_station = nxpwifi_cfg80211_del_station, + .sched_scan_start = nxpwifi_cfg80211_sched_scan_start, + .sched_scan_stop = nxpwifi_cfg80211_sched_scan_stop, + .change_station = nxpwifi_cfg80211_change_station, +#ifdef CONFIG_PM + .suspend = nxpwifi_cfg80211_suspend, + .resume = nxpwifi_cfg80211_resume, + .set_wakeup = nxpwifi_cfg80211_set_wakeup, +#endif + .set_coalesce = nxpwifi_cfg80211_set_coalesce, + .add_station = nxpwifi_cfg80211_add_station, + CFG80211_TESTMODE_CMD(nxpwifi_tm_cmd) + .get_channel = nxpwifi_cfg80211_get_channel, + .start_radar_detection = nxpwifi_cfg80211_start_radar_detection, + .channel_switch = nxpwifi_cfg80211_channel_switch, +}; + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support nxpwifi_wowlan_support = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; + +static const struct wiphy_wowlan_support nxpwifi_wowlan_support_no_gtk = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; +#endif + +static const struct wiphy_coalesce_support nxpwifi_coalesce_support = { + .n_rules = NXPWIFI_COALESCE_MAX_RULES, + .max_delay = NXPWIFI_MAX_COALESCING_DELAY, + .n_patterns = NXPWIFI_COALESCE_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, +}; + +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) +{ + u32 n_channels_bg, n_channels_a = 0; + + n_channels_bg = nxpwifi_band_2ghz.n_channels; + + if (adapter->fw_bands & BAND_A) + n_channels_a = nxpwifi_band_5ghz.n_channels; + + /* + * allocate twice the number total channels, since the driver issues an + * 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)); + + if (!adapter->chan_stats) + return -ENOMEM; + + return 0; +} + +/* + * Register the device with cfg80211. + * + * Create and initialize the wiphy, fill in defaults and handlers, + * then register it with the cfg80211 subsystem. + */ +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter) +{ + int ret; + void *wdev_priv; + struct wiphy *wiphy; + struct nxpwifi_private *priv = adapter->priv[NXPWIFI_BSS_TYPE_STA]; + struct ieee80211_sta_ht_cap *ht_cap; + struct ieee80211_sta_vht_cap *vht_cap; + u8 *country_code; + u32 thr, retry; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&nxpwifi_cfg80211_ops, + sizeof(struct nxpwifi_adapter *)); + if (!wiphy) { + nxpwifi_dbg(adapter, ERROR, + "%s: creating new wiphy\n", __func__); + return -ENOMEM; + } + + wiphy->max_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + + wiphy->mgmt_stypes = nxpwifi_mgmt_stypes; + wiphy->max_remain_on_channel_duration = 5000; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR); + + wiphy->max_num_akm_suites = CFG80211_MAX_NUM_AKM_SUITES; + + wiphy->bands[NL80211_BAND_2GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_2ghz, + sizeof(nxpwifi_band_2ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_2GHZ]) { + ret = -ENOMEM; + goto err; + } + + if (adapter->fw_bands & BAND_A) { + wiphy->bands[NL80211_BAND_5GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_5ghz, + sizeof(nxpwifi_band_5ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_5GHZ]) { + ret = -ENOMEM; + goto err; + } + } else { + wiphy->bands[NL80211_BAND_5GHZ] = NULL; + } + + ht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_2GHZ]); + + if (adapter->fw_bands & BAND_A) { + ht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_5GHZ]); + } + + if (adapter->is_hw_11ac_capable) + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta_vht; + else + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta; + wiphy->n_iface_combinations = 1; + + wiphy->max_ap_assoc_sta = adapter->max_sta_conn; + + /* Initialize cipher suits */ + wiphy->cipher_suites = nxpwifi_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(nxpwifi_cipher_suites); + + if (adapter->regd) { + wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + wiphy_apply_custom_regulatory(wiphy, adapter->regd); + } + + ether_addr_copy(wiphy->perm_addr, adapter->perm_addr); + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_REPORTS_OBSS | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_HAS_CHANNEL_SWITCH | + WIPHY_FLAG_NETNS_OK | + WIPHY_FLAG_PS_ON_BY_DEFAULT; + wiphy->max_num_csa_counters = NXPWIFI_MAX_CSA_COUNTERS; + +#ifdef CONFIG_PM + if (ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + wiphy->wowlan = &nxpwifi_wowlan_support; + else + wiphy->wowlan = &nxpwifi_wowlan_support_no_gtk; +#endif + + wiphy->coalesce = &nxpwifi_coalesce_support; + + wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2; + + wiphy->max_sched_scan_reqs = 1; + wiphy->max_sched_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_sched_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + wiphy->max_match_sets = NXPWIFI_MAX_SSID_LIST_LENGTH; + + wiphy->available_antennas_tx = BIT(adapter->number_of_antenna) - 1; + wiphy->available_antennas_rx = BIT(adapter->number_of_antenna) - 1; + + wiphy->features |= NL80211_FEATURE_SAE | + NL80211_FEATURE_INACTIVITY_TIMER | + NL80211_FEATURE_LOW_PRIORITY_SCAN | + NL80211_FEATURE_NEED_OBSS_SCAN; + + if (ISSUPP_RANDOM_MAC(adapter->fw_cap_info)) + wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + wiphy->features |= NL80211_FEATURE_SK_TX_STATUS; + + /* Reserve space for nxpwifi specific private data for BSS */ + wiphy->bss_priv_size = sizeof(struct nxpwifi_bss_priv); + + wiphy->reg_notifier = nxpwifi_reg_notifier; + + /* Set struct nxpwifi_adapter pointer in wiphy_priv */ + wdev_priv = wiphy_priv(wiphy); + *(unsigned long *)wdev_priv = (unsigned long)adapter; + + set_wiphy_dev(wiphy, priv->adapter->dev); + + ret = wiphy_register(wiphy); + if (ret < 0) { + nxpwifi_dbg(adapter, ERROR, + "%s: wiphy_register failed: %d\n", __func__, ret); + goto err; + } + + if (!adapter->regd) { + if (adapter->region_code == 0x00) { + nxpwifi_dbg(adapter, WARN, + "Ignore world regulatory domain\n"); + } else { + wiphy->regulatory_flags |= + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + country_code = + nxpwifi_11d_code_2_region(adapter->region_code); + if (country_code && + regulatory_hint(wiphy, country_code)) + nxpwifi_dbg(priv->adapter, ERROR, + "regulatory_hint() failed\n"); + } + } + + nxpwifi_get_802_11_snmp_mib(priv, FRAG_THRESH_I, &thr); + wiphy->frag_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, RTS_THRESH_I, &thr); + wiphy->rts_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, SHORT_RETRY_LIM_I, &retry); + wiphy->retry_short = (u8)retry; + nxpwifi_get_802_11_snmp_mib(priv, LONG_RETRY_LIM_I, &retry); + wiphy->retry_long = (u8)retry; + + adapter->wiphy = wiphy; + return ret; + +err: + wiphy_free(wiphy); + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.h b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h new file mode 100644 index 000000000000..3a9a204df195 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#ifndef __NXPWIFI_CFG80211__ +#define __NXPWIFI_CFG80211__ + +#include "main.h" + +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter); + +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params); +#endif diff --git a/drivers/net/wireless/nxp/nxpwifi/cfp.c b/drivers/net/wireless/nxp/nxpwifi/cfp.c new file mode 100644 index 000000000000..aec1d5014810 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfp.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: Channel, Frequency and Power + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cfg80211.h" + +/* 100mW */ +#define NXPWIFI_TX_PWR_DEFAULT 20 +/* 100mW */ +#define NXPWIFI_TX_PWR_US_DEFAULT 20 +/* 50mW */ +#define NXPWIFI_TX_PWR_JP_DEFAULT 16 +/* 100mW */ +#define NXPWIFI_TX_PWR_FR_100MW 20 +/* 10mW */ +#define NXPWIFI_TX_PWR_FR_10MW 10 +/* 100mW */ +#define NXPWIFI_TX_PWR_EMEA_DEFAULT 20 + +static u8 supported_rates_a[A_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; +static u16 nxpwifi_data_rates[NXPWIFI_SUPPORTED_RATES_EXT] = { 0x02, 0x04, + 0x0B, 0x16, 0x00, 0x0C, 0x12, 0x18, + 0x24, 0x30, 0x48, 0x60, 0x6C, 0x90, + 0x0D, 0x1A, 0x27, 0x34, 0x4E, 0x68, + 0x75, 0x82, 0x0C, 0x1B, 0x36, 0x51, + 0x6C, 0xA2, 0xD8, 0xF3, 0x10E, 0x00 }; + +static u8 supported_rates_b[B_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x16, 0 }; + +static u8 supported_rates_g[G_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6c, 0 }; + +static u8 supported_rates_bg[BG_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x0c, + 0x12, 0x16, 0x18, 0x24, 0x30, 0x48, + 0x60, 0x6c, 0 }; + +/* mcs_rate: first 8 entries for 1x1; all 16 for 2x2. */ +static const u16 mcs_rate[4][16] = { + /* LGI 40M */ + { 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e, + 0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c }, + + /* SGI 40M */ + { 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c, + 0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 }, + + /* LGI 20M */ + { 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82, + 0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 }, + + /* SGI 20M */ + { 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90, + 0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 } +}; + +/* AC rates */ +static const u16 ac_mcs_rate_nss1[8][10] = { + /* LG 160M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 160M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 80M */ + { 0x3B, 0x75, 0xB0, 0xEA, 0x15F, 0x1D4, 0x20F, + 0x249, 0x2BE, 0x30C }, + + /* SG 80M */ + { 0x41, 0x82, 0xC3, 0x104, 0x186, 0x208, 0x249, + 0x28A, 0x30C, 0x363 }, + + /* LG 40M */ + { 0x1B, 0x36, 0x51, 0x6C, 0xA2, 0xD8, 0xF3, + 0x10E, 0x144, 0x168 }, + + /* SG 40M */ + { 0x1E, 0x3C, 0x5A, 0x78, 0xB4, 0xF0, 0x10E, + 0x12C, 0x168, 0x190 }, + + /* LG 20M */ + { 0xD, 0x1A, 0x27, 0x34, 0x4E, 0x68, 0x75, 0x82, 0x9C, 0x00 }, + + /* SG 20M */ + { 0xF, 0x1D, 0x2C, 0x3A, 0x57, 0x74, 0x82, 0x91, 0xAE, 0x00 }, +}; + +/* NSS2 note: the value in the table is 2 multiplier of the actual rate */ +static const u16 ac_mcs_rate_nss2[8][10] = { + /* LG 160M */ + { 0xEA, 0x1D4, 0x2BE, 0x3A8, 0x57C, 0x750, 0x83A, + 0x924, 0xAF8, 0xC30 }, + + /* SG 160M */ + { 0x104, 0x208, 0x30C, 0x410, 0x618, 0x820, 0x924, + 0xA28, 0xC30, 0xD8B }, + + /* LG 80M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 80M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 40M */ + { 0x36, 0x6C, 0xA2, 0xD8, 0x144, 0x1B0, 0x1E6, + 0x21C, 0x288, 0x2D0 }, + + /* SG 40M */ + { 0x3C, 0x78, 0xB4, 0xF0, 0x168, 0x1E0, 0x21C, + 0x258, 0x2D0, 0x320 }, + + /* LG 20M */ + { 0x1A, 0x34, 0x4A, 0x68, 0x9C, 0xD0, 0xEA, 0x104, + 0x138, 0x00 }, + + /* SG 20M */ + { 0x1D, 0x3A, 0x57, 0x74, 0xAE, 0xE6, 0x104, 0x121, + 0x15B, 0x00 }, +}; + +struct region_code_mapping { + u8 code; + u8 region[IEEE80211_COUNTRY_STRING_LEN]; +}; + +static struct region_code_mapping region_code_mapping_t[] = { + { 0x10, "US " }, /* US FCC */ + { 0x20, "CA " }, /* IC Canada */ + { 0x30, "FR " }, /* France */ + { 0x31, "ES " }, /* Spain */ + { 0x32, "FR " }, /* France */ + { 0x40, "JP " }, /* Japan */ + { 0x41, "JP " }, /* Japan */ + { 0x50, "CN " }, /* China */ +}; + +/* Convert 11d country code to region string. */ +u8 *nxpwifi_11d_code_2_region(u8 code) +{ + u8 i; + + /* Look for code in mapping table */ + for (i = 0; i < ARRAY_SIZE(region_code_mapping_t); i++) + if (region_code_mapping_t[i].code == code) + return region_code_mapping_t[i].region; + + return NULL; +} + +/* Map supported rate index to AC/VHT data rate. */ +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 rate = 0; + u8 mcs_index = 0; + u8 bw = 0; + u8 gi = 0; + + if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_VHT) { + mcs_index = min(index & 0xF, 9); + + /* 20M: bw=0, 40M: bw=1, 80M: bw=2, 160M: bw=3 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if ((index >> 4) == 1) /* NSS = 2 */ + rate = ac_mcs_rate_nss2[2 * (3 - bw) + gi][mcs_index]; + else /* NSS = 1 */ + rate = ac_mcs_rate_nss1[2 * (3 - bw) + gi][mcs_index]; + } else if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_HT) { + /* 20M: bw=0, 40M: bw=1 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (gi == 1) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < 16) { + if (bw == 1 || bw == 0) + rate = mcs_rate[2 * (1 - bw) + gi][index]; + else + rate = nxpwifi_data_rates[0]; + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + /* 11n non-HT rates */ + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + + return rate; +} + +/* Map supported rate index to data rate. */ +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 mcs_num_supp = + (priv->adapter->user_dev_mcs_support == HT_STREAM_2X2) ? 16 : 8; + u32 rate; + + if (priv->adapter->is_hw_11ac_capable) + return nxpwifi_index_to_acs_data_rate(priv, index, ht_info); + + if (ht_info & BIT(0)) { + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (ht_info & BIT(2)) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < mcs_num_supp) { + if (ht_info & BIT(1)) { + if (ht_info & BIT(2)) + /* SGI, 40M */ + rate = mcs_rate[1][index]; + else + /* LGI, 40M */ + rate = mcs_rate[0][index]; + } else { + if (ht_info & BIT(2)) + /* SGI, 20M */ + rate = mcs_rate[3][index]; + else + /* LGI, 20M */ + rate = mcs_rate[2][index]; + } + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + return rate; +} + +/* Return current active data rates (depends on connection). */ +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, u8 *rates) +{ + if (!priv->media_connected) + return nxpwifi_get_supported_rates(priv, rates); + else + return nxpwifi_copy_rates(rates, 0, + priv->curr_bss_params.data_rates, + priv->curr_bss_params.num_of_rates); +} + +/* Find Channel/Frequency/Power by band and channel or frequency. */ +struct nxpwifi_chan_freq_power * +nxpwifi_get_cfp(struct nxpwifi_private *priv, u8 band, u16 channel, u32 freq) +{ + struct nxpwifi_chan_freq_power *cfp = NULL; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch = NULL; + int i; + + if (!channel && !freq) + return cfp; + + if (nxpwifi_band_to_radio_type(band) == HOST_SCAN_RADIO_TYPE_BG) + sband = priv->wdev.wiphy->bands[NL80211_BAND_2GHZ]; + else + sband = priv->wdev.wiphy->bands[NL80211_BAND_5GHZ]; + + if (!sband) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot find cfp by band %d\n", + __func__, band); + return cfp; + } + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (freq) { + if (ch->center_freq == freq) + break; + } else { + /* Find by valid channel. */ + if (ch->hw_value == channel || + channel == FIRST_VALID_CHANNEL) + break; + } + } + if (i == sband->n_channels) { + nxpwifi_dbg(priv->adapter, WARN, + "%s: cannot find cfp by band %d\t" + "& channel=%d freq=%d\n", + __func__, band, channel, freq); + } else { + if (!ch) + return cfp; + + priv->cfp.channel = ch->hw_value; + priv->cfp.freq = ch->center_freq; + priv->cfp.max_tx_power = ch->max_power; + cfp = &priv->cfp; + } + + return cfp; +} + +/* Return true if data rate is set to auto. */ +u8 +nxpwifi_is_rate_auto(struct nxpwifi_private *priv) +{ + u32 i; + int rate_num = 0; + + for (i = 0; i < ARRAY_SIZE(priv->bitmap_rates); i++) + if (priv->bitmap_rates[i]) + rate_num++; + + if (rate_num > 1) + return true; + else + return false; +} + +/* Extract supported rates from cfg80211_scan_request bitmask. */ +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type) +{ + struct wiphy *wiphy = priv->adapter->wiphy; + struct cfg80211_scan_request *request = priv->scan_request; + u32 num_rates, rate_mask; + struct ieee80211_supported_band *sband; + int i; + + if (radio_type) { + sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_5GHZ]; + } else { + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_2GHZ]; + } + + num_rates = 0; + for (i = 0; i < sband->n_bitrates; i++) { + if ((BIT(i) & rate_mask) == 0) + continue; /* skip rate */ + rates[num_rates++] = (u8)(sband->bitrates[i].bitrate / 5); + } + + return num_rates; +} + +/* Convert config_bands to B/G/A band */ +static u16 nxpwifi_convert_config_bands(u16 config_bands) +{ + u16 bands = 0; + + if (config_bands & BAND_B) + bands |= BAND_B; + if (config_bands & BAND_G || config_bands & BAND_GN || + config_bands & BAND_GAC || config_bands & BAND_GAX) + bands |= BAND_G; + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + bands |= BAND_A; + + return bands; +} + +/* Get supported rates in infrastructure (STA/P2P client) mode. */ +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 k = 0; + u16 bands = 0; + + bands = nxpwifi_convert_config_bands(adapter->fw_bands); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (bands == BAND_B) { + /* B only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_b\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_b, + sizeof(supported_rates_b)); + } else if (bands == BAND_G) { + /* G only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_g\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_g, + sizeof(supported_rates_g)); + } else if (bands & (BAND_B | BAND_G)) { + /* BG only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_bg\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_bg, + sizeof(supported_rates_bg)); + } else if (bands & BAND_A) { + /* support A */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_a\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_a, + sizeof(supported_rates_a)); + } + } + + return k; +} + +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 rate_info) +{ + u8 rate_index = 0; + + /* HT40 */ + if ((rate_info & BIT(0)) && (rate_info & BIT(1))) + rate_index = NXPWIFI_RATE_INDEX_MCS0 + + NXPWIFI_BW20_MCS_NUM + rx_rate; + else if (rate_info & BIT(0)) /* HT20 */ + rate_index = NXPWIFI_RATE_INDEX_MCS0 + rx_rate; + else + rate_index = (rx_rate > NXPWIFI_RATE_INDEX_OFDM0) ? + rx_rate - 1 : rx_rate; + + if (rate_index >= NXPWIFI_MAX_AC_RX_RATES) + rate_index = NXPWIFI_MAX_AC_RX_RATES - 1; + + return rate_index; +} + +/* Check if the given region code is a valid NXP-defined region code. + * Valid codes are defined by the FW v18 region enum in IEEE_types.h: + * 0x00 (World), 0x10 (US/FCC), 0x20 (Canada/IC), 0x30 (ETSI), + * 0x31 (Spain), 0x32 (France), 0x40 (Japan), 0x41 (Japan1), 0x50 (China) + */ +bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code) +{ + switch (code) { + case NXPWIFI_REGION_WORLD: + case NXPWIFI_REGION_FCC: + case NXPWIFI_REGION_IC: + case NXPWIFI_REGION_ETSI: + case NXPWIFI_REGION_SPAIN: + case NXPWIFI_REGION_FRANCE: + case NXPWIFI_REGION_JAPAN: + case NXPWIFI_REGION_JAPAN1: + case NXPWIFI_REGION_CHINA: + return true; + default: + return false; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.c b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c new file mode 100644 index 000000000000..4eb17ada5db8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static void nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter); + +/* Initialize command node; set defaults; buffers are supplied by caller. */ +static void +nxpwifi_init_cmd_node(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u32 cmd_no, void *data_buf, bool sync) +{ + cmd_node->priv = priv; + cmd_node->cmd_no = cmd_no; + + if (sync) { + cmd_node->wait_q_enabled = true; + cmd_node->cmd_wait_q_woken = false; + cmd_node->condition = &cmd_node->cmd_wait_q_woken; + } + cmd_node->data_buf = data_buf; + cmd_node->cmd_skb = cmd_node->skb; + cmd_node->cmd_resp = NULL; +} + +/* Get a free command node from cmd_free_q; return NULL if none. */ +static struct cmd_ctrl_node * +nxpwifi_get_cmd_node(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->cmd_free_q_lock); + if (list_empty(&adapter->cmd_free_q)) { + nxpwifi_dbg(adapter, ERROR, + "GET_CMD_NODE: cmd node not available\n"); + spin_unlock_bh(&adapter->cmd_free_q_lock); + return NULL; + } + cmd_node = list_first_entry(&adapter->cmd_free_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_free_q_lock); + + return cmd_node; +} + +/* Reset cmd node state; trim cmd skb; complete and clear resp_skb if present. */ +static void +nxpwifi_clean_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + cmd_node->cmd_no = 0; + cmd_node->cmd_flag = 0; + cmd_node->data_buf = NULL; + cmd_node->wait_q_enabled = false; + + if (cmd_node->cmd_skb) + skb_trim(cmd_node->cmd_skb, 0); + + if (cmd_node->resp_skb) { + adapter->if_ops.cmdrsp_complete(adapter, cmd_node->resp_skb); + cmd_node->resp_skb = NULL; + } +} + +/* Optionally complete waiters, clean the node, and add it back to cmd_free_q. */ +static void +nxpwifi_insert_cmd_to_free_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + if (!cmd_node) + return; + + if (cmd_node->wait_q_enabled) + nxpwifi_complete_cmd(adapter, cmd_node); + /* Clean the node */ + nxpwifi_clean_cmd_node(adapter, cmd_node); + + /* Insert node into cmd_free_q */ + spin_lock_bh(&adapter->cmd_free_q_lock); + list_add_tail(&cmd_node->list, &adapter->cmd_free_q); + spin_unlock_bh(&adapter->cmd_free_q_lock); +} + +/* Reuse a command node. */ +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = (void *)cmd_node->cmd_skb->data; + + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + + atomic_dec(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: FREE_CMD: cmd=%#x, cmd_pending=%d\n", + le16_to_cpu(host_cmd->command), + atomic_read(&adapter->cmd_pending)); +} + +/* Copy host command (userspace-provided) into the driver cmd buffer. */ +static int nxpwifi_cmd_host_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *cmd; + struct nxpwifi_ds_misc_cmd *pcmd_ptr; + + cmd = (struct host_cmd_ds_command *)cmd_node->skb->data; + pcmd_ptr = (struct nxpwifi_ds_misc_cmd *)cmd_node->data_buf; + + /* Copy the HOST command to command buffer */ + memcpy(cmd, pcmd_ptr->cmd, pcmd_ptr->len); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: host cmd size = %d\n", pcmd_ptr->len); + return 0; +} + +/* Send prepared command to FW: set seq no, adjust skb length, log, start timer. */ +static int nxpwifi_dnld_cmd_to_fw(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct host_cmd_ds_command *host_cmd; + u16 cmd_code; + u16 cmd_size; + + if (!adapter || !cmd_node) + return -EINVAL; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + + /* Sanity test */ + if (host_cmd->size == 0) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host_cmd is null\t" + "or cmd size is 0, not sending\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + return -EINVAL; + } + + cmd_code = le16_to_cpu(host_cmd->command); + cmd_node->cmd_no = cmd_code; + cmd_size = le16_to_cpu(host_cmd->size); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET && + cmd_code != HOST_CMD_FUNC_SHUTDOWN && + cmd_code != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: FW in reset state, ignore cmd %#x\n", + cmd_code); + nxpwifi_recycle_cmd_node(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + return -EPERM; + } + + /* Set command sequence number */ + adapter->seq_num++; + host_cmd->seq_num = cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, + cmd_node->priv->bss_num, + cmd_node->priv->bss_type)); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = cmd_node; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + /* Adjust skb length */ + if (cmd_node->cmd_skb->len > cmd_size) + /* + * cmd_size is less than sizeof(struct host_cmd_ds_command). + * Trim off the unused portion. + */ + skb_trim(cmd_node->cmd_skb, cmd_size); + else if (cmd_node->cmd_skb->len < cmd_size) + /* + * cmd_size is larger than sizeof(struct host_cmd_ds_command) + * because we have appended custom element TLV. Increase skb length + * accordingly. + */ + skb_put(cmd_node->cmd_skb, cmd_size - cmd_node->cmd_skb->len); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + cmd_code, + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN), + cmd_size, le16_to_cpu(host_cmd->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "cmd buffer:", host_cmd, cmd_size); + + skb_push(cmd_node->cmd_skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + cmd_node->cmd_skb, NULL); + skb_pull(cmd_node->cmd_skb, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host to card failed\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + adapter->dbg.num_cmd_host_to_card_failure++; + return ret; + } + + /* Save the last command id and action to debug log */ + adapter->dbg.last_cmd_index = + (adapter->dbg.last_cmd_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index] = cmd_code; + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index] = + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN); + + /* + * Setup the timer after transmit command, except that specific + * command might not have command response. + */ + if (cmd_code != HOST_CMD_FW_DUMP_EVENT) + mod_timer(&adapter->cmd_timer, + jiffies + msecs_to_jiffies(NXPWIFI_TIMER_10S)); + + /* Clear BSS_NO_BITS from HOST */ + cmd_code &= HOST_CMD_ID_MASK; + + return 0; +} + +/* Send sleep-confirm command to FW; set seq no; resp may be skipped when resp_ctrl=0. */ +static int nxpwifi_dnld_sleep_confirm_cmd(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = + (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + adapter->seq_num++; + sleep_cfm_buf->seq_num = + cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, priv->bss_num, + priv->bss_type)); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + le16_to_cpu(sleep_cfm_buf->command), + le16_to_cpu(sleep_cfm_buf->action), + le16_to_cpu(sleep_cfm_buf->size), + le16_to_cpu(sleep_cfm_buf->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "SLEEP_CFM buffer: ", sleep_cfm_buf, + le16_to_cpu(sleep_cfm_buf->size)); + + skb_push(adapter->sleep_cfm, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + adapter->sleep_cfm, NULL); + skb_pull(adapter->sleep_cfm, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SLEEP_CFM: failed\n"); + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure++; + return ret; + } + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl)) + /* Response is not needed for sleep confirm command */ + adapter->ps_state = PS_STATE_SLEEP; + else + adapter->ps_state = PS_STATE_SLEEP_CFM; + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl) && + (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags) && + !adapter->sleep_period.period)) { + adapter->pm_wakeup_card_req = true; + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), true); + } + + return ret; +} + +/* Allocate cmd pool and link all nodes to cmd_free_q (used/returned by cmds). */ +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Allocate and initialize struct cmd_ctrl_node */ + cmd_array = kzalloc_objs(struct cmd_ctrl_node, + NXPWIFI_NUM_OF_CMD_BUFFER, GFP_KERNEL); + if (!cmd_array) + return -ENOMEM; + + adapter->cmd_pool = cmd_array; + + /* Allocate and initialize command buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + cmd_array[i].skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + if (!cmd_array[i].skb) + return -ENOMEM; + } + + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) + nxpwifi_insert_cmd_to_free_q(adapter, &cmd_array[i]); + + return 0; +} + +/* Free cmd pool; release any remaining resp skbs. */ +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Need to check if cmd pool is allocated or not */ + if (!adapter->cmd_pool) { + nxpwifi_dbg(adapter, FATAL, + "info: FREE_CMD_BUF: cmd_pool is null\n"); + return; + } + + cmd_array = adapter->cmd_pool; + + /* Release shared memory buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + if (cmd_array[i].skb) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd buffer %d\n", i); + dev_kfree_skb_any(cmd_array[i].skb); + } + if (!cmd_array[i].resp_skb) + continue; + + dev_kfree_skb_any(cmd_array[i].resp_skb); + } + /* Release struct cmd_ctrl_node */ + if (adapter->cmd_pool) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd pool\n"); + kfree(adapter->cmd_pool); + adapter->cmd_pool = NULL; + } +} + +/* + * Handle FW event: select per-BSS priv, fill rxinfo, dispatch to STA/UAP handler, complete. + */ +int nxpwifi_process_event(struct nxpwifi_adapter *adapter) +{ + int ret, i; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct sk_buff *skb = adapter->event_skb; + u32 eventcause; + struct nxpwifi_rxinfo *rx_info; + + if ((adapter->event_cause & EVENT_ID_MASK) == EVENT_RADAR_DETECTED) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (nxpwifi_is_11h_active(priv)) { + adapter->event_cause |= + ((priv->bss_num & 0xff) << 16) | + ((priv->bss_type & 0xff) << 24); + break; + } + } + } + + eventcause = adapter->event_cause; + + /* Save the last event to debug log */ + adapter->dbg.last_event_index = + (adapter->dbg.last_event_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_event[adapter->dbg.last_event_index] = + (u16)eventcause; + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, EVENT_GET_BSS_NUM(eventcause), + EVENT_GET_BSS_TYPE(eventcause)); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + /* Clear BSS_NO_BITS from event */ + eventcause &= EVENT_ID_MASK; + adapter->event_cause = eventcause; + + if (skb) { + rx_info = NXPWIFI_SKB_RXCB(skb); + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + nxpwifi_dbg_dump(adapter, EVT_D, "Event Buf:", + skb->data, skb->len); + } + + nxpwifi_dbg(adapter, EVENT, "EVENT: cause: %#x\n", eventcause); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_event(priv); + else + ret = nxpwifi_process_sta_event(priv); + + adapter->event_cause = 0; + adapter->event_skb = NULL; + adapter->if_ops.event_complete(adapter, skb); + + return ret; +} + +/* + * Prepare and queue a command: sanity checks, get node, init, fill, and enqueue/dispatch. + */ +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + if (!adapter) { + pr_err("PREP_CMD: adapter is NULL\n"); + return -EINVAL; + } + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: device in suspended state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags) && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: host entering sleep state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: card is removed\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW is in bad state\n"); + return -EPERM; + } + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET) { + if (cmd_no != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW in reset state\n"); + return -EPERM; + } + } + + if (priv->adapter->hs_activated_manually && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + priv->adapter->hs_activated_manually = false; + } + + /* Get a new command node */ + cmd_node = nxpwifi_get_cmd_node(adapter); + + if (!cmd_node) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd node\n"); + return -ENOMEM; + } + + /* Initialize the command node */ + nxpwifi_init_cmd_node(priv, cmd_node, cmd_no, data_buf, sync); + + if (!cmd_node->cmd_skb) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd buf\n"); + return -ENOMEM; + } + + skb_put_zero(cmd_node->cmd_skb, sizeof(struct host_cmd_ds_command)); + + /* Prepare command */ + if (cmd_no) { + switch (cmd_no) { + case HOST_CMD_UAP_SYS_CONFIG: + case HOST_CMD_UAP_BSS_START: + case HOST_CMD_UAP_BSS_STOP: + case HOST_CMD_UAP_STA_DEAUTH: + case HOST_CMD_APCMD_SYS_RESET: + case HOST_CMD_APCMD_STA_LIST: + case HOST_CMD_CHAN_REPORT_REQUEST: + case HOST_CMD_ADD_NEW_STATION: + ret = nxpwifi_uap_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + default: + ret = nxpwifi_sta_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + } + } else { + ret = nxpwifi_cmd_host_cmd(priv, cmd_node); + cmd_node->cmd_flag |= CMD_F_HOSTCMD; + } + + /* Return error, since the command preparation failed */ + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: cmd %#x preparation failed\n", + cmd_no); + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + return ret; + } + + /* Send command */ + if (cmd_no == HOST_CMD_802_11_SCAN || + cmd_no == HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_queue_scan_cmd(priv, cmd_node); + } else { + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + if (cmd_node->wait_q_enabled) + ret = nxpwifi_wait_queue_complete(adapter, cmd_node); + } + + return ret; +} + +/* Queue command to cmd_pending_q; EXIT_PS and HS_ACTIVATE go to the head. */ +void +nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = NULL; + u16 command; + bool add_tail = true; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + if (!host_cmd) { + nxpwifi_dbg(adapter, ERROR, "QUEUE_CMD: host_cmd is NULL\n"); + return; + } + + command = le16_to_cpu(host_cmd->command); + + /* Exit_PS command needs to be queued in the header always. */ + if (command == HOST_CMD_802_11_PS_MODE_ENH) { + struct host_cmd_ds_802_11_ps_mode_enh *pm = + &host_cmd->params.psmode_enh; + if ((le16_to_cpu(pm->action) == DIS_PS) || + (le16_to_cpu(pm->action) == DIS_AUTO_PS)) { + if (adapter->ps_state != PS_STATE_AWAKE) + add_tail = false; + } + } + + /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ + if (command == HOST_CMD_802_11_HS_CFG_ENH) { + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = + &host_cmd->params.opt_hs_cfg; + + if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) + add_tail = false; + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); + else + list_add(&cmd_node->list, &adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + atomic_inc(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: QUEUE_CMD: cmd=%#x, cmd_pending=%d\n", + command, atomic_read(&adapter->cmd_pending)); +} + +/* Dequeue next cmd and download to FW; if HS active (except HS_CFG), deactivate it. */ +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + struct cmd_ctrl_node *cmd_node; + int ret = 0; + struct host_cmd_ds_command *host_cmd; + + /* Check if already in processing */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, FATAL, + "EXEC_NEXT_CMD: cmd in processing\n"); + return -EBUSY; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Check if any command is pending */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (list_empty(&adapter->cmd_pending_q)) { + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -ENODATA; + } + cmd_node = list_first_entry(&adapter->cmd_pending_q, + struct cmd_ctrl_node, list); + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + priv = cmd_node->priv; + + if (adapter->ps_state != PS_STATE_AWAKE) { + nxpwifi_dbg(adapter, ERROR, + "%s: cannot send cmd in sleep state,\t" + "this should not happen\n", __func__); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return ret; + } + + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + ret = nxpwifi_dnld_cmd_to_fw(priv, cmd_node); + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* + * Any command sent to the firmware when host is in sleep + * mode should de-configure host sleep. We should skip the + * host sleep configuration command itself though + */ + if (priv && host_cmd->command != + cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH)) { + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event(priv, false); + } + } + + return ret; +} + +static void +nxpwifi_process_cmdresp_error(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *pm; + + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: cmd %#x error, result=%#x\n", + resp->command, resp->result); + + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + switch (le16_to_cpu(resp->command)) { + case HOST_CMD_802_11_PS_MODE_ENH: + pm = &resp->params.psmode_enh; + nxpwifi_dbg(adapter, ERROR, + "PS_MODE_ENH cmd failed: result=0x%x action=0x%X\n", + resp->result, le16_to_cpu(pm->action)); + break; + case HOST_CMD_802_11_SCAN: + case HOST_CMD_802_11_SCAN_EXT: + nxpwifi_cancel_scan(adapter); + break; + + case HOST_CMD_MAC_CONTROL: + break; + + case HOST_CMD_SDIO_SP_RX_AGGR_CFG: + nxpwifi_dbg(adapter, MSG, + "SDIO RX single-port aggregation Not support\n"); + break; + + default: + break; + } + /* Handling errors here */ + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); +} + +/* Handle command response: validate, cancel timer, dispatch, set status, recycle node. */ +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter) +{ + struct host_cmd_ds_command *resp; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = 0; + u16 orig_cmdresp_no; + u16 cmdresp_no; + u16 cmdresp_result; + + if (!adapter->curr_cmd || !adapter->curr_cmd->resp_skb) { + resp = (struct host_cmd_ds_command *)adapter->upld_buf; + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: NULL curr_cmd, %#x\n", + le16_to_cpu(resp->command)); + return -EINVAL; + } + + resp = (struct host_cmd_ds_command *)adapter->curr_cmd->resp_skb->data; + orig_cmdresp_no = le16_to_cpu(resp->command); + cmdresp_no = (orig_cmdresp_no & HOST_CMD_ID_MASK); + + if (adapter->curr_cmd->cmd_no != cmdresp_no) { + nxpwifi_dbg(adapter, ERROR, + "cmdresp error: cmd=0x%x cmd_resp=0x%x\n", + adapter->curr_cmd->cmd_no, cmdresp_no); + return -EINVAL; + } + /* Now we got response from FW, cancel the command timer */ + timer_delete_sync(&adapter->cmd_timer); + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + /* Copy original response back to response buffer */ + struct nxpwifi_ds_misc_cmd *hostcmd; + u16 size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: host cmd resp size = %d\n", size); + size = min_t(u16, size, NXPWIFI_SIZE_OF_CMD_BUFFER); + if (adapter->curr_cmd->data_buf) { + hostcmd = adapter->curr_cmd->data_buf; + hostcmd->len = size; + memcpy(hostcmd->cmd, resp, size); + } + } + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id + (adapter, HOST_GET_BSS_NO(le16_to_cpu(resp->seq_num)), + HOST_GET_BSS_TYPE(le16_to_cpu(resp->seq_num))); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* Clear RET_BIT from HOST */ + resp->command = cpu_to_le16(orig_cmdresp_no & HOST_CMD_ID_MASK); + + cmdresp_no = le16_to_cpu(resp->command); + cmdresp_result = le16_to_cpu(resp->result); + + /* Save the last command response to debug log */ + adapter->dbg.last_cmd_resp_index = + (adapter->dbg.last_cmd_resp_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_resp_id[adapter->dbg.last_cmd_resp_index] = + orig_cmdresp_no; + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + orig_cmdresp_no, cmdresp_result, + le16_to_cpu(resp->size), le16_to_cpu(resp->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "CMD_RESP buffer:", resp, + le16_to_cpu(resp->size)); + + if (!(orig_cmdresp_no & HOST_RET_BIT)) { + nxpwifi_dbg(adapter, ERROR, "CMD_RESP: invalid cmd resp\n"); + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -EINVAL; + } + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + adapter->curr_cmd->cmd_flag &= ~CMD_F_HOSTCMD; + if (cmdresp_result == HOST_RESULT_OK && + cmdresp_no == HOST_CMD_802_11_HS_CFG_ENH) + ret = nxpwifi_ret_802_11_hs_cfg(priv, resp); + } else { + if (resp->result != HOST_RESULT_OK) { + nxpwifi_process_cmdresp_error(priv, resp); + return -EFAULT; + } + if (adapter->curr_cmd->cmd_resp) { + void *data_buf = adapter->curr_cmd->data_buf; + + ret = adapter->curr_cmd->cmd_resp(priv, resp, + cmdresp_no, + data_buf); + } + } + + if (adapter->curr_cmd) { + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = ret; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + + return ret; +} + +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter) +{ + struct cfg80211_rx_assoc_resp_data assoc_resp = { + .uapsd_queues = -1, + }; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv->assoc_rsp_size) { + assoc_resp.links[0].bss = priv->req_bss; + assoc_resp.buf = priv->assoc_rsp_buf; + assoc_resp.len = priv->assoc_rsp_size; + cfg80211_rx_assoc_resp(priv->netdev, + &assoc_resp); + priv->assoc_rsp_size = 0; + } +} + +/* + * Command timeout handler: mark timed out, cancel pending IOCTL, dump/reset device if provided. + */ +void +nxpwifi_cmd_timeout_func(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, cmd_timer); + struct cmd_ctrl_node *cmd_node; + + set_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + if (!adapter->curr_cmd) { + nxpwifi_dbg(adapter, ERROR, + "cmd: empty curr_cmd\n"); + return; + } + cmd_node = adapter->curr_cmd; + if (cmd_node) { + adapter->dbg.timeout_cmd_id = + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index]; + adapter->dbg.timeout_cmd_act = + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index]; + nxpwifi_dbg(adapter, MSG, + "%s: Timeout cmd id = %#x, act = %#x\n", __func__, + adapter->dbg.timeout_cmd_id, + adapter->dbg.timeout_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "num_data_h2c_failure = %d\n", + adapter->dbg.num_tx_host_to_card_failure); + nxpwifi_dbg(adapter, MSG, + "num_cmd_h2c_failure = %d\n", + adapter->dbg.num_cmd_host_to_card_failure); + + nxpwifi_dbg(adapter, MSG, + "is_cmd_timedout = %d\n", + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags)); + nxpwifi_dbg(adapter, MSG, + "num_tx_timeout = %d\n", + adapter->dbg.num_tx_timeout); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_index = %d\n", + adapter->dbg.last_cmd_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_id), + adapter->dbg.last_cmd_id); + nxpwifi_dbg(adapter, MSG, + "last_cmd_act: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_act), + adapter->dbg.last_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_index = %d\n", + adapter->dbg.last_cmd_resp_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_resp_id), + adapter->dbg.last_cmd_resp_id); + + nxpwifi_dbg(adapter, MSG, + "last_event_index = %d\n", + adapter->dbg.last_event_index); + nxpwifi_dbg(adapter, MSG, + "last_event: %*ph\n", + (int)sizeof(adapter->dbg.last_event), + adapter->dbg.last_event); + + nxpwifi_dbg(adapter, MSG, + "data_sent=%d cmd_sent=%d\n", + adapter->data_sent, adapter->cmd_sent); + + nxpwifi_dbg(adapter, MSG, + "ps_mode=%d ps_state=%d\n", + adapter->ps_mode, adapter->ps_state); + + if (cmd_node->wait_q_enabled) { + adapter->cmd_wait_q.status = -ETIMEDOUT; + nxpwifi_cancel_pending_ioctl(adapter); + } + } + + if (adapter->if_ops.device_dump) + adapter->if_ops.device_dump(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +void +nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + /* Cancel all pending scan command */ + spin_lock_bh(&adapter->scan_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + cmd_node->wait_q_enabled = false; + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + } + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* + * Cancel current cmd (if waiting), all pending cmds, and pending scan cmds; complete with error. + */ +void +nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Cancel current cmd */ + if (adapter->curr_cmd && adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = -1; + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + adapter->curr_cmd->wait_q_enabled = false; + /* no recycle probably wait for response */ + } + /* Cancel all pending command */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->cmd_pending_q, list) { + list_del(&cmd_node->list); + + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_cancel_scan(adapter); +} + +/* Cancel current/pending commands for the pending IOCTL; also cancel scan cmds. */ +static void +nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL; + + if (adapter->curr_cmd && + adapter->curr_cmd->wait_q_enabled) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + cmd_node = adapter->curr_cmd; + /* + * Be careful when setting curr_cmd = NULL: + * nxpwifi_process_cmdresp expects a non-NULL pointer. + * This is safe here because only cmd_timeout calls this path + * and no response is expected at that point. + */ + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + + nxpwifi_cancel_scan(adapter); +} + +/* If no cmd/event/tx is pending, send sleep-confirm to FW; otherwise defer. */ +void +nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !atomic_read(&adapter->tx_hw_pending) && + !adapter->curr_cmd && !IS_CARD_RX_RCVD(adapter)) + nxpwifi_dnld_sleep_confirm_cmd(adapter); + else + nxpwifi_dbg(adapter, CMD, + "cmd: Delay Sleep Confirm (%s%s%s%s)\n", + (adapter->cmd_sent) ? "D" : "", + atomic_read(&adapter->tx_hw_pending) ? "T" : "", + (adapter->curr_cmd) ? "C" : "", + (IS_CARD_RX_RCVD(adapter)) ? "R" : ""); +} + +/* Generate HS activated/deactivated event for userspace; update flags and wake waiters. */ +void +nxpwifi_hs_activated_event(struct nxpwifi_private *priv, u8 activated) +{ + if (activated) { + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, + &priv->adapter->work_flags)) { + priv->adapter->hs_activated = true; + nxpwifi_update_rxreor_flags(priv->adapter, + RXREOR_FORCE_NO_DROP); + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_activated\n"); + priv->adapter->hs_activate_wait_q_woken = true; + wake_up_interruptible(&priv->adapter->hs_activate_wait_q); + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: HS not configured\n"); + } + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_deactivated\n"); + priv->adapter->hs_activated = false; + } +} + +/* Handle HS_CFG response: update HS configured/activated flags and emit HS events. */ +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *phs_cfg = + &resp->params.opt_hs_cfg; + u32 conditions = le32_to_cpu(phs_cfg->params.hs_config.conditions); + + if (phs_cfg->action == cpu_to_le16(HS_ACTIVATE)) { + nxpwifi_hs_activated_event(priv, true); + goto done; + } else { + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: HS_CFG cmd reply\t" + " result=%#x, conditions=0x%x gpio=0x%x gap=0x%x\n", + resp->result, conditions, + phs_cfg->params.hs_config.gpio, + phs_cfg->params.hs_config.gap); + } + if (conditions != HS_CFG_CANCEL) { + set_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + } else { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + if (adapter->hs_activated) + nxpwifi_hs_activated_event(priv, false); + } + +done: + return 0; +} + +/* On power-up interrupt, wake device and cancel HS if armed; clear flags and notify. */ +void +nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, INFO, + "info: %s: auto cancelling host sleep\t" + "since there is interrupt from the firmware\n", + __func__); + + adapter->if_ops.wakeup(adapter); + + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + nxpwifi_hs_activated_event(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY), + false); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_hs_config); + +/* Handle sleep-confirm response; set ps_state and hs activation accordingly. */ +void +nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len) +{ + struct host_cmd_ds_command *cmd = (struct host_cmd_ds_command *)pbuf; + u16 result = le16_to_cpu(cmd->result); + u16 command = le16_to_cpu(cmd->command); + u16 seq_num = le16_to_cpu(cmd->seq_num); + + if (!upld_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: cmd size is 0\n", __func__); + return; + } + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + command, result, le16_to_cpu(cmd->size), seq_num); + + /* Update sequence number */ + seq_num = HOST_GET_SEQ_NO(seq_num); + /* Clear RET_BIT from HOST */ + command &= HOST_CMD_ID_MASK; + + if (command != HOST_CMD_802_11_PS_MODE_ENH) { + nxpwifi_dbg(adapter, ERROR, + "%s: rcvd unexpected resp for cmd %#x, result = %x\n", + __func__, command, result); + return; + } + + if (result) { + nxpwifi_dbg(adapter, ERROR, + "%s: sleep confirm cmd failed\n", + __func__); + adapter->pm_wakeup_card_req = false; + adapter->ps_state = PS_STATE_AWAKE; + return; + } + adapter->pm_wakeup_card_req = true; + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags)) + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + true); + adapter->ps_state = PS_STATE_SLEEP; + cmd->command = cpu_to_le16(command); + cmd->seq_num = cpu_to_le16(seq_num); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_sleep_confirm_resp); + +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, HOST_ACT_GEN_SET, + 0, &mask, false); +} + +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, cfg, false); +} + +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr) +{ + if (rts_thr < NXPWIFI_RTS_THRESHOLD_MIN || rts_thr > NXPWIFI_RTS_THRESHOLD_MAX) + rts_thr = NXPWIFI_RTS_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, RTS_THRESH_I, &rts_thr, true); +} + +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr) +{ + if (frag_thr < NXPWIFI_FRAG_THRESHOLD_MIN || + frag_thr > NXPWIFI_FRAG_THRESHOLD_MAX) + frag_thr = NXPWIFI_FRAG_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, FRAG_THRESH_I, &frag_thr, + true); +} + +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_SET_BSS_MODE, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, cfg, true); +} + +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, HOST_ACT_GEN_SET, + 0, NULL, false); +} + +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, HOST_ACT_GEN_GET, + oid, value, true); +} + +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_SET, 0, antcfg, + true); +} + +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + *tx_ant = priv->tx_ant; + *rx_ant = priv->rx_ant; + } + + return ret; +} + +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_SYS_RESET, + HOST_ACT_GEN_SET, 0, NULL, true); +} + +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_STA_LIST, HOST_ACT_GEN_GET, + 0, NULL, true); +} + +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, HOST_ACT_GEN_SET, 0, + bitmap_rates, true); +} + +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, 0, 0, subsc_evt, + true); +} + +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_STA_DEAUTH, HOST_ACT_GEN_SET, 0, mac, true); +} + +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, HOST_ACT_GEN_SET, + 0, bg_scan_cfg, true); +} + +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MEF_CFG, HOST_ACT_GEN_SET, 0, mef_cfg, + true); +} + +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_COALESCE_CFG, HOST_ACT_GEN_SET, 0, + coalesce_cfg, true); +} + +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_ADD_NEW_STATION, HOST_ACT_ADD_STA, 0, + add_sta, true); +} + +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd) +{ + return nxpwifi_send_cmd(priv, 0, 0, 0, hostcmd, true); +} + +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, HOST_ACT_GEN_SET, 0, + radar_params, true); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.h b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h new file mode 100644 index 000000000000..6112760b697e --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CMD_EVT_H_ +#define _NXPWIFI_CMD_EVT_H_ + +struct nxpwifi_cmd_entry { + u16 cmd_no; + int (*prepare_cmd)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type); + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_evt_entry { + u32 event_cause; + int (*event_handler)(struct nxpwifi_private *priv); +}; + +static inline int +nxpwifi_cmd_fill_head_only(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync); +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid); +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init); +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type); +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params); +void nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_disable); +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config); +void nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data); +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef); +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg); +int nxpwifi_process_event(struct nxpwifi_adapter *adapter); +int nxpwifi_process_sta_event(struct nxpwifi_private *priv); +int nxpwifi_process_uap_event(struct nxpwifi_private *priv); +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason, + bool from_ap); +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event); +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask); +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg); +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr); +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr); +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv); +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg); +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv); +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv); +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv); +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value); +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg); +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant); +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv); +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv); +int nxpwifi_cfg80211_deinit_p2p(struct nxpwifi_private *priv); +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv); +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates); +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt); +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac); +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg); +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg); +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg); +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta); +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd); +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params); +#endif /* !_NXPWIFI_CMD_EVT_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/debugfs.c b/drivers/net/wireless/nxp/nxpwifi/debugfs.c new file mode 100644 index 000000000000..ccaf0eae37e3 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/debugfs.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: debugfs + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +static struct dentry *nxpwifi_dfs_dir; + +static char *bss_modes[] = { + "UNSPECIFIED", + "ADHOC", + "STATION", + "AP", + "AP_VLAN", + "WDS", + "MONITOR", + "MESH_POINT", + "P2P_CLIENT", + "P2P_GO", + "P2P_DEVICE", +}; + +/* + * debugfs "info" read handler: dump driver name/version, interface, BSS mode, + * link state, MAC, counters; STA adds SSID/BSSID/channel/country/region and + * multicast list. + */ +static ssize_t +nxpwifi_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + struct net_device *netdev = priv->netdev; + struct netdev_hw_addr *ha; + struct netdev_queue *txq; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page, fmt[64]; + struct nxpwifi_bss_info info; + ssize_t ret; + int i = 0; + + if (!p) + return -ENOMEM; + + memset(&info, 0, sizeof(info)); + ret = nxpwifi_get_bss_info(priv, &info); + if (ret) + goto free_and_exit; + + nxpwifi_drv_get_driver_version(priv->adapter, fmt, sizeof(fmt) - 1); + + nxpwifi_get_ver_ext(priv, 0); + + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + p += sprintf(p, "driver_version = %s", fmt); + p += sprintf(p, "\nverext = %s", priv->version_str); + p += sprintf(p, "\ninterface_name=\"%s\"\n", netdev->name); + + if (info.bss_mode >= ARRAY_SIZE(bss_modes)) + p += sprintf(p, "bss_mode=\"%d\"\n", info.bss_mode); + else + p += sprintf(p, "bss_mode=\"%s\"\n", bss_modes[info.bss_mode]); + + p += sprintf(p, "media_state=\"%s\"\n", + (!priv->media_connected ? "Disconnected" : "Connected")); + p += sprintf(p, "mac_address=\"%pM\"\n", netdev->dev_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + p += sprintf(p, "multicast_count=\"%d\"\n", + netdev_mc_count(netdev)); + p += sprintf(p, "essid=\"%.*s\"\n", info.ssid.ssid_len, + info.ssid.ssid); + p += sprintf(p, "bssid=\"%pM\"\n", info.bssid); + p += sprintf(p, "channel=\"%d\"\n", (int)info.bss_chan); + p += sprintf(p, "country_code = \"%s\"\n", info.country_code); + p += sprintf(p, "region_code=\"0x%x\"\n", + priv->adapter->region_code); + + netdev_for_each_mc_addr(ha, netdev) + p += sprintf(p, "multicast_address[%d]=\"%pM\"\n", + i++, ha->addr); + } + + p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes); + p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes); + p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets); + p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets); + p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped); + p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped); + p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors); + p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors); + p += sprintf(p, "carrier %s\n", ((netif_carrier_ok(priv->netdev)) + ? "on" : "off")); + p += sprintf(p, "tx queue"); + for (i = 0; i < netdev->num_tx_queues; i++) { + txq = netdev_get_tx_queue(netdev, i); + p += sprintf(p, " %d:%s", i, netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "getlog" read handler: dump firmware/802.11 counters (retry, RTS/ACK, dup, + * frag, mcast, FCS, beacon stats). + */ +static ssize_t +nxpwifi_getlog_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_ds_get_stats stats; + + if (!p) + return -ENOMEM; + + memset(&stats, 0, sizeof(stats)); + ret = nxpwifi_get_stats_info(priv, &stats); + if (ret) + goto free_and_exit; + + p += sprintf(p, "\n" + "mcasttxframe %u\n" + "failed %u\n" + "retry %u\n" + "multiretry %u\n" + "framedup %u\n" + "rtssuccess %u\n" + "rtsfailure %u\n" + "ackfailure %u\n" + "rxfrag %u\n" + "mcastrxframe %u\n" + "fcserror %u\n" + "txframe %u\n" + "wepicverrcnt-1 %u\n" + "wepicverrcnt-2 %u\n" + "wepicverrcnt-3 %u\n" + "wepicverrcnt-4 %u\n" + "bcn_rcv_cnt %u\n" + "bcn_miss_cnt %u\n", + stats.mcast_tx_frame, + stats.failed, + stats.retry, + stats.multi_retry, + stats.frame_dup, + stats.rts_success, + stats.rts_failure, + stats.ack_failure, + stats.rx_frag, + stats.mcast_rx_frame, + stats.fcs_error, + stats.tx_frame, + stats.wep_icv_error[0], + stats.wep_icv_error[1], + stats.wep_icv_error[2], + stats.wep_icv_error[3], + stats.bcn_rcv_cnt, + stats.bcn_miss_cnt); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "histogram" read handler: report sample count and per-rate/SNR/noise + * floor/signal strength histograms. + */ +static ssize_t +nxpwifi_histogram_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + ssize_t ret; + struct nxpwifi_histogram_data *phist_data; + int i, value; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + + if (!p) + return -ENOMEM; + + if (!priv || !priv->hist_data) { + ret = -EFAULT; + goto free_and_exit; + } + + phist_data = priv->hist_data; + + p += sprintf(p, "\n" + "total samples = %d\n", + atomic_read(&phist_data->num_samples)); + + p += sprintf(p, + "rx rates (in Mbps): 0=1M 1=2M 2=5.5M 3=11M 4=6M 5=9M 6=12M\n" + "7=18M 8=24M 9=36M 10=48M 11=54M 12-27=MCS0-15(BW20) 28-43=MCS0-15(BW40)\n"); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + p += sprintf(p, + "44-53=MCS0-9(VHT:BW20) 54-63=MCS0-9(VHT:BW40) 64-73=MCS0-9(VHT:BW80)\n\n"); + } else { + p += sprintf(p, "\n"); + } + + for (i = 0; i < NXPWIFI_MAX_RX_RATES; i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", i, value); + } + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + for (i = NXPWIFI_MAX_RX_RATES; i < NXPWIFI_MAX_AC_RX_RATES; + i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", + i, value); + } + } + + for (i = 0; i < NXPWIFI_MAX_SNR; i++) { + value = atomic_read(&phist_data->snr[i]); + if (value) + p += sprintf(p, "snr[%02ddB] = %d\n", i, value); + } + for (i = 0; i < NXPWIFI_MAX_NOISE_FLR; i++) { + value = atomic_read(&phist_data->noise_flr[i]); + if (value) + p += sprintf(p, "noise_flr[%02ddBm] = %d\n", + (int)(i - 128), value); + } + for (i = 0; i < NXPWIFI_MAX_SIG_STRENGTH; i++) { + value = atomic_read(&phist_data->sig_str[i]); + if (value) + p += sprintf(p, "sig_strength[-%02ddBm] = %d\n", + i, value); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_histogram_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + + if (priv && priv->hist_data) + nxpwifi_hist_data_reset(priv); + return 0; +} + +static struct nxpwifi_debug_info info; + +/* debugfs "debug" read handler: dump adapter debug info and BA/reorder tables. */ +static ssize_t +nxpwifi_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + + if (!p) + return -ENOMEM; + + ret = nxpwifi_get_debug_info(priv, &info); + if (ret) + goto free_and_exit; + + p += nxpwifi_debug_info_to_buffer(priv, p, &info); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static u32 saved_reg_type, saved_reg_offset, saved_reg_value; + +/* + * debugfs "regrdwr" write handler: parse and store for + * readback/IO. + */ +static ssize_t +nxpwifi_regrdwr_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret; + u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value); + + if (rv != 3) { + ret = -EINVAL; + goto done; + } + + if (reg_type == 0 || reg_offset == 0) { + ret = -EINVAL; + goto done; + } else { + saved_reg_type = reg_type; + saved_reg_offset = reg_offset; + saved_reg_value = reg_value; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* + * debugfs "regrdwr" read handler: perform pending register read/write and return + * . + */ +static ssize_t +nxpwifi_regrdwr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos = 0, ret = 0; + u32 reg_value; + + if (!buf) + return -ENOMEM; + + if (!saved_reg_type) { + /* No command has been given */ + pos += snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + /* Set command has been given */ + if (saved_reg_value != UINT_MAX) { + ret = nxpwifi_reg_write(priv, saved_reg_type, saved_reg_offset, + saved_reg_value); + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", + saved_reg_type, saved_reg_offset, + saved_reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + goto done; + } + /* Get command has been given */ + ret = nxpwifi_reg_read(priv, saved_reg_type, + saved_reg_offset, ®_value); + if (ret) { + ret = -EINVAL; + goto done; + } + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", saved_reg_type, + saved_reg_offset, reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + +done: + free_page(addr); + return ret; +} + +/* debugfs "debug_mask" read handler: show driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)page; + size_t ret = 0; + int pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "debug mask=0x%08x\n", + priv->adapter->debug_mask); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(page); + return ret; +} + +/* debugfs "debug_mask" write handler: set driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + unsigned long debug_mask; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (kstrtoul(buf, 0, &debug_mask)) { + ret = -EINVAL; + goto done; + } + + priv->adapter->debug_mask = debug_mask; + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "verext" write handler: select extended version string. */ +static ssize_t +nxpwifi_verext_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + u32 versionstrsel; + struct nxpwifi_private *priv = (void *)file->private_data; + + ret = kstrtou32_from_user(ubuf, count, 10, &versionstrsel); + if (ret) + return ret; + + priv->versionstrsel = versionstrsel; + + return count; +} + +/* debugfs "verext" read handler: show extended version string. */ +static ssize_t +nxpwifi_verext_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + char buf[256]; + int ret; + + nxpwifi_get_ver_ext(priv, priv->versionstrsel); + ret = snprintf(buf, sizeof(buf), "version string: %s\n", + priv->version_str); + + return simple_read_from_buffer(ubuf, count, ppos, buf, ret); +} + +/* debugfs "memrw" write handler: read/write firmware memory (addr, value). */ +static ssize_t +nxpwifi_memrw_write(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos) +{ + int ret; + char cmd; + struct nxpwifi_ds_mem_rw mem_rw; + u16 cmd_action; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%c %x %x", &cmd, &mem_rw.addr, &mem_rw.value); + if (ret != 3) { + ret = -EINVAL; + goto done; + } + + if ((cmd == 'r') || (cmd == 'R')) { + cmd_action = HOST_ACT_GEN_GET; + mem_rw.value = 0; + } else if ((cmd == 'w') || (cmd == 'W')) { + cmd_action = HOST_ACT_GEN_SET; + } else { + ret = -EINVAL; + goto done; + } + + memcpy(&priv->mem_rw, &mem_rw, sizeof(mem_rw)); + ret = nxpwifi_send_cmd(priv, HOST_CMD_MEM_ACCESS, cmd_action, 0, + &mem_rw, true); + if (!ret) + ret = count; + +done: + kfree(buf); + return ret; +} + +/* debugfs "memrw" read handler: show last memory access result. */ +static ssize_t +nxpwifi_memrw_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int ret, pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "0x%x 0x%x\n", priv->mem_rw.addr, + priv->mem_rw.value); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static u32 saved_offset = -1, saved_bytes = -1; + +/* debugfs "rdeeprom" write handler: set EEPROM offset/length to read. */ +static ssize_t +nxpwifi_rdeeprom_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret = 0; + int offset = -1, bytes = -1; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%d %d", &offset, &bytes); + + if (rv != 2) { + ret = -EINVAL; + goto done; + } + + if (offset == -1 || bytes == -1) { + ret = -EINVAL; + goto done; + } else { + saved_offset = offset; + saved_bytes = bytes; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* debugfs "rdeeprom" read handler: dump EEPROM bytes from saved offset/length. */ +static ssize_t +nxpwifi_rdeeprom_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret, i; + u8 value[MAX_EEPROM_DATA]; + + if (!buf) + return -ENOMEM; + + if (saved_offset == -1) { + /* No command has been given */ + pos = snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + + /* Get command has been given */ + ret = nxpwifi_eeprom_read(priv, (u16)saved_offset, + (u16)saved_bytes, value); + if (ret) { + ret = -EINVAL; + goto out_free; + } + + pos = snprintf(buf, PAGE_SIZE, "%d %d ", saved_offset, saved_bytes); + + for (i = 0; i < saved_bytes; i++) + pos += scnprintf(buf + pos, PAGE_SIZE - pos, "%d ", value[i]); + +done: + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); +out_free: + free_page(addr); + return ret; +} + +/* + * debugfs "hscfg" write handler: configure host-sleep (conditions/gpio/gap) or + * cancel. + */ +static ssize_t +nxpwifi_hscfg_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + int ret, arg_num; + struct nxpwifi_ds_hs_cfg hscfg; + int conditions = HS_CFG_COND_DEF; + u32 gpio = HS_CFG_GPIO_DEF, gap = HS_CFG_GAP_DEF; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + arg_num = sscanf(buf, "%d %x %x", &conditions, &gpio, &gap); + + memset(&hscfg, 0, sizeof(struct nxpwifi_ds_hs_cfg)); + + if (arg_num > 3) { + nxpwifi_dbg(priv->adapter, ERROR, + "Too many arguments\n"); + ret = -EINVAL; + goto done; + } + + if (arg_num >= 1 && arg_num < 3) + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + if (arg_num) { + if (conditions == HS_CFG_CANCEL) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + ret = count; + goto done; + } + hscfg.conditions = conditions; + } + if (arg_num >= 2) + hscfg.gpio = gpio; + if (arg_num == 3) + hscfg.gap = gap; + + hscfg.is_invoke_hostcmd = false; + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hscfg); + + nxpwifi_enable_hs(priv->adapter); + clear_bit(NXPWIFI_IS_HS_ENABLING, &priv->adapter->work_flags); + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "hscfg" read handler: show current host-sleep configuration. */ +static ssize_t +nxpwifi_hscfg_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret; + struct nxpwifi_ds_hs_cfg hscfg; + + if (!buf) + return -ENOMEM; + + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + pos = snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", hscfg.conditions, + hscfg.gpio, hscfg.gap); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static ssize_t +nxpwifi_timeshare_coex_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + char buf[3]; + bool timeshare_coex; + int ret; + unsigned int len; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_GET, 0, ×hare_coex, true); + if (ret) + return ret; + + len = sprintf(buf, "%d\n", timeshare_coex); + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static ssize_t +nxpwifi_timeshare_coex_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + bool timeshare_coex; + struct nxpwifi_private *priv = file->private_data; + int ret; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = kstrtobool_from_user(ubuf, count, ×hare_coex); + if (ret) + return ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_SET, 0, ×hare_coex, true); + if (ret) + return ret; + else + return count; +} + +static ssize_t +nxpwifi_reset_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (adapter->if_ops.card_reset) { + nxpwifi_dbg(adapter, INFO, "Resetting per request\n"); + adapter->if_ops.card_reset(adapter); + } + + return count; +} + +static ssize_t +nxpwifi_fake_radar_detect_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected during CAC\n"); + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + cfg80211_radar_event(adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + } else { + if (priv->bss_chandef.chan->dfs_cac_ms) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected\n"); + cfg80211_radar_event(adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + } + + return count; +} + +static ssize_t +nxpwifi_netmon_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_802_11_net_monitor netmon_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + ret = sscanf(buf, "%u %u %u %u %u", + &netmon_cfg.enable_net_mon, + &netmon_cfg.filter_flag, + &netmon_cfg.band, + &netmon_cfg.channel, + &netmon_cfg.chan_bandwidth); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, &netmon_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_setup_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u16 twt_mantissa, bcn_miss_threshold; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hu %hhu %hu", + &twt_cfg.param.twt_setup.implicit, + &twt_cfg.param.twt_setup.announced, + &twt_cfg.param.twt_setup.trigger_enabled, + &twt_cfg.param.twt_setup.twt_info_disabled, + &twt_cfg.param.twt_setup.negotiation_type, + &twt_cfg.param.twt_setup.twt_wakeup_duration, + &twt_cfg.param.twt_setup.flow_identifier, + &twt_cfg.param.twt_setup.hard_constraint, + &twt_cfg.param.twt_setup.twt_exponent, + &twt_mantissa, + &twt_cfg.param.twt_setup.twt_request, + &bcn_miss_threshold); + + twt_cfg.param.twt_setup.twt_mantissa = cpu_to_le16(twt_mantissa); + twt_cfg.param.twt_setup.bcn_miss_threshold = cpu_to_le16(bcn_miss_threshold); + twt_cfg.sub_id = NXPWIFI_11AX_TWT_SETUP_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_teardown_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu", + &twt_cfg.param.twt_teardown.flow_identifier, + &twt_cfg.param.twt_teardown.negotiation_type, + &twt_cfg.param.twt_teardown.teardown_all_twt); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_TEARDOWN_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_report_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_twt_cfg twt_cfg; + u8 num, i, j; + + if (!p) + return -ENOMEM; + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_REPORT_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_GET, 0, + &twt_cfg, true); + if (ret) + goto done; + num = twt_cfg.param.twt_report.length / NXPWIFI_BTWT_REPORT_LEN; + num = num <= NXPWIFI_BTWT_REPORT_MAX_NUM ? num : NXPWIFI_BTWT_REPORT_MAX_NUM; + p += sprintf(p, "\ntwt_report len %hhu, num %hhu, twt_report_info:\n", + twt_cfg.param.twt_report.length, num); + for (i = 0; i < num; i++) { + p += sprintf(p, "id[%hu]:\r\n", i); + for (j = 0; j < NXPWIFI_BTWT_REPORT_LEN; j++) { + p += sprintf(p, + " 0x%02x", + twt_cfg.param.twt_report.data[i * NXPWIFI_BTWT_REPORT_LEN + j]); + } + p += sprintf(p, "\r\n"); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +done: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_twt_information_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u32 suspend_duration; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %u", + &twt_cfg.param.twt_information.flow_identifier, &suspend_duration); + twt_cfg.param.twt_information.suspend_duration = cpu_to_le32(suspend_duration); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_INFORMATION_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +#define NXPWIFI_DFS_ADD_FILE(name) debugfs_create_file(#name, 0644, \ + priv->dfs_dev_dir, priv, \ + &nxpwifi_dfs_##name##_fops) + +#define NXPWIFI_DFS_FILE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_READ_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_WRITE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +NXPWIFI_DFS_FILE_READ_OPS(info); +NXPWIFI_DFS_FILE_READ_OPS(debug); +NXPWIFI_DFS_FILE_READ_OPS(getlog); +NXPWIFI_DFS_FILE_OPS(regrdwr); +NXPWIFI_DFS_FILE_OPS(rdeeprom); +NXPWIFI_DFS_FILE_OPS(memrw); +NXPWIFI_DFS_FILE_OPS(hscfg); +NXPWIFI_DFS_FILE_OPS(histogram); +NXPWIFI_DFS_FILE_OPS(debug_mask); +NXPWIFI_DFS_FILE_OPS(timeshare_coex); +NXPWIFI_DFS_FILE_WRITE_OPS(reset); +NXPWIFI_DFS_FILE_WRITE_OPS(fake_radar_detect); +NXPWIFI_DFS_FILE_OPS(verext); +NXPWIFI_DFS_FILE_WRITE_OPS(netmon); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_setup); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_teardown); +NXPWIFI_DFS_FILE_READ_OPS(twt_report); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_information); + +/* Create per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv) +{ + if (!nxpwifi_dfs_dir || !priv) + return; + + priv->dfs_dev_dir = debugfs_create_dir(priv->netdev->name, + nxpwifi_dfs_dir); + + NXPWIFI_DFS_ADD_FILE(info); + NXPWIFI_DFS_ADD_FILE(debug); + NXPWIFI_DFS_ADD_FILE(getlog); + NXPWIFI_DFS_ADD_FILE(regrdwr); + NXPWIFI_DFS_ADD_FILE(rdeeprom); + + NXPWIFI_DFS_ADD_FILE(memrw); + NXPWIFI_DFS_ADD_FILE(hscfg); + NXPWIFI_DFS_ADD_FILE(histogram); + NXPWIFI_DFS_ADD_FILE(debug_mask); + NXPWIFI_DFS_ADD_FILE(timeshare_coex); + NXPWIFI_DFS_ADD_FILE(reset); + NXPWIFI_DFS_ADD_FILE(fake_radar_detect); + NXPWIFI_DFS_ADD_FILE(verext); + NXPWIFI_DFS_ADD_FILE(netmon); + NXPWIFI_DFS_ADD_FILE(twt_setup); + NXPWIFI_DFS_ADD_FILE(twt_teardown); + NXPWIFI_DFS_ADD_FILE(twt_report); + NXPWIFI_DFS_ADD_FILE(twt_information); +} + +/* Remove per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv) +{ + if (!priv) + return; + + debugfs_remove_recursive(priv->dfs_dev_dir); +} + +/* Create top-level debugfs directory. */ +void +nxpwifi_debugfs_init(void) +{ + if (!nxpwifi_dfs_dir) + nxpwifi_dfs_dir = debugfs_create_dir("nxpwifi", NULL); +} + +/* Remove top-level debugfs directory. */ +void +nxpwifi_debugfs_remove(void) +{ + debugfs_remove(nxpwifi_dfs_dir); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/ethtool.c b/drivers/net/wireless/nxp/nxpwifi/ethtool.c new file mode 100644 index 000000000000..aabb635afcf5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ethtool.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: ethtool + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" + +static void nxpwifi_ethtool_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = le32_to_cpu(priv->adapter->hs_cfg.conditions); + + wol->supported = WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY; + + if (conditions == HS_CFG_COND_DEF) + return; + + if (conditions & HS_CFG_COND_UNICAST_DATA) + wol->wolopts |= WAKE_UCAST; + if (conditions & HS_CFG_COND_MULTICAST_DATA) + wol->wolopts |= WAKE_MCAST; + if (conditions & HS_CFG_COND_BROADCAST_DATA) + wol->wolopts |= WAKE_BCAST; + if (conditions & HS_CFG_COND_MAC_EVENT) + wol->wolopts |= WAKE_PHY; +} + +static int nxpwifi_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = 0; + + if (wol->wolopts & ~(WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY)) + return -EOPNOTSUPP; + + if (wol->wolopts & WAKE_UCAST) + conditions |= HS_CFG_COND_UNICAST_DATA; + if (wol->wolopts & WAKE_MCAST) + conditions |= HS_CFG_COND_MULTICAST_DATA; + if (wol->wolopts & WAKE_BCAST) + conditions |= HS_CFG_COND_BROADCAST_DATA; + if (wol->wolopts & WAKE_PHY) + conditions |= HS_CFG_COND_MAC_EVENT; + if (wol->wolopts == 0) + conditions |= HS_CFG_COND_DEF; + priv->adapter->hs_cfg.conditions = cpu_to_le32(conditions); + + return 0; +} + +const struct ethtool_ops nxpwifi_ethtool_ops = { + .get_wol = nxpwifi_ethtool_get_wol, + .set_wol = nxpwifi_ethtool_set_wol, +}; diff --git a/drivers/net/wireless/nxp/nxpwifi/fw.h b/drivers/net/wireless/nxp/nxpwifi/fw.h new file mode 100644 index 000000000000..188110b020cf --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/fw.h @@ -0,0 +1,2475 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: Firmware-specific macros and structures + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_FW_H_ +#define _NXPWIFI_FW_H_ + +#include + +#define INTF_HEADER_LEN 4 + +struct rfc_1042_hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + __be16 snap_type; +} __packed; + +struct rx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct tx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct nxpwifi_fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_length; + __le32 crc; +} __packed; + +struct nxpwifi_fw_data { + struct nxpwifi_fw_header header; + __le32 seq_num; + u8 data[]; +} __packed; + +struct nxpwifi_fw_dump_header { + __le16 seq_num; + __le16 reserved; + __le16 type; + __le16 len; +} __packed; + +#define FW_DUMP_INFO_ENDED 0x0002 + +#define NXPWIFI_FW_DNLD_CMD_1 0x1 +#define NXPWIFI_FW_DNLD_CMD_5 0x5 +#define NXPWIFI_FW_DNLD_CMD_6 0x6 +#define NXPWIFI_FW_DNLD_CMD_7 0x7 + +#define B_SUPPORTED_RATES 5 +#define G_SUPPORTED_RATES 9 +#define BG_SUPPORTED_RATES 13 +#define A_SUPPORTED_RATES 9 +#define HOSTCMD_SUPPORTED_RATES 14 +#define N_SUPPORTED_RATES 3 +#define ALL_802_11_BANDS \ + (BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN | BAND_AAC | BAND_GAC) +#define FW_MULTI_BANDS_SUPPORT \ + (BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12) | BIT(13)) +#define IS_SUPPORT_MULTI_BANDS(adapter) \ + ((adapter)->fw_cap_info & FW_MULTI_BANDS_SUPPORT) + +/* + * Map fw_cap_info for default bands: shift 11ac flags so bits + * 11:GN, 12:AN, 13:GAC, 14:AAC match driver layout after >>8. + */ +#define GET_FW_DEFAULT_BANDS(adapter) ({\ + typeof(adapter) (_adapter) = adapter; \ + (((((_adapter->fw_cap_info & 0x3000) << 1) | \ + (_adapter->fw_cap_info & ~0xF000)) \ + >> 8) & \ + ALL_802_11_BANDS); \ + }) + +#define HOST_WEP_KEY_INDEX_MASK 0x3fff + +#define KEY_INFO_ENABLED 0x01 +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES, + KEY_TYPE_ID_WAPI, + KEY_TYPE_ID_AES_CMAC, + KEY_TYPE_ID_GCMP, + KEY_TYPE_ID_GCMP_256, + KEY_TYPE_ID_CCMP_256, + KEY_TYPE_ID_BIP_GMAC_128, + KEY_TYPE_ID_BIP_GMAC_256, +}; + +#define WPA_PN_SIZE 8 +#define KEY_PARAMS_FIXED_LEN 10 +#define KEY_INDEX_MASK 0xf +#define KEY_API_VER_MAJOR_V2 2 + +#define KEY_MCAST BIT(0) +#define KEY_UNICAST BIT(1) +#define KEY_ENABLED BIT(2) +#define KEY_DEFAULT BIT(3) +#define KEY_TX_KEY BIT(4) +#define KEY_RX_KEY BIT(5) +#define KEY_IGTK BIT(10) + +#define MAX_POLL_TRIES 10000 +#define MAX_FIRMWARE_POLL_TRIES 300 + +#define FIRMWARE_READY_SDIO 0xfedc +#define FIRMWARE_READY_PCIE 0xfedcba00 + +#define NXPWIFI_COEX_MODE_TIMESHARE 0x01 +#define NXPWIFI_COEX_MODE_SPATIAL 0x82 + +enum nxpwifi_usb_ep { + NXPWIFI_USB_EP_CMD_EVENT = 1, + NXPWIFI_USB_EP_DATA = 2, + NXPWIFI_USB_EP_DATA_CH2 = 3, +}; + +enum NXPWIFI_802_11_PRIVACY_FILTER { + NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL, + NXPWIFI_802_11_PRIV_FILTER_8021X_WEP +}; + +#define CAL_SNR(RSSI, NF) ((s16)((s16)(RSSI) - (s16)(NF))) +#define CAL_RSSI(SNR, NF) ((s16)((s16)(SNR) + (s16)(NF))) + +#define UAP_BSS_PARAMS_I 0 +#define UAP_CUSTOM_IE_I 1 +#define NXPWIFI_AUTO_IDX_MASK 0xffff +#define NXPWIFI_DELETE_MASK 0x0000 +#define MGMT_MASK_ASSOC_REQ 0x01 +#define MGMT_MASK_REASSOC_REQ 0x04 +#define MGMT_MASK_ASSOC_RESP 0x02 +#define MGMT_MASK_REASSOC_RESP 0x08 +#define MGMT_MASK_PROBE_REQ 0x10 +#define MGMT_MASK_PROBE_RESP 0x20 +#define MGMT_MASK_BEACON 0x100 + +#define TLV_TYPE_UAP_SSID 0x0000 +#define TLV_TYPE_UAP_RATES 0x0001 +#define TLV_TYPE_PWR_CONSTRAINT 0x0020 +#define TLV_TYPE_HT_CAPABILITY 0x002d +#define TLV_TYPE_EXTENSION_ID 0x00ff + +#define PROPRIETARY_TLV_BASE_ID 0x0100 +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_BGSCAN_START_LATER (PROPRIETARY_TLV_BASE_ID + 30) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_STA_MAC_ADDR (PROPRIETARY_TLV_BASE_ID + 32) +#define TLV_TYPE_BSSID (PROPRIETARY_TLV_BASE_ID + 35) +#define TLV_TYPE_CHANNELBANDLIST (PROPRIETARY_TLV_BASE_ID + 42) +#define TLV_TYPE_UAP_MAC_ADDRESS (PROPRIETARY_TLV_BASE_ID + 43) +#define TLV_TYPE_UAP_BEACON_PERIOD (PROPRIETARY_TLV_BASE_ID + 44) +#define TLV_TYPE_UAP_DTIM_PERIOD (PROPRIETARY_TLV_BASE_ID + 45) +#define TLV_TYPE_UAP_BCAST_SSID (PROPRIETARY_TLV_BASE_ID + 48) +#define TLV_TYPE_UAP_PREAMBLE_CTL (PROPRIETARY_TLV_BASE_ID + 49) +#define TLV_TYPE_UAP_RTS_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 51) +#define TLV_TYPE_UAP_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 57) +#define TLV_TYPE_UAP_WEP_KEY (PROPRIETARY_TLV_BASE_ID + 59) +#define TLV_TYPE_UAP_WPA_PASSPHRASE (PROPRIETARY_TLV_BASE_ID + 60) +#define TLV_TYPE_UAP_ENCRY_PROTOCOL (PROPRIETARY_TLV_BASE_ID + 64) +#define TLV_TYPE_UAP_AKMP (PROPRIETARY_TLV_BASE_ID + 65) +#define TLV_TYPE_UAP_FRAG_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 70) +#define TLV_TYPE_RATE_DROP_CONTROL (PROPRIETARY_TLV_BASE_ID + 82) +#define TLV_TYPE_RATE_SCOPE (PROPRIETARY_TLV_BASE_ID + 83) +#define TLV_TYPE_POWER_GROUP (PROPRIETARY_TLV_BASE_ID + 84) +#define TLV_TYPE_BSS_SCAN_RSP (PROPRIETARY_TLV_BASE_ID + 86) +#define TLV_TYPE_BSS_SCAN_INFO (PROPRIETARY_TLV_BASE_ID + 87) +#define TLV_TYPE_CHANRPT_11H_BASIC (PROPRIETARY_TLV_BASE_ID + 91) +#define TLV_TYPE_UAP_RETRY_LIMIT (PROPRIETARY_TLV_BASE_ID + 93) +#define TLV_TYPE_ROBUST_COEX (PROPRIETARY_TLV_BASE_ID + 96) +#define TLV_TYPE_UAP_MGMT_FRAME (PROPRIETARY_TLV_BASE_ID + 104) +#define TLV_TYPE_MGMT_IE (PROPRIETARY_TLV_BASE_ID + 105) +#define TLV_TYPE_AUTO_DS_PARAM (PROPRIETARY_TLV_BASE_ID + 113) +#define TLV_TYPE_PS_PARAM (PROPRIETARY_TLV_BASE_ID + 114) +#define TLV_TYPE_UAP_PS_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 123) +#define TLV_TYPE_PWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 145) +#define TLV_TYPE_GWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 146) +#define TLV_TYPE_TX_PAUSE (PROPRIETARY_TLV_BASE_ID + 148) +#define TLV_TYPE_RXBA_SYNC (PROPRIETARY_TLV_BASE_ID + 153) +#define TLV_TYPE_COALESCE_RULE (PROPRIETARY_TLV_BASE_ID + 154) +#define TLV_TYPE_KEY_PARAM_V2 (PROPRIETARY_TLV_BASE_ID + 156) +#define TLV_TYPE_REGION_DOMAIN_CODE (PROPRIETARY_TLV_BASE_ID + 171) +#define TLV_TYPE_REPEAT_COUNT (PROPRIETARY_TLV_BASE_ID + 176) +#define TLV_TYPE_PS_PARAMS_IN_HS (PROPRIETARY_TLV_BASE_ID + 181) +#define TLV_TYPE_MULTI_CHAN_INFO (PROPRIETARY_TLV_BASE_ID + 183) +#define TLV_TYPE_MC_GROUP_INFO (PROPRIETARY_TLV_BASE_ID + 184) +#define TLV_TYPE_SCAN_CHANNEL_GAP (PROPRIETARY_TLV_BASE_ID + 197) +#define TLV_TYPE_API_REV (PROPRIETARY_TLV_BASE_ID + 199) +#define TLV_TYPE_CHANNEL_STATS (PROPRIETARY_TLV_BASE_ID + 198) +#define TLV_BTCOEX_WL_AGGR_WINSIZE (PROPRIETARY_TLV_BASE_ID + 202) +#define TLV_BTCOEX_WL_SCANTIME (PROPRIETARY_TLV_BASE_ID + 203) +#define TLV_TYPE_BSS_MODE (PROPRIETARY_TLV_BASE_ID + 206) +#define TLV_TYPE_RANDOM_MAC (PROPRIETARY_TLV_BASE_ID + 236) +#define TLV_TYPE_CHAN_ATTR_CFG (PROPRIETARY_TLV_BASE_ID + 237) +#define TLV_TYPE_MAX_CONN (PROPRIETARY_TLV_BASE_ID + 279) +#define TLV_TYPE_HOST_MLME (PROPRIETARY_TLV_BASE_ID + 307) +#define TLV_TYPE_UAP_STA_FLAGS (PROPRIETARY_TLV_BASE_ID + 313) +#define TLV_TYPE_FW_CAP_INFO (PROPRIETARY_TLV_BASE_ID + 318) +#define TLV_TYPE_AX_ENABLE_SR (PROPRIETARY_TLV_BASE_ID + 322) +#define TLV_TYPE_AX_OBSS_PD_OFFSET (PROPRIETARY_TLV_BASE_ID + 323) +#define TLV_TYPE_SAE_PWE_MODE (PROPRIETARY_TLV_BASE_ID + 339) +#define TLV_TYPE_6E_INBAND_FRAMES (PROPRIETARY_TLV_BASE_ID + 345) +#define TLV_TYPE_SECURE_BOOT_UUID (PROPRIETARY_TLV_BASE_ID + 348) + +#define NXPWIFI_TX_DATA_BUF_SIZE_2K 2048 + +#define SSN_MASK 0xfff0 + +#define BA_RESULT_SUCCESS 0x0 +#define BA_RESULT_TIMEOUT 0x2 + +#define IS_BASTREAM_SETUP(ptr) ((ptr)->ba_status) + +#define BA_STREAM_NOT_ALLOWED 0xff + +#define IS_11N_ENABLED(priv) ({ \ + typeof(priv) (_priv) = priv; \ + (((_priv)->config_bands & BAND_GN || \ + (_priv)->config_bands & BAND_AN) && \ + (_priv)->curr_bss_params.bss_descriptor.bcn_ht_cap && \ + !(_priv)->curr_bss_params.bss_descriptor.disable_11n); \ + }) +#define INITIATOR_BIT(del_ba_param_set) (((del_ba_param_set) &\ + BIT(DELBA_INITIATOR_POS)) >> DELBA_INITIATOR_POS) + +#define NXPWIFI_TX_DATA_BUF_SIZE_4K 4096 +#define NXPWIFI_TX_DATA_BUF_SIZE_8K 8192 +#define NXPWIFI_TX_DATA_BUF_SIZE_12K 12288 + +#define ISSUPP_11NENABLED(fw_cap_info) ((fw_cap_info) & BIT(11)) +#define ISSUPP_DRCS_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(15)) +#define ISSUPP_SDIO_SPA_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(16)) +#define ISSUPP_RANDOM_MAC(fw_cap_info) ((fw_cap_info) & BIT(27)) +#define ISSUPP_FIRMWARE_SUPPLICANT(fw_cap_info) ((fw_cap_info) & BIT(21)) + +#define NXPWIFI_DEF_HT_CAP (IEEE80211_HT_CAP_DSSSCCK40 | \ + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | \ + IEEE80211_HT_CAP_SM_PS) + +#define NXPWIFI_DEF_11N_TX_BF_CAP 0x09E1E008 + +#define NXPWIFI_DEF_AMPDU IEEE80211_HT_AMPDU_PARM_FACTOR + +#define RXPD_FLAG_EXTRA_HEADER BIT(1) +/* channel number at bit 5-13 */ +#define RXPD_CHAN_MASK 0x3FE0 +/* DCM at bit 16 */ +#define RXPD_DCM_MASK 0x10000 + +/* + * dot11n dev_cap bits: 17:20/40MHz, 23:SGI20, 24:SGI40, 25:TXSTBC, + * 26:RXSTBC, 29:Greenfield. + */ +#define ISSUPP_CHANWIDTH40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(17)) +#define ISSUPP_SHORTGI20(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(23)) +#define ISSUPP_SHORTGI40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(24)) +#define ISSUPP_TXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(25)) +#define ISSUPP_RXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(26)) +#define ISSUPP_GREENFIELD(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(29)) +#define ISENABLED_40MHZ_INTOLERANT(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(8)) +#define ISSUPP_RXLDPC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(22)) +#define ISSUPP_BEAMFORMING(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(30)) +#define ISALLOWED_CHANWIDTH40(ht_param) ((ht_param) & BIT(2)) +#define GETSUPP_TXBASTREAMS(dot_11n_dev_cap) (((dot_11n_dev_cap) >> 18) & 0xF) + +/* AMPDU factor size */ +#define AMPDU_FACTOR_64K 0x03 +/* hw_dev_cap : MPDU DENSITY */ +#define GET_MPDU_DENSITY(hw_dev_cap) ((hw_dev_cap) & 0x7) + +/* httxcfg bits: 1:20/40, 4:GF, 5:SGI20, 6:SGI40. */ +#define NXPWIFI_FW_DEF_HTTXCFG (BIT(1) | BIT(4) | BIT(5) | BIT(6)) + +/* 11ac MCS map (1x1): stream0 supports 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_1X1 0xfffefffe + +/* 11ac MCS map (2x2): stream0/1 support 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_2X2 0xfffafffa + +#define GET_TXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) >> 4) +#define GET_RXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) & 0x0f) +#define SETHT_MCS32(x) (x[4] |= 1) +#define HT_STREAM_1X1 0x11 +#define HT_STREAM_2X2 0x22 + +#define SET_SECONDARYCHAN(radio_type, sec_chan) \ + ((radio_type) |= ((sec_chan) << 4)) + +#define LLC_SNAP_LEN 8 + +/* HW_SPEC fw_cap_info */ + +#define ISSUPP_11ACENABLED(fw_cap_info) ((fw_cap_info) & BIT(13)) +#define NO_NSS_SUPPORT 0x3 +#define GET_VHTNSSMCS(mcs_mapset, nss) \ + (((mcs_mapset) >> (2 * ((nss) - 1))) & 0x3) +#define SET_VHTNSSMCS(mcs_mapset, nss, value) \ + ((mcs_mapset) |= ((value) & 0x3) << (2 * ((nss) - 1))) +#define GET_DEVTXMCSMAP(dev_mcs_map) ((dev_mcs_map) >> 16) +#define GET_DEVRXMCSMAP(dev_mcs_map) ((dev_mcs_map) & 0xFFFF) + +/* Clear SU/MU beamformer/beamformee and sounding dimension bits. */ +#define NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK \ + (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE | \ + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK) + +#define MOD_CLASS_HR_DSSS 0x03 +#define MOD_CLASS_OFDM 0x07 +#define MOD_CLASS_HT 0x08 +#define HT_BW_20 0 +#define HT_BW_40 1 + +#define DFS_CHAN_MOVE_TIME 10000 + +#define ISSUPP_11AXENABLED(fw_cap_ext) ((fw_cap_ext) & BIT(7)) + +#define HOST_CMD_GET_HW_SPEC 0x0003 +#define HOST_CMD_802_11_SCAN 0x0006 +#define HOST_CMD_802_11_GET_LOG 0x000b +#define HOST_CMD_MAC_MULTICAST_ADR 0x0010 +#define HOST_CMD_802_11_ASSOCIATE 0x0012 +#define HOST_CMD_802_11_SNMP_MIB 0x0016 +#define HOST_CMD_MAC_REG_ACCESS 0x0019 +#define HOST_CMD_BBP_REG_ACCESS 0x001a +#define HOST_CMD_RF_REG_ACCESS 0x001b +#define HOST_CMD_RF_TX_PWR 0x001e +#define HOST_CMD_RF_ANTENNA 0x0020 +#define HOST_CMD_802_11_DEAUTHENTICATE 0x0024 +#define HOST_CMD_MAC_CONTROL 0x0028 +#define HOST_CMD_802_11_MAC_ADDRESS 0x004D +#define HOST_CMD_802_11_EEPROM_ACCESS 0x0059 +#define HOST_CMD_802_11D_DOMAIN_INFO 0x005b +#define HOST_CMD_802_11_KEY_MATERIAL 0x005e +#define HOST_CMD_802_11_BG_SCAN_CONFIG 0x006b +#define HOST_CMD_802_11_BG_SCAN_QUERY 0x006c +#define HOST_CMD_WMM_GET_STATUS 0x0071 +#define HOST_CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define HOST_CMD_802_11_TX_RATE_QUERY 0x007f +#define HOST_CMD_MEM_ACCESS 0x0086 +#define HOST_CMD_CFG_DATA 0x008f +#define HOST_CMD_VERSION_EXT 0x0097 +#define HOST_CMD_MEF_CFG 0x009a +#define HOST_CMD_RSSI_INFO 0x00a4 +#define HOST_CMD_FUNC_INIT 0x00a9 +#define HOST_CMD_FUNC_SHUTDOWN 0x00aa +#define HOST_CMD_PMIC_REG_ACCESS 0x00ad +#define HOST_CMD_APCMD_SYS_RESET 0x00af +#define HOST_CMD_UAP_SYS_CONFIG 0x00b0 +#define HOST_CMD_UAP_BSS_START 0x00b1 +#define HOST_CMD_UAP_BSS_STOP 0x00b2 +#define HOST_CMD_APCMD_STA_LIST 0x00b3 +#define HOST_CMD_UAP_STA_DEAUTH 0x00b5 +#define HOST_CMD_11N_CFG 0x00cd +#define HOST_CMD_11N_ADDBA_REQ 0x00ce +#define HOST_CMD_11N_ADDBA_RSP 0x00cf +#define HOST_CMD_11N_DELBA 0x00d0 +#define HOST_CMD_TXPWR_CFG 0x00d1 +#define HOST_CMD_TX_RATE_CFG 0x00d6 +#define HOST_CMD_RECONFIGURE_TX_BUFF 0x00d9 +#define HOST_CMD_CHAN_REPORT_REQUEST 0x00dd +#define HOST_CMD_AMSDU_AGGR_CTRL 0x00df +#define HOST_CMD_ROBUST_COEX 0x00e0 +#define HOST_CMD_802_11_PS_MODE_ENH 0x00e4 +#define HOST_CMD_802_11_HS_CFG_ENH 0x00e5 +#define HOST_CMD_CAU_REG_ACCESS 0x00ed +#define HOST_CMD_SET_BSS_MODE 0x00f7 +#define HOST_CMD_PCIE_DESC_DETAILS 0x00fa +#define HOST_CMD_802_11_NET_MONITOR 0x0102 +#define HOST_CMD_802_11_SCAN_EXT 0x0107 +#define HOST_CMD_COALESCE_CFG 0x010a +#define HOST_CMD_MGMT_FRAME_REG 0x010c +#define HOST_CMD_REMAIN_ON_CHAN 0x010d +#define HOST_CMD_GTK_REKEY_OFFLOAD_CFG 0x010f +#define HOST_CMD_11AC_CFG 0x0112 +#define HOST_CMD_HS_WAKEUP_REASON 0x0116 +#define HOST_CMD_MC_POLICY 0x0121 +#define HOST_CMD_FW_DUMP_EVENT 0x0125 +#define HOST_CMD_SDIO_SP_RX_AGGR_CFG 0x0223 +#define HOST_CMD_STA_CONFIGURE 0x023f +#define HOST_CMD_VDLL 0x0240 +#define HOST_CMD_CHAN_REGION_CFG 0x0242 +#define HOST_CMD_PACKET_AGGR_CTRL 0x0251 +#define HOST_CMD_ADD_NEW_STATION 0x025f +#define HOST_CMD_11AX_CFG 0x0266 +#define HOST_CMD_11AX_CMD 0x026d +#define HOST_CMD_TWT_CFG 0x0270 + +#define PROTOCOL_NO_SECURITY 0x01 +#define PROTOCOL_STATIC_WEP 0x02 +#define PROTOCOL_WPA 0x08 +#define PROTOCOL_WPA2 0x20 +#define PROTOCOL_WPA2_MIXED 0x28 +#define PROTOCOL_EAP 0x40 +#define KEY_MGMT_EAP 0x01 +#define KEY_MGMT_PSK 0x02 +#define KEY_MGMT_NONE 0x04 +#define KEY_MGMT_PSK_SHA256 0x100 +#define KEY_MGMT_OWE 0x200 +#define KEY_MGMT_SAE 0x400 +#define CIPHER_TKIP 0x04 +#define CIPHER_AES_CCMP 0x08 +#define VALID_CIPHER_BITMAP 0x0c + +enum ENH_PS_MODES { + EN_PS = 1, + DIS_PS = 2, + EN_AUTO_DS = 3, + DIS_AUTO_DS = 4, + SLEEP_CONFIRM = 5, + GET_PS = 0, + EN_AUTO_PS = 0xff, + DIS_AUTO_PS = 0xfe, +}; + +enum nxpwifi_channel_flags { + NXPWIFI_CHANNEL_PASSIVE = BIT(0), + NXPWIFI_CHANNEL_DFS = BIT(1), + NXPWIFI_CHANNEL_NOHT40 = BIT(2), + NXPWIFI_CHANNEL_NOHT80 = BIT(3), + NXPWIFI_CHANNEL_DISABLED = BIT(7), +}; + +#define HOST_RET_BIT 0x8000 +#define HOST_ACT_GEN_GET 0x0000 +#define HOST_ACT_GEN_SET 0x0001 +#define HOST_ACT_GEN_REMOVE 0x0004 +#define HOST_ACT_BITWISE_SET 0x0002 +#define HOST_ACT_BITWISE_CLR 0x0003 +#define HOST_RESULT_OK 0x0000 +#define HOST_ACT_MAC_RX_ON BIT(0) +#define HOST_ACT_MAC_TX_ON BIT(1) +#define HOST_ACT_MAC_WEP_ENABLE BIT(3) +#define HOST_ACT_MAC_ETHERNETII_ENABLE BIT(4) +#define HOST_ACT_MAC_PROMISCUOUS_ENABLE BIT(7) +#define HOST_ACT_MAC_ALL_MULTICAST_ENABLE BIT(8) +#define HOST_ACT_MAC_DYNAMIC_BW_ENABLE BIT(16) + +#define HOST_BSS_MODE_IBSS 0x0002 +#define HOST_BSS_MODE_ANY 0x0003 + +#define HOST_SCAN_RADIO_TYPE_BG 0 +#define HOST_SCAN_RADIO_TYPE_A 1 + +#define HS_CFG_CANCEL 0xffffffff +#define HS_CFG_COND_DEF 0x00000000 +#define HS_CFG_GPIO_DEF 0xff +#define HS_CFG_GAP_DEF 0xff +#define HS_CFG_COND_BROADCAST_DATA 0x00000001 +#define HS_CFG_COND_UNICAST_DATA 0x00000002 +#define HS_CFG_COND_MAC_EVENT 0x00000004 +#define HS_CFG_COND_MULTICAST_DATA 0x00000008 + +#define CONNECT_ERR_AUTH_ERR_STA_FAILURE 0xFFFB +#define CONNECT_ERR_ASSOC_ERR_TIMEOUT 0xFFFC +#define CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED 0xFFFD +#define CONNECT_ERR_AUTH_MSG_UNHANDLED 0xFFFE +#define CONNECT_ERR_STA_FAILURE 0xFFFF + +#define CMD_F_HOSTCMD BIT(0) + +#define HOST_CMD_ID_MASK 0x0fff + +#define HOST_SEQ_NUM_MASK 0x00ff + +#define HOST_BSS_NUM_MASK 0x0f00 + +#define HOST_BSS_TYPE_MASK 0xf000 + +#define HOST_ACT_SET_RX 0x0001 +#define HOST_ACT_SET_TX 0x0002 +#define HOST_ACT_SET_BOTH 0x0003 +#define HOST_ACT_GET_RX 0x0004 +#define HOST_ACT_GET_TX 0x0008 +#define HOST_ACT_GET_BOTH 0x000c + +#define HOST_ACT_REMOVE_STA 0x0 +#define HOST_ACT_ADD_STA 0x1 + +#define RF_ANTENNA_AUTO 0xFFFF + +#define HOST_SET_SEQ_NO_BSS_INFO(seq, num, type) \ + ((((seq) & 0x00ff) | \ + (((num) & 0x000f) << 8)) | \ + (((type) & 0x000f) << 12)) + +#define HOST_GET_SEQ_NO(seq) \ + ((seq) & HOST_SEQ_NUM_MASK) + +#define HOST_GET_BSS_NO(seq) \ + (((seq) & HOST_BSS_NUM_MASK) >> 8) + +#define HOST_GET_BSS_TYPE(seq) \ + (((seq) & HOST_BSS_TYPE_MASK) >> 12) + +#define EVENT_DUMMY_HOST_WAKEUP_SIGNAL 0x00000001 +#define EVENT_LINK_LOST 0x00000003 +#define EVENT_LINK_SENSED 0x00000004 +#define EVENT_MIB_CHANGED 0x00000006 +#define EVENT_INIT_DONE 0x00000007 +#define EVENT_DEAUTHENTICATED 0x00000008 +#define EVENT_DISASSOCIATED 0x00000009 +#define EVENT_PS_AWAKE 0x0000000a +#define EVENT_PS_SLEEP 0x0000000b +#define EVENT_MIC_ERR_MULTICAST 0x0000000d +#define EVENT_MIC_ERR_UNICAST 0x0000000e +#define EVENT_DEEP_SLEEP_AWAKE 0x00000010 +#define EVENT_WMM_STATUS_CHANGE 0x00000017 +#define EVENT_BG_SCAN_REPORT 0x00000018 +#define EVENT_RSSI_LOW 0x00000019 +#define EVENT_SNR_LOW 0x0000001a +#define EVENT_MAX_FAIL 0x0000001b +#define EVENT_RSSI_HIGH 0x0000001c +#define EVENT_SNR_HIGH 0x0000001d +#define EVENT_DATA_RSSI_LOW 0x00000024 +#define EVENT_DATA_SNR_LOW 0x00000025 +#define EVENT_DATA_RSSI_HIGH 0x00000026 +#define EVENT_DATA_SNR_HIGH 0x00000027 +#define EVENT_LINK_QUALITY 0x00000028 +#define EVENT_PORT_RELEASE 0x0000002b +#define EVENT_UAP_STA_DEAUTH 0x0000002c +#define EVENT_UAP_STA_ASSOC 0x0000002d +#define EVENT_UAP_BSS_START 0x0000002e +#define EVENT_PRE_BEACON_LOST 0x00000031 +#define EVENT_ADDBA 0x00000033 +#define EVENT_DELBA 0x00000034 +#define EVENT_BA_STREAM_TIEMOUT 0x00000037 +#define EVENT_AMSDU_AGGR_CTRL 0x00000042 +#define EVENT_UAP_BSS_IDLE 0x00000043 +#define EVENT_UAP_BSS_ACTIVE 0x00000044 +#define EVENT_WEP_ICV_ERR 0x00000046 +#define EVENT_HS_ACT_REQ 0x00000047 +#define EVENT_BW_CHANGE 0x00000048 +#define EVENT_UAP_MIC_COUNTERMEASURES 0x0000004c +#define EVENT_HOSTWAKE_STAIE 0x0000004d +#define EVENT_CHANNEL_SWITCH_ANN 0x00000050 +#define EVENT_RADAR_DETECTED 0x00000053 +#define EVENT_CHANNEL_REPORT_RDY 0x00000054 +#define EVENT_TX_DATA_PAUSE 0x00000055 +#define EVENT_EXT_SCAN_REPORT 0x00000058 +#define EVENT_RXBA_SYNC 0x00000059 +#define EVENT_REMAIN_ON_CHAN_EXPIRED 0x0000005f +#define EVENT_UNKNOWN_DEBUG 0x00000063 +#define EVENT_BG_SCAN_STOPPED 0x00000065 +#define EVENT_MULTI_CHAN_INFO 0x0000006a +#define EVENT_FW_DUMP_INFO 0x00000073 +#define EVENT_TX_STATUS_REPORT 0x00000074 +#define EVENT_BT_COEX_WLAN_PARA_CHANGE 0X00000076 +#define EVENT_VDLL_IND 0x00000081 + +#define EVENT_ID_MASK 0xffff +#define BSS_NUM_MASK 0xf + +#define EVENT_GET_BSS_NUM(event_cause) \ + (((event_cause) >> 16) & BSS_NUM_MASK) + +#define EVENT_GET_BSS_TYPE(event_cause) \ + (((event_cause) >> 24) & 0x00ff) + +#define NXPWIFI_MAX_PATTERN_LEN 40 +#define NXPWIFI_MAX_OFFSET_LEN 100 +#define NXPWIFI_MAX_ND_MATCH_SETS 10 + +#define STACK_NBYTES 100 +#define TYPE_DNUM 1 +#define TYPE_BYTESEQ 2 +#define MAX_OPERAND 0x40 +#define TYPE_EQ (MAX_OPERAND + 1) +#define TYPE_EQ_DNUM (MAX_OPERAND + 2) +#define TYPE_EQ_BIT (MAX_OPERAND + 3) +#define TYPE_AND (MAX_OPERAND + 4) +#define TYPE_OR (MAX_OPERAND + 5) +#define MEF_MODE_HOST_SLEEP 1 +#define MEF_ACTION_ALLOW_AND_WAKEUP_HOST 3 +#define MEF_ACTION_AUTO_ARP 0x10 +#define NXPWIFI_CRITERIA_BROADCAST BIT(0) +#define NXPWIFI_CRITERIA_UNICAST BIT(1) +#define NXPWIFI_CRITERIA_MULTICAST BIT(3) +#define NXPWIFI_MAX_SUPPORTED_IPADDR 4 + +#define NXPWIFI_DEF_CS_UNIT_TIME 2 +#define NXPWIFI_DEF_CS_THR_OTHERLINK 10 +#define NXPWIFI_DEF_THR_DIRECTLINK 0 +#define NXPWIFI_DEF_CS_TIME 10 +#define NXPWIFI_DEF_CS_TIMEOUT 16 +#define NXPWIFI_DEF_CS_REG_CLASS 12 +#define NXPWIFI_DEF_CS_PERIODICITY 1 + +#define NXPWIFI_FW_V15 15 + +#define NXPWIFI_MASTER_RADAR_DET_MASK BIT(1) + +struct nxpwifi_ie_types_header { + __le16 type; + __le16 len; +} __packed; + +struct nxpwifi_ie_types_data { + struct nxpwifi_ie_types_header header; + u8 data[]; +} __packed; + +/* Generic TLV wrapper for firmware data */ +struct nxpwifi_tlv { + __le16 type; + __le16 len; + u8 data[]; +} __packed; + +#define NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET 0x08 +#define NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS 0x20 + +enum HS_WAKEUP_REASON { + NO_HSWAKEUP_REASON = 0, + BCAST_DATA_MATCHED, + MCAST_DATA_MATCHED, + UCAST_DATA_MATCHED, + MASKTABLE_EVENT_MATCHED, + NON_MASKABLE_EVENT_MATCHED, + NON_MASKABLE_CONDITION_MATCHED, + MAGIC_PATTERN_MATCHED, + CONTROL_FRAME_MATCHED, + MANAGEMENT_FRAME_MATCHED, + GTK_REKEY_FAILURE, + RESERVED +}; + +struct txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + /* + * rate_info bit definition (FW encoded) + * + * [1:0] format + * 00 = legacy + * 01 = HT + * 10 = VHT + * 11 = HE + * + * [3:2] bandwidth + * 00 = 20 MHz + * 01 = 40 MHz + * 10 = 80 MHz + * 11 = 160 MHz + * + * [4] GI (HT/VHT) / HE GI LSB + * HT/VHT: + * 0 = LGI + * 1 = SGI + * + * HE: + * used as GI[0] + * + * [5] STBC + * 0 = no STBC + * 1 = STBC enabled + * + * [6] LDPC + * 0 = BCC + * 1 = LDPC + * + * [7] HE GI MSB + * + * HE GI encoding (combined from bit7:bit4): + * GI[1:0] = {bit7, bit4} + * + * 00 = 0.8 us + * 01 = 1.6 us + * 10 = 3.2 us + * 11 = reserved / undefined + */ + u8 rate_info; + u8 reserved[3]; + u8 flags; + u8 antenna; + /* toa_tod_tstamps: [31:0] ToA, [63:32] ToD (ns). */ + __le64 toa_tod_tstamps; + /* rx info */ + __le32 rx_info; + /* Reserved */ + u8 reserved3[8]; + u8 ta_mac[6]; + u8 reserved4[2]; +} __packed; + +struct radiotap_timestamp { + /* device timestamp */ + u64 device_timestamp; + /* accuracy */ + u16 accuracy; + /* + * unit: + * 0 milliseconds, + * 1 microseconds, + * 2 nanoseconds, + * 3-15 reserved + */ + u8 unit : 4; + /* + * position: + * 0 first bit (or symbol containing it) of MPDU - matches TSFT field + * 1 signal acquisition at start of PLCP + * 2 end of PPDU + * 3 end of MPDU (after FCS) + * 4-14 reserved + * 15 unknown or vendor/OOB defined + */ + u8 position : 4; + /* + * flags + * 0x01 32-bit counter (high 32 bits are unused) + * 0x02 accuracy known + * 0xFC reserved + */ + u8 flags; +} __packed; + +struct rxpd_extra_info { + /* flags */ + u8 flags; + /* channel.flags */ + u16 channel_flags; + /* mcs.known */ + u8 mcs_known; + /* mcs.flags */ + u8 mcs_flags; + /* vht/he sig1 */ + u32 vht_he_sig1; + /* vht/he sig2 */ + u32 vht_he_sig2; + /* HE user idx */ + u32 user_idx; + /** timestamp */ + struct radiotap_timestamp timestamp; + /** PLCP CRC Failed */ + u8 plcp_crc_failed; + u8 rssi_dbm_a; + u8 rssi_dbm_b; +} __packed; + +struct uap_txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct uap_rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + u8 ht_info; + u8 reserved[3]; + u8 flags; +} __packed; + +struct nxpwifi_auth { + __le16 auth_alg; + __le16 auth_transaction; + __le16 status_code; + /* possibly followed by Challenge text */ + u8 variable[]; +} __packed; + +struct nxpwifi_ieee80211_mgmt { + __le16 frame_control; + __le16 duration; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + struct nxpwifi_auth auth; +} __packed; + +struct nxpwifi_fw_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + __le16 total_bss; + __le16 cca_scan_dur; + __le16 cca_busy_dur; +} __packed; + +enum nxpwifi_chan_scan_mode_bitmasks { + NXPWIFI_PASSIVE_SCAN = BIT(0), + NXPWIFI_DISABLE_CHAN_FILT = BIT(1), + NXPWIFI_HIDDEN_SSID_REPORT = BIT(4), +}; + +struct nxpwifi_chan_scan_param_set { + u8 band_cfg; + u8 chan_number; + u8 chan_scan_mode_bmap; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_chan_list_param_set { + struct nxpwifi_ie_types_header header; + struct nxpwifi_chan_scan_param_set chan_scan_param[]; +} __packed; + +struct nxpwifi_ie_types_rxba_sync { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 tid; + u8 reserved; + __le16 seq_num; + __le16 bitmap_len; + u8 bitmap[]; +} __packed; + +struct chan_band_param_set { + u8 radio_type; + u8 chan_number; +}; + +struct nxpwifi_ie_types_chan_band_list_param_set { + struct nxpwifi_ie_types_header header; + struct chan_band_param_set chan_band_param[]; +} __packed; + +struct nxpwifi_ie_types_rates_param_set { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_ssid_param_set { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct nxpwifi_ie_types_host_mlme { + struct nxpwifi_ie_types_header header; + u8 host_mlme; +} __packed; + +struct nxpwifi_ie_types_num_probes { + struct nxpwifi_ie_types_header header; + __le16 num_probes; +} __packed; + +struct nxpwifi_ie_types_repeat_count { + struct nxpwifi_ie_types_header header; + __le16 repeat_count; +} __packed; + +struct nxpwifi_ie_types_min_rssi_threshold { + struct nxpwifi_ie_types_header header; + __le16 rssi_threshold; +} __packed; + +struct nxpwifi_ie_types_bgscan_start_later { + struct nxpwifi_ie_types_header header; + __le16 start_later; +} __packed; + +struct nxpwifi_ie_types_scan_chan_gap { + struct nxpwifi_ie_types_header header; + /* time gap in TUs to be used between two consecutive channels scan */ + __le16 chan_gap; +} __packed; + +struct nxpwifi_ie_types_random_mac { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; +} __packed; + +struct nxpwifi_ietypes_chanstats { + struct nxpwifi_ie_types_header header; + struct nxpwifi_fw_chan_stats chanstats[]; +} __packed; + +struct nxpwifi_ie_types_wildcard_ssid_params { + struct nxpwifi_ie_types_header header; + u8 max_ssid_length; + u8 ssid[]; +} __packed; + +#define TSF_DATA_SIZE 8 +struct nxpwifi_ie_types_tsf_timestamp { + struct nxpwifi_ie_types_header header; + u8 tsf_data[]; +} __packed; + +struct nxpwifi_cf_param_set { + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct nxpwifi_ibss_param_set { + __le16 atim_window; +} __packed; + +struct nxpwifi_ie_types_ss_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_cf_param_set cf_param_set[1]; + struct nxpwifi_ibss_param_set ibss_param_set[1]; + } cf_ibss; +} __packed; + +struct nxpwifi_fh_param_set { + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct nxpwifi_ds_param_set { + u8 current_chan; +} __packed; + +struct nxpwifi_ie_types_phy_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_fh_param_set fh_param_set[1]; + struct nxpwifi_ds_param_set ds_param_set[1]; + } fh_ds; +} __packed; + +struct nxpwifi_ie_types_auth_type { + struct nxpwifi_ie_types_header header; + __le16 auth_type; +} __packed; + +struct nxpwifi_ie_types_vendor_param_set { + struct nxpwifi_ie_types_header header; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +#define NXPWIFI_AUTHTYPE_SAE 6 + +struct nxpwifi_ie_types_sae_pwe_mode { + struct nxpwifi_ie_types_header header; + u8 pwe[]; +} __packed; + +struct nxpwifi_ie_types_rsn_param_set { + struct nxpwifi_ie_types_header header; + u8 rsn_ie[]; +} __packed; + +#define KEYPARAMSET_FIXED_LEN 6 + +#define IGTK_PN_LEN 8 + +struct nxpwifi_cmac_param { + u8 ipn[IGTK_PN_LEN]; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_wep_param { + __le16 key_len; + u8 key[WLAN_KEY_LEN_WEP104]; +} __packed; + +struct nxpwifi_tkip_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_TKIP]; +} __packed; + +struct nxpwifi_aes_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_CCMP_256]; +} __packed; + +struct nxpwifi_cmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_gmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_BIP_GMAC_256]; +} __packed; + +struct nxpwifi_ie_type_key_param_set { + __le16 type; + __le16 len; + u8 mac_addr[ETH_ALEN]; + u8 key_idx; + u8 key_type; + __le16 key_info; + union { + struct nxpwifi_wep_param wep; + struct nxpwifi_tkip_param tkip; + struct nxpwifi_aes_param aes; + struct nxpwifi_cmac_aes_param cmac_aes; + struct nxpwifi_gmac_aes_param gmac_aes; + } key_params; +} __packed; + +struct host_cmd_ds_802_11_key_material { + __le16 action; + struct nxpwifi_ie_type_key_param_set key_param_set; +} __packed; + +struct host_cmd_ds_gen { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; +}; + +#define S_DS_GEN sizeof(struct host_cmd_ds_gen) + +enum sleep_resp_ctrl { + RESP_NOT_NEEDED = 0, + RESP_NEEDED, +}; + +struct nxpwifi_ps_param { + __le16 null_pkt_interval; + __le16 multiple_dtims; + __le16 bcn_miss_timeout; + __le16 local_listen_interval; + __le16 reserved; + __le16 mode; + __le16 delay_to_ps; +} __packed; + +#define HS_DEF_WAKE_INTERVAL 100 +#define HS_DEF_INACTIVITY_TIMEOUT 50 + +struct nxpwifi_ps_param_in_hs { + struct nxpwifi_ie_types_header header; + __le32 hs_wake_int; + __le32 hs_inact_timeout; +} __packed; + +#define BITMAP_AUTO_DS 0x01 +#define BITMAP_STA_PS 0x10 + +struct nxpwifi_ie_types_auto_ds_param { + struct nxpwifi_ie_types_header header; + __le16 deep_sleep_timeout; +} __packed; + +struct nxpwifi_ie_types_ps_param { + struct nxpwifi_ie_types_header header; + struct nxpwifi_ps_param param; +} __packed; + +struct host_cmd_ds_802_11_ps_mode_enh { + __le16 action; + + union { + struct nxpwifi_ps_param opt_ps; + __le16 ps_bitmap; + } params; +} __packed; + +enum API_VER_ID { + KEY_API_VER_ID = 1, + FW_API_VER_ID = 2, + UAP_FW_API_VER_ID = 3, + CHANRPT_API_VER_ID = 4, + FW_HOTFIX_VER_ID = 5, +}; + +struct hw_spec_api_rev { + struct nxpwifi_ie_types_header header; + __le16 api_id; + u8 major_ver; + u8 minor_ver; +} __packed; + +struct hw_spec_max_conn { + struct nxpwifi_ie_types_header header; + u8 reserved; + u8 max_sta_conn; +} __packed; + +struct hw_spec_extension { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 tlv[]; +} __packed; + +/* HE MAC Capabilities Information field BIT 1 for TWT Req */ +#define HE_MAC_CAP_TWT_REQ_SUPPORT BIT(1) +/* HE MAC Capabilities Information field BIT 2 for TWT Resp*/ +#define HE_MAC_CAP_TWT_RESP_SUPPORT BIT(2) + +struct nxpwifi_ie_types_he_cap { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 he_mac_cap[6]; + u8 he_phy_cap[11]; + __le16 rx_mcs_80; + __le16 tx_mcs_80; + __le16 rx_mcs_160; + __le16 tx_mcs_160; + __le16 rx_mcs_80p80; + __le16 tx_mcs_80p80; + u8 val[20]; +} __packed; + +struct nxpwifi_ie_types_he_op { + struct nxpwifi_ie_types_header header; + u8 ext_id; + __le16 he_op_param1; + u8 he_op_param2; + u8 bss_color_info; + __le16 basic_he_mcs_nss; + u8 option[9]; +} __packed; + +struct hw_spec_secure_boot_uuid { + struct nxpwifi_ie_types_header header; + __le64 uuid_lo; + __le64 uuid_hi; +} __packed; + +struct hw_spec_fw_cap_info { + struct nxpwifi_ie_types_header header; + __le32 fw_cap_info; + __le32 fw_cap_ext; +} __packed; + +/* NXP proprietary region codes reported by firmware via GET_HW_SPEC. + * These values are stored in the device OTP/calibration data and + * correspond to regulatory domains used for channel/power table selection. + */ +enum nxpwifi_region_code { + NXPWIFI_REGION_WORLD = 0x00, + NXPWIFI_REGION_FCC = 0x10, /* US, Canada-like */ + NXPWIFI_REGION_IC = 0x20, /* Canada */ + NXPWIFI_REGION_ETSI = 0x30, /* Europe */ + NXPWIFI_REGION_SPAIN = 0x31, + NXPWIFI_REGION_FRANCE = 0x32, + NXPWIFI_REGION_JAPAN = 0x40, + NXPWIFI_REGION_JAPAN1 = 0x41, + NXPWIFI_REGION_CHINA = 0x50, +}; + +struct host_cmd_ds_get_hw_spec { + __le16 hw_if_version; + __le16 version; + __le16 reserved; + __le16 num_of_mcast_adr; + u8 permanent_addr[ETH_ALEN]; + __le16 region_code; + __le16 number_of_antenna; + __le32 fw_release_number; + __le32 hw_dev_cap; + __le32 reserved_1; + __le32 reserved_2; + __le32 fw_cap_info; + __le32 dot_11n_dev_cap; + u8 dev_mcs_support; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 mgmt_buf_count; /* mgmt element buffer count */ + __le32 reserved_3; + __le32 reserved_4; + __le32 dot_11ac_dev_cap; + __le32 dot_11ac_mcs_support; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_rssi_info { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 reserved[9]; + long long reserved_1; +} __packed; + +struct host_cmd_ds_802_11_rssi_info_rsp { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 data_rssi_last; + __le16 data_nf_last; + __le16 data_rssi_avg; + __le16 data_nf_avg; + __le16 bcn_rssi_last; + __le16 bcn_nf_last; + __le16 bcn_rssi_avg; + __le16 bcn_nf_avg; + long long tsf_bcn; +} __packed; + +struct host_cmd_ds_802_11_mac_address { + __le16 action; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_ds_mac_control { + __le32 action; +}; + +struct host_cmd_ds_mac_multicast_adr { + __le16 action; + __le16 num_of_adrs; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +} __packed; + +struct host_cmd_ds_802_11_deauthenticate { + u8 mac_addr[ETH_ALEN]; + __le16 reason_code; +} __packed; + +struct host_cmd_ds_802_11_associate { + u8 peer_sta_addr[ETH_ALEN]; + __le16 cap_info_bitmap; + __le16 listen_interval; + __le16 beacon_period; + u8 dtim_period; +} __packed; + +struct ieee_types_assoc_rsp { + __le16 cap_info_bitmap; + __le16 status_code; + __le16 a_id; + u8 ie_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_associate_rsp { + struct ieee_types_assoc_rsp assoc_rsp; +} __packed; + +struct ieee_types_cf_param_set { + u8 element_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct ieee_types_fh_param_set { + u8 element_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ieee_types_ds_param_set { + u8 element_id; + u8 len; + u8 current_chan; +} __packed; + +union ieee_types_phy_param_set { + struct ieee_types_fh_param_set fh_param_set; + struct ieee_types_ds_param_set ds_param_set; +} __packed; + +struct ieee_types_oper_mode_ntf { + u8 element_id; + u8 len; + u8 oper_mode; +} __packed; + +struct host_cmd_ds_802_11_get_log { + __le32 mcast_tx_frame; + __le32 failed; + __le32 retry; + __le32 multi_retry; + __le32 frame_dup; + __le32 rts_success; + __le32 rts_failure; + __le32 ack_failure; + __le32 rx_frag; + __le32 mcast_rx_frame; + __le32 fcs_error; + __le32 tx_frame; + __le32 reserved; + __le32 wep_icv_err_cnt[4]; + __le32 bcn_rcv_cnt; + __le32 bcn_miss_cnt; +} __packed; + +/* Enumeration for rate format */ +enum nxpwifi_rate_format { + NXPWIFI_RATE_FORMAT_LG = 0, + NXPWIFI_RATE_FORMAT_HT, + NXPWIFI_RATE_FORMAT_VHT, + NXPWIFI_RATE_FORMAT_HE, + NXPWIFI_RATE_FORMAT_AUTO = 0xFF, +}; + +struct host_cmd_ds_tx_rate_query { + u8 tx_rate; + /* + * Tx Rate Info: For 802.11 AC cards + * + * [Bit 0-1] tx rate format: LG = 0, HT = 1, VHT = 2 + * [Bit 2-3] HT/VHT Bandwidth: BW20 = 0, BW40 = 1, BW80 = 2, BW160 = 3 + * [Bit 4] HT/VHT Guard Interval: LGI = 0, SGI = 1 + * + * For non-802.11 AC cards + * Ht Info [Bit 0] RxRate format: LG=0, HT=1 + * [Bit 1] HT Bandwidth: BW20 = 0, BW40 = 1 + * [Bit 2] HT Guard Interval: LGI = 0, SGI = 1 + */ + u8 ht_info; +} __packed; + +struct nxpwifi_tx_pause_tlv { + struct nxpwifi_ie_types_header header; + u8 peermac[ETH_ALEN]; + u8 tx_pause; + u8 pkt_cnt; +} __packed; + +enum host_sleep_action { + HS_CONFIGURE = 0x0001, + HS_ACTIVATE = 0x0002, +}; + +struct nxpwifi_hs_config_param { + __le32 conditions; + u8 gpio; + u8 gap; +} __packed; + +struct hs_activate_param { + __le16 resp_ctrl; +} __packed; + +struct host_cmd_ds_802_11_hs_cfg_enh { + __le16 action; + + union { + struct nxpwifi_hs_config_param hs_config; + struct hs_activate_param hs_activate; + } params; +} __packed; + +enum SNMP_MIB_INDEX { + OP_RATE_SET_I = 1, + DTIM_PERIOD_I = 3, + RTS_THRESH_I = 5, + SHORT_RETRY_LIM_I = 6, + LONG_RETRY_LIM_I = 7, + FRAG_THRESH_I = 8, + DOT11D_I = 9, + DOT11H_I = 10, +}; + +enum nxpwifi_assocmd_failurepoint { + NXPWIFI_ASSOC_CMD_SUCCESS = 0, + NXPWIFI_ASSOC_CMD_FAILURE_ASSOC, + NXPWIFI_ASSOC_CMD_FAILURE_AUTH, + NXPWIFI_ASSOC_CMD_FAILURE_JOIN +}; + +#define MAX_SNMP_BUF_SIZE 128 + +struct host_cmd_ds_802_11_snmp_mib { + __le16 query_type; + __le16 oid; + __le16 buf_size; + u8 value[]; +} __packed; + +struct nxpwifi_rate_scope { + __le16 type; + __le16 length; + __le16 hr_dsss_rate_bitmap; + __le16 ofdm_rate_bitmap; + __le16 ht_mcs_rate_bitmap[8]; + __le16 vht_mcs_rate_bitmap[8]; +} __packed; + +struct nxpwifi_rate_drop_pattern { + __le16 type; + __le16 length; + __le32 rate_drop_mode; +} __packed; + +struct host_cmd_ds_tx_rate_cfg { + __le16 action; + __le16 cfg_index; +} __packed; + +struct nxpwifi_power_group { + u8 modulation_class; + u8 first_rate_code; + u8 last_rate_code; + s8 power_step; + s8 power_min; + s8 power_max; + u8 ht_bandwidth; + u8 reserved; +} __packed; + +struct nxpwifi_types_power_group { + __le16 type; + __le16 length; +} __packed; + +struct host_cmd_ds_txpwr_cfg { + __le16 action; + __le16 cfg_index; + __le32 mode; +} __packed; + +struct host_cmd_ds_rf_tx_pwr { + __le16 action; + __le16 cur_level; + u8 max_power; + u8 min_power; +} __packed; + +struct host_cmd_ds_rf_ant_mimo { + __le16 action_tx; + __le16 tx_ant_mode; + __le16 action_rx; + __le16 rx_ant_mode; +} __packed; + +struct host_cmd_ds_rf_ant_siso { + __le16 action; + __le16 ant_mode; +} __packed; + +#define BAND_CFG_CHAN_BAND_MASK 0x03 +#define BAND_CFG_CHAN_BAND_SHIFT_BIT 0 +#define BAND_CFG_CHAN_WIDTH_MASK 0x0C +#define BAND_CFG_CHAN_WIDTH_SHIFT_BIT 2 +#define BAND_CFG_CHAN2_OFFSET_MASK 0x30 +#define BAND_CFG_CHAN2_SHIFT_BIT 4 + +struct nxpwifi_chan_desc { + __le16 start_freq; + u8 band_cfg; + u8 chan_num; +} __packed; + +struct host_cmd_ds_chan_rpt_req { + struct nxpwifi_chan_desc chan_desc; + __le32 msec_dwell_time; +} __packed; + +struct host_cmd_ds_chan_rpt_event { + __le32 result; + __le64 start_tsf; + __le32 duration; + u8 tlvbuf[]; +} __packed; + +struct host_cmd_sdio_sp_rx_aggr_cfg { + u8 action; + u8 enable; + __le16 block_size; +} __packed; + +struct nxpwifi_fixed_bcn_param { + __le64 timestamp; + __le16 beacon_period; + __le16 cap_info_bitmap; +} __packed; + +struct nxpwifi_event_scan_result { + __le16 event_id; + u8 bss_index; + u8 bss_type; + u8 more_event; + u8 reserved[3]; + __le16 buf_size; + u8 num_of_set; +} __packed; + +struct tx_status_event { + u8 packet_type; + u8 tx_token_id; + u8 status; +} __packed; + +#define NXPWIFI_USER_SCAN_CHAN_MAX 50 + +#define NXPWIFI_MAX_SSID_LIST_LENGTH 10 + +struct nxpwifi_scan_cmd_config { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + + /* Specific BSSID used to filter scan results in the firmware */ + u8 specific_bssid[ETH_ALEN]; + + /* Length of TLVs sent in command starting at tlvBuffer */ + u32 tlv_buf_len; + + /* + * SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * TLV_TYPE_CHANLIST, nxpwifi_ie_types_chan_list_param_set + * WLAN_EID_SSID, nxpwifi_ie_types_ssid_param_set + */ + u8 tlv_buf[]; /* SSID TLV(s) and ChanList TLVs are stored here */ +} __packed; + +struct nxpwifi_user_scan_chan { + u8 chan_number; + u8 radio_type; + u8 scan_type; + u8 reserved; + u32 scan_time; +} __packed; + +struct nxpwifi_user_scan_cfg { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + /* Configure the number of probe requests for active chan scans */ + u8 num_probes; + u8 reserved; + /* BSSID filter sent in the firmware command to limit the results */ + u8 specific_bssid[ETH_ALEN]; + /* SSID filter list used in the firmware to limit the scan results */ + struct cfg80211_ssid *ssid_list; + u8 num_ssids; + /* Variable number (fixed maximum) of channels to scan up */ + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_USER_SCAN_CHAN_MAX]; + u16 scan_chan_gap; + u8 random_mac[ETH_ALEN]; +} __packed; + +#define NXPWIFI_BG_SCAN_CHAN_MAX 38 +#define NXPWIFI_BSS_MODE_INFRA 1 +#define NXPWIFI_BGSCAN_ACT_GET 0x0000 +#define NXPWIFI_BGSCAN_ACT_SET 0x0001 +#define NXPWIFI_BGSCAN_ACT_SET_ALL 0xff01 +/** ssid match */ +#define NXPWIFI_BGSCAN_SSID_MATCH 0x0001 +/** ssid match and RSSI exceeded */ +#define NXPWIFI_BGSCAN_SSID_RSSI_MATCH 0x0004 +/**wait for all channel scan to complete to report scan result*/ +#define NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE 0x80000000 + +struct nxpwifi_bg_scan_cfg { + u16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u32 scan_interval; + u32 report_condition; + u8 num_probes; + u8 rssi_threshold; + u8 snr_threshold; + u16 repeat_count; + u16 start_later; + struct cfg80211_match_set *ssid_list; + u8 num_ssids; + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_BG_SCAN_CHAN_MAX]; + u16 scan_chan_gap; +} __packed; + +struct ie_body { + u8 grp_key_oui[4]; + u8 ptk_cnt[2]; + u8 ptk_body[4]; +} __packed; + +struct host_cmd_ds_802_11_scan { + u8 bss_mode; + u8 bssid[ETH_ALEN]; + u8 tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_rsp { + __le16 bss_descript_size; + u8 number_of_sets; + u8 bss_desc_and_tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_ext { + u32 reserved; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_bss_mode { + struct nxpwifi_ie_types_header header; + u8 bss_mode; +} __packed; + +struct nxpwifi_ie_types_scan_rsp { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; + u8 frame_body[]; +} __packed; + +struct nxpwifi_ie_types_scan_inf { + struct nxpwifi_ie_types_header header; + __le16 rssi; + __le16 anpi; + u8 cca_busy_fraction; + u8 radio_type; + u8 channel; + u8 reserved; + __le64 tsf; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_config { + __le16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u8 reserved; + __le16 reserved1; + __le32 scan_interval; + __le32 reserved2; + __le32 report_condition; + __le16 reserved3; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query { + u8 flush; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query_rsp { + __le32 report_condition; + struct host_cmd_ds_802_11_scan_rsp scan_resp; +} __packed; + +struct nxpwifi_ietypes_domain_code { + struct nxpwifi_ie_types_header header; + u8 domain_code; + u8 reserved; +} __packed; + +struct nxpwifi_ietypes_domain_param_set { + struct nxpwifi_ie_types_header header; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + struct ieee80211_country_ie_triplet triplet[]; +} __packed; + +struct host_cmd_ds_802_11d_domain_info { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_802_11d_domain_info_rsp { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_11n_addba_req { + u8 add_req_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_addba_rsp { + u8 add_rsp_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 status_code; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_delba { + u8 del_result; + u8 peer_mac_addr[ETH_ALEN]; + __le16 del_ba_param_set; + __le16 reason_code; + u8 reserved; +} __packed; + +struct host_cmd_ds_11n_batimeout { + u8 tid; + u8 peer_mac_addr[ETH_ALEN]; + u8 origninator; +} __packed; + +struct host_cmd_ds_11n_cfg { + __le16 action; + __le16 ht_tx_cap; + __le16 ht_tx_info; + __le16 misc_config; /* Needed for 802.11AC cards only */ +} __packed; + +struct host_cmd_ds_txbuf_cfg { + __le16 action; + __le16 buff_size; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 reserved3; +} __packed; + +struct host_cmd_ds_amsdu_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 curr_buf_size; +} __packed; + +struct host_cmd_ds_sta_deauth { + u8 mac[ETH_ALEN]; + __le16 reason; +} __packed; + +struct nxpwifi_ie_types_sta_info { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 power_mfg_status; + s8 rssi; +}; + +struct host_cmd_ds_sta_list { + __le16 sta_count; + u8 tlv[]; +} __packed; + +struct nxpwifi_ie_types_pwr_capability { + struct nxpwifi_ie_types_header header; + s8 min_pwr; + s8 max_pwr; +}; + +struct nxpwifi_ie_types_local_pwr_constraint { + struct nxpwifi_ie_types_header header; + u8 chan; + u8 constraint; +}; + +struct nxpwifi_ie_types_wmm_param_set { + struct nxpwifi_ie_types_header header; + u8 wmm_ie[]; +} __packed; + +struct nxpwifi_ie_types_mgmt_frame { + struct nxpwifi_ie_types_header header; + __le16 frame_control; + u8 frame_contents[]; +}; + +struct nxpwifi_ie_types_wmm_queue_status { + struct nxpwifi_ie_types_header header; + u8 queue_index; + u8 disabled; + __le16 medium_time; + u8 flow_required; + u8 flow_created; + u32 reserved; +}; + +struct ieee_types_wmm_info { + /* + * WMM Info element - Vendor Specific Header: + * element_id [221/0xdd] + * Len [7] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [0] + * Version [1] + */ + struct ieee80211_vendor_ie vend_hdr; + u8 oui_subtype; + u8 version; + + u8 qos_info_bitmap; +} __packed; + +struct host_cmd_ds_wmm_get_status { + u8 queue_status_tlv[sizeof(struct nxpwifi_ie_types_wmm_queue_status) * + IEEE80211_NUM_ACS]; + u8 wmm_param_tlv[sizeof(struct ieee80211_wmm_param_ie) + 2]; +} __packed; + +struct nxpwifi_wmm_ac_status { + u8 disabled; + u8 flow_required; + u8 flow_created; +}; + +struct nxpwifi_ie_types_htcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_cap ht_cap; +} __packed; + +struct nxpwifi_ie_types_vhtcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_vht_cap vht_cap; +} __packed; + +struct nxpwifi_ie_types_aid { + struct nxpwifi_ie_types_header header; + __le16 aid; +} __packed; + +struct nxpwifi_ie_types_oper_mode_ntf { + struct nxpwifi_ie_types_header header; + u8 oper_mode; +} __packed; + +/* VHT Operations element */ +struct nxpwifi_ie_types_vht_oper { + struct nxpwifi_ie_types_header header; + u8 chan_width; + u8 chan_center_freq_1; + u8 chan_center_freq_2; + /* Basic MCS set map, each 2 bits stands for a NSS */ + __le16 basic_mcs_map; +} __packed; + +struct nxpwifi_ie_types_wmmcap { + struct nxpwifi_ie_types_header header; + struct nxpwifi_types_wmm_info wmm_info; +} __packed; + +struct nxpwifi_ie_types_htinfo { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_operation ht_oper; +} __packed; + +struct nxpwifi_ie_types_2040bssco { + struct nxpwifi_ie_types_header header; + u8 bss_co_2040; +} __packed; + +struct nxpwifi_ie_types_extcap { + struct nxpwifi_ie_types_header header; + u8 ext_capab[]; +} __packed; + +struct host_cmd_ds_mem_access { + __le16 action; + __le16 reserved; + __le32 addr; + __le32 value; +} __packed; + +struct nxpwifi_ie_types_qos_info { + struct nxpwifi_ie_types_header header; + u8 qos_info; +} __packed; + +struct host_cmd_ds_mac_reg_access { + __le16 action; + __le16 offset; + __le32 value; +} __packed; + +struct host_cmd_ds_bbp_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_rf_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_pmic_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_802_11_eeprom_access { + __le16 action; + + __le16 offset; + __le16 byte_count; + u8 value; +} __packed; + +struct nxpwifi_assoc_event { + u8 sta_addr[ETH_ALEN]; + __le16 type; + __le16 len; + __le16 frame_control; + __le16 cap_info; + __le16 listen_interval; + u8 data[]; +} __packed; + +struct host_cmd_ds_sys_config { + __le16 action; + u8 tlv[]; +}; + +struct host_cmd_11ac_vht_cfg { + __le16 action; + u8 band_config; + u8 misc_config; + __le32 cap_info; + __le32 mcs_tx_set; + __le32 mcs_rx_set; +} __packed; + +struct host_cmd_tlv_akmp { + struct nxpwifi_ie_types_header header; + __le16 key_mgmt; + __le16 key_mgmt_operation; +} __packed; + +struct host_cmd_tlv_pwk_cipher { + struct nxpwifi_ie_types_header header; + __le16 proto; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_gwk_cipher { + struct nxpwifi_ie_types_header header; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_passphrase { + struct nxpwifi_ie_types_header header; + u8 passphrase[]; +} __packed; + +struct host_cmd_tlv_wep_key { + struct nxpwifi_ie_types_header header; + u8 key_index; + u8 is_default; + u8 key[]; +}; + +struct host_cmd_tlv_auth_type { + struct nxpwifi_ie_types_header header; + u8 auth_type; + u8 pwe_derivation; + u8 transition_disable; +} __packed; + +struct host_cmd_tlv_encrypt_protocol { + struct nxpwifi_ie_types_header header; + __le16 proto; +} __packed; + +struct host_cmd_tlv_ssid { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct host_cmd_tlv_rates { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_bssid_list { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_bcast_ssid { + struct nxpwifi_ie_types_header header; + u8 bcast_ctl; +} __packed; + +struct host_cmd_tlv_beacon_period { + struct nxpwifi_ie_types_header header; + __le16 period; +} __packed; + +struct host_cmd_tlv_dtim_period { + struct nxpwifi_ie_types_header header; + u8 period; +} __packed; + +struct host_cmd_tlv_frag_threshold { + struct nxpwifi_ie_types_header header; + __le16 frag_thr; +} __packed; + +struct host_cmd_tlv_rts_threshold { + struct nxpwifi_ie_types_header header; + __le16 rts_thr; +} __packed; + +struct host_cmd_tlv_retry_limit { + struct nxpwifi_ie_types_header header; + u8 limit; +} __packed; + +struct host_cmd_tlv_mac_addr { + struct nxpwifi_ie_types_header header; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_channel_band { + struct nxpwifi_ie_types_header header; + u8 band_config; + u8 channel; +} __packed; + +struct host_cmd_tlv_ageout_timer { + struct nxpwifi_ie_types_header header; + __le32 sta_ao_timer; +} __packed; + +struct host_cmd_tlv_power_constraint { + struct nxpwifi_ie_types_header header; + u8 constraint; +} __packed; + +struct nxpwifi_ie_types_btcoex_scan_time { + struct nxpwifi_ie_types_header header; + u8 coex_scan; + u8 reserved; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_btcoex_aggr_win_size { + struct nxpwifi_ie_types_header header; + u8 coex_win_size; + u8 tx_win_size; + u8 rx_win_size; + u8 reserved; +} __packed; + +struct nxpwifi_ie_types_robust_coex { + struct nxpwifi_ie_types_header header; + __le32 mode; +} __packed; + +#define NXPWIFI_VERSION_STR_LENGTH 128 + +struct host_cmd_ds_version_ext { + u8 version_str_sel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +} __packed; + +struct host_cmd_ds_mgmt_frame_reg { + __le16 action; + __le32 mask; +} __packed; + +struct host_cmd_ds_remain_on_chan { + __le16 action; + u8 status; + u8 reserved; + u8 band_cfg; + u8 channel; + __le32 duration; +} __packed; + +struct host_cmd_ds_802_11_ibss_status { + __le16 action; + __le16 enable; + u8 bssid[ETH_ALEN]; + __le16 beacon_interval; + __le16 atim_window; + __le16 use_g_rate_protect; +} __packed; + +struct nxpwifi_fw_mef_entry { + u8 mode; + u8 action; + __le16 exprsize; + u8 expr[]; +} __packed; + +struct host_cmd_ds_mef_cfg { + __le32 criteria; + __le16 num_entries; + u8 mef_entry_data[]; +} __packed; + +#define CONNECTION_TYPE_INFRA 0 +#define CONNECTION_TYPE_AP 2 + +struct host_cmd_ds_set_bss_mode { + u8 con_type; +} __packed; + +struct host_cmd_ds_pcie_details { + /* TX buffer descriptor ring address */ + __le32 txbd_addr_lo; + __le32 txbd_addr_hi; + /* TX buffer descriptor ring count */ + __le32 txbd_count; + + /* RX buffer descriptor ring address */ + __le32 rxbd_addr_lo; + __le32 rxbd_addr_hi; + /* RX buffer descriptor ring count */ + __le32 rxbd_count; + + /* Event buffer descriptor ring address */ + __le32 evtbd_addr_lo; + __le32 evtbd_addr_hi; + /* Event buffer descriptor ring count */ + __le32 evtbd_count; + + /* Sleep cookie buffer physical address */ + __le32 sleep_cookie_addr_lo; + __le32 sleep_cookie_addr_hi; +} __packed; + +struct nxpwifi_ie_types_rssi_threshold { + struct nxpwifi_ie_types_header header; + u8 abs_value; + u8 evt_freq; +} __packed; + +#define NXPWIFI_DFS_REC_HDR_LEN 8 +#define NXPWIFI_DFS_REC_HDR_NUM 10 +#define NXPWIFI_BIN_COUNTER_LEN 7 + +struct nxpwifi_radar_det_event { + __le32 detect_count; + u8 reg_domain; /*1=fcc, 2=etsi, 3=mic*/ + u8 det_type; /*0=none, 1=pw(chirp), 2=pri(radar)*/ + __le16 pw_chirp_type; + u8 pw_chirp_idx; + u8 pw_value; + u8 pri_radar_type; + u8 pri_bincnt; + u8 bin_counter[NXPWIFI_BIN_COUNTER_LEN]; + u8 num_dfs_records; + u8 dfs_record_hdr[NXPWIFI_DFS_REC_HDR_NUM][NXPWIFI_DFS_REC_HDR_LEN]; + __le32 passed; +} __packed; + +struct nxpwifi_ie_types_multi_chan_info { + struct nxpwifi_ie_types_header header; + __le16 status; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_mc_group_info { + struct nxpwifi_ie_types_header header; + u8 chan_group_id; + u8 chan_buf_weight; + u8 band_config; + u8 chan_num; + __le32 chan_time; + __le32 reserved; + union { + u8 sdio_func_num; + u8 usb_ep_num; + } hid_num; + u8 intf_num; + u8 bss_type_numlist[]; +} __packed; + +#define MEAS_RPT_MAP_RADAR_MASK 0x08 +#define MEAS_RPT_MAP_RADAR_SHIFT_BIT 3 + +struct nxpwifi_ie_types_chan_rpt_data { + struct nxpwifi_ie_types_header header; + u8 meas_rpt_map; +} __packed; + +struct host_cmd_ds_802_11_subsc_evt { + __le16 action; + __le16 events; +} __packed; + +struct chan_switch_result { + u8 cur_chan; + u8 status; + u8 reason; +} __packed; + +struct nxpwifi_ie { + __le16 ie_index; + __le16 mgmt_subtype_mask; + __le16 ie_length; + u8 ie_buffer[IEEE_MAX_IE_SIZE]; +} __packed; + +#define MAX_MGMT_IE_INDEX 16 +struct nxpwifi_ie_list { + __le16 type; + __le16 len; + struct nxpwifi_ie ie_list[MAX_MGMT_IE_INDEX]; +} __packed; + +struct coalesce_filt_field_param { + u8 operation; + u8 operand_len; + __le16 offset; + u8 operand_byte_stream[4]; +}; + +struct coalesce_receive_filt_rule { + struct nxpwifi_ie_types_header header; + u8 num_of_fields; + u8 pkt_type; + __le16 max_coalescing_delay; + struct coalesce_filt_field_param params[]; +} __packed; + +struct host_cmd_ds_coalesce_cfg { + __le16 action; + __le16 num_of_rules; + u8 rule_data[]; +} __packed; + +struct host_cmd_ds_multi_chan_policy { + __le16 action; + __le16 policy; +} __packed; + +struct host_cmd_ds_robust_coex { + __le16 action; + __le16 reserved; +} __packed; + +struct host_cmd_ds_wakeup_reason { + __le16 wakeup_reason; +} __packed; + +struct host_cmd_ds_gtk_rekey_params { + __le16 action; + u8 kck[NL80211_KCK_LEN]; + u8 kek[NL80211_KEK_LEN]; + __le32 replay_ctr_low; + __le32 replay_ctr_high; +} __packed; + +struct host_cmd_ds_chan_region_cfg { + __le16 action; +} __packed; + +struct host_cmd_ds_pkt_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 tx_aggr_max_size; + __le16 tx_aggr_max_num; + __le16 tx_aggr_align; +} __packed; + +struct host_cmd_ds_sta_configure { + __le16 action; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_sta_flag { + struct nxpwifi_ie_types_header header; + __le32 sta_flags; +} __packed; + +struct host_cmd_ds_add_station { + __le16 action; + __le16 aid; + u8 peer_mac[ETH_ALEN]; + __le32 listen_interval; + __le16 cap_info; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cfg { + __le16 action; + u8 band_config; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cmd { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct nxpwifi_802_11_net_monitor { + u32 enable_net_mon; + u32 filter_flag; + u32 band; + u32 channel; + u32 chan_bandwidth; +}; + +struct band_config { + /* Band: 00=2.4, 01=5, 10=6 GHz */ + u8 chan_band : 2; + /* Width: 00=20, 10=40, 11=80 MHz */ + u8 chan_width : 2; + /* Sec offset: 00=None, 01=Above, 11=Below */ + u8 chan_2O_ffset : 2; + /* Chan sel: 00=manual, 01=ACS, 02=Adoption */ + u8 scan_mode : 2; +} __packed; + +struct chan_band_param { + struct band_config band_cfg; + u8 chan_number; +} __packed; + +struct nxpwifi_ie_types_chan_band_list { + struct nxpwifi_ie_types_header header; + struct chan_band_param chan_band_param[]; +} __packed; + +struct host_cmd_ds_802_11_net_monitor { + __le16 action; + __le16 enable_net_mon; + __le16 filter_flag; + struct nxpwifi_ie_types_chan_band_list monitor_chan; +} __packed; + +struct host_cmd_twt_cfg { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct host_cmd_ds_command { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + union { + struct host_cmd_ds_get_hw_spec hw_spec; + struct host_cmd_ds_mac_control mac_ctrl; + struct host_cmd_ds_802_11_mac_address mac_addr; + struct host_cmd_ds_mac_multicast_adr mc_addr; + struct host_cmd_ds_802_11_get_log get_log; + struct host_cmd_ds_802_11_rssi_info rssi_info; + struct host_cmd_ds_802_11_rssi_info_rsp rssi_info_rsp; + struct host_cmd_ds_802_11_snmp_mib smib; + struct host_cmd_ds_tx_rate_query tx_rate; + struct host_cmd_ds_tx_rate_cfg tx_rate_cfg; + struct host_cmd_ds_txpwr_cfg txp_cfg; + struct host_cmd_ds_rf_tx_pwr txp; + struct host_cmd_ds_rf_ant_mimo ant_mimo; + struct host_cmd_ds_rf_ant_siso ant_siso; + struct host_cmd_ds_802_11_ps_mode_enh psmode_enh; + struct host_cmd_ds_802_11_hs_cfg_enh opt_hs_cfg; + struct host_cmd_ds_802_11_scan scan; + struct host_cmd_ds_802_11_scan_ext ext_scan; + struct host_cmd_ds_802_11_scan_rsp scan_resp; + struct host_cmd_ds_802_11_bg_scan_config bg_scan_config; + struct host_cmd_ds_802_11_bg_scan_query bg_scan_query; + struct host_cmd_ds_802_11_bg_scan_query_rsp bg_scan_query_resp; + struct host_cmd_ds_802_11_associate associate; + struct host_cmd_ds_802_11_associate_rsp associate_rsp; + struct host_cmd_ds_802_11_deauthenticate deauth; + struct host_cmd_ds_802_11d_domain_info domain_info; + struct host_cmd_ds_802_11d_domain_info_rsp domain_info_resp; + struct host_cmd_ds_11n_addba_req add_ba_req; + struct host_cmd_ds_11n_addba_rsp add_ba_rsp; + struct host_cmd_ds_11n_delba del_ba; + struct host_cmd_ds_txbuf_cfg tx_buf; + struct host_cmd_ds_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct host_cmd_ds_11n_cfg htcfg; + struct host_cmd_ds_wmm_get_status get_wmm_status; + struct host_cmd_ds_802_11_key_material key_material; + struct host_cmd_ds_version_ext verext; + struct host_cmd_ds_mgmt_frame_reg reg_mask; + struct host_cmd_ds_remain_on_chan roc_cfg; + struct host_cmd_ds_802_11_ibss_status ibss_coalescing; + struct host_cmd_ds_mef_cfg mef_cfg; + struct host_cmd_ds_mem_access mem; + struct host_cmd_ds_mac_reg_access mac_reg; + struct host_cmd_ds_bbp_reg_access bbp_reg; + struct host_cmd_ds_rf_reg_access rf_reg; + struct host_cmd_ds_pmic_reg_access pmic_reg; + struct host_cmd_ds_set_bss_mode bss_mode; + struct host_cmd_ds_pcie_details pcie_host_spec; + struct host_cmd_ds_802_11_eeprom_access eeprom; + struct host_cmd_ds_802_11_subsc_evt subsc_evt; + struct host_cmd_ds_sys_config uap_sys_config; + struct host_cmd_ds_sta_deauth sta_deauth; + struct host_cmd_ds_sta_list sta_list; + struct host_cmd_11ac_vht_cfg vht_cfg; + struct host_cmd_ds_coalesce_cfg coalesce_cfg; + struct host_cmd_ds_chan_rpt_req chan_rpt_req; + struct host_cmd_sdio_sp_rx_aggr_cfg sdio_rx_aggr_cfg; + struct host_cmd_ds_multi_chan_policy mc_policy; + struct host_cmd_ds_robust_coex coex; + struct host_cmd_ds_wakeup_reason hs_wakeup_reason; + struct host_cmd_ds_gtk_rekey_params rekey; + struct host_cmd_ds_chan_region_cfg reg_cfg; + struct host_cmd_ds_pkt_aggr_ctrl pkt_aggr_ctrl; + struct host_cmd_ds_sta_configure sta_cfg; + struct host_cmd_ds_add_station sta_info; + struct host_cmd_11ax_cfg ax_cfg; + struct host_cmd_11ax_cmd ax_cmd; + struct host_cmd_ds_802_11_net_monitor net_mon; + struct host_cmd_twt_cfg twt_cfg; + } params; +} __packed; + +struct nxpwifi_opt_sleep_confirm { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + __le16 action; + __le16 resp_ctrl; +} __packed; + +#define VDLL_IND_TYPE_REQ 0 +#define VDLL_IND_TYPE_OFFSET 1 +#define VDLL_IND_TYPE_ERR_SIG 2 +#define VDLL_IND_TYPE_ERR_ID 3 +#define VDLL_IND_TYPE_SEC_ERR_ID 4 +#define VDLL_IND_TYPE_INTF_RESET 5 + +struct vdll_ind_event { + __le16 type; + __le16 vdll_id; + __le32 offset; + __le16 block_len; +} __packed; +#endif /* !_NXPWIFI_FW_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/ie.c b/drivers/net/wireless/nxp/nxpwifi/ie.c new file mode 100644 index 000000000000..158755c0c905 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ie.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: management element handling - set/delete elements. + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" + +/* Return true if the IE index is used by another interface. */ +static bool +nxpwifi_ie_index_used_by_other_intf(struct nxpwifi_private *priv, u16 idx) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie *ie; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i] != priv) { + ie = &adapter->priv[i]->mgmt_ie[idx]; + if (ie->mgmt_subtype_mask && ie->ie_length) + return true; + } + } + + return false; +} + +/* Pick an unused IE index for a new element. */ +static int +nxpwifi_ie_get_autoidx(struct nxpwifi_private *priv, u16 subtype_mask, + struct nxpwifi_ie *ie, u16 *index) +{ + u16 mask, len, i; + + for (i = 0; i < priv->adapter->max_mgmt_ie_index; i++) { + mask = le16_to_cpu(priv->mgmt_ie[i].mgmt_subtype_mask); + len = le16_to_cpu(ie->ie_length); + + if (mask == NXPWIFI_AUTO_IDX_MASK) + continue; + + if (mask == subtype_mask) { + if (len > IEEE_MAX_IE_SIZE) + continue; + + *index = i; + return 0; + } + + if (!priv->mgmt_ie[i].ie_length) { + if (nxpwifi_ie_index_used_by_other_intf(priv, i)) + continue; + + *index = i; + return 0; + } + } + + return -ENOENT; +} + +/* Build IE list and resolve AUTO index before sending to FW. */ +static int +nxpwifi_update_autoindex_ies(struct nxpwifi_private *priv, + struct nxpwifi_ie_list *ie_list) +{ + u16 travel_len, index, mask; + s16 input_len, tlv_len; + struct nxpwifi_ie *ie; + u8 *tmp; + + input_len = le16_to_cpu(ie_list->len); + travel_len = sizeof(struct nxpwifi_ie_types_header); + + ie_list->len = 0; + + while (input_len >= sizeof(struct nxpwifi_ie_types_header)) { + ie = (struct nxpwifi_ie *)(((u8 *)ie_list) + travel_len); + tlv_len = le16_to_cpu(ie->ie_length); + travel_len += tlv_len + NXPWIFI_IE_HDR_SIZE; + + if (input_len < tlv_len + NXPWIFI_IE_HDR_SIZE) + return -EINVAL; + index = le16_to_cpu(ie->ie_index); + mask = le16_to_cpu(ie->mgmt_subtype_mask); + + if (index == NXPWIFI_AUTO_IDX_MASK) { + /* automatic addition */ + if (nxpwifi_ie_get_autoidx(priv, mask, ie, &index)) + return -ENOENT; + if (index == NXPWIFI_AUTO_IDX_MASK) + return -EINVAL; + + tmp = (u8 *)&priv->mgmt_ie[index].ie_buffer; + memcpy(tmp, &ie->ie_buffer, le16_to_cpu(ie->ie_length)); + priv->mgmt_ie[index].ie_length = ie->ie_length; + priv->mgmt_ie[index].ie_index = cpu_to_le16(index); + priv->mgmt_ie[index].mgmt_subtype_mask = + cpu_to_le16(mask); + + ie->ie_index = cpu_to_le16(index); + } else { + if (mask != NXPWIFI_DELETE_MASK) + return -EINVAL; + /* + * Check if this index is being used on any + * other interface. + */ + if (nxpwifi_ie_index_used_by_other_intf(priv, index)) + return -EPERM; + + ie->ie_length = 0; + memcpy(&priv->mgmt_ie[index], ie, + sizeof(struct nxpwifi_ie)); + } + + le16_unaligned_add_cpu + (&ie_list->len, + le16_to_cpu(priv->mgmt_ie[index].ie_length) + + NXPWIFI_IE_HDR_SIZE); + input_len -= tlv_len + NXPWIFI_IE_HDR_SIZE; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_CUSTOM_IE_I, ie_list, true); + + return 0; +} + +/* Pack beacon/probe/assoc IEs into one list and update auto-assigned indices. */ +static int +nxpwifi_update_uap_custom_ie(struct nxpwifi_private *priv, + struct nxpwifi_ie *beacon_ie, u16 *beacon_idx, + struct nxpwifi_ie *pr_ie, u16 *probe_idx, + struct nxpwifi_ie *ar_ie, u16 *assoc_idx) +{ + struct nxpwifi_ie_list *ap_custom_ie; + u8 *pos; + u16 len; + int ret; + + ap_custom_ie = kzalloc_obj(*ap_custom_ie, GFP_KERNEL); + if (!ap_custom_ie) + return -ENOMEM; + + ap_custom_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + pos = (u8 *)ap_custom_ie->ie_list; + + if (beacon_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + memcpy(pos, beacon_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (pr_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + memcpy(pos, pr_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (ar_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(ar_ie->ie_length); + memcpy(pos, ar_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + + ret = nxpwifi_update_autoindex_ies(priv, ap_custom_ie); + + pos = (u8 *)(&ap_custom_ie->ie_list[0].ie_index); + if (beacon_ie && *beacon_idx == NXPWIFI_AUTO_IDX_MASK) { + /* save beacon element index after auto-indexing */ + *beacon_idx = le16_to_cpu(ap_custom_ie->ie_list[0].ie_index); + len = sizeof(*beacon_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + pos += len; + } + if (pr_ie && le16_to_cpu(pr_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) { + /* save probe resp element index after auto-indexing */ + *probe_idx = *((u16 *)pos); + len = sizeof(*pr_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + pos += len; + } + if (ar_ie && le16_to_cpu(ar_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) + /* save assoc resp element index after auto-indexing */ + *assoc_idx = *((u16 *)pos); + + kfree(ap_custom_ie); + return ret; +} + +/* Append vendor IE (if present) into nxpwifi_ie, allocating as needed. */ +static int nxpwifi_update_vs_ie(const u8 *ies, int ies_len, + struct nxpwifi_ie **ie_ptr, u16 mask, + unsigned int oui, u8 oui_type) +{ + struct element *vs_ie; + struct nxpwifi_ie *ie = *ie_ptr; + const u8 *vendor_ie; + + vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len); + if (vendor_ie) { + if (!*ie_ptr) { + *ie_ptr = kzalloc_obj(struct nxpwifi_ie, GFP_KERNEL); + if (!*ie_ptr) + return -ENOMEM; + ie = *ie_ptr; + } + + vs_ie = (struct element *)vendor_ie; + if (le16_to_cpu(ie->ie_length) + vs_ie->datalen + 2 > + IEEE_MAX_IE_SIZE) + return -EINVAL; + memcpy(ie->ie_buffer + le16_to_cpu(ie->ie_length), + vs_ie, vs_ie->datalen + 2); + le16_unaligned_add_cpu(&ie->ie_length, vs_ie->datalen + 2); + ie->mgmt_subtype_mask = cpu_to_le16(mask); + ie->ie_index = cpu_to_le16(NXPWIFI_AUTO_IDX_MASK); + } + + *ie_ptr = ie; + return 0; +} + +/* Parse beacon/probe/assoc IEs from cfg80211 and push them to FW. */ +static int nxpwifi_set_mgmt_beacon_data_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL, *ar_ie = NULL; + u16 beacon_idx = NXPWIFI_AUTO_IDX_MASK, pr_idx = NXPWIFI_AUTO_IDX_MASK; + u16 ar_idx = NXPWIFI_AUTO_IDX_MASK; + int ret = 0; + + if (data->beacon_ies && data->beacon_ies_len) { + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->proberesp_ies && data->proberesp_ies_len) { + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->assocresp_ies && data->assocresp_ies_len) { + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P); + } + + if (beacon_ie || pr_ie || ar_ie) { + ret = nxpwifi_update_uap_custom_ie(priv, beacon_ie, + &beacon_idx, pr_ie, + &pr_idx, ar_ie, &ar_idx); + if (ret) + goto done; + } + + priv->beacon_idx = beacon_idx; + priv->proberesp_idx = pr_idx; + priv->assocresp_idx = ar_idx; + +done: + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} + +/* Parse head/tail IEs from cfg80211_beacon_data and send them to FW. */ +static int nxpwifi_uap_parse_tail_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + struct nxpwifi_ie *gen_ie; + struct element *hdr; + struct ieee80211_vendor_ie *vendorhdr; + u16 gen_idx = NXPWIFI_AUTO_IDX_MASK, ie_len = 0; + int left_len, parsed_len = 0; + unsigned int token_len; + int ret = 0; + + if (!info->tail || !info->tail_len) + return 0; + + gen_ie = kzalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + left_len = info->tail_len; + + /* Skip IEs generated by FW from bss configuration to avoid duplicates. */ + while (left_len > sizeof(struct element)) { + hdr = (void *)(info->tail + parsed_len); + token_len = hdr->datalen + sizeof(struct element); + if (token_len > left_len) { + ret = -EINVAL; + goto done; + } + + switch (hdr->id) { + case WLAN_EID_SSID: + case WLAN_EID_SUPP_RATES: + case WLAN_EID_COUNTRY: + case WLAN_EID_PWR_CONSTRAINT: + case WLAN_EID_ERP_INFO: + case WLAN_EID_EXT_SUPP_RATES: + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_VHT_CAPABILITY: + break; + case WLAN_EID_VENDOR_SPECIFIC: + /* Skip only Microsoft WMM element */ + if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + (const u8 *)hdr, + token_len)) + break; + fallthrough; + default: + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, hdr, token_len); + ie_len += token_len; + break; + } + left_len -= token_len; + parsed_len += token_len; + } + + /* + * parse only WPA vendor element from tail, WMM element is configured by + * bss_config command + */ + vendorhdr = (void *)cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPA, + info->tail, info->tail_len); + if (vendorhdr) { + token_len = vendorhdr->len + sizeof(struct element); + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, vendorhdr, token_len); + ie_len += token_len; + } + + if (!ie_len) + goto done; + + gen_ie->ie_index = cpu_to_le16(gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(MGMT_MASK_BEACON | + MGMT_MASK_PROBE_RESP | + MGMT_MASK_ASSOC_RESP); + gen_ie->ie_length = cpu_to_le16(ie_len); + + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &gen_idx, NULL, + NULL, NULL, NULL); + + if (ret) + goto done; + + priv->gen_idx = gen_idx; + + done: + kfree(gen_ie); + return ret; +} + +/* Parse head/tail/beacon/probe/assoc IEs and program the FW. */ +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + int ret; + + ret = nxpwifi_uap_parse_tail_ies(priv, info); + + if (ret) + return ret; + + return nxpwifi_set_mgmt_beacon_data_ies(priv, info); +} + +/* Remove previously set management IEs. */ +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL; + struct nxpwifi_ie *ar_ie = NULL, *gen_ie = NULL; + int ret = 0; + + if (priv->gen_idx != NXPWIFI_AUTO_IDX_MASK) { + gen_ie = kmalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + gen_ie->ie_index = cpu_to_le16(priv->gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + gen_ie->ie_length = 0; + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &priv->gen_idx, + NULL, &priv->proberesp_idx, + NULL, &priv->assocresp_idx); + if (ret) + goto done; + + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + } + + if (priv->beacon_idx != NXPWIFI_AUTO_IDX_MASK) { + beacon_ie = kmalloc_obj(*beacon_ie, GFP_KERNEL); + if (!beacon_ie) { + ret = -ENOMEM; + goto done; + } + beacon_ie->ie_index = cpu_to_le16(priv->beacon_idx); + beacon_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + beacon_ie->ie_length = 0; + } + if (priv->proberesp_idx != NXPWIFI_AUTO_IDX_MASK) { + pr_ie = kmalloc_obj(*pr_ie, GFP_KERNEL); + if (!pr_ie) { + ret = -ENOMEM; + goto done; + } + pr_ie->ie_index = cpu_to_le16(priv->proberesp_idx); + pr_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + pr_ie->ie_length = 0; + } + if (priv->assocresp_idx != NXPWIFI_AUTO_IDX_MASK) { + ar_ie = kmalloc_obj(*ar_ie, GFP_KERNEL); + if (!ar_ie) { + ret = -ENOMEM; + goto done; + } + ar_ie->ie_index = cpu_to_le16(priv->assocresp_idx); + ar_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + ar_ie->ie_length = 0; + } + + if (beacon_ie || pr_ie || ar_ie) + ret = nxpwifi_update_uap_custom_ie(priv, + beacon_ie, &priv->beacon_idx, + pr_ie, &priv->proberesp_idx, + ar_ie, &priv->assocresp_idx); + +done: + kfree(gen_ie); + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c new file mode 100644 index 000000000000..b128fc9fe31a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: HW/FW initialization + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +/* Add a BSS priority node to the adapter list. */ +static int nxpwifi_add_bss_prio_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bss_prio; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + + bss_prio = kzalloc_obj(*bss_prio, GFP_KERNEL); + if (!bss_prio) + return -ENOMEM; + + bss_prio->priv = priv; + INIT_LIST_HEAD(&bss_prio->list); + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + list_add_tail(&bss_prio->list, &tbl[priv->bss_priority].bss_prio_head); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + return 0; +} + +static void wakeup_timer_fn(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, wakeup_timer); + + nxpwifi_dbg(adapter, ERROR, "Firmware wakeup failed\n"); + adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + nxpwifi_cancel_all_pending_cmd(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +/* Initialize priv defaults and lists. */ +int nxpwifi_init_priv(struct nxpwifi_private *priv) +{ + u32 i; + + priv->media_connected = false; + eth_broadcast_addr(priv->curr_addr); + priv->port_open = false; + priv->usb_port = NXPWIFI_USB_EP_DATA; + priv->pkt_tx_ctrl = 0; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->data_rate = 0; /* Initially indicate the rate as auto */ + priv->is_data_rate_auto = true; + priv->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + priv->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->sec_info.wep_enabled = 0; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + priv->sec_info.encryption_mode = 0; + for (i = 0; i < ARRAY_SIZE(priv->wep_key); i++) + memset(&priv->wep_key[i], 0, sizeof(struct nxpwifi_wep_key)); + priv->wep_key_curr_index = 0; + priv->curr_pkt_filter = HOST_ACT_MAC_DYNAMIC_BW_ENABLE | + HOST_ACT_MAC_RX_ON | HOST_ACT_MAC_TX_ON | + HOST_ACT_MAC_ETHERNETII_ENABLE; + + priv->beacon_period = 100; /* beacon interval */ + priv->attempted_bss_desc = NULL; + memset(&priv->curr_bss_params, 0, sizeof(priv->curr_bss_params)); + priv->listen_interval = NXPWIFI_DEFAULT_LISTEN_INTERVAL; + + memset(&priv->prev_ssid, 0, sizeof(priv->prev_ssid)); + memset(&priv->prev_bssid, 0, sizeof(priv->prev_bssid)); + memset(&priv->assoc_rsp_buf, 0, sizeof(priv->assoc_rsp_buf)); + priv->assoc_rsp_size = 0; + priv->atim_window = 0; + priv->tx_power_level = 0; + priv->max_tx_power_level = 0; + priv->min_tx_power_level = 0; + priv->tx_ant = 0; + priv->rx_ant = 0; + priv->tx_rate = 0; + priv->rxpd_htinfo = 0; + priv->rxpd_rate = 0; + priv->rate_bitmap = 0; + priv->data_rssi_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->data_nf_last = 0; + priv->bcn_rssi_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->bcn_nf_last = 0; + memset(&priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + memset(&priv->aes_key, 0, sizeof(priv->aes_key)); + priv->wpa_ie_len = 0; + priv->wpa_is_gtk_set = false; + + memset(&priv->assoc_tlv_buf, 0, sizeof(priv->assoc_tlv_buf)); + priv->assoc_tlv_buf_len = 0; + memset(&priv->wps, 0, sizeof(priv->wps)); + memset(&priv->gen_ie_buf, 0, sizeof(priv->gen_ie_buf)); + priv->gen_ie_buf_len = 0; + memset(priv->vs_ie, 0, sizeof(priv->vs_ie)); + + priv->wmm_required = true; + priv->wmm_enabled = false; + priv->wmm_qosinfo = 0; + priv->curr_bcn_buf = NULL; + priv->curr_bcn_size = 0; + priv->wps_ie = NULL; + priv->wps_ie_len = 0; + priv->ap_11n_enabled = 0; + memset(&priv->roc_cfg, 0, sizeof(priv->roc_cfg)); + + priv->scan_block = false; + + priv->csa_chan = 0; + priv->csa_expire_time = 0; + priv->del_list_idx = 0; + priv->hs2_enabled = false; + nxpwifi_wmm_init_tos_to_tid_inv(priv); + + nxpwifi_init_11h_params(priv); + + return nxpwifi_add_bss_prio_tbl(priv); +} + +/* Allocate command buffer and sleep-confirm skb. */ +static int nxpwifi_allocate_adapter(struct nxpwifi_adapter *adapter) +{ + int ret; + + /* Allocate command buffer */ + ret = nxpwifi_alloc_cmd_buffer(adapter); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc cmd buffer\n", + __func__); + return ret; + } + + adapter->sleep_cfm = + dev_alloc_skb(sizeof(struct nxpwifi_opt_sleep_confirm) + + INTF_HEADER_LEN); + + if (!adapter->sleep_cfm) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc sleep cfm\t" + " cmd buffer\n", __func__); + return -ENOMEM; + } + skb_reserve(adapter->sleep_cfm, INTF_HEADER_LEN); + + return 0; +} + +/* Initialize adapter defaults and WMM parameters. */ +static void nxpwifi_init_adapter(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = NULL; + + skb_put(adapter->sleep_cfm, sizeof(struct nxpwifi_opt_sleep_confirm)); + + adapter->cmd_sent = false; + adapter->data_sent = true; + + adapter->intf_hdr_len = INTF_HEADER_LEN; + + adapter->cmd_resp_received = false; + adapter->event_received = false; + adapter->data_received = false; + adapter->assoc_resp_received = false; + adapter->priv_link_lost = NULL; + adapter->host_mlme_link_lost = false; + + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + adapter->ps_state = PS_STATE_AWAKE; + adapter->need_to_wakeup = false; + + adapter->scan_mode = HOST_BSS_MODE_ANY; + adapter->specific_scan_time = NXPWIFI_SPECIFIC_SCAN_CHAN_TIME; + adapter->active_scan_time = NXPWIFI_ACTIVE_SCAN_CHAN_TIME; + adapter->passive_scan_time = NXPWIFI_PASSIVE_SCAN_CHAN_TIME; + adapter->scan_chan_gap_time = NXPWIFI_DEF_SCAN_CHAN_GAP_TIME; + + adapter->scan_probes = 1; + + adapter->multiple_dtim = 1; + + /* default value in firmware will be used */ + adapter->local_listen_interval = 0; + + adapter->is_deep_sleep = false; + + adapter->delay_null_pkt = false; + adapter->delay_to_ps = 1000; + adapter->enhanced_ps_mode = PS_MODE_AUTO; + + /* Disable NULL Pkg generation by default */ + adapter->gen_null_pkt = false; + /* Disable pps/uapsd mode by default */ + adapter->pps_uapsd_mode = false; + adapter->pm_wakeup_card_req = false; + + adapter->pm_wakeup_fw_try = false; + + adapter->curr_tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_2K; + + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + adapter->hs_cfg.conditions = cpu_to_le32(HS_CFG_COND_DEF); + adapter->hs_cfg.gpio = HS_CFG_GPIO_DEF; + adapter->hs_cfg.gap = HS_CFG_GAP_DEF; + adapter->hs_activated = false; + + memset(adapter->event_body, 0, sizeof(adapter->event_body)); + adapter->hw_dot_11n_dev_cap = 0; + adapter->hw_dev_mcs_support = 0; + adapter->sec_chan_offset = 0; + + nxpwifi_wmm_init(adapter); + atomic_set(&adapter->tx_hw_pending, 0); + + sleep_cfm_buf = (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + memset(sleep_cfm_buf, 0, adapter->sleep_cfm->len); + sleep_cfm_buf->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + sleep_cfm_buf->size = cpu_to_le16(adapter->sleep_cfm->len); + sleep_cfm_buf->result = 0; + sleep_cfm_buf->action = cpu_to_le16(SLEEP_CONFIRM); + sleep_cfm_buf->resp_ctrl = cpu_to_le16(RESP_NEEDED); + + memset(&adapter->sleep_period, 0, sizeof(adapter->sleep_period)); + adapter->tx_lock_flag = false; + adapter->null_pkt_interval = 0; + adapter->fw_bands = 0; + adapter->fw_release_number = 0; + adapter->fw_cap_info = 0; + memset(&adapter->upld_buf, 0, sizeof(adapter->upld_buf)); + adapter->event_cause = 0; + adapter->region_code = 0; + adapter->bcn_miss_time_out = DEFAULT_BCN_MISS_TIMEOUT; + memset(&adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + adapter->arp_filter_size = 0; + adapter->max_mgmt_ie_index = MAX_MGMT_IE_INDEX; + adapter->key_api_major_ver = 0; + adapter->key_api_minor_ver = 0; + eth_broadcast_addr(adapter->perm_addr); + adapter->iface_limit.sta_intf = NXPWIFI_MAX_STA_NUM; + adapter->iface_limit.uap_intf = NXPWIFI_MAX_UAP_NUM; + adapter->active_scan_triggered = false; + timer_setup(&adapter->wakeup_timer, wakeup_timer_fn, 0); + adapter->devdump_len = 0; + memset(&adapter->vdll_ctrl, 0, sizeof(adapter->vdll_ctrl)); + adapter->vdll_ctrl.skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + atomic_set(&adapter->iface_changing, 0); +} + +/* Update trans_start for each Tx queue. */ +void nxpwifi_set_trans_start(struct net_device *dev) +{ + int i; + + for (i = 0; i < dev->num_tx_queues; i++) + txq_trans_cond_update(netdev_get_tx_queue(dev, i)); + + netif_trans_update(dev); +} + +/* Wake all netdev Tx queues. */ +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_wake_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Stop all netdev Tx queues. */ +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_stop_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Invalidate list heads. */ +static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + list_del(&adapter->cmd_free_q); + list_del(&adapter->cmd_pending_q); + list_del(&adapter->scan_pending_q); + + for (i = 0; i < adapter->priv_num; i++) + list_del(&adapter->bss_prio_tbl[i].bss_prio_head); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + list_del(&priv->wmm.tid_tbl_ptr[j].ra_list); + list_del(&priv->tx_ba_stream_tbl_ptr[j]); + list_del(&priv->rx_reorder_tbl_ptr[j]); + } + list_del(&priv->sta_list); + } +} + +/* Cancel pending work, stop timers, and free adapter buffers. */ +static void +nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) +{ + timer_delete(&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); + if (adapter->vdll_ctrl.vdll_mem) { + vfree(adapter->vdll_ctrl.vdll_mem); + adapter->vdll_ctrl.vdll_mem = NULL; + adapter->vdll_ctrl.vdll_len = 0; + } + if (adapter->vdll_ctrl.skb) { + dev_kfree_skb_any(adapter->vdll_ctrl.skb); + adapter->vdll_ctrl.skb = NULL; + } +} + +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter) +{ + nxpwifi_invalidate_lists(adapter); + + /* Free command buffer */ + nxpwifi_dbg(adapter, INFO, "info: free cmd buffer\n"); + nxpwifi_free_cmd_buffer(adapter); + + if (adapter->sleep_cfm) + dev_kfree_skb_any(adapter->sleep_cfm); +} + +/* Initialize locks and list heads. */ +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + spin_lock_init(&adapter->int_lock); + spin_lock_init(&adapter->nxpwifi_cmd_lock); + spin_lock_init(&adapter->queue_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + spin_lock_init(&priv->wmm.ra_list_spinlock); + spin_lock_init(&priv->curr_bcn_buf_lock); + spin_lock_init(&priv->sta_list_spinlock); + } + + /* Initialize cmd_free_q */ + INIT_LIST_HEAD(&adapter->cmd_free_q); + /* Initialize cmd_pending_q */ + INIT_LIST_HEAD(&adapter->cmd_pending_q); + /* Initialize scan_pending_q */ + INIT_LIST_HEAD(&adapter->scan_pending_q); + + spin_lock_init(&adapter->cmd_free_q_lock); + spin_lock_init(&adapter->cmd_pending_q_lock); + spin_lock_init(&adapter->scan_pending_q_lock); + + skb_queue_head_init(&adapter->rx_mlme_q); + skb_queue_head_init(&adapter->rx_data_q); + skb_queue_head_init(&adapter->tx_data_q); + + for (i = 0; i < adapter->priv_num; ++i) { + INIT_LIST_HEAD(&adapter->bss_prio_tbl[i].bss_prio_head); + spin_lock_init(&adapter->bss_prio_tbl[i].bss_prio_lock); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[j].ra_list); + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[j]); + INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr[j]); + spin_lock_init(&priv->tx_ba_stream_tbl_lock[j]); + spin_lock_init(&priv->rx_reorder_tbl_lock[j]); + } + INIT_LIST_HEAD(&priv->sta_list); + skb_queue_head_init(&priv->bypass_txq); + + spin_lock_init(&priv->ack_status_lock); + xa_init_flags(&priv->ack_status_frames, XA_FLAGS_ALLOC); + } +} + +/* Init firmware: alloc resources, init adapter/privs, send STA init. */ +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + u8 i; + bool first_sta = true; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + /* Allocate memory for member of adapter structure */ + ret = nxpwifi_allocate_adapter(adapter); + if (ret) + return ret; + + /* Initialize adapter structure */ + nxpwifi_init_adapter(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + /* Initialize private structure */ + ret = nxpwifi_init_priv(priv); + if (ret) + return ret; + } + + for (i = 0; i < adapter->priv_num; i++) { + ret = nxpwifi_sta_init_cmd(adapter->priv[i], + first_sta, true); + if (ret) + return ret; + + first_sta = false; + } + spin_lock_bh(&adapter->cmd_pending_q_lock); + WARN_ON(!list_empty(&adapter->cmd_pending_q)); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + adapter->hw_status = NXPWIFI_HW_STATUS_READY; + + return 0; +} + +/* Remove all BSS priority nodes for this priv. */ +static void nxpwifi_delete_bss_prio_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bssprio_node, *tmp_node; + struct list_head *head; + spinlock_t *lock; /* bss priority lock */ + + for (i = 0; i < adapter->priv_num; ++i) { + head = &adapter->bss_prio_tbl[i].bss_prio_head; + lock = &adapter->bss_prio_tbl[i].bss_prio_lock; + nxpwifi_dbg(adapter, INFO, + "info: delete BSS priority table,\t" + "bss_type = %d, bss_num = %d, i = %d,\t" + "head = %p\n", + priv->bss_type, priv->bss_num, i, head); + + { + spin_lock_bh(lock); + list_for_each_entry_safe(bssprio_node, tmp_node, head, + list) { + if (bssprio_node->priv == priv) { + nxpwifi_dbg(adapter, INFO, + "info: Delete\t" + "node %p, next = %p\n", + bssprio_node, tmp_node); + list_del(&bssprio_node->list); + kfree(bssprio_node); + } + } + spin_unlock_bh(lock); + } + } +} + +/* Free per-priv resources and BSS priority entries. */ +void nxpwifi_free_priv(struct nxpwifi_private *priv) +{ + nxpwifi_clean_txrx(priv); + nxpwifi_delete_bss_prio_tbl(priv); + nxpwifi_free_curr_bcn(priv); +} + +/* Shutdown driver: stop work, drain queues, free resources. */ +void +nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i; + struct sk_buff *skb; + + /* nxpwifi already shutdown */ + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + return; + + /* cancel current command */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, WARN, + "curr_cmd is still in processing\n"); + timer_delete_sync(&adapter->cmd_timer); + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + adapter->curr_cmd = NULL; + } + + /* shut down nxpwifi */ + nxpwifi_dbg(adapter, MSG, + "info: shutdown nxpwifi...\n"); + + /* Clean up Tx/Rx queues and delete BSS priority table */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + nxpwifi_abort_cac(priv); + nxpwifi_free_priv(priv); + } + + atomic_set(&adapter->tx_queued, 0); + while ((skb = skb_dequeue(&adapter->tx_data_q))) + nxpwifi_write_data_complete(adapter, skb, 0, 0); + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) + dev_kfree_skb_any(skb); + + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + + atomic_dec(&adapter->rx_pending); + priv = adapter->priv[rx_info->bss_num]; + if (priv) + priv->stats.rx_dropped++; + + dev_kfree_skb_any(skb); + } + + nxpwifi_adapter_cleanup(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_NOT_READY; +} + +/* Download FW if needed; check winner and wait until ready. */ +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *pmfw) +{ + int ret; + u32 poll_num = 1; + + /* check if firmware is already running */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (!ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN FW already running! Skip FW dnld\n"); + return 0; + } + + /* check if we are the winner for downloading FW */ + if (adapter->if_ops.check_winner_status) { + adapter->winner = 0; + ret = adapter->if_ops.check_winner_status(adapter); + + poll_num = MAX_FIRMWARE_POLL_TRIES; + if (ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN read winner status failed!\n"); + return ret; + } + + if (!adapter->winner) { + nxpwifi_dbg(adapter, MSG, + "WLAN is not the winner! Skip FW dnld\n"); + goto poll_fw; + } + } + + if (pmfw) { + /* Download firmware with helper */ + ret = adapter->if_ops.prog_fw(adapter, pmfw); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "prog_fw failed ret=%#x\n", ret); + return ret; + } + } + +poll_fw: + /* Check if the firmware is downloaded successfully or not */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "FW failed to be active in time\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_dnld_fw); diff --git a/drivers/net/wireless/nxp/nxpwifi/join.c b/drivers/net/wireless/nxp/nxpwifi/join.c new file mode 100644 index 000000000000..359006e2c391 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/join.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: association and ad-hoc start/join + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +#define CAPINFO_MASK (~(BIT(15) | BIT(14) | BIT(12) | BIT(11) | BIT(9))) + +/* Append generic IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_generic_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + /* Null Checks */ + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * If there is a generic element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->gen_ie_buf_len) { + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: append generic element len %d to %p\n", + __func__, priv->gen_ie_buf_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->gen_ie_buf_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + /* + * Copy the generic element buffer to the output buffer, advance + * pointer + */ + memcpy(*buffer, priv->gen_ie_buf, priv->gen_ie_buf_len); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += priv->gen_ie_buf_len; + ret_len += priv->gen_ie_buf_len; + + /* Reset the generic element buffer */ + priv->gen_ie_buf_len = 0; + } + + /* return the length appended to the buffer */ + return ret_len; +} + +/* Append TSF timestamp (AP TSF and local RX TSF) for reassoc */ +static int +nxpwifi_cmd_append_tsf_tlv(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_tsf_timestamp tsf_tlv; + __le64 tsf_val; + + /* Null Checks */ + if (!buffer) + return 0; + if (!*buffer) + return 0; + + memset(&tsf_tlv, 0x00, sizeof(struct nxpwifi_ie_types_tsf_timestamp)); + + tsf_tlv.header.type = cpu_to_le16(TLV_TYPE_TSFTIMESTAMP); + tsf_tlv.header.len = cpu_to_le16(2 * sizeof(tsf_val)); + + memcpy(*buffer, &tsf_tlv, sizeof(tsf_tlv.header)); + *buffer += sizeof(tsf_tlv.header); + + /* TSF at the time when beacon/probe_response was received */ + tsf_val = cpu_to_le64(bss_desc->fw_tsf); + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + tsf_val = cpu_to_le64(bss_desc->timestamp); + + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: TSF offset calc: %016llx - %016llx\n", + __func__, bss_desc->timestamp, bss_desc->fw_tsf); + + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + return sizeof(tsf_tlv.header) + (2 * sizeof(tsf_val)); +} + +/* Compute intersection of two rate sets; rate1 updated in-place */ +static int nxpwifi_get_common_rates(struct nxpwifi_private *priv, u8 *rate1, + u32 rate1_size, u8 *rate2, u32 rate2_size) +{ + int ret; + u8 *ptr = rate1, *tmp; + u32 i, j; + + tmp = kmemdup(rate1, rate1_size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + memset(rate1, 0, rate1_size); + + for (i = 0; i < rate2_size && rate2[i]; i++) { + for (j = 0; j < rate1_size && tmp[j]; j++) { + /* + * Check common rate, excluding the bit for + * basic rate + */ + if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) { + *rate1++ = tmp[j]; + break; + } + } + } + + nxpwifi_dbg(priv->adapter, INFO, "info: Tx data rate set to %#x\n", + priv->data_rate); + + if (!priv->is_data_rate_auto) { + while (*ptr) { + if ((*ptr & 0x7f) == priv->data_rate) { + ret = 0; + goto done; + } + ptr++; + } + nxpwifi_dbg(priv->adapter, ERROR, + "previously set fixed data rate %#x\t" + "is not compatible with the network\n", + priv->data_rate); + + ret = -EPERM; + goto done; + } + + ret = 0; +done: + kfree(tmp); + return ret; +} + +/* Build common rates from BSS descriptor into out_rates */ +static int +nxpwifi_setup_rates_from_bssdesc(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 *out_rates, u32 *out_rates_size) +{ + u8 card_rates[NXPWIFI_SUPPORTED_RATES]; + u32 card_rates_size; + int ret; + + /* Copy AP supported rates */ + memcpy(out_rates, bss_desc->supported_rates, NXPWIFI_SUPPORTED_RATES); + /* Get the STA supported rates */ + card_rates_size = nxpwifi_get_active_data_rates(priv, card_rates); + /* Get the common rates between AP and STA supported rates */ + ret = nxpwifi_get_common_rates(priv, out_rates, NXPWIFI_SUPPORTED_RATES, + card_rates, card_rates_size); + if (ret) { + *out_rates_size = 0; + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot get common rates\n", + __func__); + } else { + *out_rates_size = + min_t(size_t, strlen(out_rates), NXPWIFI_SUPPORTED_RATES); + } + + return ret; +} + +/* Append WPS IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_wps_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + if (!buffer || !*buffer) + return 0; + + /* + * If there is a wps element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->wps_ie_len) { + nxpwifi_dbg(priv->adapter, CMD, + "cmd: append wps element %d to %p\n", + priv->wps_ie_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->wps_ie_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + memcpy(*buffer, priv->wps_ie, priv->wps_ie_len); + *buffer += priv->wps_ie_len; + ret_len += priv->wps_ie_len; + } + + kfree(priv->wps_ie); + priv->wps_ie_len = 0; + return ret_len; +} + +/* Append WPA/WPA2 RSN IE TLV */ +static int nxpwifi_append_rsn_ie_wpa_wpa2(struct nxpwifi_private *priv, + u8 **buffer) +{ + struct nxpwifi_ie_types_rsn_param_set *rsn_ie_tlv; + int rsn_ie_len; + + if (!buffer || !(*buffer)) + return 0; + + rsn_ie_tlv = (struct nxpwifi_ie_types_rsn_param_set *)(*buffer); + rsn_ie_tlv->header.type = cpu_to_le16((u16)priv->wpa_ie[0]); + rsn_ie_tlv->header.type = + cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.type) & 0x00FF); + rsn_ie_tlv->header.len = cpu_to_le16((u16)priv->wpa_ie[1]); + rsn_ie_tlv->header.len = cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.len) + & 0x00FF); + if (le16_to_cpu(rsn_ie_tlv->header.len) <= (sizeof(priv->wpa_ie) - 2)) + memcpy(rsn_ie_tlv->rsn_ie, &priv->wpa_ie[2], + le16_to_cpu(rsn_ie_tlv->header.len)); + else + return -ENOMEM; + + rsn_ie_len = sizeof(rsn_ie_tlv->header) + + le16_to_cpu(rsn_ie_tlv->header.len); + *buffer += rsn_ie_len; + + return rsn_ie_len; +} + +/* Build 802.11 association command and required TLVs */ +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_associate *assoc = &cmd->params.associate; + struct nxpwifi_ie_types_host_mlme *host_mlme_tlv; + struct nxpwifi_ie_types_ssid_param_set *ssid_tlv; + struct nxpwifi_ie_types_phy_param_set *phy_tlv; + struct nxpwifi_ie_types_ss_param_set *ss_tlv; + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + struct nxpwifi_ie_types_auth_type *auth_tlv; + struct nxpwifi_ie_types_sae_pwe_mode *sae_pwe_tlv; + struct nxpwifi_ie_types_chan_list_param_set *chan_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 rates_size; + u16 tmp_cap; + u8 *pos; + int rsn_ie_len = 0; + int ret; + + pos = (u8 *)assoc; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_ASSOCIATE); + + /* Save so we know which BSS Desc to use in the response handler */ + priv->attempted_bss_desc = bss_desc; + + memcpy(assoc->peer_sta_addr, + bss_desc->mac_address, sizeof(assoc->peer_sta_addr)); + pos += sizeof(assoc->peer_sta_addr); + + /* Set the listen interval */ + assoc->listen_interval = cpu_to_le16(priv->listen_interval); + /* Set the beacon period */ + assoc->beacon_period = cpu_to_le16(bss_desc->beacon_period); + + pos += sizeof(assoc->cap_info_bitmap); + pos += sizeof(assoc->listen_interval); + pos += sizeof(assoc->beacon_period); + pos += sizeof(assoc->dtim_period); + + host_mlme_tlv = (struct nxpwifi_ie_types_host_mlme *)pos; + host_mlme_tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + host_mlme_tlv->header.len = cpu_to_le16(sizeof(host_mlme_tlv->host_mlme)); + host_mlme_tlv->host_mlme = 1; + pos += sizeof(host_mlme_tlv->header) + sizeof(host_mlme_tlv->host_mlme); + + ssid_tlv = (struct nxpwifi_ie_types_ssid_param_set *)pos; + ssid_tlv->header.type = cpu_to_le16(WLAN_EID_SSID); + ssid_tlv->header.len = cpu_to_le16((u16)bss_desc->ssid.ssid_len); + memcpy(ssid_tlv->ssid, bss_desc->ssid.ssid, + le16_to_cpu(ssid_tlv->header.len)); + pos += sizeof(ssid_tlv->header) + le16_to_cpu(ssid_tlv->header.len); + + phy_tlv = (struct nxpwifi_ie_types_phy_param_set *)pos; + phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS); + phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set)); + memcpy(&phy_tlv->fh_ds.ds_param_set, + &bss_desc->phy_param_set.ds_param_set.current_chan, + sizeof(phy_tlv->fh_ds.ds_param_set)); + pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len); + + ss_tlv = (struct nxpwifi_ie_types_ss_param_set *)pos; + ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS); + ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set)); + pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len); + + /* Get the common rates supported between the driver and the BSS Desc */ + ret = nxpwifi_setup_rates_from_bssdesc(priv, bss_desc, + rates, &rates_size); + if (ret) + return ret; + + /* Save the data rates into Current BSS state structure */ + priv->curr_bss_params.num_of_rates = rates_size; + memcpy(&priv->curr_bss_params.data_rates, rates, rates_size); + + /* Setup the Rates TLV in the association command */ + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + pos += sizeof(rates_tlv->header) + rates_size; + nxpwifi_dbg(adapter, INFO, "info: ASSOC_CMD: rates size = %d\n", + rates_size); + + /* Add the Authentication type */ + auth_tlv = (struct nxpwifi_ie_types_auth_type *)pos; + auth_tlv->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_tlv->header.len = cpu_to_le16(sizeof(auth_tlv->auth_type)); + if (priv->sec_info.wep_enabled) + auth_tlv->auth_type = + cpu_to_le16((u16)priv->sec_info.authentication_mode); + else + auth_tlv->auth_type = cpu_to_le16(NL80211_AUTHTYPE_OPEN_SYSTEM); + + pos += sizeof(auth_tlv->header) + le16_to_cpu(auth_tlv->header.len); + + if (priv->sec_info.authentication_mode == WLAN_AUTH_SAE) { + auth_tlv->auth_type = cpu_to_le16(NXPWIFI_AUTHTYPE_SAE); + if (bss_desc->bcn_rsnx_ie && + bss_desc->bcn_rsnx_ie->datalen && + (bss_desc->bcn_rsnx_ie->data[0] & + WLAN_RSNX_CAPA_SAE_H2E)) { + sae_pwe_tlv = + (struct nxpwifi_ie_types_sae_pwe_mode *)pos; + sae_pwe_tlv->header.type = + cpu_to_le16(TLV_TYPE_SAE_PWE_MODE); + sae_pwe_tlv->header.len = + cpu_to_le16(sizeof(sae_pwe_tlv->pwe[0])); + sae_pwe_tlv->pwe[0] = bss_desc->bcn_rsnx_ie->data[0]; + pos += sizeof(sae_pwe_tlv->header) + + sizeof(sae_pwe_tlv->pwe[0]); + } + } + + if (IS_SUPPORT_MULTI_BANDS(adapter) && + !(ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap)) { + /* + * Append a channel TLV for the channel the attempted AP was + * found on + */ + chan_tlv = (struct nxpwifi_ie_types_chan_list_param_set *)pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct nxpwifi_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (bss_desc->phy_param_set.ds_param_set.current_chan); + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Chan = %d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Band = %d\n", + chan_tlv->chan_scan_param[0].band_cfg); + pos += sizeof(chan_tlv->header) + + sizeof(struct nxpwifi_chan_scan_param_set); + } + + if (!priv->wps.session_enable) { + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + rsn_ie_len = nxpwifi_append_rsn_ie_wpa_wpa2(priv, &pos); + + if (rsn_ie_len == -ENOMEM) + return -ENOMEM; + } + + if (ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) + nxpwifi_cmd_append_11n_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->config_bands & BAND_GAC || + priv->config_bands & BAND_AAC)) + nxpwifi_cmd_append_11ac_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + nxpwifi_11ax_bandconfig_allowed(priv, bss_desc)) + nxpwifi_cmd_append_11ax_tlv(priv, bss_desc, &pos); + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_ASSOC, &pos); + + nxpwifi_wmm_process_association_req(priv, &pos, &bss_desc->wmm_ie, + bss_desc->bcn_ht_cap); + + if (priv->wps.session_enable && priv->wps_ie_len) + nxpwifi_cmd_append_wps_ie(priv, &pos); + + nxpwifi_cmd_append_generic_ie(priv, &pos); + + nxpwifi_cmd_append_tsf_tlv(priv, &pos, bss_desc); + + nxpwifi_11h_process_join(priv, &pos, bss_desc); + + cmd->size = cpu_to_le16((u16)(pos - (u8 *)assoc) + S_DS_GEN); + + /* Set the Capability info at last */ + tmp_cap = bss_desc->cap_info_bitmap; + + if (priv->config_bands == BAND_B) + tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME; + + tmp_cap &= CAPINFO_MASK; + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n", + tmp_cap, CAPINFO_MASK); + assoc->cap_info_bitmap = cpu_to_le16(tmp_cap); + + return ret; +} + +static const char *assoc_failure_reason_to_str(u16 cap_info) +{ + switch (cap_info) { + case CONNECT_ERR_AUTH_ERR_STA_FAILURE: + return "CONNECT_ERR_AUTH_ERR_STA_FAILURE"; + case CONNECT_ERR_AUTH_MSG_UNHANDLED: + return "CONNECT_ERR_AUTH_MSG_UNHANDLED"; + case CONNECT_ERR_ASSOC_ERR_TIMEOUT: + return "CONNECT_ERR_ASSOC_ERR_TIMEOUT"; + case CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED: + return "CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED"; + case CONNECT_ERR_STA_FAILURE: + return "CONNECT_ERR_STA_FAILURE"; + } + + return "Unknown connect failure"; +} + +/* + * Handle association command response. + * Parse cap_info/status_code/AID and copy IEs, update connection state, + * WMM and HT/HE parameters, queues and filters. On failure, return the + * IEEE status code or a mapped timeout error. + */ +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct ieee_types_assoc_rsp *assoc_rsp; + struct nxpwifi_bssdescriptor *bss_desc; + bool enable_data = true; + u16 cap_info, status_code, aid; + const u8 *ie_ptr; + struct ieee80211_ht_operation *assoc_resp_ht_oper; + struct ieee80211_mgmt *hdr; + + if (!priv->attempted_bss_desc) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed, association terminated by host\n", + __func__); + goto done; + } + + hdr = (struct ieee80211_mgmt *)&resp->params; + if (!memcmp(hdr->bssid, priv->attempted_bss_desc->mac_address, + ETH_ALEN)) + assoc_rsp = (struct ieee_types_assoc_rsp *)&hdr->u.assoc_resp; + else + assoc_rsp = (struct ieee_types_assoc_rsp *)&resp->params; + + cap_info = le16_to_cpu(assoc_rsp->cap_info_bitmap); + status_code = le16_to_cpu(assoc_rsp->status_code); + aid = le16_to_cpu(assoc_rsp->a_id); + + if ((aid & (BIT(15) | BIT(14))) != (BIT(15) | BIT(14))) + nxpwifi_dbg(adapter, ERROR, + "invalid AID value 0x%x; bits 15:14 not set\n", aid); + + aid &= ~(BIT(15) | BIT(14)); + + priv->assoc_rsp_size = min(le16_to_cpu(resp->size) - S_DS_GEN, + sizeof(priv->assoc_rsp_buf)); + + assoc_rsp->a_id = cpu_to_le16(aid); + memcpy(priv->assoc_rsp_buf, &resp->params, priv->assoc_rsp_size); + + if (status_code) { + adapter->dbg.num_cmd_assoc_failure++; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: failed,\t" + "status code=%d err=%#x a_id=%#x\n", + status_code, cap_info, + le16_to_cpu(assoc_rsp->a_id)); + + nxpwifi_dbg(adapter, ERROR, "assoc failure: reason %s\n", + assoc_failure_reason_to_str(cap_info)); + if (cap_info == CONNECT_ERR_ASSOC_ERR_TIMEOUT) { + if (status_code == NXPWIFI_ASSOC_CMD_FAILURE_AUTH) { + ret = WLAN_STATUS_AUTH_TIMEOUT; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: AUTH timeout\n"); + } else { + ret = WLAN_STATUS_UNSPECIFIED_FAILURE; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: UNSPECIFIED failure\n"); + } + + priv->assoc_rsp_size = 0; + } else { + ret = status_code; + } + + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + priv->media_connected = true; + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pps_uapsd_mode = false; + adapter->tx_lock_flag = false; + + /* Set the attempted BSSID Index to current */ + bss_desc = priv->attempted_bss_desc; + + nxpwifi_dbg(adapter, INFO, "info: ASSOC_RESP: %s\n", + bss_desc->ssid.ssid); + + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, + bss_desc, sizeof(struct nxpwifi_bssdescriptor)); + + /* Update curr_bss_params */ + priv->curr_bss_params.bss_descriptor.channel = + bss_desc->phy_param_set.ds_param_set.current_chan; + + priv->curr_bss_params.band = (u8)bss_desc->bss_band; + + if (bss_desc->wmm_ie.element_id == WLAN_EID_VENDOR_SPECIFIC) + priv->curr_bss_params.wmm_enabled = true; + else + priv->curr_bss_params.wmm_enabled = false; + + if ((priv->wmm_required || bss_desc->bcn_ht_cap) && + priv->curr_bss_params.wmm_enabled) + priv->wmm_enabled = true; + else + priv->wmm_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = priv->wmm_enabled && + (bss_desc->wmm_ie.qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD); + + /* Store the bandwidth information from assoc response */ + ie_ptr = cfg80211_find_ie(WLAN_EID_HT_OPERATION, assoc_rsp->ie_buffer, + priv->assoc_rsp_size + - sizeof(struct ieee_types_assoc_rsp)); + if (ie_ptr) { + assoc_resp_ht_oper = (struct ieee80211_ht_operation *)(ie_ptr + + sizeof(struct element)); + priv->assoc_resp_ht_param = assoc_resp_ht_oper->ht_param; + priv->ht_param_present = true; + } else { + priv->ht_param_present = false; + } + + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_RESP: curr_pkt_filter is %#x\n", + priv->curr_pkt_filter); + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->wpa_is_gtk_set = false; + + if (priv->wmm_enabled) { + /* Don't re-enable carrier until we get the WMM_GET_STATUS event */ + enable_data = false; + } else { + /* Since WMM is not enabled, setup the queues with the defaults */ + nxpwifi_wmm_setup_queue_priorities(priv, NULL); + nxpwifi_wmm_setup_ac_downgrade(priv); + } + + if (enable_data) + nxpwifi_dbg(adapter, INFO, + "info: post association, re-enabling data flow\n"); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + + nxpwifi_save_curr_bcn(priv); + + adapter->dbg.num_cmd_assoc_success++; + + nxpwifi_dbg(adapter, MSG, "assoc: associated with %pM\n", + priv->attempted_bss_desc->mac_address); + + /* Add the ra_list here for infra mode as there will be only 1 ra always */ + nxpwifi_ralist_add(priv, + priv->curr_bss_params.bss_descriptor.mac_address); + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->scan_block = true; + else + priv->port_open = true; + +done: + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + if (ret) + adapter->cmd_wait_q.status = -1; + else + adapter->cmd_wait_q.status = 0; + } + + return ret; +} + +/* Associate to the specified BSS (STA only) */ +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + /* + * Return error if the adapter is not STA role or table entry + * is not marked as infra. + */ + if ((GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) || + bss_desc->bss_mode != NL80211_IFTYPE_STATION) + return -EINVAL; + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + priv->config_bands & BAND_AAC) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + /* + * Clear any past association response stored for application + * retrieval + */ + priv->assoc_rsp_size = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_ASSOCIATE, + HOST_ACT_GEN_SET, 0, bss_desc, true); +} + +/* Send deauth to disconnect from an infrastructure BSS */ +static int nxpwifi_deauthenticate_infra(struct nxpwifi_private *priv, u8 *mac) +{ + u8 mac_address[ETH_ALEN]; + int ret; + + if (!mac || is_zero_ether_addr(mac)) + memcpy(mac_address, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + else + memcpy(mac_address, mac, ETH_ALEN); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, mac_address, true); + + return ret; +} + +/* Disconnect from the current BSS (STA/P2P/AP) */ +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac) +{ + int ret = 0; + + if (!priv->media_connected) + return 0; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + priv->host_mlme_reg = false; + priv->mgmt_frame_mask = 0; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, + HOST_ACT_GEN_SET, 0, + &priv->mgmt_frame_mask, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_deauthenticate_infra(priv, mac); + if (ret) + cfg80211_disconnected(priv->netdev, 0, NULL, 0, + true, GFP_KERNEL); + break; + case NL80211_IFTYPE_AP: + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true); + break; + default: + break; + } + + return ret; +} + +/* Deauthenticate/disconnect from all BSS. */ +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } +} +EXPORT_SYMBOL_GPL(nxpwifi_deauthenticate_all); + +/* Convert band to radio type used in channel TLV. */ +u8 nxpwifi_band_to_radio_type(u16 config_bands) +{ + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + return HOST_SCAN_RADIO_TYPE_A; + + return HOST_SCAN_RADIO_TYPE_BG; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c new file mode 100644 index 000000000000..b4c63829024a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -0,0 +1,1673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: major functions + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "cfg80211.h" +#include "11n.h" + +#define VERSION "1.0" + +static unsigned int debug_mask = NXPWIFI_DEFAULT_DEBUG_MASK; + +char nxpwifi_driver_version[] = "nxpwifi " VERSION " (%s) "; + +const u16 nxpwifi_1d_to_wmm_queue[8] = { 1, 0, 0, 1, 2, 2, 3, 3 }; + +/* Optional RF calibration data file */ +static const char *cal_data_name = "nxp/cal_data.conf"; + +/* Register device; init adapter/privs/if_ops/locks; cleanup on fail. */ +static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev, + struct nxpwifi_if_ops *if_ops) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + int i; + + adapter = kzalloc_obj(*adapter, GFP_KERNEL); + if (!adapter) + return ERR_PTR(-ENOMEM); + + adapter->dev = dev; + adapter->card = card; + + /* Save interface specific operations in adapter */ + memmove(&adapter->if_ops, if_ops, sizeof(struct nxpwifi_if_ops)); + adapter->debug_mask = debug_mask; + + /* card specific initialization has been deferred until now .. */ + if (adapter->if_ops.init_if) { + ret = adapter->if_ops.init_if(adapter); + if (ret) + goto error; + } + + adapter->priv_num = 0; + + for (i = 0; i < NXPWIFI_MAX_BSS_NUM; i++) { + /* Allocate memory for private structure */ + adapter->priv[i] = + kzalloc_obj(struct nxpwifi_private, GFP_KERNEL); + if (!adapter->priv[i]) { + ret = -ENOMEM; + goto error; + } + + adapter->priv[i]->adapter = adapter; + adapter->priv_num++; + } + nxpwifi_init_lock_list(adapter); + + timer_setup(&adapter->cmd_timer, nxpwifi_cmd_timeout_func, 0); + + if (ret) + return ERR_PTR(ret); + else + return adapter; + +error: + nxpwifi_dbg(adapter, ERROR, + "info: leave %s with error\n", __func__); + + for (i = 0; i < adapter->priv_num; i++) + kfree(adapter->priv[i]); + + kfree(adapter); + + return ERR_PTR(ret); +} + +/* Unregister device; free timers, beacons, privs, nd_info, adapter. */ +static void nxpwifi_unregister(struct nxpwifi_adapter *adapter) +{ + s32 i; + + if (adapter->if_ops.cleanup_if) + adapter->if_ops.cleanup_if(adapter); + + timer_delete_sync(&adapter->cmd_timer); + + /* Free private structures */ + for (i = 0; i < adapter->priv_num; i++) { + nxpwifi_free_curr_bcn(adapter->priv[i]); + kfree(adapter->priv[i]); + } + + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + kfree(adapter->regd); + + kfree(adapter); +} + +static void nxpwifi_queue_rx_work(struct nxpwifi_adapter *adapter) +{ + queue_work(adapter->rx_workqueue, &adapter->rx_work); +} + +static void nxpwifi_process_rx(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + + if (atomic_read(&adapter->iface_changing) || + atomic_read(&adapter->rx_ba_teardown_pending)) + return; + + /* Check for Rx data */ + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + atomic_dec(&adapter->rx_pending); + if (adapter->delay_main_work && + (atomic_read(&adapter->rx_pending) < LOW_RX_PENDING)) { + adapter->delay_main_work = false; + nxpwifi_queue_work(adapter, &adapter->main_work); + } + rx_info = NXPWIFI_SKB_RXCB(skb); + if (rx_info->buf_type == NXPWIFI_TYPE_AGGR_DATA) { + if (adapter->if_ops.deaggr_pkt) + adapter->if_ops.deaggr_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + } +} + +static void maybe_quirk_fw_disable_ds(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + struct nxpwifi_ver_ext ver_ext; + + if (test_and_set_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &adapter->work_flags)) + return; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = 1; + if (nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Checking hardware revision failed.\n"); + } +} + +static void nxpwifi_handle_irq_status(struct nxpwifi_adapter *adapter, u8 istat) +{ + if (adapter->hs_activated) + nxpwifi_process_hs_config(adapter); + if (adapter->if_ops.process_int_status) + adapter->if_ops.process_int_status(adapter, istat); +} + +static bool nxpwifi_drain_tx(struct nxpwifi_adapter *adapter) +{ + bool ret = false; + + if ((adapter->scan_chan_gap_enabled || !adapter->scan_processing) && + !adapter->data_sent && !skb_queue_empty(&adapter->tx_data_q)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_process_tx_queue(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && + !nxpwifi_bypass_txlist_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + nxpwifi_process_bypass_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && !nxpwifi_wmm_lists_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_wmm_process_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + return ret; +} + +static bool nxpwifi_handle_rx(struct nxpwifi_adapter *adapter) +{ + if (adapter->rx_work_enabled && adapter->data_received) { + nxpwifi_queue_rx_work(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_cmd_response(struct nxpwifi_adapter *adapter) +{ + /* Check for Cmd Resp */ + if (adapter->cmd_resp_received) { + adapter->cmd_resp_received = false; + nxpwifi_process_cmdresp(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_events(struct nxpwifi_adapter *adapter) +{ + if (adapter->event_received) { + adapter->event_received = false; + nxpwifi_process_event(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_tx_has_pending(struct nxpwifi_adapter *adapter) +{ + return !skb_queue_empty(&adapter->tx_data_q) || + !nxpwifi_bypass_txlist_empty(adapter) || + !nxpwifi_wmm_lists_empty(adapter); +} + +static bool nxpwifi_cmd_has_pending(struct nxpwifi_adapter *adapter) +{ + return !list_empty(&adapter->cmd_pending_q); +} + +static bool nxpwifi_events_has_pending(struct nxpwifi_adapter *adapter) +{ + return adapter->event_received; +} + +static bool nxpwifi_should_wakeup_card(struct nxpwifi_adapter *adapter) +{ + if (adapter->ps_state != PS_STATE_SLEEP) + return false; + + if (!adapter->pm_wakeup_card_req || adapter->pm_wakeup_fw_try) + return false; + + return nxpwifi_is_command_pending(adapter) || nxpwifi_tx_has_pending(adapter); +} + +static bool nxpwifi_should_exit_main_loop(struct nxpwifi_adapter *adapter) +{ + if (adapter->pm_wakeup_fw_try) + return true; + + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + if (adapter->ps_state != PS_STATE_AWAKE) + return true; + + if (adapter->tx_lock_flag) + return true; + + if ((!adapter->scan_chan_gap_enabled && adapter->scan_processing) || + adapter->data_sent || !nxpwifi_tx_has_pending(adapter)) { + if (adapter->cmd_sent || adapter->curr_cmd || + !nxpwifi_is_command_pending(adapter)) + return true; + } + + return false; +} + +static void nxpwifi_wakeup_card(struct nxpwifi_adapter *adapter) +{ + adapter->pm_wakeup_fw_try = true; + mod_timer(&adapter->wakeup_timer, jiffies + (HZ * 3)); + adapter->if_ops.wakeup(adapter); +} + +static void nxpwifi_handle_vdll_download(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && adapter->vdll_ctrl.pending_block) { + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + + nxpwifi_download_vdll_block(adapter, ctrl->pending_block, + ctrl->pending_block_len); + ctrl->pending_block = NULL; + } +} + +static void nxpwifi_finish_delayed_null_pkt(struct nxpwifi_adapter *adapter) +{ + if (!adapter->delay_null_pkt) + return; + + if (adapter->cmd_sent || adapter->curr_cmd || nxpwifi_is_command_pending(adapter)) + return; + + if (nxpwifi_tx_has_pending(adapter)) + return; + + if (!nxpwifi_send_null_packet(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) { + adapter->delay_null_pkt = false; + adapter->ps_state = PS_STATE_SLEEP; + } +} + +static bool nxpwifi_pump_command(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !adapter->curr_cmd) { + if (!nxpwifi_exec_next_cmd(adapter)) + return true; + } + + return false; +} + +/* Main loop: IRQ/RX/CMD/EVENT; wake card; TX; PS null; exit if idle. */ +void nxpwifi_main_process(struct nxpwifi_adapter *adapter) +{ + unsigned long flags; + + /* Check if virtual interface changing */ + if (atomic_read(&adapter->iface_changing)) { + nxpwifi_dbg(adapter, + INFO, "main_process skipped due to iface_changing"); + return; + } + + for (;;) { + bool did_work = false; + u8 istat = 0; + + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + break; + + /* + * For non-USB interfaces, If we process interrupts first, it + * would increase RX pending even further. Avoid this by + * checking if rx_pending has crossed high threshold and + * schedule rx work queue and then process interrupts. + * For USB interface, there are no interrupts. We already have + * HIGH_RX_PENDING check in usb.c + */ + if (atomic_read(&adapter->rx_pending) >= HIGH_RX_PENDING) { + adapter->delay_main_work = true; + nxpwifi_queue_rx_work(adapter); + break; + } + + /* + * Snapshot-and-clear the interrupt status. + * + * Take the same lock as the producer (nxpwifi_sdio_interrupt()) uses + * when OR-ing new bits into adapter->int_status. We atomically grab + * what has accumulated and clear it, so this consumer owns this batch. + */ + spin_lock_irqsave(&adapter->int_lock, flags); + istat = adapter->int_status; + adapter->int_status = 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + /* Handle pending interrupt if any */ + if (istat) { + nxpwifi_handle_irq_status(adapter, istat); + did_work = true; + } + + did_work |= nxpwifi_handle_rx(adapter); + + if (nxpwifi_should_wakeup_card(adapter)) { + nxpwifi_wakeup_card(adapter); + continue; + } + + if (IS_CARD_RX_RCVD(adapter)) { + /* Card has responded, clear wakeup state and update power state */ + adapter->data_received = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + if (adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + } else { + if (nxpwifi_should_exit_main_loop(adapter)) + break; + } + + did_work |= nxpwifi_handle_events(adapter); + + did_work |= nxpwifi_handle_cmd_response(adapter); + + /* Check if we need to confirm Sleep Request received previously */ + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + /* + * The ps_state may have been changed during processing of + * Sleep Request event. + */ + if (adapter->ps_state != PS_STATE_AWAKE) + continue; + + if (adapter->tx_lock_flag) + continue; + + nxpwifi_handle_vdll_download(adapter); + + did_work |= nxpwifi_pump_command(adapter); + + did_work |= nxpwifi_drain_tx(adapter); + + /* + * Attempt to send delayed null packet. + * If successful, firmware will enter sleep and ps_state will be updated. + * We check ps_state here to determine if main loop can safely exit. + */ + nxpwifi_finish_delayed_null_pkt(adapter); + + if (adapter->ps_state == PS_STATE_SLEEP) + break; + /* + * Step 3) Cooperative preemption point. + * cond_resched() yields ONLY if need_resched() is set. Placing it + * BEFORE the final check improves fairness: it lets ksdioirqd (RT/FIFO) + * or other producers run and set new int_status bits. Immediately + * after we return here, we perform the final "net cast" (Step 4) to + * decide if we should continue or return. + */ + + cond_resched(); + + /* + * Step 4) Exit decision with lost-kick closure. + * + * We consider exiting ONLY when this round did no real work. + * Rationale: + * - If did_work == true: we will loop anyway; at Step 1 we will + * re-snapshot int_status, so there's no need to re-check now. + * - If did_work == false: we appear idle and may return. But during + * our execution window, a producer may have just set int_status and + * queue_work(); since this work is still running, queue_work() + * returns false (no second instance queued). If we return now, + * we'd leave unprocessed status with no pending work => lost-kick. + * + * Therefore, perform a single final check: if *anything* is pending, + * continue looping; otherwise, break and return. + */ + if (!did_work) { + bool more = false; + unsigned long flags; + /* 4a) New IRQ bits raced in while we were running? */ + spin_lock_irqsave(&adapter->int_lock, flags); + more |= adapter->int_status != 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + /* 4b) Any other sources still pending? (driver-specific) */ + more |= nxpwifi_tx_has_pending(adapter); + more |= nxpwifi_cmd_has_pending(adapter); + more |= nxpwifi_events_has_pending(adapter); + + if (!more) + break; /* Truly quiescent now: safe to return. */ + /* else: loop back to Step 1 to consume what just arrived. */ + } + /* If did_work == true, we loop unconditionally and re-snapshot. */ + }; +} + +/* Free adapter via nxpwifi_unregister(). */ +static void nxpwifi_free_adapter(struct nxpwifi_adapter *adapter) +{ + if (!adapter) { + pr_err("%s: adapter is NULL\n", __func__); + return; + } + + nxpwifi_unregister(adapter); + pr_debug("info: %s: free adapter\n", __func__); +} + +/* Destroy main and RX workqueues. */ +static void nxpwifi_terminate_workqueue(struct nxpwifi_adapter *adapter) +{ + if (adapter->workqueue) { + destroy_workqueue(adapter->workqueue); + adapter->workqueue = NULL; + } + + if (adapter->rx_workqueue) { + destroy_workqueue(adapter->rx_workqueue); + adapter->rx_workqueue = NULL; + } +} + +/* FW bring-up: download, enable IRQ, init FW; cfg80211+ifaces; cleanup. */ +static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + int ret = 0; + char fmt[64]; + struct nxpwifi_adapter *adapter = context; + struct nxpwifi_fw_image fw; + bool init_failed = false; + struct wireless_dev *wdev; + struct completion *fw_done = adapter->fw_done; + + if (!firmware) { + nxpwifi_dbg(adapter, ERROR, + "Failed to get firmware %s\n", adapter->fw_name); + ret = -EINVAL; + goto err_dnld_fw; + } + + memset(&fw, 0, sizeof(struct nxpwifi_fw_image)); + adapter->firmware = firmware; + fw.fw_buf = (u8 *)adapter->firmware->data; + fw.fw_len = adapter->firmware->size; + + if (adapter->if_ops.dnld_fw) + ret = adapter->if_ops.dnld_fw(adapter, &fw); + else + ret = nxpwifi_dnld_fw(adapter, &fw); + + if (ret) + goto err_dnld_fw; + + nxpwifi_dbg(adapter, MSG, "WLAN FW is active\n"); + + /* Load optional calibration data */ + ret = request_firmware(&adapter->cal_data, cal_data_name, adapter->dev); + if (ret) { + nxpwifi_dbg(adapter, INFO, "no %s, using default cal\n", + cal_data_name); + adapter->cal_data = NULL; + } + + /* enable host interrupt after fw dnld is successful */ + if (adapter->if_ops.enable_int) { + ret = adapter->if_ops.enable_int(adapter); + if (ret) + goto err_dnld_fw; + } + + ret = nxpwifi_init_fw(adapter); + if (ret) + goto err_init_fw; + + maybe_quirk_fw_disable_ds(adapter); + + if (!adapter->wiphy) { + if (nxpwifi_register_cfg80211(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "cannot register with cfg80211\n"); + goto err_init_fw; + } + } + + if (nxpwifi_init_channel_scan_gap(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "could not init channel stats table\n"); + goto err_init_chan_scan; + } + + rtnl_lock(); + /* Create station interface by default */ + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "mlan%d", NET_NAME_ENUM, + NL80211_IFTYPE_STATION, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create default STA interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "uap%d", NET_NAME_ENUM, + NL80211_IFTYPE_AP, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create AP interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + rtnl_unlock(); + + nxpwifi_drv_get_driver_version(adapter, fmt, sizeof(fmt) - 1); + nxpwifi_dbg(adapter, MSG, "driver_version = %s\n", fmt); + adapter->is_up = true; + goto done; + +err_add_intf: + vfree(adapter->chan_stats); +err_init_chan_scan: + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); +err_init_fw: + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); +err_dnld_fw: + nxpwifi_dbg(adapter, ERROR, + "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + init_failed = true; +done: + if (adapter->cal_data) { + release_firmware(adapter->cal_data); + adapter->cal_data = NULL; + } + if (adapter->firmware) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + if (init_failed) + nxpwifi_free_adapter(adapter); + + /* Tell all current and future waiters we're finished */ + complete_all(fw_done); + + return ret; +} + +static void nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + _nxpwifi_fw_dpc(firmware, context); +} + +/* Request firmware (sync/async) and start HW init. */ +static int nxpwifi_init_hw_fw(struct nxpwifi_adapter *adapter, + bool req_fw_nowait) +{ + int ret; + + if (req_fw_nowait) { + ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name, + adapter->dev, GFP_KERNEL, adapter, + nxpwifi_fw_dpc); + } else { + ret = request_firmware(&adapter->firmware, + adapter->fw_name, + adapter->dev); + } + + if (ret < 0) + nxpwifi_dbg(adapter, ERROR, "request_firmware%s error %d\n", + req_fw_nowait ? "_nowait" : "", ret); + return ret; +} + +/* ndo_open: bring carrier down. */ +static int +nxpwifi_open(struct net_device *dev) +{ + netif_carrier_off(dev); + + return 0; +} + +/* ndo_stop: abort scan/sched-scan if running. */ +static int +nxpwifi_close(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(priv->adapter, INFO, + "aborting scan on ndo_stop\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = true; + } + + if (priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, INFO, + "aborting bgscan on ndo_stop\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + } + + return 0; +} + +static bool +nxpwifi_bypass_tx_queue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + if (eth_hdr->h_proto == htons(ETH_P_PAE) || + nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(priv->adapter, DATA, + "bypass txqueue; eth type %#x, mgmt %d\n", + ntohs(eth_hdr->h_proto), + nxpwifi_is_skb_mgmt_frame(skb)); + if (eth_hdr->h_proto == htons(ETH_P_PAE)) + nxpwifi_dbg(priv->adapter, MSG, + "key: send EAPOL to %pM\n", + eth_hdr->h_dest); + return true; + } + + return false; +} + +/* Queue SKB (WMM or bypass) and schedule main work. */ +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct netdev_queue *txq; + int index = nxpwifi_1d_to_wmm_queue[skb->priority]; + + if (atomic_inc_return(&priv->wmm_tx_pending[index]) >= MAX_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (!netif_tx_queue_stopped(txq)) { + netif_tx_stop_queue(txq); + nxpwifi_dbg(adapter, DATA, + "stop queue: %d\n", index); + } + } + + if (nxpwifi_bypass_tx_queue(priv, skb)) { + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->bypass_tx_pending); + nxpwifi_wmm_add_buf_bypass_txqueue(priv, skb); + } else { + atomic_inc(&adapter->tx_pending); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie) +{ + struct sk_buff *orig_skb = skb; + struct nxpwifi_txinfo *tx_info, *orig_tx_info; + u32 id32 = 0; + int ret; + + skb = skb_clone(skb, GFP_ATOMIC); + if (skb) { + spin_lock_bh(&priv->ack_status_lock); + /* + * Use XArray to allocate IDs in the range 1..0x0F. + * Limit ensures the allocated token ID is always within this + * range. + */ + ret = xa_alloc(&priv->ack_status_frames, &id32, orig_skb, + XA_LIMIT(1, 0x0f), GFP_ATOMIC); + spin_unlock_bh(&priv->ack_status_lock); + + if (ret == 0) { + tx_info = NXPWIFI_SKB_TXCB(skb); + tx_info->ack_frame_id = id32; + tx_info->flags |= flag; + orig_tx_info = NXPWIFI_SKB_TXCB(orig_skb); + orig_tx_info->ack_frame_id = id32; + orig_tx_info->flags |= flag; + + if (flag == NXPWIFI_BUF_FLAG_ACTION_TX_STATUS && cookie) + orig_tx_info->cookie = *cookie; + + } else if (skb_shared(skb)) { + kfree_skb(orig_skb); + } else { + kfree_skb(skb); + skb = orig_skb; + } + } else { + /* couldn't clone -- lose tx status ... */ + skb = orig_skb; + } + + return skb; +} + +/* ndo_start_xmit: fix headroom, fill TXCB, timestamp, enqueue. */ +static netdev_tx_t +nxpwifi_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + bool multicast; + + nxpwifi_dbg(priv->adapter, DATA, + "data: %lu BSS(%d-%d): Data <= kernel\n", + jiffies, priv->bss_type, priv->bss_num); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &priv->adapter->work_flags)) { + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (!skb->len || skb->len > ETH_FRAME_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: bad skb len %d\n", skb->len); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(priv->adapter, DATA, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: cannot alloca new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(priv->adapter, INFO, + "info: new skb headroomd %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = skb->len; + + multicast = is_multicast_ether_addr(skb->data); + + if (unlikely(!multicast && sk_requests_wifi_status(skb->sk) && + priv->adapter->fw_api_ver == NXPWIFI_FW_V15)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS, NULL); + + /* + * Record the current time the packet was queued; used to + * determine the amount of time the packet was queued in + * the driver before it was sent to the firmware. + * The delay is then sent along with the packet to the + * firmware for aggregate delay calculation for stats and + * MSDU lifetime expiry. + */ + __net_timestamp(skb); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, bool external, + u8 *new_mac) +{ + int ret; + u64 mac_addr, old_mac_addr; + + old_mac_addr = ether_addr_to_u64(priv->curr_addr); + + if (external) { + mac_addr = ether_addr_to_u64(new_mac); + } else { + /* Internal mac address change */ + if (priv->bss_type == NXPWIFI_BSS_TYPE_ANY) + return -EOPNOTSUPP; + + mac_addr = old_mac_addr; + + if (priv->adapter->priv[0] != priv) { + /* Set mac address based on bss_type/bss_num */ + mac_addr ^= BIT_ULL(priv->bss_type + 8); + mac_addr += priv->bss_num; + } + } + + u64_to_ether_addr(mac_addr, priv->curr_addr); + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_MAC_ADDRESS, + HOST_ACT_GEN_SET, 0, NULL, true); + + if (ret) { + u64_to_ether_addr(old_mac_addr, priv->curr_addr); + nxpwifi_dbg(priv->adapter, ERROR, + "set mac address failed: ret=%d\n", ret); + return ret; + } + + eth_hw_addr_set(dev, priv->curr_addr); + return 0; +} + +/* ndo_set_mac_address: set MAC via firmware. */ +static int +nxpwifi_ndo_set_mac_address(struct net_device *dev, void *addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sockaddr *hw_addr = addr; + + return nxpwifi_set_mac_address(priv, dev, true, hw_addr->sa_data); +} + +/* ndo_set_rx_mode: promisc/allmulti or program multicast. */ +static void nxpwifi_set_multicast_list(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_multicast_list mcast_list; + + if (dev->flags & IFF_PROMISC) { + mcast_list.mode = NXPWIFI_PROMISC_MODE; + } else if (dev->flags & IFF_ALLMULTI || + netdev_mc_count(dev) > NXPWIFI_MAX_MULTICAST_LIST_SIZE) { + mcast_list.mode = NXPWIFI_ALL_MULTI_MODE; + } else { + mcast_list.mode = NXPWIFI_MULTICAST_MODE; + mcast_list.num_multicast_addr = + nxpwifi_copy_mcast_addr(&mcast_list, dev); + } + nxpwifi_request_set_multicast_list(priv, &mcast_list); +} + +/* ndo_tx_timeout: account; reset card on threshold. */ +static void +nxpwifi_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + priv->num_tx_timeout++; + priv->tx_timeout_cnt++; + nxpwifi_dbg(priv->adapter, ERROR, + "%lu : Tx timeout(#%d), bss_type-num = %d-%d\n", + jiffies, priv->tx_timeout_cnt, priv->bss_type, + priv->bss_num); + nxpwifi_set_trans_start(dev); + + if (priv->tx_timeout_cnt > TX_TIMEOUT_THRESHOLD && + priv->adapter->if_ops.card_reset) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_timeout_cnt exceeds threshold.\t" + "Triggering card reset!\n"); + priv->adapter->if_ops.card_reset(priv->adapter); + } +} + +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter) +{ + /* + * Dump all the memory data into single file, a userspace script will + * be used to split all the memory data to multiple files + */ + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump start\n"); + dev_coredumpv(adapter->dev, adapter->devdump_data, adapter->devdump_len, + GFP_KERNEL); + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump end\n"); + + /* + * Device dump data will be freed in device coredump release function + * after 5 min. Here reset adapter->devdump_data and ->devdump_len + * to avoid it been accidentally reused. + */ + adapter->devdump_data = NULL; + adapter->devdump_len = 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_upload_device_dump); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter) +{ + char *p; + char drv_version[64]; + struct sdio_mmc_card *sdio_card; + struct nxpwifi_private *priv; + int i, idx; + struct netdev_queue *txq; + struct nxpwifi_debug_info *debug_info; + + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump start===\n"); + + p = adapter->devdump_data; + strscpy(p, "========Start dump driverinfo========\n", NXPWIFI_FW_DUMP_SIZE); + p += strlen("========Start dump driverinfo========\n"); + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + + nxpwifi_drv_get_driver_version(adapter, drv_version, + sizeof(drv_version) - 1); + p += sprintf(p, "driver_version = %s\n", drv_version); + + p += sprintf(p, "tx_pending = %d\n", + atomic_read(&adapter->tx_pending)); + p += sprintf(p, "rx_pending = %d\n", + atomic_read(&adapter->rx_pending)); + + if (adapter->iface_type == NXPWIFI_SDIO) { + sdio_card = (struct sdio_mmc_card *)adapter->card; + p += sprintf(p, "\nmp_rd_bitmap=0x%x curr_rd_port=0x%x\n", + sdio_card->mp_rd_bitmap, sdio_card->curr_rd_port); + p += sprintf(p, "mp_wr_bitmap=0x%x curr_wr_port=0x%x\n", + sdio_card->mp_wr_bitmap, sdio_card->curr_wr_port); + } + + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + p += sprintf(p, "\n[interface : \"%s\"]\n", + priv->netdev->name); + p += sprintf(p, "wmm_tx_pending[0] = %d\n", + atomic_read(&priv->wmm_tx_pending[0])); + p += sprintf(p, "wmm_tx_pending[1] = %d\n", + atomic_read(&priv->wmm_tx_pending[1])); + p += sprintf(p, "wmm_tx_pending[2] = %d\n", + atomic_read(&priv->wmm_tx_pending[2])); + p += sprintf(p, "wmm_tx_pending[3] = %d\n", + atomic_read(&priv->wmm_tx_pending[3])); + p += sprintf(p, "media_state=\"%s\"\n", !priv->media_connected ? + "Disconnected" : "Connected"); + p += sprintf(p, "carrier %s\n", (netif_carrier_ok(priv->netdev) + ? "on" : "off")); + for (idx = 0; idx < priv->netdev->num_tx_queues; idx++) { + txq = netdev_get_tx_queue(priv->netdev, idx); + p += sprintf(p, "tx queue %d:%s ", idx, + netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n%s: num_tx_timeout = %d\n", + priv->netdev->name, priv->num_tx_timeout); + } + + if (adapter->iface_type == NXPWIFI_SDIO) { + p += sprintf(p, "\n=== %s register dump===\n", "SDIO"); + if (adapter->if_ops.reg_dump) + p += adapter->if_ops.reg_dump(adapter, p); + } + p += sprintf(p, "\n=== more debug information\n"); + debug_info = kzalloc_obj(*debug_info, GFP_KERNEL); + if (debug_info) { + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + nxpwifi_get_debug_info(priv, debug_info); + p += nxpwifi_debug_info_to_buffer(priv, p, debug_info); + break; + } + kfree(debug_info); + } + + p += sprintf(p, "\n========End dump========\n"); + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump end===\n"); + adapter->devdump_len = p - (char *)adapter->devdump_data; +} +EXPORT_SYMBOL_GPL(nxpwifi_drv_info_dump); + +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter) +{ + u8 idx; + char *fw_dump_ptr; + u32 dump_len = 0; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + dump_len += (strlen("========Start dump ") + + strlen(entry->mem_name) + + strlen("========\n") + + (entry->mem_size + 1) + + strlen("\n========End dump========\n")); + } + } + + if (dump_len + 1 + adapter->devdump_len > NXPWIFI_FW_DUMP_SIZE) { + /* Realloc in case buffer overflow */ + fw_dump_ptr = vzalloc(dump_len + 1 + adapter->devdump_len); + nxpwifi_dbg(adapter, MSG, "Realloc device dump data.\n"); + if (!fw_dump_ptr) { + vfree(adapter->devdump_data); + nxpwifi_dbg(adapter, ERROR, + "vzalloc devdump data failure!\n"); + return; + } + + memmove(fw_dump_ptr, adapter->devdump_data, + adapter->devdump_len); + vfree(adapter->devdump_data); + adapter->devdump_data = fw_dump_ptr; + } + + fw_dump_ptr = (char *)adapter->devdump_data + adapter->devdump_len; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + fw_dump_ptr += sprintf(fw_dump_ptr, "========Start dump "); + fw_dump_ptr += sprintf(fw_dump_ptr, "%s", entry->mem_name); + fw_dump_ptr += sprintf(fw_dump_ptr, "========\n"); + memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size); + fw_dump_ptr += entry->mem_size; + fw_dump_ptr += sprintf(fw_dump_ptr, "\n========End dump========\n"); + } + } + + adapter->devdump_len = fw_dump_ptr - (char *)adapter->devdump_data; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + entry->mem_size = 0; + } +} +EXPORT_SYMBOL_GPL(nxpwifi_prepare_fw_dump_info); + +/* ndo_get_stats: return netdev stats. */ +static struct net_device_stats *nxpwifi_get_stats(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + return &priv->stats; +} + +static u16 +nxpwifi_netdev_select_wmm_queue(struct net_device *dev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + skb->priority = cfg80211_classify8021d(skb, NULL); + return nxpwifi_1d_to_wmm_queue[skb->priority]; +} + +/* Network device handlers */ +static const struct net_device_ops nxpwifi_netdev_ops = { + .ndo_open = nxpwifi_open, + .ndo_stop = nxpwifi_close, + .ndo_start_xmit = nxpwifi_hard_start_xmit, + .ndo_set_mac_address = nxpwifi_ndo_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_tx_timeout = nxpwifi_tx_timeout, + .ndo_get_stats = nxpwifi_get_stats, + .ndo_set_rx_mode = nxpwifi_set_multicast_list, + .ndo_select_queue = nxpwifi_netdev_select_wmm_queue, +}; + +/* Init per-interface defaults: ops, addrs, mgmt IEs, stats. */ +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev) +{ + dev->netdev_ops = &nxpwifi_netdev_ops; + dev->needs_free_netdev = true; + /* Initialize private structure */ + priv->current_key_index = 0; + priv->media_connected = false; + memset(priv->mgmt_ie, 0, + sizeof(struct nxpwifi_ie) * MAX_MGMT_IE_INDEX); + priv->beacon_idx = NXPWIFI_AUTO_IDX_MASK; + priv->proberesp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->assocresp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + priv->num_tx_timeout = 0; + if (is_valid_ether_addr(dev->dev_addr)) + ether_addr_copy(priv->curr_addr, dev->dev_addr); + else + ether_addr_copy(priv->curr_addr, priv->adapter->perm_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->hist_data = kmalloc_obj(*priv->hist_data, GFP_KERNEL); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + } +} + +/* Return true if any command is pending. */ +int nxpwifi_is_command_pending(struct nxpwifi_adapter *adapter) +{ + int is_cmd_pend_q_empty; + + spin_lock_bh(&adapter->cmd_pending_q_lock); + is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + return !is_cmd_pend_q_empty; +} + +/* Host MLME work: deliver RX; handle assoc/link-loss. */ +static void nxpwifi_host_mlme_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, host_mlme_work); + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + struct nxpwifi_private *priv; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) { + rx_info = NXPWIFI_SKB_RXCB(skb); + priv = adapter->priv[rx_info->bss_num]; + cfg80211_rx_mlme_mgmt(priv->netdev, + skb->data, + rx_info->pkt_len); + } + + /* Check for host mlme disconnection */ + if (adapter->host_mlme_link_lost) { + if (adapter->priv_link_lost) { + nxpwifi_reset_connect_state(adapter->priv_link_lost, + WLAN_REASON_DEAUTH_LEAVING, + true); + adapter->priv_link_lost = NULL; + } + adapter->host_mlme_link_lost = false; + } + + /* Check for host mlme Assoc Resp */ + if (adapter->assoc_resp_received) { + nxpwifi_process_assoc_resp(adapter); + adapter->assoc_resp_received = false; + } +} + +/* RX work: process RX queue. */ +static void nxpwifi_rx_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, rx_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_process_rx(adapter); +} + +/* Main work: run nxpwifi_main_process(). */ +static void nxpwifi_main_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, main_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_main_process(adapter); +} + +/* Teardown: disable IRQs, stop queues, shutdown, remove ifaces, unreg. */ +static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + /* + * We can no longer handle interrupts once we start doing the teardown + * below. + */ + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + adapter->int_status = 0; + + /* Stop data */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) { + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + netif_device_detach(priv->netdev); + } + } + + nxpwifi_dbg(adapter, CMD, "cmd: calling nxpwifi_shutdown_drv...\n"); + nxpwifi_shutdown_drv(adapter); + nxpwifi_dbg(adapter, CMD, "cmd: nxpwifi_shutdown_drv done\n"); + + if (atomic_read(&adapter->rx_pending) || + atomic_read(&adapter->tx_pending) || + atomic_read(&adapter->cmd_pending)) { + nxpwifi_dbg(adapter, ERROR, + "rx_pending=%d, tx_pending=%d,\t" + "cmd_pending=%d\n", + atomic_read(&adapter->rx_pending), + atomic_read(&adapter->tx_pending), + atomic_read(&adapter->cmd_pending)); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rtnl_lock(); + if (priv->netdev && + priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) { + /* + * Close the netdev now, because if we do it later, the + * netdev notifiers will need to acquire the wiphy lock + * again --> deadlock. + */ + dev_close(priv->wdev.netdev); + wiphy_lock(adapter->wiphy); + nxpwifi_del_virtual_intf(adapter->wiphy, &priv->wdev); + wiphy_unlock(adapter->wiphy); + } + rtnl_unlock(); + } + + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); + adapter->wiphy = NULL; + + vfree(adapter->chan_stats); + nxpwifi_free_cmd_buffers(adapter); +} + +/* Shut down SW/FW and mark device down. */ +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + + if (!adapter) + return; + + wait_for_completion(adapter->fw_done); + /* Caller should ensure we aren't suspending while this happens */ + reinit_completion(adapter->fw_done); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_deauthenticate(priv, NULL); + + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + + nxpwifi_uninit_sw(adapter); + adapter->is_up = false; +} +EXPORT_SYMBOL_GPL(nxpwifi_shutdown_sw); + +/* Re-init adapter SW and bring device up. */ +int +nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter) +{ + int ret = 0; + + nxpwifi_init_lock_list(adapter); + if (adapter->if_ops.up_dev) + adapter->if_ops.up_dev(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with + * relevant information from the card. Some code extracted from + * nxpwifi_register_dev() + */ + nxpwifi_dbg(adapter, INFO, "%s, nxpwifi_init_hw_fw()...\n", __func__); + + ret = nxpwifi_init_hw_fw(adapter, false); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + /* _nxpwifi_fw_dpc() does its own cleanup */ + ret = _nxpwifi_fw_dpc(adapter->firmware, adapter); + if (ret) { + pr_err("Failed to bring up adapter: %d\n", ret); + return ret; + } + nxpwifi_dbg(adapter, INFO, "%s, successful\n", __func__); + + return ret; + +err_init_fw: + nxpwifi_dbg(adapter, ERROR, "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + +err_kmalloc: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + nxpwifi_dbg(adapter, ERROR, + "info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + complete_all(adapter->fw_done); + nxpwifi_dbg(adapter, INFO, "%s, error\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_reinit_sw); + +/* Add card: register adapter, workqueues, device; request FW (async). */ +int +nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + + adapter = nxpwifi_register(card, dev, if_ops); + if (IS_ERR(adapter)) { + ret = PTR_ERR(adapter); + pr_err("%s: adapter register failed %d\n", __func__, ret); + goto err_init_sw; + } + + adapter->iface_type = iface_type; + adapter->fw_done = fw_done; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with relevant + * information from the card. + */ + ret = adapter->if_ops.register_dev(adapter); + if (ret) { + pr_err("%s: failed to register nxpwifi device\n", __func__); + goto err_registerdev; + } + + ret = nxpwifi_init_hw_fw(adapter, true); + if (ret) { + pr_err("%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + return ret; + +err_init_fw: + pr_debug("info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); +err_registerdev: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + +err_kmalloc: + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + nxpwifi_free_adapter(adapter); + +err_init_sw: + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_add_card); + +/* Remove card: teardown SW, unregister device, free adapter. */ +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter) +{ + if (!adapter) + return; + + if (adapter->is_up) + nxpwifi_uninit_sw(adapter); + + /* Unregister device */ + nxpwifi_dbg(adapter, INFO, + "info: unregister device\n"); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + /* Free adapter structure */ + nxpwifi_dbg(adapter, INFO, + "info: free adapter\n"); + nxpwifi_free_adapter(adapter); +} +EXPORT_SYMBOL_GPL(nxpwifi_remove_card); + +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!(adapter->debug_mask & mask)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (adapter->dev) + dev_info(adapter->dev, "%pV", &vaf); + else + pr_info("%pV", &vaf); + + va_end(args); +} +EXPORT_SYMBOL_GPL(_nxpwifi_dbg); + +/* Module init: init debugfs if enabled. */ +static int +nxpwifi_init_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_init(); +#endif + return 0; +} + +/* Module exit: remove debugfs if enabled. */ +static void +nxpwifi_cleanup_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_remove(); +#endif +} + +module_init(nxpwifi_init_module); +module_exit(nxpwifi_cleanup_module); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi Driver version " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h new file mode 100644 index 000000000000..b25a6a4f2936 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.h @@ -0,0 +1,1436 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: main data structures and prototypes + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_MAIN_H_ +#define _NXPWIFI_MAIN_H_ + +#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 +#include +#include +#include + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "sdio.h" + +extern char nxpwifi_driver_version[]; + +struct nxpwifi_adapter; +struct nxpwifi_private; + +/* command type */ +enum { + NXPWIFI_ASYNC_CMD, + NXPWIFI_SYNC_CMD +}; + +#define NXPWIFI_MAX_AP 64 + +#define NXPWIFI_MAX_PKTS_TXQ 16 + +#define NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ) + +#define NXPWIFI_TIMER_10S 10000 +#define NXPWIFI_TIMER_1S 1000 + +#define MAX_TX_PENDING 400 +#define LOW_TX_PENDING 380 + +#define HIGH_RX_PENDING 50 +#define LOW_RX_PENDING 20 + +#define NXPWIFI_UPLD_SIZE (2312) + +#define MAX_EVENT_SIZE 2048 + +#define NXPWIFI_FW_DUMP_SIZE (2 * 1024 * 1024) + +#define ARP_FILTER_MAX_BUF_SIZE 68 + +#define NXPWIFI_KEY_BUFFER_SIZE 16 +#define NXPWIFI_DEFAULT_LISTEN_INTERVAL 10 +#define NXPWIFI_MAX_REGION_CODE 9 + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 + +#define FIRST_VALID_CHANNEL 0xff + +#define DEFAULT_BCN_MISS_TIMEOUT 5 + +#define MAX_SCAN_BEACON_BUFFER 8000 + +#define SCAN_BEACON_ENTRY_PAD 6 + +#define NXPWIFI_PASSIVE_SCAN_CHAN_TIME 110 +#define NXPWIFI_ACTIVE_SCAN_CHAN_TIME 40 +#define NXPWIFI_SPECIFIC_SCAN_CHAN_TIME 40 +#define NXPWIFI_DEF_SCAN_CHAN_GAP_TIME 50 + +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define NXPWIFI_MAX_TOTAL_SCAN_TIME (NXPWIFI_TIMER_10S - NXPWIFI_TIMER_1S) + +#define WPA_GTK_OUI_OFFSET 2 +#define RSN_GTK_OUI_OFFSET 2 + +#define NXPWIFI_OUI_NOT_PRESENT 0 +#define NXPWIFI_OUI_PRESENT 1 + +#define PKT_TYPE_MGMT 0xE5 +#define PKT_TYPE_802DOT11 0x05 +/* check if any data / resp / event is received from card */ +#define IS_CARD_RX_RCVD(adapter) ({ \ + typeof(adapter) (_adapter) = adapter; \ + ((_adapter)->cmd_resp_received || \ + (_adapter)->event_received || \ + (_adapter)->data_received); \ + }) + +#define NXPWIFI_TYPE_DATA 0 +#define NXPWIFI_TYPE_CMD 1 +#define NXPWIFI_TYPE_EVENT 3 +#define NXPWIFI_TYPE_VDLL 4 +#define NXPWIFI_TYPE_AGGR_DATA 10 + +#define MAX_BITMAP_RATES_SIZE 18 + +#define MAX_CHANNEL_BAND_BG 14 +#define MAX_CHANNEL_BAND_A 165 + +#define MAX_FREQUENCY_BAND_BG 2484 + +#define NXPWIFI_EVENT_HEADER_LEN 4 +#define NXPWIFI_UAP_EVENT_EXTRA_HEADER 2 + +#define NXPWIFI_TYPE_LEN 4 +#define NXPWIFI_USB_TYPE_CMD 0xF00DFACE +#define NXPWIFI_USB_TYPE_DATA 0xBEADC0DE +#define NXPWIFI_USB_TYPE_EVENT 0xBEEFFACE + +/* tx_timeout threshold to trigger card reset */ +#define TX_TIMEOUT_THRESHOLD 6 + +#define NXPWIFI_DRV_INFO_SIZE_MAX 0x40000 + +/* address alignment helper */ +#define NXPWIFI_ALIGN_ADDR(p, a) ({ \ + typeof(a) (_a) = a; \ + (((long)(p) + (_a) - 1) & ~((_a) - 1)); \ + }) + +#define NXPWIFI_MAC_LOCAL_ADMIN_BIT 41 + +/* bit helper */ +#define MBIT(x) (((u32)1) << (x)) + +/* enum nxpwifi_debug_level - nxp wifi debug level */ +enum NXPWIFI_DEBUG_LEVEL { + NXPWIFI_DBG_MSG = 0x00000001, + NXPWIFI_DBG_FATAL = 0x00000002, + NXPWIFI_DBG_ERROR = 0x00000004, + NXPWIFI_DBG_DATA = 0x00000008, + NXPWIFI_DBG_CMD = 0x00000010, + NXPWIFI_DBG_EVENT = 0x00000020, + NXPWIFI_DBG_INTR = 0x00000040, + NXPWIFI_DBG_IOCTL = 0x00000080, + NXPWIFI_DBG_MPA_D = 0x00008000, + NXPWIFI_DBG_DAT_D = 0x00010000, + NXPWIFI_DBG_CMD_D = 0x00020000, + NXPWIFI_DBG_EVT_D = 0x00040000, + NXPWIFI_DBG_FW_D = 0x00080000, + NXPWIFI_DBG_IF_D = 0x00100000, + NXPWIFI_DBG_ENTRY = 0x10000000, + NXPWIFI_DBG_WARN = 0x20000000, + NXPWIFI_DBG_INFO = 0x40000000, + NXPWIFI_DBG_DUMP = 0x80000000, + NXPWIFI_DBG_ANY = 0xffffffff +}; + +#define NXPWIFI_DEFAULT_DEBUG_MASK (NXPWIFI_DBG_MSG | \ + NXPWIFI_DBG_FATAL | \ + NXPWIFI_DBG_ERROR) + +__printf(3, 4) +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...); +#define nxpwifi_dbg(adapter, mask, fmt, ...) \ + _nxpwifi_dbg(adapter, NXPWIFI_DBG_##mask, fmt, ##__VA_ARGS__) + +#define DEBUG_DUMP_DATA_MAX_LEN 128 +#define nxpwifi_dbg_dump(adapter, dbg_mask, str, buf, len) \ +do { \ + if ((adapter)->debug_mask & NXPWIFI_DBG_##dbg_mask) \ + print_hex_dump(KERN_DEBUG, str, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, len, false); \ +} while (0) + +/* Min BGSCAN interval 15 second */ +#define NXPWIFI_BGSCAN_INTERVAL 15000 +/* bgscan interval (ms) and default repeat count */ +#define NXPWIFI_BGSCAN_REPEAT_COUNT 6 + +struct nxpwifi_dbg { + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +enum NXPWIFI_HARDWARE_STATUS { + NXPWIFI_HW_STATUS_READY, + NXPWIFI_HW_STATUS_INITIALIZING, + NXPWIFI_HW_STATUS_RESET, + NXPWIFI_HW_STATUS_NOT_READY +}; + +enum NXPWIFI_802_11_POWER_MODE { + NXPWIFI_802_11_POWER_MODE_CAM, + NXPWIFI_802_11_POWER_MODE_PSP +}; + +struct nxpwifi_tx_param { + u32 next_pkt_len; +}; + +enum NXPWIFI_PS_STATE { + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP_CFM, + PS_STATE_SLEEP +}; + +enum nxpwifi_iface_type { + NXPWIFI_SDIO +}; + +struct nxpwifi_add_ba_param { + u32 tx_win_size; + u32 rx_win_size; + u32 timeout; + u8 tx_amsdu; + u8 rx_amsdu; +}; + +struct nxpwifi_tx_aggr { + u8 ampdu_user; + u8 ampdu_ap; + u8 amsdu; +}; + +enum nxpwifi_ba_status { + BA_SETUP_NONE = 0, + BA_SETUP_INPROGRESS, + BA_SETUP_COMPLETE +}; + +struct nxpwifi_ra_list_tbl { + struct list_head list; + struct sk_buff_head skb_head; + u8 ra[ETH_ALEN]; + u32 is_11n_enabled; + u16 max_amsdu; + u16 ba_pkt_count; + u8 ba_packet_thr; + enum nxpwifi_ba_status ba_status; + u8 amsdu_in_ampdu; + u16 total_pkt_count; + bool tx_paused; +}; + +struct nxpwifi_tid_tbl { + struct list_head ra_list; +}; + +#define WMM_HIGHEST_PRIORITY 7 +#define HIGH_PRIO_TID 7 +#define LOW_PRIO_TID 0 +#define NO_PKT_PRIO_TID -1 +#define NXPWIFI_WMM_DRV_DELAY_MAX 510 + +struct nxpwifi_wmm_desc { + struct nxpwifi_tid_tbl tid_tbl_ptr[MAX_NUM_TID]; + u32 packets_out[MAX_NUM_TID]; + u32 pkts_paused[MAX_NUM_TID]; + /* protects ra_list */ + spinlock_t ra_list_spinlock; + struct nxpwifi_wmm_ac_status ac_status[IEEE80211_NUM_ACS]; + enum nxpwifi_wmm_ac_e ac_down_graded_vals[IEEE80211_NUM_ACS]; + u32 drv_pkt_delay_max; + u8 queue_priority[IEEE80211_NUM_ACS]; + u32 user_pri_pkt_tx_ctrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */ + /* number of queued TX packets */ + atomic_t tx_pkts_queued; + /* highest priority currently queued */ + atomic_t highest_queued_prio; +}; + +struct nxpwifi_802_11_security { + u8 wpa_enabled; + u8 wpa2_enabled; + u8 wep_enabled; + u32 authentication_mode; + u8 is_authtype_auto; + u32 encryption_mode; +}; + +struct ieee_types_vendor_specific { + struct ieee80211_vendor_ie vend_hdr; + u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee80211_vendor_ie)]; +} __packed; + +struct nxpwifi_bssdescriptor { + u8 mac_address[ETH_ALEN]; + struct cfg80211_ssid ssid; + u32 privacy; + s32 rssi; + u32 channel; + u32 freq; + u16 beacon_period; + u8 erp_flags; + u32 bss_mode; + u8 supported_rates[NXPWIFI_SUPPORTED_RATES]; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; + u16 bss_band; + u64 fw_tsf; + u64 timestamp; + union ieee_types_phy_param_set phy_param_set; + struct ieee_types_cf_param_set cf_param_set; + u16 cap_info_bitmap; + struct ieee80211_wmm_param_ie wmm_ie; + u8 disable_11n; + struct ieee80211_ht_cap *bcn_ht_cap; + u16 ht_cap_offset; + struct ieee80211_ht_operation *bcn_ht_oper; + u16 ht_info_offset; + u8 *bcn_bss_co_2040; + u16 bss_co_2040_offset; + u8 *bcn_ext_cap; + u16 ext_cap_offset; + struct ieee80211_vht_cap *bcn_vht_cap; + u16 vht_cap_offset; + struct ieee80211_vht_operation *bcn_vht_oper; + u16 vht_info_offset; + struct ieee_types_oper_mode_ntf *oper_mode; + u16 oper_mode_offset; + u8 disable_11ac; + struct ieee80211_he_cap_elem *bcn_he_cap; + u16 he_cap_offset; + struct ieee80211_he_operation *bcn_he_oper; + u16 he_info_offset; + u8 disable_11ax; + struct ieee_types_vendor_specific *bcn_wpa_ie; + u16 wpa_offset; + struct element *bcn_rsn_ie; + u16 rsn_offset; + struct element *bcn_rsnx_ie; + u16 rsnx_offset; + u8 *beacon_buf; + u32 beacon_buf_size; + u8 sensed_11h; + u8 local_constraint; + u8 chan_sw_ie_present; +}; + +struct nxpwifi_current_bss_params { + struct nxpwifi_bssdescriptor bss_descriptor; + bool wmm_enabled; + bool wmm_uapsd_enabled; + u8 band; + u32 num_of_rates; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; +}; + +struct nxpwifi_sleep_period { + u16 period; + u16 reserved; +}; + +struct nxpwifi_wep_key { + u32 length; + u32 key_index; + u32 key_length; + u8 key_material[NXPWIFI_KEY_BUFFER_SIZE]; +}; + +#define MAX_REGION_CHANNEL_NUM 2 + +struct nxpwifi_chan_freq_power { + u16 channel; + u32 freq; + u16 max_tx_power; + u8 unsupported; +}; + +enum state_11d_t { + DISABLE_11D = 0, + ENABLE_11D = 1, +}; + +#define NXPWIFI_MAX_TRIPLET_802_11D 83 + +struct nxpwifi_802_11d_domain_reg { + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u8 no_of_triplet; + struct ieee80211_country_ie_triplet + triplet[NXPWIFI_MAX_TRIPLET_802_11D]; +}; + +struct nxpwifi_vendor_spec_cfg_ie { + u16 mask; + u16 flag; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +struct wps { + u8 session_enable; +}; + +struct nxpwifi_roc_cfg { + u64 cookie; + struct ieee80211_channel chan; +}; + +enum nxpwifi_iface_work_flags { + NXPWIFI_IFACE_WORK_DEVICE_DUMP, + NXPWIFI_IFACE_WORK_CARD_RESET, +}; + +enum nxpwifi_adapter_work_flags { + NXPWIFI_SURPRISE_REMOVED, + NXPWIFI_IS_CMD_TIMEDOUT, + NXPWIFI_IS_SUSPENDED, + NXPWIFI_IS_HS_CONFIGURED, + NXPWIFI_IS_HS_ENABLING, + NXPWIFI_IS_REQUESTING_FW_VEREXT, +}; + +struct nxpwifi_band_config { + u8 chan_band:2; + u8 chan_width:2; + u8 chan2_offset:2; + u8 scan_mode:2; +} __packed; + +struct nxpwifi_channel_band { + struct nxpwifi_band_config band_config; + u8 channel; +}; + +struct nxpwifi_private { + struct nxpwifi_adapter *adapter; + u8 bss_type; + u8 bss_role; + u8 bss_priority; + u8 bss_num; + u8 bss_started; + u8 auth_flag; + u16 auth_alg; + u8 frame_type; + u8 curr_addr[ETH_ALEN]; + u8 media_connected; + u8 port_open; + u8 usb_port; + u32 num_tx_timeout; + /* track consecutive timeout */ + u8 tx_timeout_cnt; + struct net_device *netdev; + struct net_device_stats stats; + u32 curr_pkt_filter; + u32 bss_mode; + u32 pkt_tx_ctrl; + u16 tx_power_level; + u8 max_tx_power_level; + u8 min_tx_power_level; + u32 tx_ant; + u32 rx_ant; + u8 tx_rate; + u8 tx_htinfo; + u8 rxpd_htinfo; + u8 rxpd_rate; + u16 rate_bitmap; + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + u32 data_rate; + u8 is_data_rate_auto; + u16 bcn_avg_factor; + u16 data_avg_factor; + s16 data_rssi_last; + s16 data_nf_last; + s16 data_rssi_avg; + s16 data_nf_avg; + s16 bcn_rssi_last; + s16 bcn_nf_last; + s16 bcn_rssi_avg; + s16 bcn_nf_avg; + struct nxpwifi_bssdescriptor *attempted_bss_desc; + struct cfg80211_ssid prev_ssid; + u8 prev_bssid[ETH_ALEN]; + struct nxpwifi_current_bss_params curr_bss_params; + u16 beacon_period; + u8 dtim_period; + u16 listen_interval; + u16 atim_window; + struct nxpwifi_802_11_security sec_info; + struct nxpwifi_wep_key wep_key[NUM_WEP_KEYS]; + u16 wep_key_curr_index; + u8 wpa_ie[256]; + u16 wpa_ie_len; + u8 wpa_is_gtk_set; + struct host_cmd_ds_802_11_key_material aes_key; + u8 *wps_ie; + u16 wps_ie_len; + u8 wmm_required; + bool wmm_enabled; + u8 wmm_qosinfo; + struct nxpwifi_wmm_desc wmm; + atomic_t wmm_tx_pending[IEEE80211_NUM_ACS]; + struct list_head sta_list; + /* spin lock for associated station list */ + spinlock_t sta_list_spinlock; + struct list_head tx_ba_stream_tbl_ptr[MAX_NUM_TID]; + /* spin lock for tx_ba_stream_tbl_ptr queue */ + struct spinlock tx_ba_stream_tbl_lock[MAX_NUM_TID]; + struct nxpwifi_tx_aggr aggr_prio_tbl[MAX_NUM_TID]; + struct nxpwifi_add_ba_param add_ba_param; + u16 rx_seq[MAX_NUM_TID]; + u8 tos_to_tid_inv[MAX_NUM_TID]; + struct list_head rx_reorder_tbl_ptr[MAX_NUM_TID]; + /* spin lock for rx_reorder_tbl_ptr queue */ + struct spinlock rx_reorder_tbl_lock[MAX_NUM_TID]; +#define NXPWIFI_ASSOC_RSP_BUF_SIZE 500 + u8 assoc_rsp_buf[NXPWIFI_ASSOC_RSP_BUF_SIZE]; + u32 assoc_rsp_size; + struct cfg80211_bss *req_bss; + +#define NXPWIFI_GENIE_BUF_SIZE 256 + u8 gen_ie_buf[NXPWIFI_GENIE_BUF_SIZE]; + u8 gen_ie_buf_len; + + struct nxpwifi_vendor_spec_cfg_ie vs_ie[NXPWIFI_MAX_VSIE_NUM]; + +#define NXPWIFI_ASSOC_TLV_BUF_SIZE 256 + u8 assoc_tlv_buf[NXPWIFI_ASSOC_TLV_BUF_SIZE]; + u8 assoc_tlv_buf_len; + + u8 *curr_bcn_buf; + u32 curr_bcn_size; + /* spin lock for beacon buffer */ + spinlock_t curr_bcn_buf_lock; + struct wireless_dev wdev; + struct nxpwifi_chan_freq_power cfp; + u32 versionstrsel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +#ifdef CONFIG_DEBUG_FS + struct dentry *dfs_dev_dir; +#endif + u16 current_key_index; + struct cfg80211_scan_request *scan_request; + u8 cfg_bssid[6]; + struct wps wps; + u8 scan_block; + s32 cqm_rssi_thold; + u32 cqm_rssi_hyst; + u8 subsc_evt_rssi_state; + struct nxpwifi_ds_misc_subsc_evt async_subsc_evt_storage; + struct nxpwifi_ie mgmt_ie[MAX_MGMT_IE_INDEX]; + u16 beacon_idx; + u16 proberesp_idx; + u16 assocresp_idx; + u16 gen_idx; + u8 ap_11n_enabled; + u8 ap_11ac_enabled; + u8 ap_11ax_enabled; + u16 config_bands; + /* 11AX */ + u8 user_he_cap_len; + u8 user_he_cap[HE_CAP_MAX_SIZE]; + u8 user_2g_he_cap_len; + u8 user_2g_he_cap[HE_CAP_MAX_SIZE]; + bool host_mlme_reg; + u32 mgmt_frame_mask; + struct nxpwifi_roc_cfg roc_cfg; + bool scan_aborting; + u8 sched_scanning; + u8 csa_chan; + unsigned long csa_expire_time; + u8 del_list_idx; + bool hs2_enabled; + struct nxpwifi_uap_bss_param bss_cfg; + struct cfg80211_chan_def bss_chandef; + struct station_parameters *sta_params; + struct xarray ack_status_frames; + /* spin lock for ack status */ + spinlock_t ack_status_lock; + /** rx histogram data */ + struct nxpwifi_histogram_data *hist_data; + struct cfg80211_chan_def dfs_chandef; + struct wiphy_work reset_conn_state_work; + struct wiphy_delayed_work dfs_cac_work; + struct wiphy_delayed_work dfs_chan_sw_work; + bool uap_stop_tx; + struct cfg80211_ap_update ap_update_info; + struct nxpwifi_11h_intf_state state_11h; + struct nxpwifi_ds_mem_rw mem_rw; + struct sk_buff_head bypass_txq; + struct nxpwifi_user_scan_chan hidden_chan[NXPWIFI_USER_SCAN_CHAN_MAX]; + u8 assoc_resp_ht_param; + bool ht_param_present; + u16 last_deauth_reason; +}; + +struct nxpwifi_tx_ba_stream_tbl { + struct list_head list; + struct rcu_head rcu; + int tid; + u8 ra[ETH_ALEN]; + enum nxpwifi_ba_status ba_status; + u8 amsdu; +}; + +struct nxpwifi_rx_reorder_tbl; + +struct reorder_tmr_cnxt { + struct timer_list timer; + struct nxpwifi_rx_reorder_tbl *ptr; + struct nxpwifi_private *priv; + u8 timer_is_set; +}; + +struct nxpwifi_rx_reorder_tbl { + struct list_head list; + struct list_head tmp_list; + struct rcu_head rcu; + int tid; + u8 ta[ETH_ALEN]; + int init_win; + int start_win; + int win_size; + struct reorder_tmr_cnxt timer_context; + u8 amsdu; + u8 flags; + struct sk_buff *rx_reorder_ptr[] __counted_by(win_size); +}; + +struct nxpwifi_bss_prio_node { + struct list_head list; + struct nxpwifi_private *priv; +}; + +struct nxpwifi_bss_prio_tbl { + struct list_head bss_prio_head; + spinlock_t bss_prio_lock; /* protects BSS priority */ + struct nxpwifi_bss_prio_node *bss_prio_cur; +}; + +struct cmd_ctrl_node { + struct list_head list; + struct nxpwifi_private *priv; + u32 cmd_no; + u32 cmd_flag; + struct sk_buff *cmd_skb; + struct sk_buff *resp_skb; + void *data_buf; + u32 wait_q_enabled; + struct sk_buff *skb; + u8 *condition; + u8 cmd_wait_q_woken; + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_bss_priv { + u16 band; + u64 fw_tsf; +}; + +struct nxpwifi_station_stats { + u64 last_rx; + s8 rssi; + u64 rx_bytes; + u64 tx_bytes; + u32 rx_packets; + u32 tx_packets; + u32 tx_failed; + u8 last_tx_rate; + u8 last_tx_htinfo; +}; + +/*AP - side structure tracking associated STA info */ +struct nxpwifi_sta_node { + struct list_head list; + struct rcu_head rcu; + u8 mac_addr[ETH_ALEN]; + u8 is_wmm_enabled; + u8 is_11n_enabled; + u8 is_11ac_enabled; + u8 is_11ax_enabled; + u8 ampdu_sta[MAX_NUM_TID]; + u16 rx_seq[MAX_NUM_TID]; + u16 max_amsdu; + struct nxpwifi_station_stats stats; + u8 tx_pause; +}; + +#define NXPWIFI_TYPE_AGGR_DATA_V2 11 +#define NXPWIFI_BUS_AGGR_MODE_LEN_V2 (2) +#define NXPWIFI_BUS_AGGR_MAX_LEN 16000 +#define NXPWIFI_BUS_AGGR_MAX_NUM 10 +struct bus_aggr_params { + u16 enable; + u16 mode; + u16 tx_aggr_max_size; + u16 tx_aggr_max_num; + u16 tx_aggr_align; +}; + +struct vdll_dnld_ctrl { + u8 *pending_block; + u16 pending_block_len; + u8 *vdll_mem; + u32 vdll_len; + struct sk_buff *skb; +}; + +struct nxpwifi_if_ops { + int (*init_if)(struct nxpwifi_adapter *adapter); + void (*cleanup_if)(struct nxpwifi_adapter *adapter); + int (*check_fw_status)(struct nxpwifi_adapter *adapter, u32 poll_num); + int (*check_winner_status)(struct nxpwifi_adapter *adapter); + int (*prog_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + int (*register_dev)(struct nxpwifi_adapter *adapter); + void (*unregister_dev)(struct nxpwifi_adapter *adapter); + int (*enable_int)(struct nxpwifi_adapter *adapter); + void (*disable_int)(struct nxpwifi_adapter *adapter); + int (*process_int_status)(struct nxpwifi_adapter *adapter, u8 istat); + int (*host_to_card)(struct nxpwifi_adapter *adapter, u8 type, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); + int (*wakeup)(struct nxpwifi_adapter *adapter); + int (*wakeup_complete)(struct nxpwifi_adapter *adapter); + + /* interface-specific operations */ + void (*update_mp_end_port)(struct nxpwifi_adapter *adapter, u16 port); + void (*cleanup_mpa_buf)(struct nxpwifi_adapter *adapter); + int (*cmdrsp_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*event_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*dnld_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + void (*card_reset)(struct nxpwifi_adapter *adapter); + int (*reg_dump)(struct nxpwifi_adapter *adapter, char *drv_buf); + void (*device_dump)(struct nxpwifi_adapter *adapter); + void (*deaggr_pkt)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + void (*up_dev)(struct nxpwifi_adapter *adapter); +}; + +#define NXPWIFI_DEFAULT_REGION_CODE NXPWIFI_REGION_FCC + +struct nxpwifi_adapter { + u8 iface_type; + unsigned int debug_mask; + struct nxpwifi_iface_comb iface_limit; + struct nxpwifi_iface_comb curr_iface_comb; + struct nxpwifi_private *priv[NXPWIFI_MAX_BSS_NUM]; + u8 priv_num; + const struct firmware *firmware; + char fw_name[32]; + int winner; + struct device *dev; + struct wiphy *wiphy; + u8 perm_addr[ETH_ALEN]; + unsigned long work_flags; + u32 fw_release_number; + u8 intf_hdr_len; + void *card; + struct nxpwifi_if_ops if_ops; + atomic_t bypass_tx_pending; + atomic_t rx_pending; + atomic_t tx_pending; + atomic_t cmd_pending; + atomic_t tx_hw_pending; + struct workqueue_struct *workqueue; + struct work_struct main_work; + struct workqueue_struct *rx_workqueue; + struct work_struct rx_work; + struct wiphy_work host_mlme_work; + bool rx_work_enabled; + bool rx_processing; + bool delay_main_work; + atomic_t rx_ba_teardown_pending; + atomic_t iface_changing; + struct nxpwifi_bss_prio_tbl bss_prio_tbl[NXPWIFI_MAX_BSS_NUM]; + u32 nxpwifi_processing; + u16 tx_buf_size; + u16 curr_tx_buf_size; + /* SDIO single port rx aggregation capability */ + bool host_disable_sdio_rx_aggr; + bool sdio_rx_aggr_enable; + u16 sdio_rx_block_size; + u32 ioport; + enum NXPWIFI_HARDWARE_STATUS hw_status; + u16 number_of_antenna; + u32 fw_cap_info; + u32 fw_cap_ext; + u16 user_htstream; + u64 uuid_lo; + u64 uuid_hi; + /* interrupt lock */ + spinlock_t int_lock; + u8 int_status; + u32 event_cause; + struct sk_buff *event_skb; + u8 upld_buf[NXPWIFI_UPLD_SIZE]; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + bool event_received; + u8 data_received; + u8 assoc_resp_received; + struct nxpwifi_private *priv_link_lost; + u8 host_mlme_link_lost; + u16 seq_num; + struct cmd_ctrl_node *cmd_pool; + struct cmd_ctrl_node *curr_cmd; + /* spin lock for command */ + spinlock_t nxpwifi_cmd_lock; + struct timer_list cmd_timer; + struct list_head cmd_free_q; + spinlock_t cmd_free_q_lock; /* protects cmd_free_q */ + struct list_head cmd_pending_q; + spinlock_t cmd_pending_q_lock; /* protects cmd_pending_q */ + struct list_head scan_pending_q; + spinlock_t scan_pending_q_lock; /* protects scan_pending_q */ + struct sk_buff_head tx_data_q; + atomic_t tx_queued; + u32 scan_processing; + enum nxpwifi_region_code region_code; + struct nxpwifi_802_11d_domain_reg domain_reg; + u16 scan_probes; + u32 scan_mode; + u16 specific_scan_time; + u16 active_scan_time; + u16 passive_scan_time; + u16 scan_chan_gap_time; + u16 fw_bands; + u8 tx_lock_flag; + struct nxpwifi_sleep_period sleep_period; + u16 ps_mode; + u32 ps_state; + u8 need_to_wakeup; + u16 multiple_dtim; + u16 local_listen_interval; + u16 null_pkt_interval; + struct sk_buff *sleep_cfm; + u16 bcn_miss_time_out; + u8 is_deep_sleep; + u8 delay_null_pkt; + u16 delay_to_ps; + u16 enhanced_ps_mode; + u8 pm_wakeup_card_req; + u16 gen_null_pkt; + u16 pps_uapsd_mode; + u32 pm_wakeup_fw_try; + struct timer_list wakeup_timer; + struct nxpwifi_hs_config_param hs_cfg; + u8 hs_activated; + u8 hs_activated_manually; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + u8 event_body[MAX_EVENT_SIZE]; + u32 hw_dot_11n_dev_cap; + u8 hw_dev_mcs_support; + u8 hw_mpdu_density; + u8 user_dev_mcs_support; + u8 sec_chan_offset; + struct nxpwifi_dbg dbg; + u8 arp_filter[ARP_FILTER_MAX_BUF_SIZE]; + u32 arp_filter_size; + struct nxpwifi_wait_queue cmd_wait_q; + u8 scan_wait_q_woken; + spinlock_t queue_lock; /* protects TX queues */ + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u16 max_mgmt_ie_index; + const struct firmware *cal_data; + /* 11AC capability fields */ + u32 is_hw_11ac_capable; + u32 hw_dot_11ac_dev_cap; + u32 hw_dot_11ac_mcs_support; + u32 usr_dot_11ac_dev_cap_bg; + u32 usr_dot_11ac_dev_cap_a; + u32 usr_dot_11ac_mcs_support; + /* 11AX capability fields */ + u8 is_hw_11ax_capable; + u8 hw_he_cap_len; + u8 hw_he_cap[HE_CAP_MAX_SIZE]; + u8 hw_2g_he_cap_len; + u8 hw_2g_he_cap[HE_CAP_MAX_SIZE]; + atomic_t pending_bridged_pkts; + struct completion *fw_done; /* FW init completion */ + bool is_up; + bool ext_scan; + u8 fw_api_ver; + u8 fw_hotfix_ver; + u8 key_api_major_ver, key_api_minor_ver; + u8 max_sta_conn; + struct memory_type_mapping *mem_type_mapping_tbl; + u8 num_mem_types; + bool scan_chan_gap_enabled; + struct sk_buff_head rx_mlme_q; + struct sk_buff_head rx_data_q; + struct nxpwifi_chan_stats *chan_stats; + u32 num_in_chan_stats; + int survey_idx; + u8 coex_scan; + u8 coex_min_scan_time; + u8 coex_max_scan_time; + u8 coex_win_size; + u8 coex_tx_win_size; + u8 coex_rx_win_size; + u8 active_scan_triggered; + bool usb_mc_status; + bool usb_mc_setup; + struct cfg80211_wowlan_nd_info *nd_info; + struct ieee80211_regdomain *regd; + /* Aggregation parameters*/ + struct bus_aggr_params bus_aggr; + void *devdump_data; /* device dump storage */ + int devdump_len; /* device dump length */ + bool ignore_btcoex_events; + struct vdll_dnld_ctrl vdll_ctrl; + u64 roc_cookie_counter; + u32 enable_net_mon; + bool wowlan_enabled; + bool chandef_valid; + struct cfg80211_chan_def chandef; + atomic_t uap_count; +}; + +void nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter); + +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter); + +void nxpwifi_set_trans_start(struct net_device *dev); + +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +int nxpwifi_init_priv(struct nxpwifi_private *priv); +void nxpwifi_free_priv(struct nxpwifi_private *priv); + +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter); + +void nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter); + +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb); +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa); + +int nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_cmd_timeout_func(struct timer_list *t); + +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info); + +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter); + +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter); +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter); +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter); +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags); +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status); +void nxpwifi_clean_txrx(struct nxpwifi_private *priv); +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv); +void nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter); +void nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len); +void nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter); +void nxpwifi_hs_activated_event(struct nxpwifi_private *priv, + u8 activated); +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg); +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_delete_all_station_list(struct nxpwifi_private *priv); +void nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, + const u8 *ra_addr); +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg); +void nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node); +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +u8 nxpwifi_band_to_radio_type(u16 config_bands); +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac); +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter); +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd); +struct nxpwifi_chan_freq_power *nxpwifi_get_cfp(struct nxpwifi_private *priv, + u8 band, u16 channel, u32 freq); +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +int nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, u16 vsie_mask, + u8 **buffer); +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, + u8 *rates); +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates); +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type); +u8 nxpwifi_is_rate_auto(struct nxpwifi_private *priv); +void nxpwifi_save_curr_bcn(struct nxpwifi_private *priv); +void nxpwifi_free_curr_bcn(struct nxpwifi_private *priv); +int nxpwifi_is_command_pending(struct nxpwifi_adapter *adapter); +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev); +void nxpwifi_set_ba_params(struct nxpwifi_private *priv); +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *pmadapter); +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv); +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf); +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv); + +/* check if RA-based queuing */ +static inline u8 +nxpwifi_queuing_ra_based(struct nxpwifi_private *priv) +{ + /* In STA mode DA==RA; subject to future revision */ + if (priv->bss_mode == NL80211_IFTYPE_STATION && + (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)) + return false; + + return true; +} + +/* copy rates from src to dest */ +static inline u32 +nxpwifi_copy_rates(u8 *dest, u32 pos, u8 *src, int len) +{ + int i; + + for (i = 0; i < len && src[i]; i++, pos++) { + if (pos >= NXPWIFI_SUPPORTED_RATES) + break; + dest[pos] = src[i]; + } + + return pos; +} + +/* return priv matching the given BSS type and number */ +static inline struct nxpwifi_private * +nxpwifi_get_priv_by_id(struct nxpwifi_adapter *adapter, + u8 bss_num, u8 bss_type) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) + continue; + if (adapter->priv[i]->bss_num == bss_num && + adapter->priv[i]->bss_type == bss_type) + break; + } + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return first priv matching BSS role */ +static inline struct nxpwifi_private * +nxpwifi_get_priv(struct nxpwifi_adapter *adapter, + enum nxpwifi_bss_role bss_role) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (bss_role == NXPWIFI_BSS_ROLE_ANY || + GET_BSS_ROLE(adapter->priv[i]) == bss_role) + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* find unused BSS number for new interface */ +static inline int +nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type, + u8 *bss_num) +{ + u8 i, j; + int index[NXPWIFI_MAX_BSS_NUM]; + + memset(index, 0, sizeof(index)); + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_type == bss_type && + !(adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED)) { + index[adapter->priv[i]->bss_num] = 1; + } + + for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++) { + if (!index[j]) { + *bss_num = j; + return 0; + } + } + + return -ENOENT; +} + +/* return unused private entry for requested bss type */ +static inline struct nxpwifi_private * +nxpwifi_get_unused_priv_by_bss_type(struct nxpwifi_adapter *adapter, + u8 bss_type) +{ + u8 i; + + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) { + if (nxpwifi_get_unused_bss_num(adapter, bss_type, + &adapter->priv[i]->bss_num)) + return NULL; + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return private structure attached to netdev */ +static inline struct nxpwifi_private * +nxpwifi_netdev_get_priv(struct net_device *dev) +{ + return (struct nxpwifi_private *)(*(unsigned long *)netdev_priv(dev)); +} + +/* return true if skb contains a management frame */ +static inline bool nxpwifi_is_skb_mgmt_frame(struct sk_buff *skb) +{ + return (get_unaligned_le32(skb->data) == PKT_TYPE_MGMT); +} + +/* channel closed by CSA */ +static inline u8 +nxpwifi_11h_get_csa_closed_channel(struct nxpwifi_private *priv) +{ + if (!priv->csa_chan) + return 0; + + /* clear CSA if DFS switch timeout expired */ + if (time_after(jiffies, priv->csa_expire_time)) { + priv->csa_chan = 0; + priv->csa_expire_time = 0; + } + + return priv->csa_chan; +} + +static inline u8 nxpwifi_is_any_intf_active(struct nxpwifi_private *priv) +{ + struct nxpwifi_private *priv_tmp; + int i; + + for (i = 0; i < priv->adapter->priv_num; i++) { + priv_tmp = priv->adapter->priv[i]; + if ((GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_UAP && + priv_tmp->bss_started) || + (GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_STA && + priv_tmp->media_connected)) + return 1; + } + + return 0; +} + +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown); + +int nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev); +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter); + +void nxpwifi_get_version(struct nxpwifi_adapter *adapter, char *version, + int maxlen); +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list); +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev); +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued); +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid); +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type); +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter); +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv); +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate); + +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in); +int nxpwifi_set_radio(struct nxpwifi_private *priv, u8 option); + +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable); + +int nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len); + +int nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel); + +int nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration); + +int nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log); + +int nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value); + +int nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value); + +int nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value); + +int nxpwifi_set_11n_httx_cfg(struct nxpwifi_private *priv, int data); + +int nxpwifi_get_11n_httx_cfg(struct nxpwifi_private *priv, int *data); + +int nxpwifi_set_tx_rate_cfg(struct nxpwifi_private *priv, int tx_rate_index); + +int nxpwifi_get_tx_rate_cfg(struct nxpwifi_private *priv, int *tx_rate_index); + +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode); + +int nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, + char *version, int max_len); + +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg); + +void nxpwifi_main_process(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb); + +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info); +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry); +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type); +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv); + +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params); +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev); + +int nxpwifi_add_wowlan_magic_pkt_filter(struct nxpwifi_adapter *adapter); + +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data); +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv); +u8 *nxpwifi_11d_code_2_region(u8 code); +void nxpwifi_init_11h_params(struct nxpwifi_private *priv); +int nxpwifi_is_11h_active(struct nxpwifi_private *priv); +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag); +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_11h_handle_event_chanswann(struct nxpwifi_private *priv); + +extern const struct ethtool_ops nxpwifi_ethtool_ops; + +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv); +void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node); +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac); +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter); + +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body); + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie); +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_abort_cac(struct nxpwifi_private *priv); +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef); +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr); +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv); +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr); +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 ht_info); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter); +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter); +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter); +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags); +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv); +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason); +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band); +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter); +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid); +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band); +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, + bool external, u8 *new_mac); +void nxpwifi_devdump_tmo_func(unsigned long function_context); + +#ifdef CONFIG_DEBUG_FS +void nxpwifi_debugfs_init(void); +void nxpwifi_debugfs_remove(void); + +void nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv); +void nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv); +#endif +int nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter); +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter); +bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code); +#endif /* !_NXPWIFI_MAIN_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c new file mode 100644 index 000000000000..b77056983e83 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/scan.c @@ -0,0 +1,2698 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: scan ioctl and command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" +#include "cfg80211.h" + +/* The maximum number of channels the firmware can scan per command */ +#define NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN 14 + +#define NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD 4 + +/* Memory needed to store a max sized Channel List TLV for a firmware scan */ +#define CHAN_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_header) \ + + (NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN \ + * sizeof(struct nxpwifi_chan_scan_param_set))) + +/* Memory needed to store supported rate */ +#define RATE_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_rates_param_set) \ + + HOSTCMD_SUPPORTED_RATES) + +/* Memory needed to store a max number/size WildCard SSID TLV for a firmware scan */ +#define WILDCARD_SSID_TLV_MAX_SIZE \ + (NXPWIFI_MAX_SSID_LIST_LENGTH * \ + (sizeof(struct nxpwifi_ie_types_wildcard_ssid_params) \ + + IEEE80211_MAX_SSID_LEN)) + +/* Maximum memory needed for a nxpwifi_scan_cmd_config with all TLVs at max */ +#define MAX_SCAN_CFG_ALLOC (sizeof(struct nxpwifi_scan_cmd_config) \ + + sizeof(struct nxpwifi_ie_types_num_probes) \ + + sizeof(struct nxpwifi_ie_types_htcap) \ + + sizeof(struct nxpwifi_ie_types_vhtcap) \ + + sizeof(struct nxpwifi_ie_types_he_cap) \ + + CHAN_TLV_MAX_SIZE \ + + RATE_TLV_MAX_SIZE \ + + WILDCARD_SSID_TLV_MAX_SIZE) + +union nxpwifi_scan_cmd_config_tlv { + /* Scan configuration (variable length) */ + struct nxpwifi_scan_cmd_config config; + /* Max allocated block */ + u8 config_alloc_buf[MAX_SCAN_CFG_ALLOC]; +}; + +#define NXPWIFI_WPA_CIPHER_SUITE_TKIP SUITE(WLAN_OUI_MICROSOFT, 2) +#define NXPWIFI_WPA_CIPHER_SUITE_CCMP SUITE(WLAN_OUI_MICROSOFT, 4) + +static void +_dbg_security_flags(int log_level, const char *func, const char *desc, + struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + _nxpwifi_dbg(priv->adapter, log_level, + "info: %s: %s:\twpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s\tEncMode=%#x privacy=%#x\n", + func, desc, + bss_desc->bcn_wpa_ie ? + bss_desc->bcn_wpa_ie->vend_hdr.element_id : 0, + bss_desc->bcn_rsn_ie ? + bss_desc->bcn_rsn_ie->id : 0, + priv->sec_info.wep_enabled ? "e" : "d", + priv->sec_info.wpa_enabled ? "e" : "d", + priv->sec_info.wpa2_enabled ? "e" : "d", + priv->sec_info.encryption_mode, + bss_desc->privacy); +} + +#define dbg_security_flags(mask, desc, priv, bss_desc) \ + _dbg_security_flags(NXPWIFI_DBG_##mask, __func__, desc, priv, bss_desc) + +/* Parse a WPA/RSN element and check whether its PTK list contains the OUI */ +static u8 +nxpwifi_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +{ + u8 count; + + count = iebody->ptk_cnt[0]; + + /* + * PTK may contain multiple OUIs; iterate through the list and compare + * each one + */ + while (count) { + if (!memcmp(iebody->ptk_body, oui, sizeof(iebody->ptk_body))) + return NXPWIFI_OUI_PRESENT; + + --count; + if (count) + iebody = (struct ie_body *)((u8 *)iebody + + sizeof(iebody->ptk_body)); + } + + pr_debug("info: %s: OUI is not found in PTK\n", __func__); + return NXPWIFI_OUI_NOT_PRESENT; +} + +/* Check whether the RSN IE is present and if its PTK list contains the OUI */ +static u8 +nxpwifi_is_rsn_oui_present(struct nxpwifi_bssdescriptor *bss_desc, + u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_rsn_ie) { + iebody = (struct ie_body *) + (((u8 *)bss_desc->bcn_rsn_ie->data) + + RSN_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check if the WPA IE exists and whether its PTK list contains the OUI */ +static u8 +nxpwifi_is_wpa_oui_present(struct nxpwifi_bssdescriptor *bss_desc, u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_wpa_ie) { + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + + WPA_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check whether both driver and BSS operate with no security */ +static bool +nxpwifi_is_bss_no_sec(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_rsn_ie && + !bss_desc->bcn_wpa_ie && + !priv->sec_info.encryption_mode && !bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether static WEP is enabled and the BSS privacy setting matches */ +static bool +nxpwifi_is_bss_static_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether WPA is enabled and the BSS contains a WPA IE */ +static bool +nxpwifi_is_bss_wpa(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + bss_desc->bcn_wpa_ie) { + dbg_security_flags(INFO, "WPA", priv, bss_desc); + return true; + } + return false; +} + +/* Check whether WPA2 is enabled and the BSS includes an RSN IE */ +static bool +nxpwifi_is_bss_wpa2(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + priv->sec_info.wpa2_enabled && + bss_desc->bcn_rsn_ie) { + /* + * Some APs (e.g., WRT54G) may omit the privacy bit even when + * using WPA2 + */ + dbg_security_flags(ERROR, "WPA2", priv, bss_desc); + return true; + } + return false; +} + +/* Check dynamic WEP: enabled in driver, privacy set, and no WPA/RSN IE present */ +static bool +nxpwifi_is_bss_dynamic_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_wpa_ie && + !bss_desc->bcn_rsn_ie && + priv->sec_info.encryption_mode && bss_desc->privacy) { + dbg_security_flags(INFO, "dynamic", priv, bss_desc); + return true; + } + return false; +} + +/* + * Check whether a scanned network is compatible with the driver's security + * configuration. The decision considers WEP, WPA, WPA2, privacy settings, + * and whether HT must be disabled when required (e.g., no AES). + * + * General rules: + * - Open networks: always compatible. + * - WPA-only: compatible; HT disabled if AES is not supported. + * - WPA2-only: compatible; HT disabled if AES is not supported. + * - Static WEP: compatible; HT disabled. + * - Dynamic WEP: compatible when privacy is enabled. + * + * Note: Compatibility is not enforced during roaming except for security mode. + */ +static int +nxpwifi_is_network_compatible(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, u32 mode) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + bss_desc->disable_11n = false; + + /* Skip compatibility checks while roaming */ + if (priv->media_connected && + priv->bss_mode == NL80211_IFTYPE_STATION && + bss_desc->bss_mode == NL80211_IFTYPE_STATION) + return 0; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, IOCTL, + "info: return success directly in WPS period\n"); + return 0; + } + + if (bss_desc->chan_sw_ie_present) { + nxpwifi_dbg(adapter, INFO, + "Don't connect to AP with WLAN_EID_CHANNEL_SWITCH\n"); + return -EPERM; + } + + if (bss_desc->bss_mode == mode) { + if (nxpwifi_is_bss_no_sec(priv, bss_desc)) { + return 0; + } else if (nxpwifi_is_bss_static_wep(priv, bss_desc)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n in WEP mode.\n"); + bss_desc->disable_11n = true; + return 0; + } else if (nxpwifi_is_bss_wpa(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_wpa_oui_present(bss_desc, + NXPWIFI_WPA_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_wpa_oui_present + (bss_desc, NXPWIFI_WPA_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_wpa2(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_rsn_oui_present(bss_desc, + WLAN_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_GCMP_256) || + nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_CCMP_256)) { + return 0; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_dynamic_wep(priv, bss_desc)) { + return 0; + } + + /* Security mismatch */ + dbg_security_flags(ERROR, "failed", priv, bss_desc); + return -EINVAL; + } + + return -EINVAL; +} + +/* + * Build the channel list for scanning based on region and band settings. + * Used when a scan request does not specify its own channel list. + */ +static int +nxpwifi_scan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg + *user_scan_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list, + u8 filtered_scan) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0; + + if (user_scan_in) + scan_time = (u16)user_scan_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS) ; band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if ((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR)) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->active_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + if (filtered_scan && + !((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR))) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->specific_scan_time); + + chan_idx++; + } + } + return chan_idx; +} + +/* + * Build the channel-list TLV for bgscan based on region and band settings. + */ +static int +nxpwifi_bgscan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_bg_scan_cfg + *bgscan_cfg_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0, specific_scan_time = adapter->specific_scan_time; + + if (bgscan_cfg_in) + scan_time = (u16)bgscan_cfg_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS); band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(specific_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + chan_idx++; + } + } + return chan_idx; +} + +/* Append the rate TLV to the scan configuration command */ +static int +nxpwifi_append_rate_tlv(struct nxpwifi_private *priv, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + u8 radio) +{ + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES], *tlv_pos; + u32 rates_size; + + memset(rates, 0, sizeof(rates)); + + tlv_pos = (u8 *)scan_cfg_out->tlv_buf + scan_cfg_out->tlv_buf_len; + + if (priv->scan_request) + rates_size = nxpwifi_get_rates_from_cfg80211(priv, rates, + radio); + else + rates_size = nxpwifi_get_supported_rates(priv, rates); + + nxpwifi_dbg(priv->adapter, CMD, + "info: SCAN_CMD: Rates size = %d\n", + rates_size); + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)tlv_pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + scan_cfg_out->tlv_buf_len += sizeof(rates_tlv->header) + rates_size; + + return rates_size; +} + +/* + * Build and send multiple scan commands by chunking channel TLVs per scan + * limit. + */ +static int +nxpwifi_scan_channel_list(struct nxpwifi_private *priv, + u32 max_chan_per_scan, u8 filtered_scan, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set *tlv_o, + struct nxpwifi_chan_scan_param_set *scan_chan_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct nxpwifi_chan_scan_param_set *tmp_chan_list; + u32 tlv_idx, rates_size, cmd_no; + u32 total_scan_time; + u32 done_early; + u8 radio_type; + + if (!scan_cfg_out || !tlv_o || !scan_chan_list) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: Scan: Null detect: %p, %p, %p\n", + scan_cfg_out, tlv_o, scan_chan_list); + return -EINVAL; + } + + /* Check csa channel expiry before preparing scan list */ + nxpwifi_11h_get_csa_closed_channel(priv); + + tlv_o->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + tmp_chan_list = scan_chan_list; + + /* + * Iterate through the channel list and send a firmware scan command for + * each group of max_chan_per_scan channels, or individually for + * channels 1, 6, and 11 when configured. + */ + while (tmp_chan_list->chan_number) { + tlv_idx = 0; + total_scan_time = 0; + radio_type = 0; + tlv_o->header.len = 0; + done_early = false; + + /* + * Build the channel TLV for the scan command. Continue adding + * channel TLVs until one of the following conditions is met: + * - tlv_idx reaches the maximum allowed per scan command + * - the next channel is 0 (end of the desired channel list) + * - done_early is set (used for per-channel scanning of 1, 6, + * and 11) + */ + while (tlv_idx < max_chan_per_scan && + tmp_chan_list->chan_number && !done_early) { + if (tmp_chan_list->chan_number == priv->csa_chan) { + tmp_chan_list++; + continue; + } + + radio_type = tmp_chan_list->band_cfg; + nxpwifi_dbg(priv->adapter, INFO, + "info: Scan: Chan(%3d), Band(%d),\t" + "Mode(%d, %d), Dur(%d)\n", + tmp_chan_list->chan_number, + tmp_chan_list->band_cfg, + tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_PASSIVE_SCAN, + (tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_DISABLE_CHAN_FILT) >> 1, + le16_to_cpu(tmp_chan_list->max_scan_time)); + + /* Copy the current channel TLV into the command being prepared */ + memcpy(&tlv_o->chan_scan_param[tlv_idx], tmp_chan_list, + sizeof(*tlv_o->chan_scan_param)); + + /* + * Increment the TLV header length by the size + * appended + */ + le16_unaligned_add_cpu(&tlv_o->header.len, + sizeof(*tlv_o->chan_scan_param)); + + /* + * The tlv buffer length is set to the number of bytes + * of the between the channel tlv pointer and the start + * of the tlv buffer. This compensates for any TLVs + * that were appended before the channel list. + */ + scan_cfg_out->tlv_buf_len = + (u32)((u8 *)tlv_o - scan_cfg_out->tlv_buf); + + scan_cfg_out->tlv_buf_len += + (sizeof(tlv_o->header) + + le16_to_cpu(tlv_o->header.len)); + + /* Advance the index for the channel TLV being constructed. */ + tlv_idx++; + + /* Count the total scan time per command */ + total_scan_time += + le16_to_cpu(tmp_chan_list->max_scan_time); + + done_early = false; + + /* + * Stop the loop if the current channel is one of 1, 6, + * or 11 and no SSID or BSSID filter is applied. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + + /* Advance the tmp pointer to the next channel to be scanned. */ + tmp_chan_list++; + + /* + * Stop the loop if the next channel is one of 1, 6, + * or 11. This causes that channel to be scanned alone + * in the next iteration. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + } + + /* Ensure the total scan time does not exceed the scan-command timeout. */ + if (total_scan_time > NXPWIFI_MAX_TOTAL_SCAN_TIME) { + nxpwifi_dbg(priv->adapter, ERROR, + "total scan time %dms\t" + "is over limit (%dms), scan skipped\n", + total_scan_time, + NXPWIFI_MAX_TOTAL_SCAN_TIME); + ret = -EINVAL; + break; + } + + rates_size = nxpwifi_append_rate_tlv(priv, scan_cfg_out, + radio_type); + + if (priv->adapter->ext_scan) + cmd_no = HOST_CMD_802_11_SCAN_EXT; + else + cmd_no = HOST_CMD_802_11_SCAN; + + ret = nxpwifi_send_cmd(priv, cmd_no, HOST_ACT_GEN_SET, + 0, scan_cfg_out, false); + + /* + * The rate element is updated for each scan command, but the + * same starting pointer is reused, so the previous rate element + * in scan_cfg_out->buf is overwritten. + */ + scan_cfg_out->tlv_buf_len -= + sizeof(struct nxpwifi_ie_types_header) + rates_size; + + if (ret) { + nxpwifi_cancel_pending_scan_cmd(adapter); + break; + } + } + + return ret; +} + +/* + * Build final scan config from user params, disabling missing filters and using + * defaults. + */ +static void +nxpwifi_config_scan(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set **chan_list_out, + struct nxpwifi_chan_scan_param_set *scan_chan_list, + u8 *max_chan_per_scan, u8 *filtered_scan, + u8 *scan_current_only) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_scan_chan_gap *chan_gap_tlv; + struct nxpwifi_ie_types_random_mac *random_mac_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_bssid_list *bssid_tlv; + struct nxpwifi_ie_types_extcap *ext_cap; + u8 *ext_capab = NULL; + u8 *tlv_pos; + u32 num_probes; + u32 ssid_len; + u32 chan_idx; + u32 scan_time; + u32 scan_type; + u16 scan_dur; + u8 channel; + u8 radio_type; + int i, vsid; + u8 ssid_filter; + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_bss_mode *bss_mode; + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_he_cap *he_cap; + + /* + * tlv_buf_len is recalculated for each scan command. TLVs added in this + * routine are preserved because the send routine appends channel TLVs + * at chan_list_out. The difference between chan_list_out and the start + * of the TLV buffer determines the size of the TLVs added here. + */ + scan_cfg_out->tlv_buf_len = 0; + + /* + * Running TLV pointer. It is assigned to chan_list_out at the end of + * the function so later routines know where channel TLVs can be + * appended in the command buffer. + */ + tlv_pos = scan_cfg_out->tlv_buf; + + /* + * Initialize the scan as un-filtered; the flag is later set to TRUE + * below if a SSID or BSSID filter is sent in the command + */ + *filtered_scan = false; + + /* + * Initialize the scan as not being only on the current channel. If + * the channel list is customized, only contains one channel, and is + * the active channel, this is set true and data flow is not halted. + */ + *scan_current_only = false; + + if (user_scan_in) { + u8 tmpaddr[ETH_ALEN]; + + /* + * Default the ssid_filter flag to TRUE, set false under + * certain wildcard conditions and qualified by the existence + * of an SSID list before marking the scan as filtered + */ + ssid_filter = true; + + /* + * Set the BSS type scan filter, use Adapter setting if + * unset + */ + scan_cfg_out->bss_mode = + (u8)(user_scan_in->bss_mode ?: adapter->scan_mode); + + /* + * Set the number of probes to send, use Adapter setting + * if unset + */ + num_probes = user_scan_in->num_probes ?: adapter->scan_probes; + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled + * (all zeros). + */ + memcpy(scan_cfg_out->specific_bssid, + user_scan_in->specific_bssid, + sizeof(scan_cfg_out->specific_bssid)); + + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + + if (adapter->ext_scan && + !is_zero_ether_addr(tmpaddr)) { + bssid_tlv = + (struct nxpwifi_ie_types_bssid_list *)tlv_pos; + bssid_tlv->header.type = cpu_to_le16(TLV_TYPE_BSSID); + bssid_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(bssid_tlv->bssid, user_scan_in->specific_bssid, + ETH_ALEN); + tlv_pos += sizeof(struct nxpwifi_ie_types_bssid_list); + } + + for (i = 0; i < user_scan_in->num_ssids; i++) { + ssid_len = user_scan_in->ssid_list[i].ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *) + tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to perform + * specific scan for the SSID filled, whereas + * max_ssid_length = IEEE80211_MAX_SSID_LEN is for + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + if (!memcmp(user_scan_in->ssid_list[i].ssid, + "DIRECT-", 7)) + wildcard_ssid_tlv->max_ssid_length = 0xfe; + + memcpy(wildcard_ssid_tlv->ssid, + user_scan_in->ssid_list[i].ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + + nxpwifi_dbg(adapter, INFO, + "info: scan: ssid[%d]: %s, %d\n", + i, wildcard_ssid_tlv->ssid, + wildcard_ssid_tlv->max_ssid_length); + + /* + * Empty wildcard ssid with a maxlen will match many or + * potentially all SSIDs (maxlen == 32), therefore do + * not treat the scan as + * filtered. + */ + if (!ssid_len && wildcard_ssid_tlv->max_ssid_length) + ssid_filter = false; + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not + * truncate scan results. That is not an issue with an SSID + * or BSSID filter applied to the scan results in the firmware. + */ + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + if ((i && ssid_filter) || + !is_zero_ether_addr(tmpaddr)) + *filtered_scan = true; + + if (user_scan_in->scan_chan_gap) { + nxpwifi_dbg(adapter, INFO, + "info: scan: channel gap = %d\n", + user_scan_in->scan_chan_gap); + *max_chan_per_scan = + NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + + chan_gap_tlv = (void *)tlv_pos; + chan_gap_tlv->header.type = + cpu_to_le16(TLV_TYPE_SCAN_CHANNEL_GAP); + chan_gap_tlv->header.len = + cpu_to_le16(sizeof(chan_gap_tlv->chan_gap)); + chan_gap_tlv->chan_gap = + cpu_to_le16((user_scan_in->scan_chan_gap)); + tlv_pos += + sizeof(struct nxpwifi_ie_types_scan_chan_gap); + } + + if (!is_zero_ether_addr(user_scan_in->random_mac)) { + random_mac_tlv = (void *)tlv_pos; + random_mac_tlv->header.type = + cpu_to_le16(TLV_TYPE_RANDOM_MAC); + random_mac_tlv->header.len = + cpu_to_le16(sizeof(random_mac_tlv->mac)); + ether_addr_copy(random_mac_tlv->mac, + user_scan_in->random_mac); + tlv_pos += + sizeof(struct nxpwifi_ie_types_random_mac); + } + } else { + scan_cfg_out->bss_mode = (u8)adapter->scan_mode; + num_probes = adapter->scan_probes; + } + + /* + * If a specific BSSID or SSID is used, the number of channels in the + * scan command will be increased to the absolute maximum. + */ + if (*filtered_scan) { + *max_chan_per_scan = NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + } else { + if (!priv->media_connected) + *max_chan_per_scan = NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD; + else + *max_chan_per_scan = + NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD / 2; + } + + if (adapter->ext_scan) { + bss_mode = (struct nxpwifi_ie_types_bss_mode *)tlv_pos; + bss_mode->header.type = cpu_to_le16(TLV_TYPE_BSS_MODE); + bss_mode->header.len = cpu_to_le16(sizeof(bss_mode->bss_mode)); + bss_mode->bss_mode = scan_cfg_out->bss_mode; + tlv_pos += sizeof(bss_mode->header) + + le16_to_cpu(bss_mode->header.len); + } + + /* + * If the input config or adapter has the number of Probes set, + * add tlv + */ + if (num_probes) { + nxpwifi_dbg(adapter, INFO, + "info: scan: num_probes = %d\n", + num_probes); + + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) { + ht_cap = (struct nxpwifi_ie_types_htcap *)tlv_pos; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + radio_type = + nxpwifi_band_to_radio_type(priv->config_bands); + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + tlv_pos += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + (priv->config_bands & BAND_AAC)) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)tlv_pos; + memset(vht_cap, 0, sizeof(struct nxpwifi_ie_types_vhtcap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, priv->config_bands); + tlv_pos += sizeof(*vht_cap); + } + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || + priv->config_bands & BAND_AAX)) { + he_cap = (struct nxpwifi_ie_types_he_cap *)tlv_pos; + memset(he_cap, 0, sizeof(struct nxpwifi_ie_types_he_cap)); + tlv_pos += nxpwifi_fill_he_cap_tlv(priv, he_cap, priv->config_bands); + } + + if (nxpwifi_is_sta_11ax_twt_req_supported(priv)) { + for (vsid = 0; vsid < NXPWIFI_MAX_VSIE_NUM; vsid++) { + if (priv->vs_ie[vsid].mask & NXPWIFI_VSIE_MASK_SCAN) { + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, + priv->vs_ie[vsid].ie, + sizeof(priv->vs_ie[vsid].ie)); + break; + } + } + + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)tlv_pos; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + + NXPWIFI_EXT_CAPAB_IE_LEN); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(NXPWIFI_EXT_CAPAB_IE_LEN); + ext_capab = ext_cap->ext_capab; + tlv_pos += sizeof(struct nxpwifi_ie_types_extcap) + + le16_to_cpu(ext_cap->header.len); + } + + ext_capab[9] |= WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_SCAN, &tlv_pos); + + /* + * Set the channel TLV output pointer to the end of the newly added TLVs + * (SSID, num_probes). Channel TLVs for each scan will be appended after + * these, preserving previously added TLVs. + */ + *chan_list_out = + (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (user_scan_in && user_scan_in->chan_list[0].chan_number) { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Using supplied channel list\n"); + + for (chan_idx = 0; + chan_idx < NXPWIFI_USER_SCAN_CHAN_MAX && + user_scan_in->chan_list[chan_idx].chan_number; + chan_idx++) { + channel = user_scan_in->chan_list[chan_idx].chan_number; + scan_chan_list[chan_idx].chan_number = channel; + + radio_type = + user_scan_in->chan_list[chan_idx].radio_type; + scan_chan_list[chan_idx].band_cfg = radio_type; + + scan_type = user_scan_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | + NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + scan_time = user_scan_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_dur = adapter->passive_scan_time; + else if (*filtered_scan) + scan_dur = adapter->specific_scan_time; + else + scan_dur = adapter->active_scan_time; + } + + scan_chan_list[chan_idx].min_scan_time = + cpu_to_le16(scan_dur); + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_dur); + } + + /* Check if we are only scanning the current channel */ + if (chan_idx == 1 && + user_scan_in->chan_list[0].chan_number == + priv->curr_bss_params.bss_descriptor.channel) { + *scan_current_only = true; + nxpwifi_dbg(adapter, INFO, + "info: Scan: Scanning current channel only\n"); + } + } else { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Creating full region channel list\n"); + nxpwifi_scan_create_channel_list(priv, user_scan_in, + scan_chan_list, + *filtered_scan); + } +} + +/* Parse the beacon buffer and update the BSS descriptor fields. */ +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry) +{ + u8 element_id; + u16 elem_size = sizeof(struct element); + struct ieee_types_fh_param_set *fh_param_set; + struct ieee_types_ds_param_set *ds_param_set; + struct ieee_types_cf_param_set *cf_param_set; + u8 *current_ptr; + u8 *rate; + u8 element_len; + u16 total_ie_len; + u8 bytes_to_copy; + u8 rate_size; + u8 found_data_rate_ie; + u32 bytes_left; + struct ieee_types_vendor_specific *vendor_ie; + const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 }; + struct element *elem; + + found_data_rate_ie = false; + rate_size = 0; + current_ptr = bss_entry->beacon_buf; + bytes_left = bss_entry->beacon_buf_size; + + /* Process variable element */ + while (bytes_left >= 2) { + element_id = *current_ptr; + element_len = *(current_ptr + 1); + total_ie_len = element_len + elem_size; + + if (bytes_left < total_ie_len) { + nxpwifi_dbg(adapter, ERROR, + "err: InterpretIE: in processing\t" + "element, bytes left < element length\n"); + return -EINVAL; + } + switch (element_id) { + case WLAN_EID_SSID: + if (element_len > IEEE80211_MAX_SSID_LEN) + return -EINVAL; + bss_entry->ssid.ssid_len = element_len; + memcpy(bss_entry->ssid.ssid, (current_ptr + 2), + element_len); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: ssid: %-32s\n", + bss_entry->ssid.ssid); + break; + + case WLAN_EID_SUPP_RATES: + if (element_len > NXPWIFI_SUPPORTED_RATES) + return -EINVAL; + memcpy(bss_entry->data_rates, current_ptr + 2, + element_len); + memcpy(bss_entry->supported_rates, current_ptr + 2, + element_len); + rate_size = element_len; + found_data_rate_ie = true; + break; + + case WLAN_EID_FH_PARAMS: + if (total_ie_len < sizeof(*fh_param_set)) + return -EINVAL; + fh_param_set = + (struct ieee_types_fh_param_set *)current_ptr; + memcpy(&bss_entry->phy_param_set.fh_param_set, + fh_param_set, + sizeof(struct ieee_types_fh_param_set)); + break; + + case WLAN_EID_DS_PARAMS: + if (total_ie_len < sizeof(*ds_param_set)) + return -EINVAL; + ds_param_set = + (struct ieee_types_ds_param_set *)current_ptr; + + bss_entry->channel = ds_param_set->current_chan; + + memcpy(&bss_entry->phy_param_set.ds_param_set, + ds_param_set, + sizeof(struct ieee_types_ds_param_set)); + break; + + case WLAN_EID_CF_PARAMS: + if (total_ie_len < sizeof(*cf_param_set)) + return -EINVAL; + cf_param_set = + (struct ieee_types_cf_param_set *)current_ptr; + memcpy(&bss_entry->cf_param_set, + cf_param_set, + sizeof(struct ieee_types_cf_param_set)); + break; + + case WLAN_EID_ERP_INFO: + if (!element_len) + return -EINVAL; + bss_entry->erp_flags = *(current_ptr + 2); + break; + + case WLAN_EID_PWR_CONSTRAINT: + if (!element_len) + return -EINVAL; + bss_entry->local_constraint = *(current_ptr + 2); + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_CHANNEL_SWITCH: + bss_entry->chan_sw_ie_present = true; + fallthrough; + case WLAN_EID_PWR_CAPABILITY: + case WLAN_EID_TPC_REPORT: + case WLAN_EID_QUIET: + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_EXT_SUPP_RATES: + /* + * Only process extended supported rate + * if data rate is already found. + * Data rate element should come before + * extended supported rate element + */ + if (found_data_rate_ie) { + if ((element_len + rate_size) > + NXPWIFI_SUPPORTED_RATES) + bytes_to_copy = + (NXPWIFI_SUPPORTED_RATES - + rate_size); + else + bytes_to_copy = element_len; + + rate = (u8 *)bss_entry->data_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + + rate = (u8 *)bss_entry->supported_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + } + break; + + case WLAN_EID_VENDOR_SPECIFIC: + vendor_ie = (struct ieee_types_vendor_specific *) + current_ptr; + + /* 802.11 requires at least 3-byte OUI. */ + if (element_len < sizeof(vendor_ie->vend_hdr.oui)) + return -EINVAL; + + /* Not long enough for a match? Skip it. */ + if (element_len < sizeof(wpa_oui)) + break; + + if (!memcmp(&vendor_ie->vend_hdr.oui, wpa_oui, + sizeof(wpa_oui))) { + bss_entry->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + current_ptr; + bss_entry->wpa_offset = + (u16)(current_ptr - + bss_entry->beacon_buf); + } else if (!memcmp(&vendor_ie->vend_hdr.oui, wmm_oui, + sizeof(wmm_oui))) { + if (total_ie_len == + sizeof(struct ieee80211_wmm_param_ie) || + total_ie_len == + sizeof(struct ieee_types_wmm_info)) + /* + * Only accept and copy the WMM element if + * it matches the size expected for the + * WMM Info element or the WMM Parameter element. + */ + memcpy((u8 *)&bss_entry->wmm_ie, + current_ptr, total_ie_len); + } + break; + case WLAN_EID_RSN: + bss_entry->bcn_rsn_ie = + (struct element *)current_ptr; + bss_entry->rsn_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_RSNX: + bss_entry->bcn_rsnx_ie = + (struct element *)current_ptr; + bss_entry->rsnx_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_HT_CAPABILITY: + bss_entry->bcn_ht_cap = + (struct ieee80211_ht_cap *)(current_ptr + + elem_size); + bss_entry->ht_cap_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_HT_OPERATION: + bss_entry->bcn_ht_oper = + (struct ieee80211_ht_operation *)(current_ptr + + elem_size); + bss_entry->ht_info_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_CAPABILITY: + bss_entry->disable_11ac = false; + bss_entry->bcn_vht_cap = (void *)(current_ptr + + elem_size); + bss_entry->vht_cap_offset = + (u16)((u8 *)bss_entry->bcn_vht_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_OPERATION: + bss_entry->bcn_vht_oper = + (void *)(current_ptr + elem_size); + bss_entry->vht_info_offset = + (u16)((u8 *)bss_entry->bcn_vht_oper - + bss_entry->beacon_buf); + break; + case WLAN_EID_BSS_COEX_2040: + bss_entry->bcn_bss_co_2040 = current_ptr; + bss_entry->bss_co_2040_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_CAPABILITY: + bss_entry->bcn_ext_cap = current_ptr; + bss_entry->ext_cap_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_OPMODE_NOTIF: + bss_entry->oper_mode = (void *)current_ptr; + bss_entry->oper_mode_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXTENSION: + if (!element_len) + return -EINVAL; + + elem = (struct element *)current_ptr; + + switch (elem->data[0]) { + case WLAN_EID_EXT_HE_CAPABILITY: + bss_entry->disable_11ax = false; + bss_entry->bcn_he_cap = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_cap_offset = + (u16)((u8 *)bss_entry->bcn_he_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_HE_OPERATION: + bss_entry->bcn_he_oper = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_info_offset = + (u16)((u8 *)bss_entry->bcn_he_oper - + bss_entry->beacon_buf); + break; + default: + break; + } + break; + default: + break; + } + + current_ptr += total_ie_len; + bytes_left -= total_ie_len; + + } /* while (bytes_left > 2) */ + return 0; +} + +/* Convert the radio-type scan parameter to the join command's band config. */ +static u8 +nxpwifi_radio_type_to_band(u8 radio_type) +{ + switch (radio_type) { + case HOST_SCAN_RADIO_TYPE_A: + return BAND_A; + case HOST_SCAN_RADIO_TYPE_BG: + default: + return BAND_G; + } +} + +/* Internal helper to start a scan using the given configuration. */ +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + union nxpwifi_scan_cmd_config_tlv *scan_cfg_out; + struct nxpwifi_ie_types_chan_list_param_set *chan_list_out; + struct nxpwifi_chan_scan_param_set *scan_chan_list; + u8 filtered_scan; + u8 scan_current_chan_only; + u8 max_chan_per_scan; + + if (adapter->scan_processing) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan already in process...\n"); + return -EBUSY; + } + + if (priv->scan_block) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan is blocked during association...\n"); + return -EBUSY; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "Ignore scan. Card removed or firmware in bad state\n"); + return -EPERM; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = true; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv, + GFP_KERNEL); + if (!scan_cfg_out) { + ret = -ENOMEM; + goto done; + } + + scan_chan_list = kzalloc_objs(struct nxpwifi_chan_scan_param_set, + NXPWIFI_USER_SCAN_CHAN_MAX, GFP_KERNEL); + if (!scan_chan_list) { + kfree(scan_cfg_out); + ret = -ENOMEM; + goto done; + } + + nxpwifi_config_scan(priv, user_scan_in, &scan_cfg_out->config, + &chan_list_out, scan_chan_list, &max_chan_per_scan, + &filtered_scan, &scan_current_chan_only); + + ret = nxpwifi_scan_channel_list(priv, max_chan_per_scan, filtered_scan, + &scan_cfg_out->config, chan_list_out, + scan_chan_list); + + /* Get scan command from scan_pending_q and put to cmd_pending_q */ + if (!ret) { + spin_lock_bh(&adapter->scan_pending_q_lock); + if (!list_empty(&adapter->scan_pending_q)) { + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + + /* Perform internal scan synchronously */ + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "wait internal scan\n"); + nxpwifi_wait_queue_complete(adapter, cmd_node); + } + } else { + spin_unlock_bh(&adapter->scan_pending_q_lock); + } + } + + kfree(scan_cfg_out); + kfree(scan_chan_list); +done: + if (ret) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + return ret; +} + +/* + * Build the firmware scan command from the given configuration, including + * fixed fields and TLVs, and set the command ID, size, and endianness. + */ +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg) +{ + struct host_cmd_ds_802_11_scan *scan_cmd = &cmd->params.scan; + + /* Set fixed field variables in scan command */ + scan_cmd->bss_mode = scan_cfg->bss_mode; + memcpy(scan_cmd->bssid, scan_cfg->specific_bssid, + sizeof(scan_cmd->bssid)); + memcpy(scan_cmd->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(scan_cmd->bss_mode) + + sizeof(scan_cmd->bssid) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Check compatibility of the requested network with current driver settings. */ +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + int ret = 0; + + if (!bss_desc) + return -EINVAL; + + if ((nxpwifi_get_cfp(priv, (u8)bss_desc->bss_band, + (u16)bss_desc->channel, 0))) { + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_is_network_compatible(priv, bss_desc, + priv->bss_mode); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Incompatible network settings\n"); + break; + default: + ret = 0; + } + } + + return ret; +} + +/* Check if the SSID length is zero or all bytes are zero. */ +static bool nxpwifi_is_hidden_ssid(struct cfg80211_ssid *ssid) +{ + int idx; + + for (idx = 0; idx < ssid->ssid_len; idx++) { + if (ssid->ssid[idx]) + return false; + } + + return true; +} + +/* Find hidden SSIDs on passive channels and save those channels for active scan. */ +static int nxpwifi_save_hidden_ssid_channels(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + int chid; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_is_hidden_ssid(&bss_desc->ssid)) { + nxpwifi_dbg(priv->adapter, INFO, "found hidden SSID\n"); + for (chid = 0 ; chid < NXPWIFI_USER_SCAN_CHAN_MAX; chid++) { + if (priv->hidden_chan[chid].chan_number == + bss->channel->hw_value) + break; + + if (!priv->hidden_chan[chid].chan_number) { + priv->hidden_chan[chid].chan_number = + bss->channel->hw_value; + priv->hidden_chan[chid].radio_type = + bss->channel->band; + priv->hidden_chan[chid].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + break; + } + } + } + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int nxpwifi_update_curr_bss_params(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + spin_lock_bh(&priv->curr_bcn_buf_lock); + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, bss_desc, + sizeof(priv->curr_bss_params.bss_descriptor)); + + /* beacon_ie will be copied to its own buffer in nxpwifi_save_curr_bcn(). */ + nxpwifi_save_curr_bcn(priv); + spin_unlock_bh(&priv->curr_bcn_buf_lock); + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int +nxpwifi_parse_single_response_buf(struct nxpwifi_private *priv, u8 **bss_info, + u32 *bytes_left, u64 fw_tsf, const u8 *radio_type, + bool ext_scan, s32 rssi_val) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_chan_freq_power *cfp; + struct cfg80211_bss *bss; + u8 bssid[ETH_ALEN]; + s32 rssi; + const u8 *ie_buf; + size_t ie_len; + u16 channel = 0; + u16 beacon_size = 0; + u32 curr_bcn_bytes; + u32 freq; + u16 beacon_period; + u16 cap_info_bitmap; + u8 *current_ptr; + u64 timestamp; + struct nxpwifi_fixed_bcn_param *bcn_param; + struct nxpwifi_bss_priv *bss_priv; + + if (*bytes_left >= sizeof(beacon_size)) { + /* Extract & convert beacon size from command buffer */ + beacon_size = get_unaligned_le16((*bss_info)); + *bytes_left -= sizeof(beacon_size); + *bss_info += sizeof(beacon_size); + } + + if (!beacon_size || beacon_size > *bytes_left) { + *bss_info += *bytes_left; + *bytes_left = 0; + return -EINVAL; + } + + /* + * Initialize the current working beacon pointer for this BSS + * iteration + */ + current_ptr = *bss_info; + + /* Advance the return beacon pointer past the current beacon */ + *bss_info += beacon_size; + *bytes_left -= beacon_size; + + curr_bcn_bytes = beacon_size; + + /* + * First 5 fields are bssid, RSSI(for legacy scan only), + * time stamp, beacon interval, and capability information + */ + if (curr_bcn_bytes < ETH_ALEN + sizeof(u8) + + sizeof(struct nxpwifi_fixed_bcn_param)) { + nxpwifi_dbg(adapter, ERROR, + "InterpretIE: not enough bytes left\n"); + return -EINVAL; + } + + memcpy(bssid, current_ptr, ETH_ALEN); + current_ptr += ETH_ALEN; + curr_bcn_bytes -= ETH_ALEN; + + if (!ext_scan) { + rssi = (s32)*current_ptr; + rssi = (-rssi) * 100; /* Convert dBm to mBm */ + current_ptr += sizeof(u8); + curr_bcn_bytes -= sizeof(u8); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + } else { + rssi = rssi_val; + } + + bcn_param = (struct nxpwifi_fixed_bcn_param *)current_ptr; + current_ptr += sizeof(*bcn_param); + curr_bcn_bytes -= sizeof(*bcn_param); + + timestamp = le64_to_cpu(bcn_param->timestamp); + beacon_period = le16_to_cpu(bcn_param->beacon_period); + + cap_info_bitmap = le16_to_cpu(bcn_param->cap_info_bitmap); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: capabilities=0x%X\n", + cap_info_bitmap); + + /* Rest of the current buffer are element's */ + ie_buf = current_ptr; + ie_len = curr_bcn_bytes; + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: IELength for this AP = %d\n", + curr_bcn_bytes); + + while (curr_bcn_bytes >= sizeof(struct element)) { + u8 element_id, element_len; + + element_id = *current_ptr; + element_len = *(current_ptr + 1); + if (curr_bcn_bytes < element_len + + sizeof(struct element)) { + nxpwifi_dbg(adapter, ERROR, + "%s: bytes left < element length\n", __func__); + return -EFAULT; + } + if (element_id == WLAN_EID_DS_PARAMS) { + channel = *(current_ptr + + sizeof(struct element)); + break; + } + + current_ptr += element_len + sizeof(struct element); + curr_bcn_bytes -= element_len + + sizeof(struct element); + } + + if (channel) { + struct ieee80211_channel *chan; + struct nxpwifi_bssdescriptor *bss_desc; + u8 band; + + /* Skip entry if on csa closed channel */ + if (channel == priv->csa_chan) { + nxpwifi_dbg(adapter, WARN, + "Dropping entry on csa closed channel\n"); + return 0; + } + + band = BAND_G; + if (radio_type) + band = nxpwifi_radio_type_to_band(*radio_type & + (BIT(0) | BIT(1))); + + cfp = nxpwifi_get_cfp(priv, band, channel, 0); + + freq = cfp ? cfp->freq : 0; + + chan = ieee80211_get_channel(priv->wdev.wiphy, freq); + + if (chan && !(chan->flags & IEEE80211_CHAN_DISABLED)) { + bss = cfg80211_inform_bss(priv->wdev.wiphy, chan, + CFG80211_BSS_FTYPE_UNKNOWN, + bssid, timestamp, + cap_info_bitmap, + beacon_period, + ie_buf, ie_len, rssi, + GFP_ATOMIC); + if (bss) { + bss_priv = (struct nxpwifi_bss_priv *)bss->priv; + bss_priv->band = band; + bss_priv->fw_tsf = fw_tsf; + bss_desc = + &priv->curr_bss_params.bss_descriptor; + if (priv->media_connected && + !memcmp(bssid, bss_desc->mac_address, + ETH_ALEN)) + nxpwifi_update_curr_bss_params(priv, + bss); + + if ((chan->flags & IEEE80211_CHAN_RADAR) || + (chan->flags & IEEE80211_CHAN_NO_IR)) { + nxpwifi_dbg(adapter, INFO, + "radar or passive channel %d\n", + channel); + nxpwifi_save_hidden_ssid_channels(priv, + bss); + } + + cfg80211_put_bss(priv->wdev.wiphy, bss); + } + } + } else { + nxpwifi_dbg(adapter, WARN, "missing BSS channel element\n"); + } + + return 0; +} + +static void nxpwifi_complete_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->survey_idx = 0; + if (adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = 0; + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "complete internal scan\n"); + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + } + } +} + +/* Find hidden SSIDs on passive channels and run active scans on them. */ +static int +nxpwifi_active_scan_req_for_passive_chan(struct nxpwifi_private *priv) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 id = 0; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + + if (adapter->active_scan_triggered || !priv->scan_request || + priv->scan_aborting) { + adapter->active_scan_triggered = false; + return 0; + } + + if (!priv->hidden_chan[0].chan_number) { + nxpwifi_dbg(adapter, INFO, "No BSS with hidden SSID found on DFS channels\n"); + return 0; + } + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + + if (!user_scan_cfg) + return -ENOMEM; + + for (id = 0; id < NXPWIFI_USER_SCAN_CHAN_MAX; id++) { + if (!priv->hidden_chan[id].chan_number) + break; + memcpy(&user_scan_cfg->chan_list[id], + &priv->hidden_chan[id], + sizeof(struct nxpwifi_user_scan_chan)); + } + + adapter->active_scan_triggered = true; + if (priv->scan_request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + ether_addr_copy(user_scan_cfg->random_mac, + priv->scan_request->mac_addr); + user_scan_cfg->num_ssids = priv->scan_request->n_ssids; + user_scan_cfg->ssid_list = priv->scan_request->ssids; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + + memset(&priv->hidden_chan, 0, sizeof(priv->hidden_chan)); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "scan failed: %d\n", ret); + + return ret; +} + +static void nxpwifi_check_next_scan_command(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_active_scan_req_for_passive_chan(priv); + + if (!adapter->ext_scan) + nxpwifi_complete_scan(priv); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = false, + }; + + nxpwifi_dbg(adapter, INFO, + "info: notifying scan done\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } else if ((priv->scan_aborting && !priv->scan_request) || + priv->scan_block) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + nxpwifi_cancel_pending_scan_cmd(adapter); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + if (!adapter->active_scan_triggered) { + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } + } else { + /* Move a scan command from scan_pending_q to cmd_pending_q. */ + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + } +} + +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + nxpwifi_cancel_pending_scan_cmd(adapter); + + if (adapter->scan_processing) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } + } + } +} + +/* + * Handle the scan command response. + * + * The scan response buffer has the following layout: + * + * ------------------------------------------------------------- + * | Header (4 * t_u16): standard command response header | + * ------------------------------------------------------------- + * | BufSize (t_u16): size of the BSS description data | + * ------------------------------------------------------------- + * | NumOfSet (t_u8): number of returned BSS descriptions | + * ------------------------------------------------------------- + * | BSS description data (variable, size = BufSize) | + * ------------------------------------------------------------- + * | TLV data (variable, size = cmd_size - fixed fields) | + * ------------------------------------------------------------- + */ +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_rsp *scan_rsp; + u8 *tlv_data; + const struct nxpwifi_ie_types_tsf_timestamp *tsf_tlv; + u8 *bss_info; + u32 scan_resp_size; + u32 bytes_left; + u32 idx; + u32 tlv_buf_size; + const struct nxpwifi_ie_types_chan_band_list_param_set *chan_band_tlv; + const struct chan_band_param_set *chan_band; + u8 is_bgscan_resp; + __le64 fw_tsf = 0; + const u8 *radio_type; + struct cfg80211_wowlan_nd_match *pmatch; + struct cfg80211_sched_scan_request *nd_config = NULL; + + is_bgscan_resp = (le16_to_cpu(resp->command) + == HOST_CMD_802_11_BG_SCAN_QUERY); + if (is_bgscan_resp) + scan_rsp = &resp->params.bg_scan_query_resp.scan_resp; + else + scan_rsp = &resp->params.scan_resp; + + if (scan_rsp->number_of_sets > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "SCAN_RESP: too many AP returned (%d)\n", + scan_rsp->number_of_sets); + ret = -EINVAL; + goto check_next_scan; + } + + /* Check csa channel expiry before parsing scan response */ + nxpwifi_11h_get_csa_closed_channel(priv); + + bytes_left = le16_to_cpu(scan_rsp->bss_descript_size); + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: bss_descript_size %d\n", + bytes_left); + + scan_resp_size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: returned %d APs before parsing\n", + scan_rsp->number_of_sets); + + bss_info = scan_rsp->bss_desc_and_tlv_buffer; + + /* + * TLV buffer size = scan_resp_size minus the fixed fields, BSS + * description data, and the command response header (S_DS_GEN). + */ + tlv_buf_size = scan_resp_size - (bytes_left + + sizeof(scan_rsp->bss_descript_size) + + sizeof(scan_rsp->number_of_sets) + + S_DS_GEN); + + tlv_data = (scan_rsp->bss_desc_and_tlv_buffer + + bytes_left); + + /* Find timestamp TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_TSFTIMESTAMP, tlv_data, tlv_buf_size); + tsf_tlv = (const struct nxpwifi_ie_types_tsf_timestamp *)t; + } + + /* Find channel-band list TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_CHANNELBANDLIST, tlv_data, + tlv_buf_size); + chan_band_tlv = + (const struct nxpwifi_ie_types_chan_band_list_param_set *)t; + } + +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config) + nd_config = priv->wdev.wiphy->wowlan_config->nd_config; +#endif + + if (nd_config) { + adapter->nd_info = + kzalloc_flex(*adapter->nd_info, matches, + scan_rsp->number_of_sets, GFP_ATOMIC); + + if (adapter->nd_info) + adapter->nd_info->n_matches = scan_rsp->number_of_sets; + } + + for (idx = 0; idx < scan_rsp->number_of_sets && bytes_left; idx++) { + /* + * If a TSF TLV is present, save its TSF value in fw_tsf. This + * is the firmware TSF at the time the beacon or probe response + * was received. + */ + if (tsf_tlv) + memcpy(&fw_tsf, &tsf_tlv->tsf_data[idx * TSF_DATA_SIZE], + sizeof(fw_tsf)); + + if (chan_band_tlv) { + chan_band = &chan_band_tlv->chan_band_param[idx]; + radio_type = &chan_band->radio_type; + } else { + radio_type = NULL; + } + + if (chan_band_tlv && adapter->nd_info) { + adapter->nd_info->matches[idx] = + kzalloc(sizeof(*pmatch) + sizeof(u32), + GFP_ATOMIC); + + pmatch = adapter->nd_info->matches[idx]; + + if (pmatch) { + pmatch->n_channels = 1; + pmatch->channels[0] = chan_band->chan_number; + } + } + + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, + le64_to_cpu(fw_tsf), + radio_type, false, 0); + if (ret) + goto check_next_scan; + } + +check_next_scan: + nxpwifi_check_next_scan_command(priv); + return ret; +} + +/* + * Prepare the extended scan command using the provided scan configuration + * and build the structure to be sent to firmware. + */ +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_scan_ext *ext_scan = &cmd->params.ext_scan; + struct nxpwifi_scan_cmd_config *scan_cfg = data_buf; + + memcpy(ext_scan->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN_EXT); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(ext_scan->reserved) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Prepare the background scan config command to send to firmware. */ +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_bg_scan_config *bgscan_config = + &cmd->params.bg_scan_config; + struct nxpwifi_bg_scan_cfg *bgscan_cfg_in = data_buf; + u8 *tlv_pos = bgscan_config->tlv; + u8 num_probes; + u32 ssid_len, chan_idx, scan_time, scan_type, scan_dur, chan_num; + int i; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_repeat_count *repeat_count_tlv; + struct nxpwifi_ie_types_min_rssi_threshold *rssi_threshold_tlv; + struct nxpwifi_ie_types_bgscan_start_later *start_later_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_chan_list_param_set *tlv_l; + struct nxpwifi_chan_scan_param_set *temp_chan; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_CONFIG); + cmd->size = cpu_to_le16(sizeof(*bgscan_config) + S_DS_GEN); + + bgscan_config->action = cpu_to_le16(bgscan_cfg_in->action); + bgscan_config->enable = bgscan_cfg_in->enable; + bgscan_config->bss_type = bgscan_cfg_in->bss_type; + bgscan_config->scan_interval = + cpu_to_le32(bgscan_cfg_in->scan_interval); + bgscan_config->report_condition = + cpu_to_le32(bgscan_cfg_in->report_condition); + + /* stop sched scan */ + if (!bgscan_config->enable) + return 0; + + bgscan_config->chan_per_scan = bgscan_cfg_in->chan_per_scan; + + num_probes = (bgscan_cfg_in->num_probes ? + bgscan_cfg_in->num_probes : priv->adapter->scan_probes); + + if (num_probes) { + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (bgscan_cfg_in->repeat_count) { + repeat_count_tlv = + (struct nxpwifi_ie_types_repeat_count *)tlv_pos; + repeat_count_tlv->header.type = + cpu_to_le16(TLV_TYPE_REPEAT_COUNT); + repeat_count_tlv->header.len = + cpu_to_le16(sizeof(repeat_count_tlv->repeat_count)); + repeat_count_tlv->repeat_count = + cpu_to_le16(bgscan_cfg_in->repeat_count); + + tlv_pos += sizeof(repeat_count_tlv->header) + + le16_to_cpu(repeat_count_tlv->header.len); + } + + if (bgscan_cfg_in->rssi_threshold) { + rssi_threshold_tlv = + (struct nxpwifi_ie_types_min_rssi_threshold *)tlv_pos; + rssi_threshold_tlv->header.type = + cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_threshold_tlv->header.len = + cpu_to_le16(sizeof(rssi_threshold_tlv->rssi_threshold)); + rssi_threshold_tlv->rssi_threshold = + cpu_to_le16(bgscan_cfg_in->rssi_threshold); + + tlv_pos += sizeof(rssi_threshold_tlv->header) + + le16_to_cpu(rssi_threshold_tlv->header.len); + } + + for (i = 0; i < bgscan_cfg_in->num_ssids; i++) { + ssid_len = bgscan_cfg_in->ssid_list[i].ssid.ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *)tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to scan only for the given + * SSID. max_ssid_length = IEEE80211_MAX_SSID_LEN triggers a + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + memcpy(wildcard_ssid_tlv->ssid, + bgscan_cfg_in->ssid_list[i].ssid.ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + } + + tlv_l = (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (bgscan_cfg_in->chan_list[0].chan_number) { + nxpwifi_dbg(priv->adapter, INFO, "info: bgscan: Using supplied channel list\n"); + + tlv_l->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + for (chan_idx = 0; + chan_idx < NXPWIFI_BG_SCAN_CHAN_MAX && + bgscan_cfg_in->chan_list[chan_idx].chan_number; + chan_idx++) { + temp_chan = &tlv_l->chan_scan_param[chan_idx]; + + /* Increment the TLV header length by size appended */ + le16_unaligned_add_cpu(&tlv_l->header.len, + sizeof(*tlv_l->chan_scan_param)); + + temp_chan->chan_number = + bgscan_cfg_in->chan_list[chan_idx].chan_number; + temp_chan->band_cfg = + bgscan_cfg_in->chan_list[chan_idx].radio_type; + + scan_type = + bgscan_cfg_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + temp_chan->chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + temp_chan->chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_time = bgscan_cfg_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + scan_dur = (scan_type == + NXPWIFI_SCAN_TYPE_PASSIVE) ? + priv->adapter->passive_scan_time : + priv->adapter->specific_scan_time; + } + + temp_chan->min_scan_time = cpu_to_le16(scan_dur); + temp_chan->max_scan_time = cpu_to_le16(scan_dur); + } + } else { + nxpwifi_dbg(priv->adapter, INFO, + "info: bgscan: Creating full region channel list\n"); + chan_num = + nxpwifi_bgscan_create_channel_list + (priv, bgscan_cfg_in, + tlv_l->chan_scan_param); + le16_unaligned_add_cpu(&tlv_l->header.len, + chan_num * + sizeof(*tlv_l->chan_scan_param)); + } + + tlv_pos += (sizeof(tlv_l->header) + + le16_to_cpu(tlv_l->header.len)); + + if (bgscan_cfg_in->start_later) { + start_later_tlv = + (struct nxpwifi_ie_types_bgscan_start_later *)tlv_pos; + start_later_tlv->header.type = + cpu_to_le16(TLV_TYPE_BGSCAN_START_LATER); + start_later_tlv->header.len = + cpu_to_le16(sizeof(start_later_tlv->start_later)); + start_later_tlv->start_later = + cpu_to_le16(bgscan_cfg_in->start_later); + + tlv_pos += sizeof(start_later_tlv->header) + + le16_to_cpu(start_later_tlv->header.len); + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_BGSCAN, &tlv_pos); + + le16_unaligned_add_cpu(&cmd->size, tlv_pos - bgscan_config->tlv); + + return 0; +} + +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + int ret; + + if (!priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, MSG, "bgscan already stopped!\n"); + return 0; + } + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = false; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, + HOST_ACT_GEN_SET, 0, bgscan_cfg, true); + if (!ret) + priv->sched_scanning = false; + + kfree(bgscan_cfg); + return ret; +} + +static void +nxpwifi_update_chan_statistics(struct nxpwifi_private *priv, + struct nxpwifi_ietypes_chanstats *tlv_stat) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 i, num_chan; + struct nxpwifi_fw_chan_stats *fw_chan_stats; + struct nxpwifi_chan_stats chan_stats; + + fw_chan_stats = (void *)((u8 *)tlv_stat + + sizeof(struct nxpwifi_ie_types_header)); + num_chan = le16_to_cpu(tlv_stat->header.len) / + sizeof(struct nxpwifi_chan_stats); + + for (i = 0 ; i < num_chan; i++) { + if (adapter->survey_idx >= adapter->num_in_chan_stats) { + nxpwifi_dbg(adapter, WARN, + "FW reported too many channel results (max %d)\n", + adapter->num_in_chan_stats); + return; + } + chan_stats.chan_num = fw_chan_stats->chan_num; + chan_stats.bandcfg = fw_chan_stats->bandcfg; + chan_stats.flags = fw_chan_stats->flags; + chan_stats.noise = fw_chan_stats->noise; + chan_stats.total_bss = le16_to_cpu(fw_chan_stats->total_bss); + chan_stats.cca_scan_dur = + le16_to_cpu(fw_chan_stats->cca_scan_dur); + chan_stats.cca_busy_dur = + le16_to_cpu(fw_chan_stats->cca_busy_dur); + nxpwifi_dbg(adapter, INFO, + "chan=%d, noise=%d, total_network=%d scan_duration=%d, busy_duration=%d\n", + chan_stats.chan_num, + chan_stats.noise, + chan_stats.total_bss, + chan_stats.cca_scan_dur, + chan_stats.cca_busy_dur); + memcpy(&adapter->chan_stats[adapter->survey_idx++], &chan_stats, + sizeof(struct nxpwifi_chan_stats)); + fw_chan_stats++; + } +} + +/* Handle the extended scan command response. */ +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_ext *ext_scan_resp; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ietypes_chanstats *tlv_stat; + u16 buf_left, type, len; + + struct host_cmd_ds_command *cmd_ptr; + struct cmd_ctrl_node *cmd_node; + bool complete_scan = false; + + nxpwifi_dbg(adapter, INFO, "info: EXT scan returns successfully\n"); + + ext_scan_resp = &resp->params.ext_scan; + + tlv = (void *)ext_scan_resp->tlv_buffer; + buf_left = le16_to_cpu(resp->size) - (sizeof(*ext_scan_resp) + S_DS_GEN); + + while (buf_left >= sizeof(struct nxpwifi_ie_types_header)) { + type = le16_to_cpu(tlv->type); + len = le16_to_cpu(tlv->len); + + if (buf_left < (sizeof(struct nxpwifi_ie_types_header) + len)) { + nxpwifi_dbg(adapter, ERROR, + "error processing scan response TLVs"); + break; + } + + switch (type) { + case TLV_TYPE_CHANNEL_STATS: + tlv_stat = (void *)tlv; + nxpwifi_update_chan_statistics(priv, tlv_stat); + break; + default: + break; + } + + buf_left -= len + sizeof(struct nxpwifi_ie_types_header); + tlv = (void *)((u8 *)tlv + len + + sizeof(struct nxpwifi_ie_types_header)); + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + complete_scan = true; + list_for_each_entry(cmd_node, &adapter->cmd_pending_q, list) { + cmd_ptr = (void *)cmd_node->cmd_skb->data; + if (le16_to_cpu(cmd_ptr->command) == + HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_dbg(adapter, INFO, + "Scan pending in command pending list"); + complete_scan = false; + break; + } + } + } + spin_unlock_bh(&adapter->scan_pending_q_lock); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + if (complete_scan) + nxpwifi_complete_scan(priv); + + return 0; +} + +/* + * Handle the extended scan report event: parse the results and notify + * cfg80211. + */ +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *bss_info; + u32 bytes_left, bytes_left_for_tlv, idx; + u16 type, len; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_ie_types_scan_rsp *scan_rsp_tlv; + struct nxpwifi_ie_types_scan_inf *scan_info_tlv; + u8 *radio_type; + u64 fw_tsf = 0; + s32 rssi = 0; + struct nxpwifi_event_scan_result *event_scan = buf; + u8 num_of_set = event_scan->num_of_set; + u8 *scan_resp = buf + sizeof(struct nxpwifi_event_scan_result); + u16 scan_resp_size = le16_to_cpu(event_scan->buf_size); + + if (num_of_set > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Invalid number of AP returned (%d)!!\n", + num_of_set); + ret = -EINVAL; + goto check_next_scan; + } + + bytes_left = scan_resp_size; + nxpwifi_dbg(adapter, INFO, + "EXT_SCAN: size %d, returned %d APs...", + scan_resp_size, num_of_set); + nxpwifi_dbg_dump(adapter, CMD_D, "EXT_SCAN buffer:", buf, + scan_resp_size + + sizeof(struct nxpwifi_event_scan_result)); + + tlv = (struct nxpwifi_ie_types_data *)scan_resp; + + for (idx = 0; idx < num_of_set && bytes_left; idx++) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left < sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error bytes left < TLV length\n"); + break; + } + scan_rsp_tlv = NULL; + scan_info_tlv = NULL; + bytes_left_for_tlv = bytes_left; + + /* + * BSS response TLV with beacon or probe response buffer + * at the initial position of each descriptor + */ + if (type != TLV_TYPE_BSS_SCAN_RSP) + break; + + bss_info = (u8 *)tlv; + scan_rsp_tlv = (struct nxpwifi_ie_types_scan_rsp *)tlv; + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + + while (bytes_left_for_tlv >= + sizeof(struct nxpwifi_ie_types_header) && + le16_to_cpu(tlv->header.type) != TLV_TYPE_BSS_SCAN_RSP) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left_for_tlv < + sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error in processing TLV,\t" + "bytes left < TLV length\n"); + scan_rsp_tlv = NULL; + bytes_left_for_tlv = 0; + continue; + } + switch (type) { + case TLV_TYPE_BSS_SCAN_INFO: + scan_info_tlv = + (struct nxpwifi_ie_types_scan_inf *)tlv; + if (len != + sizeof(struct nxpwifi_ie_types_scan_inf) - + sizeof(struct nxpwifi_ie_types_header)) { + bytes_left_for_tlv = 0; + continue; + } + break; + default: + break; + } + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left -= + (len + sizeof(struct nxpwifi_ie_types_header)); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + } + + if (!scan_rsp_tlv) + break; + + /* + * Advance pointer to the beacon buffer length and + * update the bytes count so that the function + * wlan_interpret_bss_desc_with_ie() can handle the + * scan buffer withut any change + */ + bss_info += sizeof(u16); + bytes_left -= sizeof(u16); + + if (scan_info_tlv) { + rssi = (s32)(s16)(le16_to_cpu(scan_info_tlv->rssi)); + rssi *= 100; /* Convert dBm to mBm */ + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + fw_tsf = le64_to_cpu(scan_info_tlv->tsf); + radio_type = &scan_info_tlv->radio_type; + } else { + radio_type = NULL; + } + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, fw_tsf, + radio_type, true, rssi); + if (ret) + goto check_next_scan; + } + +check_next_scan: + if (!event_scan->more_event) + nxpwifi_check_next_scan_command(priv); + + return ret; +} + +/* + * Prepare the background scan query command. Sets the command ID, size, + * flush parameter, and fixes endianness. + */ +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd) +{ + struct host_cmd_ds_802_11_bg_scan_query *bg_query = + &cmd->params.bg_scan_query; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_bg_scan_query) + + S_DS_GEN); + + bg_query->flush = 1; + + return 0; +} + +/* Insert a scan command node into the scan_pending_q. */ +void +nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + cmd_node->wait_q_enabled = true; + cmd_node->condition = &adapter->scan_wait_q_woken; + spin_lock_bh(&adapter->scan_pending_q_lock); + list_add_tail(&cmd_node->list, &adapter->scan_pending_q); + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* Append a vendor-specific element TLV to the buffer. */ +int +nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, + u16 vsie_mask, u8 **buffer) +{ + int id, ret_len = 0; + struct nxpwifi_ie_types_vendor_param_set *vs_param_set; + + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * Traverse through the saved vendor specific element array and append + * the selected(scan/assoc) element as TLV to the command + */ + for (id = 0; id < NXPWIFI_MAX_VSIE_NUM; id++) { + if (priv->vs_ie[id].mask & vsie_mask) { + vs_param_set = + (struct nxpwifi_ie_types_vendor_param_set *) + *buffer; + vs_param_set->header.type = + cpu_to_le16(TLV_TYPE_PASSTHROUGH); + vs_param_set->header.len = + cpu_to_le16((((u16)priv->vs_ie[id].ie[1]) + & 0x00FF) + 2); + if (le16_to_cpu(vs_param_set->header.len) > + NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Invalid param length!\n"); + break; + } + + memcpy(vs_param_set->ie, priv->vs_ie[id].ie, + le16_to_cpu(vs_param_set->header.len)); + *buffer += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + ret_len += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + return ret_len; +} + +/* + * Save the beacon buffer of the current BSS descriptor. + * + * The buffer is preserved so it can be restored when the current SSID's + * beacon is missing, such as when: + * - the SSID was not found in the latest scan, or + * - the SSID was the last entry in the scan table and was overwritten. + */ +void +nxpwifi_save_curr_bcn(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *curr_bss = + &priv->curr_bss_params.bss_descriptor; + + if (!curr_bss->beacon_buf_size) + return; + + /* allocate beacon buffer at 1st time; or if it's size has changed */ + if (!priv->curr_bcn_buf || + priv->curr_bcn_size != curr_bss->beacon_buf_size) { + priv->curr_bcn_size = curr_bss->beacon_buf_size; + + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = kmalloc(curr_bss->beacon_buf_size, + GFP_ATOMIC); + if (!priv->curr_bcn_buf) + return; + } + + memcpy(priv->curr_bcn_buf, curr_bss->beacon_buf, + curr_bss->beacon_buf_size); + nxpwifi_dbg(priv->adapter, INFO, + "info: current beacon saved %d\n", + priv->curr_bcn_size); + + curr_bss->beacon_buf = priv->curr_bcn_buf; + + /* adjust the pointers in the current BSS descriptor */ + if (curr_bss->bcn_wpa_ie) + curr_bss->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + (curr_bss->beacon_buf + + curr_bss->wpa_offset); + + if (curr_bss->bcn_rsn_ie) + curr_bss->bcn_rsn_ie = + (struct element *)(curr_bss->beacon_buf + + curr_bss->rsn_offset); + + if (curr_bss->bcn_ht_cap) + curr_bss->bcn_ht_cap = (struct ieee80211_ht_cap *) + (curr_bss->beacon_buf + + curr_bss->ht_cap_offset); + + if (curr_bss->bcn_ht_oper) + curr_bss->bcn_ht_oper = (struct ieee80211_ht_operation *) + (curr_bss->beacon_buf + + curr_bss->ht_info_offset); + + if (curr_bss->bcn_vht_cap) + curr_bss->bcn_vht_cap = (void *)(curr_bss->beacon_buf + + curr_bss->vht_cap_offset); + + if (curr_bss->bcn_vht_oper) + curr_bss->bcn_vht_oper = (void *)(curr_bss->beacon_buf + + curr_bss->vht_info_offset); + + if (curr_bss->bcn_he_cap) + curr_bss->bcn_he_cap = (void *)(curr_bss->beacon_buf + + curr_bss->he_cap_offset); + + if (curr_bss->bcn_he_oper) + curr_bss->bcn_he_oper = (void *)(curr_bss->beacon_buf + + curr_bss->he_info_offset); + + if (curr_bss->bcn_bss_co_2040) + curr_bss->bcn_bss_co_2040 = + (curr_bss->beacon_buf + curr_bss->bss_co_2040_offset); + + if (curr_bss->bcn_ext_cap) + curr_bss->bcn_ext_cap = curr_bss->beacon_buf + + curr_bss->ext_cap_offset; + + if (curr_bss->oper_mode) + curr_bss->oper_mode = (void *)(curr_bss->beacon_buf + + curr_bss->oper_mode_offset); +} + +/* Free the beacon buffer in the current BSS descriptor. */ +void +nxpwifi_free_curr_bcn(struct nxpwifi_private *priv) +{ + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = NULL; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.c b/drivers/net/wireless/nxp/nxpwifi/sdio.c new file mode 100644 index 000000000000..8ef0f6eb49e2 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.c @@ -0,0 +1,2328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: SDIO specific handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "sdio.h" + +#define SDIO_VERSION "1.0" + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work); + +static struct nxpwifi_if_ops sdio_ops; + +static const struct nxpwifi_sdio_card_reg nxpwifi_reg_iw61x = { + .start_rd_port = 0, + .start_wr_port = 0, + .base_0_reg = 0xF8, + .base_1_reg = 0xF9, + .poll_reg = 0x5C, + .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK | + CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK, + .host_int_rsr_reg = 0x4, + .host_int_status_reg = 0x0C, + .host_int_mask_reg = 0x08, + .host_strap_reg = 0xF4, + .host_strap_mask = 0x01, + .host_strap_value = 0x00, + .status_reg_0 = 0xE8, + .status_reg_1 = 0xE9, + .sdio_int_mask = 0xff, + .data_port_mask = 0xffffffff, + .io_port_0_reg = 0xE4, + .io_port_1_reg = 0xE5, + .io_port_2_reg = 0xE6, + .max_mp_regs = 196, + .rd_bitmap_l = 0x10, + .rd_bitmap_u = 0x11, + .rd_bitmap_1l = 0x12, + .rd_bitmap_1u = 0x13, + .wr_bitmap_l = 0x14, + .wr_bitmap_u = 0x15, + .wr_bitmap_1l = 0x16, + .wr_bitmap_1u = 0x17, + .rd_len_p0_l = 0x18, + .rd_len_p0_u = 0x19, + .card_misc_cfg_reg = 0xd8, + .card_cfg_2_1_reg = 0xd9, + .cmd_rd_len_0 = 0xc0, + .cmd_rd_len_1 = 0xc1, + .cmd_rd_len_2 = 0xc2, + .cmd_rd_len_3 = 0xc3, + .cmd_cfg_0 = 0xc4, + .cmd_cfg_1 = 0xc5, + .cmd_cfg_2 = 0xc6, + .cmd_cfg_3 = 0xc7, + .fw_dump_host_ready = 0xcc, + .fw_dump_ctrl = 0xf9, + .fw_dump_start = 0xf1, + .fw_dump_end = 0xf8, + .func1_dump_reg_start = 0x10, + .func1_dump_reg_end = 0x17, + .func1_scratch_reg = 0xE8, + .func1_spec_reg_num = 13, + .func1_spec_reg_table = {0x08, 0x58, 0x5C, 0x5D, 0x60, + 0x61, 0x62, 0x64, 0x65, 0x66, + 0x68, 0x69, 0x6a}, +}; + +static const struct nxpwifi_sdio_device nxpwifi_sdio_iw61x = { + .firmware = IW61X_SDIO_FW_NAME, + .reg = &nxpwifi_reg_iw61x, + .max_ports = 32, + .mp_agg_pkt_limit = 16, + .tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_4K, + .mp_tx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .mp_rx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .can_dump_fw = true, + .fw_dump_enh = true, + .can_ext_scan = true, +}; + +static struct memory_type_mapping generic_mem_type_map[] = { + {"DUMP", NULL, 0, 0xDD}, +}; + +static struct memory_type_mapping mem_type_mapping_tbl[] = { + {"ITCM", NULL, 0, 0xF0}, + {"DTCM", NULL, 0, 0xF1}, + {"SQRAM", NULL, 0, 0xF2}, + {"APU", NULL, 0, 0xF3}, + {"CIU", NULL, 0, 0xF4}, + {"ICU", NULL, 0, 0xF5}, + {"MAC", NULL, 0, 0xF6}, + {"EXT7", NULL, 0, 0xF7}, + {"EXT8", NULL, 0, 0xF8}, + {"EXT9", NULL, 0, 0xF9}, + {"EXT10", NULL, 0, 0xFA}, + {"EXT11", NULL, 0, 0xFB}, + {"EXT12", NULL, 0, 0xFC}, + {"EXT13", NULL, 0, 0xFD}, + {"EXTLAST", NULL, 0, 0xFE}, +}; + +/* Bind the SDIO function and start device registration. */ +static int +nxpwifi_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) +{ + int ret; + struct sdio_mmc_card *card = NULL; + + card = devm_kzalloc(&func->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + init_completion(&card->fw_done); + + card->func = func; + + if (id->driver_data) { + struct nxpwifi_sdio_device *data = (void *)id->driver_data; + + card->firmware = data->firmware; + card->firmware_sdiouart = data->firmware_sdiouart; + card->reg = data->reg; + card->max_ports = data->max_ports; + card->mp_agg_pkt_limit = data->mp_agg_pkt_limit; + card->tx_buf_size = data->tx_buf_size; + card->mp_tx_agg_buf_size = data->mp_tx_agg_buf_size; + card->mp_rx_agg_buf_size = data->mp_rx_agg_buf_size; + card->can_dump_fw = data->can_dump_fw; + card->fw_dump_enh = data->fw_dump_enh; + card->can_ext_scan = data->can_ext_scan; + INIT_WORK(&card->work, nxpwifi_sdio_work); + } + + sdio_claim_host(func); + ret = sdio_enable_func(func); + sdio_release_host(func); + + if (ret) { + dev_err(&func->dev, "failed to enable function\n"); + return ret; + } + + ret = nxpwifi_add_card(card, &card->fw_done, &sdio_ops, + NXPWIFI_SDIO, &func->dev); + if (ret) { + dev_err(&func->dev, "add card failed\n"); + goto err_disable; + } + + return 0; + +err_disable: + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + return ret; +} + +/* Resume the SDIO function and cancel host sleep. */ +static int nxpwifi_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + + card = sdio_get_drvdata(func); + + if (unlikely(!card || !card->adapter)) { + dev_dbg(dev, "resume: %s not ready\n", !card ? "card" : "adapter"); + return -ENODEV; + } + + adapter = card->adapter; + + if (!test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) + return 0; + + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + + /* Disable Host Sleep */ + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_SYNC_CMD); + + return 0; +} + +static int +nxpwifi_write_reg_locked(struct sdio_func *func, u32 reg, u8 data) +{ + int ret; + + sdio_writeb(func, data, reg, &ret); + return ret; +} + +static int +nxpwifi_write_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_write_reg_locked(card->func, reg, data); + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_read_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 *data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 val; + + sdio_claim_host(card->func); + val = sdio_readb(card->func, reg, &ret); + sdio_release_host(card->func); + + *data = val; + + return ret; +} + +static int +nxpwifi_write_data_sync(struct nxpwifi_adapter *adapter, + u8 *buffer, u32 pkt_len, u32 port) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = + (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = + (blk_mode == + BLOCK_MODE) ? (pkt_len / + NXPWIFI_SDIO_BLOCK_SIZE) : pkt_len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: not allowed while suspended\n", __func__); + return -EPERM; + } + + sdio_claim_host(card->func); + + ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size); + + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_read_data_sync(struct nxpwifi_adapter *adapter, u8 *buffer, + u32 len, u32 port, u8 claim) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE + : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / NXPWIFI_SDIO_BLOCK_SIZE) + : len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (claim) + sdio_claim_host(card->func); + + ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size); + + if (claim) + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_sdio_read_fw_status(struct nxpwifi_adapter *adapter, u16 *dat) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u8 fws0, fws1; + int ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_0, &fws0); + if (ret) + return ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_1, &fws1); + if (ret) + return ret; + + *dat = (u16)((fws1 << 8) | fws0); + return ret; +} + +static int nxpwifi_check_fw_status(struct nxpwifi_adapter *adapter, + u32 poll_num) +{ + int ret = 0; + u16 firmware_stat = 0; + + unsigned int timeout_us = poll_num * 100000; /* 100 ms * poll_num */ + /* + * Poll every 100 ms until firmware reports FIRMWARE_READY_SDIO. + * On timeout, read_poll_timeout() returns -ETIMEDOUT. + */ + ret = read_poll_timeout(nxpwifi_sdio_read_fw_status, ret, + (!ret && firmware_stat == FIRMWARE_READY_SDIO), + 100000, timeout_us, true, /* sleep */ + adapter, &firmware_stat); + + /* FW may appear ready; wait a bit to avoid early races. */ + if (firmware_stat == FIRMWARE_READY_SDIO) + msleep(100); + + return ret; +} + +static int nxpwifi_check_winner_status(struct nxpwifi_adapter *adapter) +{ + int ret; + u8 winner = 0; + struct sdio_mmc_card *card = adapter->card; + + ret = nxpwifi_read_reg(adapter, card->reg->status_reg_0, &winner); + if (ret) + return ret; + + if (winner) + adapter->winner = 0; + else + adapter->winner = 1; + + return ret; +} + +/* Remove the SDIO function and tear down the adapter. */ +static void +nxpwifi_sdio_remove(struct sdio_func *func) +{ + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + struct nxpwifi_private *priv; + int ret = 0; + u16 firmware_stat; + + card = sdio_get_drvdata(func); + if (!card) + return; + + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter || !adapter->priv_num) + return; + + ret = nxpwifi_sdio_read_fw_status(adapter, &firmware_stat); + if (!ret && firmware_stat == FIRMWARE_READY_SDIO) { + nxpwifi_deauthenticate_all(adapter); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_disable_auto_ds(priv); + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + } + + nxpwifi_remove_card(adapter); +} + +/* Suspend the SDIO function while keeping SDIO power. */ +static int nxpwifi_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + mmc_pm_flag_t caps; + unsigned long flags = 0; + int ret = 0; + + caps = sdio_get_host_pm_caps(func); + + if (!(caps & MMC_PM_KEEP_POWER)) { + /* host lacks keep-power capability */ + dev_warn(dev, "suspend: host does not support MMC_PM_KEEP_POWER\n"); + return -EOPNOTSUPP; + } + + card = sdio_get_drvdata(func); + + if (!card) { + dev_warn(dev, "suspend: card not ready\n"); + return -ENODEV; + } + + /* Might still be loading firmware */ + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter) { + dev_warn(dev, "suspend: adapter not ready\n"); + return -ENODEV; + } + + /* Enable the Host Sleep */ + if (!nxpwifi_enable_hs(adapter)) { + nxpwifi_dbg(adapter, ERROR, "suspend: enable host sleep failed\n"); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + return -ETIMEDOUT; + } + + flags |= MMC_PM_KEEP_POWER; + + if (adapter->wowlan_enabled && (caps & MMC_PM_WAKE_SDIO_IRQ)) + flags |= MMC_PM_WAKE_SDIO_IRQ; + + ret = sdio_set_host_pm_flags(func, flags); + + /* Indicate device suspended */ + set_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + + return ret; +} + +static void nxpwifi_sdio_coredump(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(card->adapter, &card->work); +} + +/* WLAN IDs */ +static const struct sdio_device_id nxpwifi_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X), + .driver_data = (unsigned long)&nxpwifi_sdio_iw61x}, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, nxpwifi_ids); + +static const struct dev_pm_ops nxpwifi_sdio_pm_ops = { + .suspend = nxpwifi_sdio_suspend, + .resume = nxpwifi_sdio_resume, +}; + +static struct sdio_driver nxpwifi_sdio = { + .name = "nxpwifi_sdio", + .id_table = nxpwifi_ids, + .probe = nxpwifi_sdio_probe, + .remove = nxpwifi_sdio_remove, + .drv = { + .coredump = nxpwifi_sdio_coredump, + .pm = &nxpwifi_sdio_pm_ops, + } +}; + +static int nxpwifi_pm_wakeup_card(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "event: wakeup device...\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, HOST_POWER_UP); +} + +static int nxpwifi_pm_wakeup_card_complete(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "cmd: wakeup device completed\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0); +} + +/* SDIO wrapper for firmware download (claims host). */ +static int nxpwifi_sdio_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_dnld_fw(adapter, fw); + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_init_sdio_new_mode(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + adapter->ioport = MEM_PORT; + + /* enable sdio new mode */ + ret = nxpwifi_read_reg(adapter, card->reg->card_cfg_2_1_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_cfg_2_1_reg, + reg | CMD53_NEW_MODE); + if (ret) + return ret; + + /* Configure cmd port and enable reading rx length from the register */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_0, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_0, + reg | CMD_PORT_RD_LEN_EN); + if (ret) + return ret; + + /* + * Enable Dnld/Upld ready auto reset for cmd port after cmd53 is + * completed + */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_1, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_1, + reg | CMD_PORT_AUTO_EN); + + return ret; +} + +/* Initialize SDIO IO ports and host-int behavior. */ +static int nxpwifi_init_sdio_ioport(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + ret = nxpwifi_init_sdio_new_mode(adapter); + if (ret) + return ret; + + /* Set Host interrupt reset to read to clear */ + ret = nxpwifi_read_reg(adapter, card->reg->host_int_rsr_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->host_int_rsr_reg, + reg | card->reg->sdio_int_mask); + if (ret) + return ret; + + /* Dnld/Upld ready set to auto reset */ + ret = nxpwifi_read_reg(adapter, card->reg->card_misc_cfg_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_misc_cfg_reg, + reg | AUTO_RE_ENABLE_INT); + + return ret; +} + +static int nxpwifi_write_data_to_card(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port) +{ + u32 i = 0; + int ret; + + do { + ret = nxpwifi_write_data_sync(adapter, payload, pkt_len, port); + if (ret) { + i++; + nxpwifi_dbg(adapter, ERROR, "host_to_card, write iomem\t" + "(%d) failed: %d\n", i, ret); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + if (i > MAX_WRITE_IOMEM_RETRY) + return ret; + } + } while (ret); + + return ret; +} + +static int nxpwifi_get_rd_port(struct nxpwifi_adapter *adapter, u8 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 rd_bitmap = card->mp_rd_bitmap; + + if (!(rd_bitmap & reg->data_port_mask)) + return -EINVAL; + + if (!(card->mp_rd_bitmap & (1 << card->curr_rd_port))) + return -EINVAL; + + /* We are now handling the SDIO data ports */ + card->mp_rd_bitmap &= (u32)(~(1 << card->curr_rd_port)); + *port = card->curr_rd_port; + + if (++card->curr_rd_port == card->max_ports) + card->curr_rd_port = reg->start_rd_port; + + return 0; +} + +static int nxpwifi_get_wr_port_data(struct nxpwifi_adapter *adapter, u32 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 wr_bitmap = card->mp_wr_bitmap; + + if (!(wr_bitmap & card->mp_data_port_mask)) { + adapter->data_sent = true; + return -EBUSY; + } + + if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) { + card->mp_wr_bitmap &= (u32)(~(1 << card->curr_wr_port)); + *port = card->curr_wr_port; + if (++card->curr_wr_port == card->mp_end_port) + card->curr_wr_port = reg->start_wr_port; + } else { + adapter->data_sent = true; + return -EBUSY; + } + + return 0; +} + +static int +nxpwifi_sdio_poll_card_status(struct nxpwifi_adapter *adapter, u8 bits) +{ + struct sdio_mmc_card *card = adapter->card; + u32 tries; + u8 cs; + int ret; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, card->reg->poll_reg, &cs); + if (ret) + break; + else if ((cs & bits) == bits) + return 0; + + usleep_range(10, 20); + } + + nxpwifi_dbg(adapter, ERROR, "poll card status failed, tries = %d\n", tries); + + return ret; +} + +/* Disable SDIO host interrupt and release IRQ. */ +static void nxpwifi_sdio_disable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + + sdio_claim_host(func); + nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, 0); + sdio_release_irq(func); + sdio_release_host(func); +} + +static void nxpwifi_interrupt_status(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + unsigned long flags; + + if (nxpwifi_read_data_sync(adapter, card->mp_regs, + card->reg->max_mp_regs, + REG_PORT | NXPWIFI_SDIO_BYTE_MODE_MASK, 0)) { + nxpwifi_dbg(adapter, ERROR, "read mp_regs failed\n"); + return; + } + + sdio_ireg = card->mp_regs[card->reg->host_int_status_reg]; + if (sdio_ireg) { + nxpwifi_dbg(adapter, INTR, "intr: sdio_ireg = %#x\n", sdio_ireg); + spin_lock_irqsave(&adapter->int_lock, flags); + adapter->int_status |= sdio_ireg; + spin_unlock_irqrestore(&adapter->int_lock, flags); + } +} + +/* SDIO IRQ handler: snapshot status and schedule main work. */ +static void +nxpwifi_sdio_interrupt(struct sdio_func *func) +{ + struct nxpwifi_adapter *adapter; + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + + if (!card || !card->adapter) { + /* device-scoped error logging (rate-limited to avoid flood) */ + dev_err_ratelimited(&func->dev, "interrupt: missing card/adapter\n"); + return; + } + + adapter = card->adapter; + + if (!adapter->pps_uapsd_mode && adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + + nxpwifi_interrupt_status(adapter); + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* Enable SDIO host interrupt and claim IRQ. */ +static int nxpwifi_sdio_enable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + sdio_claim_host(func); + + /* Request the SDIO IRQ */ + ret = sdio_claim_irq(func, nxpwifi_sdio_interrupt); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "claim irq failed: ret=%d\n", ret); + goto done; + } + + /* Simply write the mask to the register */ + ret = nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, + card->reg->host_int_enable); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "enable host interrupt failed\n"); + sdio_release_irq(func); + } + +done: + sdio_release_host(func); + return ret; +} + +static int nxpwifi_sdio_card_to_host(struct nxpwifi_adapter *adapter, + u32 *type, u8 *buffer, + u32 npayload, u32 ioport) +{ + int ret; + u32 nb; + + if (!buffer) + return -EINVAL; + + ret = nxpwifi_read_data_sync(adapter, buffer, npayload, ioport, 1); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "read iomem failed (ioport=%#x, len=%u): %d", + ioport, npayload, ret); + + return ret; + } + + nb = get_unaligned_le16((buffer)); + if (nb > npayload) { + nxpwifi_dbg(adapter, ERROR, + "invalid packet len: nb=%u > npayload=%u (ioport=%#x)", + nb, npayload, ioport); + return -EINVAL; + } + + *type = get_unaligned_le16((buffer + 2)); + + return ret; +} + +/* Download firmware using the helper protocol. */ +static int nxpwifi_prog_fw_w_helper(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 *firmware = fw->fw_buf; + u32 firmware_len = fw->fw_len; + u32 offset = 0; + u8 base0, base1; + u8 *fwbuf; + u16 len = 0; + u32 txlen, tx_blocks = 0, tries; + u32 i = 0; + + if (!firmware_len) { + nxpwifi_dbg(adapter, ERROR, + "firmware image not found! Terminating download\n"); + return -EINVAL; + } + + /* Assume that the allocated buffer is 8-byte aligned */ + fwbuf = kzalloc(NXPWIFI_UPLD_SIZE, GFP_KERNEL); + if (!fwbuf) + return -ENOMEM; + + sdio_claim_host(card->func); + + /* Perform firmware data transfer */ + do { + /* + * The host polls for the DN_LD_CARD_RDY and CARD_IO_READY + * bits + */ + ret = nxpwifi_sdio_poll_card_status(adapter, CARD_IO_READY | + DN_LD_CARD_RDY); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download with helper:\t" + "poll status timeout @ %d\n", offset); + goto done; + } + + /* More data? */ + if (offset >= firmware_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, reg->base_0_reg, + &base0); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE0 register read failed:\t" + "base0=%#04X(%d). Terminating dnld\n", + base0, base0); + goto done; + } + ret = nxpwifi_read_reg(adapter, reg->base_1_reg, + &base1); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE1 register read failed:\t" + "base1=%#04X(%d). Terminating dnld\n", + base1, base1); + goto done; + } + len = (u16)(((base1 & 0xff) << 8) | (base0 & 0xff)); + + if (len) + break; + + usleep_range(10, 20); + } + + if (!len) { + break; + } else if (len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, invalid length %d\n", + offset, len); + ret = -EINVAL; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + i++; + if (i > MAX_WRITE_IOMEM_RETRY) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, over max retry\n", + offset); + ret = -EIO; + goto done; + } + nxpwifi_dbg(adapter, ERROR, + "CRC indicated by the helper:\t" + "len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + i = 0; + + /* + * Set blocksize to transfer - checking for last + * block + */ + if (firmware_len - offset < txlen) + txlen = firmware_len - offset; + + tx_blocks = (txlen + NXPWIFI_SDIO_BLOCK_SIZE - 1) + / NXPWIFI_SDIO_BLOCK_SIZE; + + /* Copy payload to buffer */ + memcpy(fwbuf, &firmware[offset], txlen); + } + + ret = nxpwifi_write_data_sync(adapter, fwbuf, tx_blocks * + NXPWIFI_SDIO_BLOCK_SIZE, + adapter->ioport); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download, write iomem (%d) failed @ %d\n", + i, offset); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + goto done; + } + + offset += txlen; + } while (true); + + nxpwifi_dbg(adapter, MSG, "FW download complete (%u bytes)\n", offset); + + ret = 0; +done: + sdio_release_host(card->func); + kfree(fwbuf); + return ret; +} + +/* Deaggregate an SDIO RX aggregation packet. */ +static void nxpwifi_deaggr_sdio_pkt(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + u32 total_pkt_len, pkt_len; + struct sk_buff *skb_deaggr; + u16 blk_size; + u8 blk_num; + u8 *data; + + data = skb->data; + total_pkt_len = skb->len; + + while (total_pkt_len >= (SDIO_HEADER_OFFSET + adapter->intf_hdr_len)) { + if (total_pkt_len < adapter->sdio_rx_block_size) + break; + blk_num = *(data + BLOCK_NUMBER_OFFSET); + blk_size = adapter->sdio_rx_block_size * blk_num; + if (blk_size > total_pkt_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in blk_size,\t" + "blk_num=%d, blk_size=%d, total_pkt_len=%d\n", + __func__, blk_num, blk_size, total_pkt_len); + break; + } + pkt_len = get_unaligned_le16((data + + SDIO_HEADER_OFFSET)); + if ((pkt_len + SDIO_HEADER_OFFSET) > blk_size) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in pkt_len,\t" + "pkt_len=%d, blk_size=%d\n", + __func__, pkt_len, blk_size); + break; + } + + skb_deaggr = nxpwifi_alloc_dma_align_buf(pkt_len, GFP_KERNEL); + if (!skb_deaggr) + break; + skb_put(skb_deaggr, pkt_len); + memcpy(skb_deaggr->data, data + SDIO_HEADER_OFFSET, pkt_len); + skb_pull(skb_deaggr, adapter->intf_hdr_len); + + nxpwifi_handle_rx_packet(adapter, skb_deaggr); + data += blk_size; + total_pkt_len -= blk_size; + } +} + +static void nxpwifi_decode_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, u32 upld_typ) +{ + u8 *cmd_buf; + u16 pkt_len; + struct nxpwifi_rxinfo *rx_info; + + pkt_len = get_unaligned_le16(skb->data); + + if (upld_typ != NXPWIFI_TYPE_AGGR_DATA) { + skb_trim(skb, pkt_len); + skb_pull(skb, adapter->intf_hdr_len); + } + + switch (upld_typ) { + case NXPWIFI_TYPE_AGGR_DATA: + nxpwifi_dbg(adapter, DATA, + "Rx Aggr Data packet\n"); + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->buf_type = NXPWIFI_TYPE_AGGR_DATA; + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + atomic_inc(&adapter->rx_pending); + adapter->data_received = true; + } else { + /* Deaggregate an SDIO RX aggregation packet. */ + nxpwifi_deaggr_sdio_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } + break; + + case NXPWIFI_TYPE_DATA: + nxpwifi_dbg(adapter, DATA, "Rx Data packet\n"); + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + adapter->data_received = true; + atomic_inc(&adapter->rx_pending); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + break; + + case NXPWIFI_TYPE_CMD: + nxpwifi_dbg(adapter, CMD, "Rx Cmd Response\n"); + /* take care of curr_cmd = NULL case */ + if (!adapter->curr_cmd) { + cmd_buf = adapter->upld_buf; + + if (adapter->ps_state == PS_STATE_SLEEP_CFM) + nxpwifi_process_sleep_confirm_resp(adapter, + skb->data, + skb->len); + + memcpy(cmd_buf, skb->data, + min_t(u32, NXPWIFI_SIZE_OF_CMD_BUFFER, + skb->len)); + + dev_kfree_skb_any(skb); + } else { + adapter->cmd_resp_received = true; + adapter->curr_cmd->resp_skb = skb; + } + break; + + case NXPWIFI_TYPE_EVENT: + nxpwifi_dbg(adapter, EVENT, "Rx Event\n"); + adapter->event_cause = get_unaligned_le32(skb->data); + + if (skb->len > NXPWIFI_EVENT_HEADER_LEN) { + u32 body_len = min_t(u32, skb->len - NXPWIFI_EVENT_HEADER_LEN, + MAX_EVENT_SIZE); + memcpy(adapter->event_body, skb->data + NXPWIFI_EVENT_HEADER_LEN, + body_len); + } + + /* event cause has been saved to adapter->event_cause */ + adapter->event_received = true; + adapter->event_skb = skb; + + break; + + default: + nxpwifi_dbg(adapter, ERROR, "unknown upload type %#x\n", upld_typ); + dev_kfree_skb_any(skb); + break; + } +} + +/* Receive path with SDIO multi-port aggregation. */ +static int nxpwifi_sdio_card_to_host_mp_aggr(struct nxpwifi_adapter *adapter, + u16 rx_len, u8 port) +{ + struct sdio_mmc_card *card = adapter->card; + s32 f_do_rx_aggr = 0; + s32 f_do_rx_cur = 0; + s32 f_aggr_cur = 0; + s32 f_post_aggr_cur = 0; + struct sk_buff *skb_deaggr; + struct sk_buff *skb = NULL; + u32 pkt_len, pkt_type, mport, pind; + u8 *curr_ptr; + int ret = 0; + + if (!card->mpa_rx.enabled) { + nxpwifi_dbg(adapter, WARN, "rx aggregation disabled\n"); + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (card->mp_rd_bitmap & card->reg->data_port_mask) { + /* Some more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) { + f_aggr_cur = 1; + } else { + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_aggr = 1; + f_post_aggr_cur = 1; + } + } else { + /* Rx aggr not in progress */ + f_aggr_cur = 1; + } + + } else { + /* No more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + f_do_rx_aggr = 1; + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) + f_aggr_cur = 1; + else + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_cur = 1; + } else { + f_do_rx_cur = 1; + } + } + + if (f_aggr_cur) { + /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) || + mp_rx_aggr_port_limit_reached(card)) { + /* No more pkts allowed in Aggr buf, rx it */ + f_do_rx_aggr = 1; + } + } + + if (f_do_rx_aggr) { + u32 port_count; + int i; + + /* do aggr RX now */ + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_rx.ports & BIT(i)) + port_count++; + + /* + * Reading data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_rx.start_port; + + if (card->mpa_rx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_rx.start_port; + + ret = nxpwifi_read_data_sync(adapter, card->mpa_rx.buf, + card->mpa_rx.buf_len, mport, 1); + if (ret) + goto error; + + curr_ptr = card->mpa_rx.buf; + + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + u32 *len_arr = card->mpa_rx.len_arr; + + /* get curr PKT len & type */ + pkt_len = get_unaligned_le16(&curr_ptr[0]); + pkt_type = get_unaligned_le16(&curr_ptr[2]); + + /* copy pkt to deaggr buf */ + skb_deaggr = nxpwifi_alloc_dma_align_buf(len_arr[pind], + GFP_KERNEL); + if (!skb_deaggr) { + nxpwifi_dbg(adapter, ERROR, "skb allocation failure\t" + "drop pkt len=%d type=%d\n", + pkt_len, pkt_type); + curr_ptr += len_arr[pind]; + continue; + } + + skb_put(skb_deaggr, len_arr[pind]); + + if ((pkt_type == NXPWIFI_TYPE_DATA || + (pkt_type == NXPWIFI_TYPE_AGGR_DATA && + adapter->sdio_rx_aggr_enable)) && + pkt_len <= len_arr[pind]) { + memcpy(skb_deaggr->data, curr_ptr, pkt_len); + + skb_trim(skb_deaggr, pkt_len); + + nxpwifi_decode_rx_packet(adapter, skb_deaggr, + pkt_type); + } else { + nxpwifi_dbg(adapter, ERROR, + "drop wrong aggr pkt:\t" + "sdio_single_port_rx_aggr=%d\t" + "type=%d len=%d max_len=%d\n", + adapter->sdio_rx_aggr_enable, + pkt_type, pkt_len, len_arr[pind]); + dev_kfree_skb_any(skb_deaggr); + } + curr_ptr += len_arr[pind]; + } + MP_RX_AGGR_BUF_RESET(card); + } + +rx_curr_single: + if (f_do_rx_cur) { + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "single skb allocated fail,\t" + "drop pkt port=%d len=%d\n", port, rx_len); + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + card->mpa_rx.buf, + rx_len, + adapter->ioport + port); + if (ret) + goto error; + return 0; + } + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + skb->data, skb->len, + adapter->ioport + port); + if (ret) + goto error; + if (!adapter->sdio_rx_aggr_enable && + pkt_type == NXPWIFI_TYPE_AGGR_DATA) { + nxpwifi_dbg(adapter, ERROR, "drop wrong pkt type %d\t" + "current SDIO RX Aggr not enabled\n", + pkt_type); + dev_kfree_skb_any(skb); + return 0; + } + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + if (f_post_aggr_cur) /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + return 0; +error: + if (MP_RX_AGGR_IN_PROGRESS(card)) + MP_RX_AGGR_BUF_RESET(card); + + if (f_do_rx_cur && skb) /* Single transfer pending. Free curr buff also */ + dev_kfree_skb_any(skb); + + return ret; +} + +static int nxpwifi_process_int_status(struct nxpwifi_adapter *adapter, u8 sdio_ireg) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret = 0; + struct sk_buff *skb; + u8 port; + u32 len_reg_l, len_reg_u; + u32 rx_blocks; + u16 rx_len; + u32 bitmap; + u8 cr; + + if (!sdio_ireg) + return ret; + + if (sdio_ireg & DN_LD_CMD_PORT_HOST_INT_STATUS && adapter->cmd_sent) + adapter->cmd_sent = false; + + if (sdio_ireg & UP_LD_CMD_PORT_HOST_INT_STATUS) { + u32 pkt_type; + + /* read the len of control packet */ + rx_len = card->mp_regs[reg->cmd_rd_len_1] << 8; + rx_len |= (u16)card->mp_regs[reg->cmd_rd_len_0]; + rx_blocks = DIV_ROUND_UP(rx_len, NXPWIFI_SDIO_BLOCK_SIZE); + if (rx_len <= adapter->intf_hdr_len || + (rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + NXPWIFI_RX_DATA_BUF_SIZE) + return -EINVAL; + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, skb->data, + skb->len, adapter->ioport | + CMD_PORT_SLCT); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "failed to card_to_host"); + dev_kfree_skb_any(skb); + goto term_cmd; + } + + if (pkt_type != NXPWIFI_TYPE_CMD && + pkt_type != NXPWIFI_TYPE_EVENT) + nxpwifi_dbg(adapter, ERROR, "Received wrong packet on cmd port"); + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->wr_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1u]) << 24; + card->mp_wr_bitmap = bitmap; + + nxpwifi_dbg(adapter, INTR, "intr: wr_bitmap=0x%x\n", card->mp_wr_bitmap); + if (adapter->data_sent && + (card->mp_wr_bitmap & card->mp_data_port_mask)) { + nxpwifi_dbg(adapter, INTR, "Tx DONE\n"); + adapter->data_sent = false; + } + } + + nxpwifi_dbg(adapter, INTR, "cmd_sent=%d data_sent=%d\n", + adapter->cmd_sent, adapter->data_sent); + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->rd_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1u]) << 24; + card->mp_rd_bitmap = bitmap; + nxpwifi_dbg(adapter, INTR, "intr: rd_bitmap=0x%x\n", card->mp_rd_bitmap); + + while (true) { + ret = nxpwifi_get_rd_port(adapter, &port); + + if (ret) + break; + + len_reg_l = reg->rd_len_p0_l + (port << 1); + len_reg_u = reg->rd_len_p0_u + (port << 1); + rx_len = ((u16)card->mp_regs[len_reg_u]) << 8; + rx_len |= (u16)card->mp_regs[len_reg_l]; + rx_blocks = + (rx_len + NXPWIFI_SDIO_BLOCK_SIZE - + 1) / NXPWIFI_SDIO_BLOCK_SIZE; + if (rx_len <= adapter->intf_hdr_len || + (card->mpa_rx.enabled && + ((rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + card->mpa_rx.buf_size))) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_len=%d\n", rx_len); + ret = -EINVAL; + goto term_cmd; + } + + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + ret = nxpwifi_sdio_card_to_host_mp_aggr(adapter, rx_len, + port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "card_to_host_mpa failed: int status=%#x\n", + sdio_ireg); + goto term_cmd; + } + } + } + + return 0; + +term_cmd: + /* terminate cmd */ + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val = %d\n", cr); + + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, (cr | 0x04))) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: write success\n"); + + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val =%x\n", cr); + + return ret; +} + +/* Transmit using SDIO multi-port aggregation. */ +static int nxpwifi_host_to_card_mp_aggr(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port, + u32 next_pkt_len) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + s32 f_send_aggr_buf = 0; + s32 f_send_cur_buf = 0; + s32 f_precopy_cur_buf = 0; + s32 f_postcopy_cur_buf = 0; + u32 mport; + int index; + + if (!card->mpa_tx.enabled || port == CMD_PORT_SLCT) { + nxpwifi_dbg(adapter, WARN, "tx aggregation disabled\n"); + f_send_cur_buf = 1; + goto tx_curr_single; + } + + if (next_pkt_len) { + /* More pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port)) || + !MP_TX_AGGR_BUF_HAS_ROOM + (card, pkt_len + next_pkt_len)) + f_send_aggr_buf = 1; + } else { + /* No room in Aggr buf, send it */ + f_send_aggr_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port))) + f_send_cur_buf = 1; + else + f_postcopy_cur_buf = 1; + } + } else { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) && + (card->mp_wr_bitmap & (1 << card->curr_wr_port))) + f_precopy_cur_buf = 1; + else + f_send_cur_buf = 1; + } + } else { + /* Last pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + /* some packs in Aggr buf already */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) + f_precopy_cur_buf = 1; + else + /* No room in Aggr buf, send it */ + f_send_cur_buf = 1; + } else { + f_send_cur_buf = 1; + } + } + + if (f_precopy_cur_buf) { + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) || + mp_tx_aggr_port_limit_reached(card)) + /* No more pkts allowed in Aggr buf, send it */ + f_send_aggr_buf = 1; + } + + if (f_send_aggr_buf) { + u32 port_count; + int i; + + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_tx.ports & BIT(i)) + port_count++; + + /* + * Writing data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_tx.start_port; + + if (card->mpa_tx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_tx.start_port; + + ret = nxpwifi_write_data_to_card(adapter, card->mpa_tx.buf, + card->mpa_tx.buf_len, mport); + + /* Save the last multi port tx aggregation info to debug log */ + index = adapter->dbg.last_sdio_mp_index; + index = (index + 1) % NXPWIFI_DBG_SDIO_MP_NUM; + adapter->dbg.last_sdio_mp_index = index; + adapter->dbg.last_mp_wr_ports[index] = mport; + adapter->dbg.last_mp_wr_bitmap[index] = card->mp_wr_bitmap; + adapter->dbg.last_mp_wr_len[index] = card->mpa_tx.buf_len; + adapter->dbg.last_mp_curr_wr_port[index] = card->curr_wr_port; + + MP_TX_AGGR_BUF_RESET(card); + } + +tx_curr_single: + if (f_send_cur_buf) + ret = nxpwifi_write_data_to_card(adapter, payload, pkt_len, + adapter->ioport + port); + + if (f_postcopy_cur_buf) + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + return ret; +} + +static int nxpwifi_sdio_host_to_card(struct nxpwifi_adapter *adapter, + u8 type, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u32 buf_block_len; + u32 blk_size; + u32 port; + u8 *payload = (u8 *)skb->data; + u32 pkt_len = skb->len; + + /* Allocate buffer and copy payload */ + blk_size = NXPWIFI_SDIO_BLOCK_SIZE; + buf_block_len = (pkt_len + blk_size - 1) / blk_size; + put_unaligned_le16((u16)pkt_len, payload + 0); + put_unaligned_le16((u16)type, payload + 2); + + /* + * This is SDIO specific header + * u16 length, + * u16 type (NXPWIFI_TYPE_DATA = 0, NXPWIFI_TYPE_CMD = 1, + * NXPWIFI_TYPE_EVENT = 3) + */ + if (type == NXPWIFI_TYPE_DATA) { + ret = nxpwifi_get_wr_port_data(adapter, &port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "no wr_port available\n"); + return ret; + } + } else { + adapter->cmd_sent = true; + + if (pkt_len <= adapter->intf_hdr_len || + pkt_len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "invalid upld pkt_len=%u (hdr_len=%u, max=%u)\n", + pkt_len, adapter->intf_hdr_len, NXPWIFI_UPLD_SIZE); + return -EINVAL; + } + + port = CMD_PORT_SLCT; + } + + /* Transfer data to card */ + pkt_len = buf_block_len * blk_size; + + if (tx_param) + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, tx_param->next_pkt_len + ); + else + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, 0); + + if (ret) { + if (type == NXPWIFI_TYPE_CMD || + type == NXPWIFI_TYPE_VDLL) + adapter->cmd_sent = false; + if (type == NXPWIFI_TYPE_DATA) { + adapter->data_sent = false; + /* restore curr_wr_port in error cases */ + card->curr_wr_port = port; + card->mp_wr_bitmap |= (u32)(1 << card->curr_wr_port); + } + } else { + if (type == NXPWIFI_TYPE_DATA) { + if (!(card->mp_wr_bitmap & (1 << card->curr_wr_port))) + adapter->data_sent = true; + else + adapter->data_sent = false; + } + } + + return ret; +} + +static int nxpwifi_alloc_sdio_mpa_buffers(struct nxpwifi_adapter *adapter, + u32 mpa_tx_buf_size, + u32 mpa_rx_buf_size) +{ + struct sdio_mmc_card *card = adapter->card; + u32 rx_buf_size; + int ret = 0; + + card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL); + if (!card->mpa_tx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_tx.buf_size = mpa_tx_buf_size; + + rx_buf_size = max_t(u32, mpa_rx_buf_size, + (u32)SDIO_MAX_AGGR_BUF_SIZE); + card->mpa_rx.buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!card->mpa_rx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_rx.buf_size = rx_buf_size; + +error: + if (ret) { + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); + card->mpa_tx.buf_size = 0; + card->mpa_rx.buf_size = 0; + card->mpa_tx.buf = NULL; + card->mpa_rx.buf = NULL; + } + + return ret; +} + +static void +nxpwifi_unregister_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (adapter->card) { + card->adapter = NULL; + sdio_claim_host(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); + } +} + +static int nxpwifi_register_dev(struct nxpwifi_adapter *adapter) +{ + int ret; + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + const char *firmware = card->firmware; + + /* save adapter pointer in card */ + card->adapter = adapter; + adapter->tx_buf_size = card->tx_buf_size; + + sdio_claim_host(func); + + /* Set block size */ + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + sdio_release_host(func); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "cannot set SDIO block size\n"); + return ret; + } + + /* + * Select correct firmware (sdsd or sdiouart) firmware based on the strapping + * option + */ + if (card->firmware_sdiouart) { + u8 val; + + nxpwifi_read_reg(adapter, card->reg->host_strap_reg, &val); + if ((val & card->reg->host_strap_mask) == card->reg->host_strap_value) + firmware = card->firmware_sdiouart; + } + strscpy(adapter->fw_name, firmware, sizeof(adapter->fw_name)); + + if (card->fw_dump_enh) { + adapter->mem_type_mapping_tbl = generic_mem_type_map; + adapter->num_mem_types = 1; + } else { + adapter->mem_type_mapping_tbl = mem_type_mapping_tbl; + adapter->num_mem_types = ARRAY_SIZE(mem_type_mapping_tbl); + } + + return 0; +} + +static int nxpwifi_init_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 sdio_ireg; + + sdio_set_drvdata(card->func, card); + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + /* Get SDIO ioport */ + if (nxpwifi_init_sdio_ioport(adapter)) + return -EIO; + + /* Initialize SDIO variables in card */ + card->mp_rd_bitmap = 0; + card->mp_wr_bitmap = 0; + card->curr_rd_port = reg->start_rd_port; + card->curr_wr_port = reg->start_wr_port; + + card->mp_data_port_mask = reg->data_port_mask; + + card->mpa_tx.buf_len = 0; + card->mpa_tx.pkt_cnt = 0; + card->mpa_tx.start_port = 0; + + card->mpa_tx.enabled = 1; + card->mpa_tx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + card->mpa_rx.buf_len = 0; + card->mpa_rx.pkt_cnt = 0; + card->mpa_rx.start_port = 0; + + card->mpa_rx.enabled = 1; + card->mpa_rx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + /* Allocate buffers for SDIO MP-A */ + card->mp_regs = devm_kzalloc(&card->func->dev, reg->max_mp_regs, GFP_KERNEL); + + if (!card->mp_regs) + return -ENOMEM; + + card->mpa_rx.len_arr = + devm_kcalloc(&card->func->dev, card->mp_agg_pkt_limit, + sizeof(*card->mpa_rx.len_arr), GFP_KERNEL); + + if (!card->mpa_rx.len_arr) + return -ENOMEM; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + card->mp_tx_agg_buf_size, + card->mp_rx_agg_buf_size); + + /* Allocate 32k MPA Tx/Rx buffers if 64k memory allocation fails */ + if (ret && (card->mp_tx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX || + card->mp_rx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX)) { + /* Disable rx single port aggregation */ + adapter->host_disable_sdio_rx_aggr = true; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + NXPWIFI_MP_AGGR_BSIZE_32K, + NXPWIFI_MP_AGGR_BSIZE_32K); + if (ret) { + /* Disable multi port aggregation */ + card->mpa_tx.enabled = 0; + card->mpa_rx.enabled = 0; + } + } + + adapter->ext_scan = card->can_ext_scan; + return ret; +} + +static void nxpwifi_cleanup_mpa_buf(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + MP_TX_AGGR_BUF_RESET(card); + MP_RX_AGGR_BUF_RESET(card); +} + +static void nxpwifi_cleanup_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + cancel_work_sync(&card->work); + + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); +} + +static void +nxpwifi_update_mp_end_port(struct nxpwifi_adapter *adapter, u16 port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int i; + + card->mp_end_port = port; + + card->mp_data_port_mask = reg->data_port_mask; + + if (reg->start_wr_port) { + for (i = 1; i <= card->max_ports - card->mp_end_port; i++) + card->mp_data_port_mask &= + ~(1 << (card->max_ports - i)); + } + + card->curr_wr_port = reg->start_wr_port; +} + +/* Perform an SDIO card reset in workqueue context. */ +static void nxpwifi_sdio_card_reset_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + /* Prepare the adapter for the reset. */ + nxpwifi_shutdown_sw(adapter); + clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, &card->work_flags); + clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags); + + /* Run a HW reset of the SDIO interface. */ + sdio_claim_host(func); + ret = mmc_hw_reset(func->card); + sdio_release_host(func); + + switch (ret) { + case 1: + nxpwifi_dbg(adapter, MSG, "SDIO HW reset asynchronous\n"); + complete_all(adapter->fw_done); + break; + case 0: + ret = nxpwifi_reinit_sw(adapter); + if (ret) + dev_err(&func->dev, "reinit failed: %d\n", ret); + break; + default: + dev_err(&func->dev, "SDIO HW reset failed: %d\n", ret); + break; + } +} + +static enum +rdwr_status nxpwifi_sdio_rdwr_firmware(struct nxpwifi_adapter *adapter, + u8 doneflag) +{ + struct sdio_mmc_card *card = adapter->card; + int ret, tries; + u8 ctrl_data = 0; + + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO Write ERR\n"); + return RDWR_STATUS_FAILURE; + } + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl, + &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + return RDWR_STATUS_FAILURE; + } + if (ctrl_data == FW_DUMP_DONE) + break; + if (doneflag && ctrl_data == doneflag) + return RDWR_STATUS_DONE; + if (ctrl_data != card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, WARN, + "The ctrl reg was changed, re-try again\n"); + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return RDWR_STATUS_FAILURE; + } + } + usleep_range(100, 200); + } + if (ctrl_data == card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, ERROR, "Fail to pull ctrl_data\n"); + return RDWR_STATUS_FAILURE; + } + + return RDWR_STATUS_SUCCESS; +} + +/* Dump firmware memories for post-mortem analysis. */ +static void nxpwifi_sdio_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + unsigned int reg, reg_start, reg_end; + u8 *dbg_ptr, *end_ptr, dump_num, idx, i, read_reg, doneflag = 0; + enum rdwr_status stat; + u32 memory_size; + + if (!card->can_dump_fw) + return; + + for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg = card->reg->fw_dump_start; + /* Read the number of the memories which will dump */ + dump_num = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read memory length err\n"); + goto done; + } + + /* Read the length of every memory which will dump */ + for (idx = 0; idx < dump_num; idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + memory_size = 0; + reg = card->reg->fw_dump_start; + for (i = 0; i < 4; i++) { + read_reg = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + memory_size |= (read_reg << i * 8); + reg++; + } + + if (memory_size == 0) { + nxpwifi_dbg(adapter, DUMP, "Firmware dump Finished!\n"); + ret = nxpwifi_write_reg(adapter, + card->reg->fw_dump_ctrl, + FW_DUMP_READ_DONE); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return; + } + break; + } + + nxpwifi_dbg(adapter, DUMP, + "%s_SIZE=0x%x\n", entry->mem_name, memory_size); + entry->mem_ptr = vmalloc(memory_size + 1); + entry->mem_size = memory_size; + if (!entry->mem_ptr) + goto done; + dbg_ptr = entry->mem_ptr; + end_ptr = dbg_ptr + memory_size; + + doneflag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, "Start %s output, please wait...\n", + entry->mem_name); + + do { + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (dbg_ptr < end_ptr) + dbg_ptr++; + else + nxpwifi_dbg(adapter, ERROR, "Allocated buf not enough\n"); + } + + if (stat != RDWR_STATUS_DONE) + continue; + + nxpwifi_dbg(adapter, DUMP, "%s done: size=0x%tx\n", + entry->mem_name, dbg_ptr - entry->mem_ptr); + break; + } while (1); + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + sdio_release_host(card->func); +} + +/* Generic firmware dump flow for enhanced devices. */ +static void nxpwifi_sdio_generic_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct memory_type_mapping *entry = &generic_mem_type_map[0]; + unsigned int reg, reg_start, reg_end; + u8 start_flag = 0, done_flag = 0; + u8 *dbg_ptr, *end_ptr; + enum rdwr_status stat; + int ret = -EPERM, tries; + + if (!card->fw_dump_enh) + return; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + start_flag = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (start_flag == 0) + break; + if (tries == MAX_POLL_TRIES) { + nxpwifi_dbg(adapter, ERROR, "FW not ready to dump\n"); + ret = -EPERM; + goto done; + } + } + usleep_range(100, 200); + } + + entry->mem_ptr = vmalloc(0xf0000 + 1); + if (!entry->mem_ptr) { + ret = -ENOMEM; + goto done; + } + dbg_ptr = entry->mem_ptr; + entry->mem_size = 0xf0000; + end_ptr = dbg_ptr + entry->mem_size; + + done_flag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, + "Start %s output, please wait...\n", entry->mem_name); + + while (true) { + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + dbg_ptr++; + if (dbg_ptr >= end_ptr) { + u8 *tmp_ptr; + + tmp_ptr = vmalloc(entry->mem_size + 0x4000 + 1); + if (!tmp_ptr) + goto done; + + memcpy(tmp_ptr, entry->mem_ptr, + entry->mem_size); + vfree(entry->mem_ptr); + entry->mem_ptr = tmp_ptr; + tmp_ptr = NULL; + dbg_ptr = entry->mem_ptr + entry->mem_size; + entry->mem_size += 0x4000; + end_ptr = entry->mem_ptr + entry->mem_size; + } + } + if (stat == RDWR_STATUS_DONE) { + entry->mem_size = dbg_ptr - entry->mem_ptr; + nxpwifi_dbg(adapter, DUMP, "dump %s done size=0x%x\n", + entry->mem_name, entry->mem_size); + ret = 0; + break; + } + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + if (ret) { + nxpwifi_dbg(adapter, ERROR, "firmware dump failed\n"); + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + sdio_release_host(card->func); +} + +/* Build and upload consolidated device dump. */ +static void nxpwifi_sdio_device_dump_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + adapter->devdump_data = vzalloc(NXPWIFI_FW_DUMP_SIZE); + if (!adapter->devdump_data) + return; + + nxpwifi_drv_info_dump(adapter); + + /* Generic firmware dump flow for enhanced devices. */ + if (card->fw_dump_enh) + nxpwifi_sdio_generic_fw_dump(adapter); + /* Dump firmware memories for post-mortem analysis. */ + else + nxpwifi_sdio_fw_dump(adapter); + + nxpwifi_prepare_fw_dump_info(adapter); + nxpwifi_upload_device_dump(adapter); +} + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work) +{ + struct sdio_mmc_card *card = + container_of(work, struct sdio_mmc_card, work); + + /* Build and upload consolidated device dump. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_sdio_device_dump_work(card->adapter); + + /* Perform an SDIO card reset in workqueue context. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, + &card->work_flags)) + nxpwifi_sdio_card_reset_work(card->adapter); +} + +/* Schedule SDIO card reset. */ +static void nxpwifi_sdio_card_reset(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +static void nxpwifi_sdio_device_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +/* Dump SDIO function and scratch registers into drv_buf. */ +static int +nxpwifi_sdio_reg_dump(struct nxpwifi_adapter *adapter, char *drv_buf) +{ + char *p = drv_buf; + struct sdio_mmc_card *cardp = adapter->card; + int ret = 0; + u8 count, func, data, index = 0, size = 0; + u8 reg, reg_start, reg_end; + char buf[256], *ptr; + + if (!p) + return 0; + + nxpwifi_dbg(adapter, MSG, "SDIO register dump start\n"); + + nxpwifi_pm_wakeup_card(adapter); + + sdio_claim_host(cardp->func); + + for (count = 0; count < 5; count++) { + memset(buf, 0, sizeof(buf)); + ptr = buf; + + switch (count) { + case 0: + /* Read the registers of SDIO function0 */ + func = count; + reg_start = 0; + reg_end = 9; + break; + case 1: + /* Read the registers of SDIO function1 */ + func = count; + reg_start = cardp->reg->func1_dump_reg_start; + reg_end = cardp->reg->func1_dump_reg_end; + break; + case 2: + index = 0; + func = 1; + reg_start = cardp->reg->func1_spec_reg_table[index++]; + size = cardp->reg->func1_spec_reg_num; + reg_end = cardp->reg->func1_spec_reg_table[size - 1]; + break; + default: + /* Read the scratch registers of SDIO function1 */ + if (count == 4) + msleep(100); + func = 1; + reg_start = cardp->reg->func1_scratch_reg; + reg_end = reg_start + NXPWIFI_SDIO_SCRATCH_SIZE; + } + + if (count != 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d (%#x-%#x): ", func, reg_start, + reg_end); + else + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d: ", func); + + for (reg = reg_start; reg <= reg_end;) { + if (func == 0) + data = sdio_f0_readb(cardp->func, reg, &ret); + else + data = sdio_readb(cardp->func, reg, &ret); + + if (count == 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "(%#x) ", reg); + if (!ret) { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "%02x ", data); + } else { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "ERR"); + break; + } + + if (count == 2 && reg < reg_end) + reg = cardp->reg->func1_spec_reg_table[index++]; + else + reg++; + } + + nxpwifi_dbg(adapter, MSG, "%s\n", buf); + p += sprintf(p, "%s\n", buf); + } + + sdio_release_host(cardp->func); + + nxpwifi_dbg(adapter, MSG, "SDIO register dump end\n"); + + return p - drv_buf; +} + +static void nxpwifi_sdio_up_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + int ret = 0; + + sdio_claim_host(card->func); + ret = sdio_enable_func(card->func); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_enable_func failed: %d\n", ret); + + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_set_block_size failed: %d\n", ret); + + sdio_release_host(card->func); + + /* + * tx_buf_size might be changed to 3584 by firmware during + * data transfer, we will reset to default size. + */ + adapter->tx_buf_size = card->tx_buf_size; + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + if (nxpwifi_init_sdio_ioport(adapter)) + nxpwifi_dbg(adapter, ERROR, "error enabling SDIO port\n"); +} + +static struct nxpwifi_if_ops sdio_ops = { + .init_if = nxpwifi_init_sdio, + .cleanup_if = nxpwifi_cleanup_sdio, + .check_fw_status = nxpwifi_check_fw_status, + .check_winner_status = nxpwifi_check_winner_status, + .prog_fw = nxpwifi_prog_fw_w_helper, + .register_dev = nxpwifi_register_dev, + .unregister_dev = nxpwifi_unregister_dev, + .enable_int = nxpwifi_sdio_enable_host_int, + .disable_int = nxpwifi_sdio_disable_host_int, + .process_int_status = nxpwifi_process_int_status, + .host_to_card = nxpwifi_sdio_host_to_card, + .wakeup = nxpwifi_pm_wakeup_card, + .wakeup_complete = nxpwifi_pm_wakeup_card_complete, + + /* SDIO specific */ + .update_mp_end_port = nxpwifi_update_mp_end_port, + .cleanup_mpa_buf = nxpwifi_cleanup_mpa_buf, + .cmdrsp_complete = nxpwifi_sdio_cmdrsp_complete, + .event_complete = nxpwifi_sdio_event_complete, + .dnld_fw = nxpwifi_sdio_dnld_fw, + .card_reset = nxpwifi_sdio_card_reset, + .reg_dump = nxpwifi_sdio_reg_dump, + .device_dump = nxpwifi_sdio_device_dump, + .deaggr_pkt = nxpwifi_deaggr_sdio_pkt, + .up_dev = nxpwifi_sdio_up_dev, +}; + +module_sdio_driver(nxpwifi_sdio); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi SDIO Driver version " SDIO_VERSION); +MODULE_VERSION(SDIO_VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(IW61X_SDIO_FW_NAME); diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.h b/drivers/net/wireless/nxp/nxpwifi/sdio.h new file mode 100644 index 000000000000..de5c884a5b14 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: SDIO specific definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_SDIO_H +#define _NXPWIFI_SDIO_H + +#include "main.h" + +#define IW61X_SDIO_FW_NAME "nxp/sd_w61x_v1.bin.se" + +#define BLOCK_MODE 1 +#define BYTE_MODE 0 + +#define NXPWIFI_SDIO_IO_PORT_MASK 0xfffff + +#define NXPWIFI_SDIO_BYTE_MODE_MASK 0x80000000 + +#define NXPWIFI_MAX_FUNC2_REG_NUM 13 +#define NXPWIFI_SDIO_SCRATCH_SIZE 10 + +#define SDIO_MPA_ADDR_BASE 0x1000 + +#define CMD_PORT_UPLD_INT_MASK (0x1U << 6) +#define CMD_PORT_DNLD_INT_MASK (0x1U << 7) +#define HOST_TERM_CMD53 (0x1U << 2) +#define REG_PORT 0 +#define MEM_PORT 0x10000 + +#define CMD53_NEW_MODE (0x1U << 0) +#define CMD_PORT_RD_LEN_EN (0x1U << 2) +#define CMD_PORT_AUTO_EN (0x1U << 0) +#define CMD_PORT_SLCT 0x8000 +#define UP_LD_CMD_PORT_HOST_INT_STATUS (0x40U) +#define DN_LD_CMD_PORT_HOST_INT_STATUS (0x80U) + +#define NXPWIFI_MP_AGGR_BSIZE_32K (32768) +/* we leave one block of 256 bytes for DMA alignment*/ +#define NXPWIFI_MP_AGGR_BSIZE_MAX (65280) + +/* Misc. Config Register : Auto Re-enable interrupts */ +#define AUTO_RE_ENABLE_INT BIT(4) + +/* Host Control Registers : Configuration */ +#define CONFIGURATION_REG 0x00 +/* Host Control Registers : Host power up */ +#define HOST_POWER_UP (0x1U << 1) + +/* Host Control Registers : Upload host interrupt mask */ +#define UP_LD_HOST_INT_MASK (0x1U) +/* Host Control Registers : Download host interrupt mask */ +#define DN_LD_HOST_INT_MASK (0x2U) + +/* Host Control Registers : Upload host interrupt status */ +#define UP_LD_HOST_INT_STATUS (0x1U) +/* Host Control Registers : Download host interrupt status */ +#define DN_LD_HOST_INT_STATUS (0x2U) + +/* Host Control Registers : Host interrupt status */ +#define CARD_INT_STATUS_REG 0x28 + +/* Card Control Registers : Card I/O ready */ +#define CARD_IO_READY (0x1U << 3) +/* Card Control Registers : Download card ready */ +#define DN_LD_CARD_RDY (0x1U << 0) + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/* SDIO Tx aggregation in progress ? */ +#define MP_TX_AGGR_IN_PROGRESS(a) ((a)->mpa_tx.pkt_cnt > 0) + +/* SDIO Tx aggregation buffer room for next packet ? */ +#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ({ \ + typeof(a) (_a) = a; \ + (((_a)->mpa_tx.buf_len + (len)) <= (_a)->mpa_tx.buf_size); \ + }) + +/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */ +#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do { \ + typeof(a) (_a) = (a); \ + typeof(pkt_len) (_pkt_len) = pkt_len; \ + typeof(port) (_port) = port; \ + memmove(&(_a)->mpa_tx.buf[(_a)->mpa_tx.buf_len], \ + payload, (_pkt_len)); \ + (_a)->mpa_tx.buf_len += (_pkt_len); \ + if (!(_a)->mpa_tx.pkt_cnt) \ + (_a)->mpa_tx.start_port = (_port); \ + if ((_a)->mpa_tx.start_port <= (_port)) \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt)); \ + else \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt + 1 + \ + ((_a)->max_ports - \ + (_a)->mp_end_port))); \ + (_a)->mpa_tx.pkt_cnt++; \ +} while (0) + +/* SDIO Tx aggregation limit ? */ +#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_tx.pkt_cnt == (_a)->mpa_tx.pkt_aggr_limit); \ + }) + +/* Reset SDIO Tx aggregation buffer parameters */ +#define MP_TX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_tx.pkt_cnt = 0; \ + (_a)->mpa_tx.buf_len = 0; \ + (_a)->mpa_tx.ports = 0; \ + (_a)->mpa_tx.start_port = 0; \ +} while (0) + +/* SDIO Rx aggregation limit ? */ +#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_rx.pkt_cnt == (_a)->mpa_rx.pkt_aggr_limit); \ + }) + +/* SDIO Rx aggregation in progress ? */ +#define MP_RX_AGGR_IN_PROGRESS(a) ((a)->mpa_rx.pkt_cnt > 0) + +/* SDIO Rx aggregation buffer room for next packet ? */ +#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) ({ \ + typeof(a) (_a) = a; \ + ((((_a)->mpa_rx.buf_len + (rx_len))) <= (_a)->mpa_rx.buf_size); \ + }) + +/* Reset SDIO Rx aggregation buffer parameters */ +#define MP_RX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_rx.pkt_cnt = 0; \ + (_a)->mpa_rx.buf_len = 0; \ + (_a)->mpa_rx.ports = 0; \ + (_a)->mpa_rx.start_port = 0; \ +} while (0) + +/* data structure for SDIO MPA TX */ +struct nxpwifi_sdio_mpa_tx { + /* multiport tx aggregation buffer pointer */ + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +struct nxpwifi_sdio_mpa_rx { + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u32 *len_arr; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +int nxpwifi_bus_register(void); +void nxpwifi_bus_unregister(void); + +struct nxpwifi_sdio_card_reg { + u8 start_rd_port; + u8 start_wr_port; + u8 base_0_reg; + u8 base_1_reg; + u8 poll_reg; + u8 host_int_enable; + u8 host_int_rsr_reg; + u8 host_int_status_reg; + u8 host_int_mask_reg; + u8 host_strap_reg; + u8 host_strap_mask; + u8 host_strap_value; + u8 status_reg_0; + u8 status_reg_1; + u8 sdio_int_mask; + u32 data_port_mask; + u8 io_port_0_reg; + u8 io_port_1_reg; + u8 io_port_2_reg; + u8 max_mp_regs; + u8 rd_bitmap_l; + u8 rd_bitmap_u; + u8 rd_bitmap_1l; + u8 rd_bitmap_1u; + u8 wr_bitmap_l; + u8 wr_bitmap_u; + u8 wr_bitmap_1l; + u8 wr_bitmap_1u; + u8 rd_len_p0_l; + u8 rd_len_p0_u; + u8 card_misc_cfg_reg; + u8 card_cfg_2_1_reg; + u8 cmd_rd_len_0; + u8 cmd_rd_len_1; + u8 cmd_rd_len_2; + u8 cmd_rd_len_3; + u8 cmd_cfg_0; + u8 cmd_cfg_1; + u8 cmd_cfg_2; + u8 cmd_cfg_3; + u8 fw_dump_host_ready; + u8 fw_dump_ctrl; + u8 fw_dump_start; + u8 fw_dump_end; + u8 func1_dump_reg_start; + u8 func1_dump_reg_end; + u8 func1_scratch_reg; + u8 func1_spec_reg_num; + u8 func1_spec_reg_table[NXPWIFI_MAX_FUNC2_REG_NUM]; +}; + +struct sdio_mmc_card { + struct sdio_func *func; + struct nxpwifi_adapter *adapter; + + struct completion fw_done; + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + + u32 mp_rd_bitmap; + u32 mp_wr_bitmap; + + u16 mp_end_port; + u32 mp_data_port_mask; + + u8 curr_rd_port; + u8 curr_wr_port; + + u8 *mp_regs; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; + + struct nxpwifi_sdio_mpa_tx mpa_tx; + struct nxpwifi_sdio_mpa_rx mpa_rx; + + struct work_struct work; + unsigned long work_flags; +}; + +struct nxpwifi_sdio_device { + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; +}; + +/* .cmdrsp_complete handler + */ +static inline int nxpwifi_sdio_cmdrsp_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +/* .event_complete handler + */ +static inline int nxpwifi_sdio_event_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +static inline bool +mp_rx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u8 tmp; + + if (card->curr_rd_port < card->mpa_rx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_rx.start_port) + + card->curr_rd_port) >= tmp) + return true; + } + + if ((card->curr_rd_port - card->mpa_rx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +static inline bool +mp_tx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u16 tmp; + + if (card->curr_wr_port < card->mpa_tx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_tx.start_port) + + card->curr_wr_port) >= tmp) + return true; + } + + if ((card->curr_wr_port - card->mpa_tx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */ +static inline void mp_rx_aggr_setup(struct sdio_mmc_card *card, + u16 rx_len, u8 port) +{ + card->mpa_rx.buf_len += rx_len; + + if (!card->mpa_rx.pkt_cnt) + card->mpa_rx.start_port = port; + + card->mpa_rx.ports |= (1 << port); + card->mpa_rx.len_arr[card->mpa_rx.pkt_cnt] = rx_len; + card->mpa_rx.pkt_cnt++; +} +#endif /* _NXPWIFI_SDIO_H */ diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c new file mode 100644 index 000000000000..702fa1531da1 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c @@ -0,0 +1,1177 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: functions for station ioctl + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "cfg80211.h" + +static int disconnect_on_suspend; + +/* Copies the multicast address list from device to driver */ +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev) +{ + int i = 0; + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, dev) + memcpy(&mlist->mac_list[i++], ha->addr, ETH_ALEN); + + return i; +} + +/* Wait queue completion handler */ +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued) +{ + int status; + + /* Wait for completion */ + status = wait_event_interruptible_timeout(adapter->cmd_wait_q.wait, + *cmd_queued->condition, + (12 * HZ)); + if (status <= 0) { + if (status == 0) + status = -ETIMEDOUT; + nxpwifi_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", + status); + nxpwifi_cancel_all_pending_cmd(adapter); + + /* + * The response path writes through cmd_node->data_buf. The + * caller can release a stack-allocated data_buf after an + * interrupted wait while a late response is still pending. + * Detach it from the current command before returning. + */ + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + if (adapter->curr_cmd == cmd_queued) + adapter->curr_cmd->data_buf = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + return status; + } + + status = adapter->cmd_wait_q.status; + adapter->cmd_wait_q.status = 0; + + return status; +} + +/* Set multicast list by issuing the proper firmware command */ +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list) +{ + int ret = 0; + u16 old_pkt_filter; + + old_pkt_filter = priv->curr_pkt_filter; + + if (mcast_list->mode == NXPWIFI_PROMISC_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enable Promiscuous mode\n"); + priv->curr_pkt_filter |= HOST_ACT_MAC_PROMISCUOUS_ENABLE; + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + /* Multicast */ + priv->curr_pkt_filter &= ~HOST_ACT_MAC_PROMISCUOUS_ENABLE; + if (mcast_list->mode == NXPWIFI_ALL_MULTI_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enabling All Multicast!\n"); + priv->curr_pkt_filter |= + HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + nxpwifi_dbg(priv->adapter, INFO, + "info: Set multicast list=%d\n", + mcast_list->num_multicast_addr); + /* Send multicast addresses to firmware */ + ret = nxpwifi_send_cmd(priv, + HOST_CMD_MAC_MULTICAST_ADR, + HOST_ACT_GEN_SET, 0, + mcast_list, false); + } + } + nxpwifi_dbg(priv->adapter, INFO, + "info: old_pkt_filter=%#x, curr_pkt_filter=%#x\n", + old_pkt_filter, priv->curr_pkt_filter); + if (old_pkt_filter != priv->curr_pkt_filter) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, + 0, &priv->curr_pkt_filter, false); + } + + return ret; +} + +/* Fill BSS descriptor from cfg80211_bss */ +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u8 *beacon_ie; + size_t beacon_ie_len; + struct nxpwifi_bss_priv *bss_priv = (void *)bss->priv; + const struct cfg80211_bss_ies *ies; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC); + beacon_ie_len = ies->len; + bss_desc->timestamp = ies->tsf; + rcu_read_unlock(); + + if (!beacon_ie) { + nxpwifi_dbg(priv->adapter, ERROR, + " failed to alloc beacon_ie\n"); + return -ENOMEM; + } + + memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN); + bss_desc->rssi = bss->signal; + /* The caller of this function will free beacon_ie */ + bss_desc->beacon_buf = beacon_ie; + bss_desc->beacon_buf_size = beacon_ie_len; + bss_desc->beacon_period = bss->beacon_interval; + bss_desc->cap_info_bitmap = bss->capability; + bss_desc->bss_band = bss_priv->band; + bss_desc->fw_tsf = bss_priv->fw_tsf; + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_PRIVACY) { + nxpwifi_dbg(priv->adapter, INFO, + "info: InterpretIE: AP WEP enabled\n"); + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_8021X_WEP; + } else { + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL; + } + bss_desc->bss_mode = NL80211_IFTYPE_STATION; + + /* Disable 11ac by default */ + bss_desc->disable_11ac = true; + /* Disable 11ax by default */ + bss_desc->disable_11ax = true; + + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_SPECTRUM_MGMT) + bss_desc->sensed_11h = true; + + return nxpwifi_update_bss_desc_with_ie(priv->adapter, bss_desc); +} + +static int nxpwifi_process_country_ie(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + const u8 *country_ie; + u8 country_ie_len; + struct nxpwifi_802_11d_domain_reg *domain_info = + &priv->adapter->domain_reg; + int ret; + + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); + return 0; + } + + country_ie_len = country_ie[1]; + if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) { + rcu_read_unlock(); + return 0; + } + + if (!strncmp(priv->adapter->country_code, &country_ie[2], 2)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, INFO, + "11D: skip setting domain info in FW\n"); + return 0; + } + + if (country_ie_len > + (IEEE80211_COUNTRY_STRING_LEN + NXPWIFI_MAX_TRIPLET_802_11D)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, ERROR, + "11D: country_ie_len overflow!, deauth AP\n"); + return -EINVAL; + } + + memcpy(priv->adapter->country_code, &country_ie[2], 2); + + domain_info->country_code[0] = country_ie[2]; + domain_info->country_code[1] = country_ie[3]; + domain_info->country_code[2] = ' '; + + country_ie_len -= IEEE80211_COUNTRY_STRING_LEN; + + domain_info->no_of_triplet = + country_ie_len / sizeof(struct ieee80211_country_ie_triplet); + + memcpy((u8 *)domain_info->triplet, + &country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len); + + rcu_read_unlock(); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, + HOST_ACT_GEN_SET, 0, NULL, false); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: setting domain info in FW fail\n"); + + return ret; +} + +/* In infra mode, an deauthentication is performed first */ +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc = NULL; + u16 config_bands; + + priv->scan_block = false; + + if (adapter->region_code == 0x00 && + nxpwifi_process_country_ie(priv, bss)) + return -EINVAL; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_band_to_radio_type(bss_desc->bss_band) == + HOST_SCAN_RADIO_TYPE_BG) { + config_bands = BAND_B | BAND_G | BAND_GN; + if (adapter->fw_bands & BAND_GAC) + config_bands |= BAND_GAC; + if (adapter->fw_bands & BAND_GAX) + config_bands |= BAND_GAX; + } else { + config_bands = BAND_A | BAND_AN; + if (adapter->fw_bands & BAND_AAC) + config_bands |= BAND_AAC; + if (adapter->fw_bands & BAND_AAX) + config_bands |= BAND_AAX; + } + + if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands)) + priv->config_bands = config_bands; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + if (nxpwifi_11h_get_csa_closed_channel(priv) == (u8)bss_desc->channel) { + nxpwifi_dbg(adapter, ERROR, + "Attempt to reconnect on csa closed chan(%d)\n", + bss_desc->channel); + ret = -EINVAL; + goto done; + } + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + /* Clear any past association response stored for application retrieval */ + priv->assoc_rsp_size = 0; + ret = nxpwifi_associate(priv, bss_desc); + + /* + * If auth type is auto and association fails using open mode, try to connect + * using shared mode + */ + if (ret == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG && + priv->sec_info.is_authtype_auto && + priv->sec_info.wep_enabled) { + priv->sec_info.authentication_mode = + NL80211_AUTHTYPE_SHARED_KEY; + ret = nxpwifi_associate(priv, bss_desc); + } + +done: + /* beacon_ie buffer was allocated in function nxpwifi_fill_new_bss_desc() */ + if (bss_desc) + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + + if (ret < 0) + priv->attempted_bss_desc = NULL; + + return ret; +} + +/* IOCTL request handler to set host sleep configuration */ +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int status = 0; + u32 prev_cond = 0; + + if (!hs_cfg) + return -ENOMEM; + + switch (action) { + case HOST_ACT_GEN_SET: + if (adapter->pps_uapsd_mode) { + nxpwifi_dbg(adapter, INFO, + "info: Host Sleep IOCTL\t" + "is blocked in UAPSD/PPS mode\n"); + status = -EPERM; + break; + } + if (hs_cfg->is_invoke_hostcmd) { + if (hs_cfg->conditions == HS_CFG_CANCEL) { + if (!test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags)) + /* Already cancelled */ + break; + /* Save previous condition */ + prev_cond = le32_to_cpu(adapter->hs_cfg + .conditions); + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + } else if (hs_cfg->conditions) { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + if (hs_cfg->gap) + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } else if (adapter->hs_cfg.conditions == + cpu_to_le32(HS_CFG_CANCEL)) { + status = -EINVAL; + break; + } + + status = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_HS_CFG_ENH, + HOST_ACT_GEN_SET, 0, + &adapter->hs_cfg, + cmd_type == NXPWIFI_SYNC_CMD); + + if (hs_cfg->conditions == HS_CFG_CANCEL) + /* Restore previous condition */ + adapter->hs_cfg.conditions = + cpu_to_le32(prev_cond); + } else { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } + break; + case HOST_ACT_GEN_GET: + hs_cfg->conditions = le32_to_cpu(adapter->hs_cfg.conditions); + hs_cfg->gpio = adapter->hs_cfg.gpio; + hs_cfg->gap = adapter->hs_cfg.gap; + break; + default: + status = -EINVAL; + break; + } + + return status; +} + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type) +{ + struct nxpwifi_ds_hs_cfg hscfg; + + hscfg.conditions = HS_CFG_CANCEL; + hscfg.is_invoke_hostcmd = true; + + return nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + cmd_type, &hscfg); +} +EXPORT_SYMBOL_GPL(nxpwifi_cancel_hs); + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ds_hs_cfg hscfg; + struct nxpwifi_private *priv; + int i; + + if (disconnect_on_suspend) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv && priv->sched_scanning) { +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config && + !priv->wdev.wiphy->wowlan_config->nd_config) { +#endif + nxpwifi_dbg(adapter, CMD, "aborting bgscan!\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); +#ifdef CONFIG_PM + } +#endif + } + + if (adapter->hs_activated) { + nxpwifi_dbg(adapter, CMD, + "cmd: HS Already activated\n"); + return true; + } + + adapter->hs_activate_wait_q_woken = false; + + memset(&hscfg, 0, sizeof(hscfg)); + hscfg.is_invoke_hostcmd = true; + + set_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + nxpwifi_cancel_all_pending_cmd(adapter); + + if (nxpwifi_set_hs_params(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_STA), + HOST_ACT_GEN_SET, NXPWIFI_SYNC_CMD, + &hscfg)) { + nxpwifi_dbg(adapter, ERROR, + "IOCTL request HS enable failed\n"); + return false; + } + + if (wait_event_interruptible_timeout(adapter->hs_activate_wait_q, + adapter->hs_activate_wait_q_woken, + (10 * HZ)) <= 0) { + nxpwifi_dbg(adapter, ERROR, + "hs_activate_wait_q terminated\n"); + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(nxpwifi_enable_hs); + +/* IOCTL request handler to get BSS information */ +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc; + + if (!info) + return -EINVAL; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + + info->bss_mode = priv->bss_mode; + + memcpy(&info->ssid, &bss_desc->ssid, sizeof(struct cfg80211_ssid)); + + memcpy(&info->bssid, &bss_desc->mac_address, ETH_ALEN); + + info->bss_chan = bss_desc->channel; + + memcpy(info->country_code, adapter->country_code, + IEEE80211_COUNTRY_STRING_LEN); + + info->media_connected = priv->media_connected; + + info->max_power_level = priv->max_tx_power_level; + info->min_power_level = priv->min_tx_power_level; + + info->bcn_nf_last = priv->bcn_nf_last; + + if (priv->sec_info.wep_enabled) + info->wep_status = true; + else + info->wep_status = false; + + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->is_deep_sleep = adapter->is_deep_sleep; + + return 0; +} + +/* The function disables auto deep sleep mode */ +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv) +{ + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_disable_auto_ds); + +/* Sends IOCTL request to get the data rate */ +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + if (priv->is_data_rate_auto) + *rate = nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + else + *rate = priv->data_rate; + } + + return ret; +} + +/* IOCTL request handler to set tx power configuration */ +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg) +{ + int ret; + struct host_cmd_ds_txpwr_cfg *txp_cfg; + struct nxpwifi_types_power_group *pg_tlv; + struct nxpwifi_power_group *pg; + u8 *buf; + u16 dbm = 0; + + if (!power_cfg->is_power_auto) { + dbm = (u16)power_cfg->power_level; + if (dbm < priv->min_tx_power_level || + dbm > priv->max_tx_power_level) { + nxpwifi_dbg(priv->adapter, ERROR, + "txpower value %d dBm\t" + "is out of range (%d dBm-%d dBm)\n", + dbm, priv->min_tx_power_level, + priv->max_tx_power_level); + return -EINVAL; + } + } + buf = kzalloc(NXPWIFI_SIZE_OF_CMD_BUFFER, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + txp_cfg = (struct host_cmd_ds_txpwr_cfg *)buf; + txp_cfg->action = cpu_to_le16(HOST_ACT_GEN_SET); + if (!power_cfg->is_power_auto) { + u16 dbm_min = power_cfg->is_power_fixed ? + dbm : priv->min_tx_power_level; + + txp_cfg->mode = cpu_to_le32(1); + pg_tlv = (struct nxpwifi_types_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg)); + pg_tlv->type = cpu_to_le16(TLV_TYPE_POWER_GROUP); + pg_tlv->length = + cpu_to_le16(4 * sizeof(struct nxpwifi_power_group)); + pg = (struct nxpwifi_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group)); + /* Power group for modulation class HR/DSSS */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x03; + pg->modulation_class = MOD_CLASS_HR_DSSS; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class OFDM */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x07; + pg->modulation_class = MOD_CLASS_OFDM; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class HTBW20 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_20; + pg++; + /* Power group for modulation class HTBW40 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_40; + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_TXPWR_CFG, + HOST_ACT_GEN_SET, 0, buf, true); + + kfree(buf); + return ret; +} + +/* IOCTL request handler to get power save mode */ +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u16 sub_cmd; + + if (*ps_mode) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + sub_cmd = (*ps_mode) ? EN_AUTO_PS : DIS_AUTO_PS; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + sub_cmd, BITMAP_STA_PS, NULL, true); + if (!ret && sub_cmd == DIS_AUTO_PS) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + GET_PS, 0, NULL, false); + + return ret; +} + +/* IOCTL request handler to set/reset WPA element */ +static int nxpwifi_set_wpa_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > sizeof(priv->wpa_ie)) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to copy WPA element, too big\n"); + return -EINVAL; + } + memcpy(priv->wpa_ie, ie_data_ptr, ie_len); + priv->wpa_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + + if (priv->wpa_ie[0] == WLAN_EID_VENDOR_SPECIFIC) { + priv->sec_info.wpa_enabled = true; + } else if (priv->wpa_ie[0] == WLAN_EID_RSN) { + priv->sec_info.wpa2_enabled = true; + } else { + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + } else { + memset(priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + priv->wpa_ie_len = 0; + nxpwifi_dbg(priv->adapter, INFO, + "info: reset WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + + return 0; +} + +/* IOCTL request handler to set/reset WPS element */ +static int nxpwifi_set_wps_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: failed to copy WPS element, too big\n"); + return -EINVAL; + } + + priv->wps_ie = kzalloc(NXPWIFI_MAX_VSIE_LEN, GFP_KERNEL); + if (!priv->wps_ie) + return -ENOMEM; + + memcpy(priv->wps_ie, ie_data_ptr, ie_len); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPS element len=%d element=%#x\n", + priv->wps_ie_len, priv->wps_ie[0]); + } else { + kfree(priv->wps_ie); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, INFO, + "info: Reset WPS element len=%d\n", priv->wps_ie_len); + } + return 0; +} + +/* IOCTL request handler to set WEP network key */ +static int +nxpwifi_sec_ioctl_set_wep_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_wep_key *wep_key; + int index; + + if (priv->wep_key_curr_index >= NUM_WEP_KEYS) + priv->wep_key_curr_index = 0; + wep_key = &priv->wep_key[priv->wep_key_curr_index]; + index = encrypt_key->key_index; + if (encrypt_key->key_disable) { + priv->sec_info.wep_enabled = 0; + } else if (!encrypt_key->key_len) { + /* Copy the required key as the current key */ + wep_key = &priv->wep_key[index]; + if (!wep_key->key_length) { + nxpwifi_dbg(adapter, ERROR, + "key not set, so cannot enable it\n"); + return -EINVAL; + } + + memcpy(encrypt_key->key_material, + wep_key->key_material, wep_key->key_length); + encrypt_key->key_len = wep_key->key_length; + + priv->wep_key_curr_index = (u16)index; + priv->sec_info.wep_enabled = 1; + } else { + wep_key = &priv->wep_key[index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + /* Copy the key in the driver */ + memcpy(wep_key->key_material, + encrypt_key->key_material, + encrypt_key->key_len); + wep_key->key_index = index; + wep_key->key_length = encrypt_key->key_len; + priv->sec_info.wep_enabled = 1; + } + if (wep_key->key_length) { + void *enc_key; + + if (encrypt_key->key_disable) { + memset(&priv->wep_key[index], 0, + sizeof(struct nxpwifi_wep_key)); + goto done; + } + + enc_key = encrypt_key; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, 0, enc_key, false); + if (ret) + return ret; + } + +done: + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} + +/* IOCTL request handler to set WPA key */ +static int +nxpwifi_sec_ioctl_set_wpa_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int ret; + u8 remove_key = false; + + /* Current driver only supports key length of up to 32 bytes */ + if (encrypt_key->key_len > WLAN_MAX_KEY_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "key length too long\n"); + return -EINVAL; + } + + if (!encrypt_key->key_index) + encrypt_key->key_index = NXPWIFI_KEY_INDEX_UNICAST; + + if (remove_key) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + !KEY_INFO_ENABLED, encrypt_key, true); + else + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + KEY_INFO_ENABLED, encrypt_key, true); + + return ret; +} + +/* IOCTL request handler to set/get network keys */ +static int +nxpwifi_sec_ioctl_encrypt_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int status; + + if (encrypt_key->key_len > WLAN_KEY_LEN_WEP104) + status = nxpwifi_sec_ioctl_set_wpa_key(priv, encrypt_key); + else + status = nxpwifi_sec_ioctl_set_wep_key(priv, encrypt_key); + + return status; +} + +/* Return driver version string */ +int +nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, char *version, + int max_len) +{ + union { + __le32 l; + u8 c[4]; + } ver; + char fw_ver[32]; + + ver.l = cpu_to_le32(adapter->fw_release_number); + sprintf(fw_ver, "%u.%u.%u.p%u.%u", ver.c[2], ver.c[1], + ver.c[0], ver.c[3], adapter->fw_hotfix_ver); + + snprintf(version, max_len, nxpwifi_driver_version, fw_ver); + + nxpwifi_dbg(adapter, MSG, "info: NXPWIFI VERSION: %s\n", version); + + return 0; +} + +/* Sends IOCTL request to set encoding parameters */ +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable) +{ + struct nxpwifi_ds_encrypt_key encrypt_key; + + memset(&encrypt_key, 0, sizeof(encrypt_key)); + encrypt_key.key_len = key_len; + encrypt_key.key_index = key_index; + + if (kp) { + encrypt_key.key_cipher = kp->cipher; + if (kp->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + kp->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + encrypt_key.is_igtk_key = true; + } + + if (!disable) { + if (key_len) + memcpy(encrypt_key.key_material, key, key_len); + else + encrypt_key.is_current_wep_key = true; + + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + if (kp && kp->seq && kp->seq_len) { + memcpy(encrypt_key.pn, kp->seq, kp->seq_len); + encrypt_key.pn_len = kp->seq_len; + encrypt_key.is_rx_seq_valid = true; + } + } else { + encrypt_key.key_disable = true; + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + } + + return nxpwifi_sec_ioctl_encrypt_key(priv, &encrypt_key); +} + +/* Sends IOCTL request to get extended version */ +int +nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel) +{ + struct nxpwifi_ver_ext ver_ext; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = version_str_sel; + + return nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, true); +} + +int +nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration) +{ + struct host_cmd_ds_remain_on_chan roc_cfg; + u8 sc; + int ret; + + memset(&roc_cfg, 0, sizeof(roc_cfg)); + roc_cfg.action = cpu_to_le16(action); + if (action == HOST_ACT_GEN_SET) { + roc_cfg.band_cfg = chan->band; + sc = nxpwifi_chan_type_to_sec_chan_offset(NL80211_CHAN_NO_HT); + roc_cfg.band_cfg |= (sc << 2); + + roc_cfg.channel = + ieee80211_frequency_to_channel(chan->center_freq); + roc_cfg.duration = cpu_to_le32(duration); + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_REMAIN_ON_CHAN, + action, 0, &roc_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to remain on channel\n"); + return ret; + } + + return roc_cfg.status; +} + +/* Sends IOCTL request to get statistics information */ +int +nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_GET_LOG, + HOST_ACT_GEN_GET, 0, log, true); +} + +/* IOCTL request handler to read/write register */ +static int nxpwifi_reg_mem_ioctl_reg_rw(struct nxpwifi_private *priv, + struct nxpwifi_ds_reg_rw *reg_rw, + u16 action) +{ + u16 cmd_no; + + switch (reg_rw->type) { + case NXPWIFI_REG_MAC: + cmd_no = HOST_CMD_MAC_REG_ACCESS; + break; + case NXPWIFI_REG_BBP: + cmd_no = HOST_CMD_BBP_REG_ACCESS; + break; + case NXPWIFI_REG_RF: + cmd_no = HOST_CMD_RF_REG_ACCESS; + break; + case NXPWIFI_REG_PMIC: + cmd_no = HOST_CMD_PMIC_REG_ACCESS; + break; + case NXPWIFI_REG_CAU: + cmd_no = HOST_CMD_CAU_REG_ACCESS; + break; + default: + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd_no, action, 0, reg_rw, true); +} + +/* Sends IOCTL request to write to a register */ +int +nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value) +{ + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + reg_rw.value = reg_value; + + return nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_SET); +} + +/* Sends IOCTL request to read from a register */ +int +nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value) +{ + int ret; + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + ret = nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_GET); + + if (!ret) + *value = reg_rw.value; + + return ret; +} + +/* Sends IOCTL request to read from EEPROM */ +int +nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value) +{ + int ret; + struct nxpwifi_ds_read_eeprom rd_eeprom; + + rd_eeprom.offset = offset; + rd_eeprom.byte_count = bytes; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_EEPROM_ACCESS, + HOST_ACT_GEN_GET, 0, &rd_eeprom, true); + + if (!ret) + memcpy(value, rd_eeprom.value, + min((u16)MAX_EEPROM_DATA, rd_eeprom.byte_count)); + return ret; +} + +/* Set generic IE(s); handle WPA/WPS specially */ +static int +nxpwifi_set_gen_ie_helper(struct nxpwifi_private *priv, u8 *ie_data_ptr, + u16 ie_len) +{ + struct ieee80211_vendor_ie *pvendor_ie; + static const u8 wpa_oui[] = { 0x00, 0x50, 0xf2, 0x01 }; + static const u8 wps_oui[] = { 0x00, 0x50, 0xf2, 0x04 }; + u16 unparsed_len = ie_len, cur_ie_len; + + /* If the passed length is zero, reset the buffer */ + if (!ie_len) { + priv->gen_ie_buf_len = 0; + priv->wps.session_enable = false; + return 0; + } else if (!ie_data_ptr || + ie_len <= sizeof(struct element)) { + return -EINVAL; + } + pvendor_ie = (struct ieee80211_vendor_ie *)ie_data_ptr; + + while (pvendor_ie) { + cur_ie_len = pvendor_ie->len + sizeof(struct element); + + if (pvendor_ie->element_id == WLAN_EID_RSN) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (pvendor_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + /* Test to see if it is a WPA element, if not, then it is a gen element */ + if (!memcmp(&pvendor_ie->oui, wpa_oui, + sizeof(wpa_oui))) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (!memcmp(&pvendor_ie->oui, wps_oui, + sizeof(wps_oui))) { + /* + * Test to see if it is a WPS element, if so, enable wps session + * flag + */ + priv->wps.session_enable = true; + nxpwifi_dbg(priv->adapter, MSG, + "WPS Session Enabled.\n"); + nxpwifi_set_wps_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + goto next_ie; + } + } + + /* + * Verify that the passed length is not larger than the available space + * remaining in the buffer + */ + if (cur_ie_len < + (sizeof(priv->gen_ie_buf) - priv->gen_ie_buf_len)) { + /* Append the passed data to the end of the genIeBuffer */ + memcpy(priv->gen_ie_buf + priv->gen_ie_buf_len, + (u8 *)pvendor_ie, cur_ie_len); + /* Increment the stored buffer length by the size passed */ + priv->gen_ie_buf_len += cur_ie_len; + } + +next_ie: + unparsed_len -= cur_ie_len; + + if (unparsed_len <= sizeof(struct element)) + pvendor_ie = NULL; + else + pvendor_ie = (struct ieee80211_vendor_ie *) + (((u8 *)pvendor_ie) + cur_ie_len); + } + + return 0; +} + +/* IOCTL request handler to set/get generic element */ +static int nxpwifi_misc_ioctl_gen_ie(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_gen_ie *gen_ie, + u16 action) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + switch (gen_ie->type) { + case NXPWIFI_IE_TYPE_GEN_IE: + if (action == HOST_ACT_GEN_GET) { + gen_ie->len = priv->wpa_ie_len; + memcpy(gen_ie->ie_data, priv->wpa_ie, gen_ie->len); + } else { + nxpwifi_set_gen_ie_helper(priv, gen_ie->ie_data, + (u16)gen_ie->len); + } + break; + case NXPWIFI_IE_TYPE_ARP_FILTER: + memset(adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + if (gen_ie->len > ARP_FILTER_MAX_BUF_SIZE) { + adapter->arp_filter_size = 0; + nxpwifi_dbg(adapter, ERROR, + "invalid ARP filter size\n"); + return -EINVAL; + } + memcpy(adapter->arp_filter, gen_ie->ie_data, gen_ie->len); + adapter->arp_filter_size = gen_ie->len; + break; + default: + nxpwifi_dbg(adapter, ERROR, "invalid element type\n"); + return -EINVAL; + } + return 0; +} + +/* Sends IOCTL request to set a generic element */ +int +nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len) +{ + struct nxpwifi_ds_misc_gen_ie gen_ie; + + if (ie_len > IEEE_MAX_IE_SIZE) + return -EFAULT; + + gen_ie.type = NXPWIFI_IE_TYPE_GEN_IE; + gen_ie.len = ie_len; + memcpy(gen_ie.ie_data, ie, ie_len); + + return nxpwifi_misc_ioctl_gen_ie(priv, &gen_ie, HOST_ACT_GEN_SET); +} + +/* Get Host Sleep wakeup reason */ +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_HS_WAKEUP_REASON, + HOST_ACT_GEN_GET, 0, wakeup_reason, + cmd_type == NXPWIFI_SYNC_CMD); +} + +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_STA_CONFIGURE, + HOST_ACT_GEN_GET, 0, channel_band, + NXPWIFI_SYNC_CMD); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c new file mode 100644 index 000000000000..5e8ffd306b31 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c @@ -0,0 +1,3393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: station command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +static bool disable_auto_ds; + +static int +nxpwifi_cmd_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &cmd->params.hw_spec; + + cmd->command = cpu_to_le16(HOST_CMD_GET_HW_SPEC); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_get_hw_spec) + + S_DS_GEN); + memcpy(hw_spec->permanent_addr, priv->curr_addr, ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &resp->params.hw_spec; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct hw_spec_api_rev *api_rev; + struct hw_spec_max_conn *max_conn; + struct hw_spec_extension *hw_he_cap; + struct hw_spec_fw_cap_info *fw_cap; + struct hw_spec_secure_boot_uuid *sb_uuid; + u16 resp_size, api_id; + int i, left_len, parsed_len = 0; + + adapter->fw_cap_info = le32_to_cpu(hw_spec->fw_cap_info); + + if (IS_SUPPORT_MULTI_BANDS(adapter)) + adapter->fw_bands = GET_FW_DEFAULT_BANDS(adapter); + else + adapter->fw_bands = BAND_B; + + if ((adapter->fw_bands & BAND_A) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands |= BAND_AN; + if (!(adapter->fw_bands & BAND_G) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands &= ~BAND_GN; + + adapter->fw_release_number = le32_to_cpu(hw_spec->fw_release_number); + adapter->fw_api_ver = (adapter->fw_release_number >> 16) & 0xff; + adapter->number_of_antenna = + le16_to_cpu(hw_spec->number_of_antenna) & 0xf; + + if (le32_to_cpu(hw_spec->dot_11ac_dev_cap)) { + adapter->is_hw_11ac_capable = true; + + /* Copy 11AC cap */ + adapter->hw_dot_11ac_dev_cap = + le32_to_cpu(hw_spec->dot_11ac_dev_cap); + adapter->usr_dot_11ac_dev_cap_bg = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + adapter->usr_dot_11ac_dev_cap_a = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + + /* Copy 11AC mcs */ + adapter->hw_dot_11ac_mcs_support = + le32_to_cpu(hw_spec->dot_11ac_mcs_support); + adapter->usr_dot_11ac_mcs_support = + adapter->hw_dot_11ac_mcs_support; + } else { + adapter->is_hw_11ac_capable = false; + } + + resp_size = le16_to_cpu(resp->size) - S_DS_GEN; + if (resp_size > sizeof(struct host_cmd_ds_get_hw_spec)) { + /* we have variable HW SPEC information */ + left_len = resp_size - sizeof(struct host_cmd_ds_get_hw_spec); + while (left_len > sizeof(struct nxpwifi_ie_types_header)) { + tlv = (void *)&hw_spec->tlv + parsed_len; + switch (le16_to_cpu(tlv->type)) { + case TLV_TYPE_API_REV: + api_rev = (struct hw_spec_api_rev *)tlv; + api_id = le16_to_cpu(api_rev->api_id); + switch (api_id) { + case KEY_API_VER_ID: + adapter->key_api_major_ver = + api_rev->major_ver; + adapter->key_api_minor_ver = + api_rev->minor_ver; + nxpwifi_dbg(adapter, INFO, + "key_api v%d.%d\n", + adapter->key_api_major_ver, + adapter->key_api_minor_ver); + break; + case FW_API_VER_ID: + adapter->fw_api_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, MSG, + "Firmware api version %d.%d\n", + adapter->fw_api_ver, + api_rev->minor_ver); + break; + case UAP_FW_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "uAP api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case CHANRPT_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "channel report api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case FW_HOTFIX_VER_ID: + adapter->fw_hotfix_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, INFO, + "Firmware hotfix version %d\n", + api_rev->major_ver); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown api_id: %d\n", + api_id); + break; + } + break; + case TLV_TYPE_MAX_CONN: + max_conn = (struct hw_spec_max_conn *)tlv; + adapter->max_sta_conn = max_conn->max_sta_conn; + nxpwifi_dbg(adapter, INFO, + "max sta connections: %u\n", + adapter->max_sta_conn); + break; + case TLV_TYPE_EXTENSION_ID: + hw_he_cap = (struct hw_spec_extension *)tlv; + if (hw_he_cap->ext_id == + WLAN_EID_EXT_HE_CAPABILITY) + nxpwifi_update_11ax_cap(adapter, hw_he_cap); + break; + case TLV_TYPE_FW_CAP_INFO: + fw_cap = (struct hw_spec_fw_cap_info *)tlv; + adapter->fw_cap_info = + le32_to_cpu(fw_cap->fw_cap_info); + adapter->fw_cap_ext = + le32_to_cpu(fw_cap->fw_cap_ext); + nxpwifi_dbg(adapter, INFO, + "fw_cap_info:%#x fw_cap_ext:%#x\n", + adapter->fw_cap_info, + adapter->fw_cap_ext); + break; + case TLV_TYPE_SECURE_BOOT_UUID: + sb_uuid = (struct hw_spec_secure_boot_uuid *)tlv; + adapter->uuid_lo = + le64_to_cpu(sb_uuid->uuid_lo); + adapter->uuid_hi = + le64_to_cpu(sb_uuid->uuid_hi); + nxpwifi_dbg(adapter, INFO, + "uuid: %#llx%#llx\n", + adapter->uuid_lo, adapter->uuid_hi); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown GET_HW_SPEC TLV type: %#x\n", + le16_to_cpu(tlv->type)); + break; + } + parsed_len += le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + left_len -= le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + + if (adapter->key_api_major_ver < KEY_API_VER_MAJOR_V2) + return -EOPNOTSUPP; + + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: fw_release_number- %#x\n", + adapter->fw_release_number); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: permanent addr: %pM\n", + hw_spec->permanent_addr); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: hw_if_version=%#x version=%#x\n", + le16_to_cpu(hw_spec->hw_if_version), + le16_to_cpu(hw_spec->version)); + + ether_addr_copy(priv->adapter->perm_addr, hw_spec->permanent_addr); + adapter->region_code = le16_to_cpu(hw_spec->region_code); + + /* If it's unidentified region code, use the default (USA/FCC) */ + if (!nxpwifi_is_valid_region_code(adapter->region_code)) { + nxpwifi_dbg(adapter, WARN, + "cmd: unknown region code %#x, use default (USA/%#x)\n", + adapter->region_code, NXPWIFI_DEFAULT_REGION_CODE); + adapter->region_code = NXPWIFI_DEFAULT_REGION_CODE; + } + + adapter->hw_dot_11n_dev_cap = le32_to_cpu(hw_spec->dot_11n_dev_cap); + adapter->hw_dev_mcs_support = hw_spec->dev_mcs_support; + adapter->hw_mpdu_density = GET_MPDU_DENSITY(le32_to_cpu(hw_spec->hw_dev_cap)); + adapter->user_dev_mcs_support = adapter->hw_dev_mcs_support; + adapter->user_htstream = adapter->hw_dev_mcs_support; + if (adapter->fw_bands & BAND_A) + adapter->user_htstream |= (adapter->user_htstream << 8); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = le16_to_cpu(hw_spec->mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, mp_end_port); + } + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + adapter->scan_chan_gap_enabled = true; + + for (i = 0; i < adapter->priv_num; i++) + adapter->priv[i]->config_bands = adapter->fw_bands; + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_GET_LOG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_get_log) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_get_log *get_log = + &resp->params.get_log; + struct nxpwifi_ds_get_stats *stats = + (struct nxpwifi_ds_get_stats *)data_buf; + + if (stats) { + stats->mcast_tx_frame = le32_to_cpu(get_log->mcast_tx_frame); + stats->failed = le32_to_cpu(get_log->failed); + stats->retry = le32_to_cpu(get_log->retry); + stats->multi_retry = le32_to_cpu(get_log->multi_retry); + stats->frame_dup = le32_to_cpu(get_log->frame_dup); + stats->rts_success = le32_to_cpu(get_log->rts_success); + stats->rts_failure = le32_to_cpu(get_log->rts_failure); + stats->ack_failure = le32_to_cpu(get_log->ack_failure); + stats->rx_frag = le32_to_cpu(get_log->rx_frag); + stats->mcast_rx_frame = le32_to_cpu(get_log->mcast_rx_frame); + stats->fcs_error = le32_to_cpu(get_log->fcs_error); + stats->tx_frame = le32_to_cpu(get_log->tx_frame); + stats->wep_icv_error[0] = + le32_to_cpu(get_log->wep_icv_err_cnt[0]); + stats->wep_icv_error[1] = + le32_to_cpu(get_log->wep_icv_err_cnt[1]); + stats->wep_icv_error[2] = + le32_to_cpu(get_log->wep_icv_err_cnt[2]); + stats->wep_icv_error[3] = + le32_to_cpu(get_log->wep_icv_err_cnt[3]); + stats->bcn_rcv_cnt = le32_to_cpu(get_log->bcn_rcv_cnt); + stats->bcn_miss_cnt = le32_to_cpu(get_log->bcn_miss_cnt); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_multicast_adr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_multicast_adr *mcast_addr = &cmd->params.mc_addr; + struct nxpwifi_multicast_list *mcast_list = + (struct nxpwifi_multicast_list *)data_buf; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mac_multicast_adr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_MAC_MULTICAST_ADR); + + mcast_addr->action = cpu_to_le16(cmd_action); + mcast_addr->num_of_adrs = + cpu_to_le16((u16)mcast_list->num_multicast_addr); + memcpy(mcast_addr->mac_list, mcast_list->mac_list, + mcast_list->num_multicast_addr * ETH_ALEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_associate(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_associate(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_snmp_mib *snmp_mib = &cmd->params.smib; + u16 *ul_temp = (u16 *)data_buf; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: cmd_oid = 0x%x\n", cmd_type); + cmd->command = cpu_to_le16(HOST_CMD_802_11_SNMP_MIB); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_snmp_mib) + + S_DS_GEN); + + snmp_mib->oid = cpu_to_le16((u16)cmd_type); + if (cmd_action == HOST_ACT_GEN_GET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_GET); + snmp_mib->buf_size = cpu_to_le16(MAX_SNMP_BUF_SIZE); + le16_unaligned_add_cpu(&cmd->size, MAX_SNMP_BUF_SIZE); + } else if (cmd_action == HOST_ACT_GEN_SET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_SET); + snmp_mib->buf_size = cpu_to_le16(sizeof(u16)); + put_unaligned_le16(*ul_temp, snmp_mib->value); + le16_unaligned_add_cpu(&cmd->size, sizeof(u16)); + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: Action=0x%x, OID=0x%x,\t" + "OIDSize=0x%x, Value=0x%x\n", + cmd_action, cmd_type, le16_to_cpu(snmp_mib->buf_size), + get_unaligned_le16(snmp_mib->value)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_snmp_mib *smib = &resp->params.smib; + u16 oid = le16_to_cpu(smib->oid); + u16 query_type = le16_to_cpu(smib->query_type); + u32 ul_temp; + + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: oid value = %#x,\t" + "query_type = %#x, buf size = %#x\n", + oid, query_type, le16_to_cpu(smib->buf_size)); + if (query_type == HOST_ACT_GEN_GET) { + ul_temp = get_unaligned_le16(smib->value); + if (data_buf) + *(u32 *)data_buf = ul_temp; + switch (oid) { + case FRAG_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: FragThsd =%u\n", + ul_temp); + break; + case RTS_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: RTSThsd =%u\n", + ul_temp); + break; + case SHORT_RETRY_LIM_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: TxRetryCount=%u\n", + ul_temp); + break; + case DTIM_PERIOD_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: DTIM period=%u\n", + ul_temp); + break; + default: + break; + } + } + + return 0; +} + +static int nxpwifi_cmd_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_reg_rw *reg_rw = data_buf; + + cmd->command = cpu_to_le16(cmd_no); + + switch (cmd_no) { + case HOST_CMD_MAC_REG_ACCESS: + { + struct host_cmd_ds_mac_reg_access *mac_reg; + + cmd->size = cpu_to_le16(sizeof(*mac_reg) + S_DS_GEN); + mac_reg = &cmd->params.mac_reg; + mac_reg->action = cpu_to_le16(cmd_action); + mac_reg->offset = cpu_to_le16((u16)reg_rw->offset); + mac_reg->value = cpu_to_le32(reg_rw->value); + break; + } + case HOST_CMD_BBP_REG_ACCESS: + { + struct host_cmd_ds_bbp_reg_access *bbp_reg; + + cmd->size = cpu_to_le16(sizeof(*bbp_reg) + S_DS_GEN); + bbp_reg = &cmd->params.bbp_reg; + bbp_reg->action = cpu_to_le16(cmd_action); + bbp_reg->offset = cpu_to_le16((u16)reg_rw->offset); + bbp_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_RF_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *rf_reg; + + cmd->size = cpu_to_le16(sizeof(*rf_reg) + S_DS_GEN); + rf_reg = &cmd->params.rf_reg; + rf_reg->action = cpu_to_le16(cmd_action); + rf_reg->offset = cpu_to_le16((u16)reg_rw->offset); + rf_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_PMIC_REG_ACCESS: + { + struct host_cmd_ds_pmic_reg_access *pmic_reg; + + cmd->size = cpu_to_le16(sizeof(*pmic_reg) + S_DS_GEN); + pmic_reg = &cmd->params.pmic_reg; + pmic_reg->action = cpu_to_le16(cmd_action); + pmic_reg->offset = cpu_to_le16((u16)reg_rw->offset); + pmic_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_CAU_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *cau_reg; + + cmd->size = cpu_to_le16(sizeof(*cau_reg) + S_DS_GEN); + cau_reg = &cmd->params.rf_reg; + cau_reg->action = cpu_to_le16(cmd_action); + cau_reg->offset = cpu_to_le16((u16)reg_rw->offset); + cau_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_802_11_EEPROM_ACCESS: + { + struct nxpwifi_ds_read_eeprom *rd_eeprom = data_buf; + struct host_cmd_ds_802_11_eeprom_access *cmd_eeprom = + &cmd->params.eeprom; + + cmd->size = cpu_to_le16(sizeof(*cmd_eeprom) + S_DS_GEN); + cmd_eeprom->action = cpu_to_le16(cmd_action); + cmd_eeprom->offset = cpu_to_le16(rd_eeprom->offset); + cmd_eeprom->byte_count = cpu_to_le16(rd_eeprom->byte_count); + cmd_eeprom->value = 0; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_ret_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_ds_reg_rw *reg_rw; + struct nxpwifi_ds_read_eeprom *eeprom; + union reg { + struct host_cmd_ds_mac_reg_access *mac; + struct host_cmd_ds_bbp_reg_access *bbp; + struct host_cmd_ds_rf_reg_access *rf; + struct host_cmd_ds_pmic_reg_access *pmic; + struct host_cmd_ds_802_11_eeprom_access *eeprom; + } r; + + if (!data_buf) + return 0; + + reg_rw = data_buf; + eeprom = data_buf; + switch (cmdresp_no) { + case HOST_CMD_MAC_REG_ACCESS: + r.mac = &resp->params.mac_reg; + reg_rw->offset = (u32)le16_to_cpu(r.mac->offset); + reg_rw->value = le32_to_cpu(r.mac->value); + break; + case HOST_CMD_BBP_REG_ACCESS: + r.bbp = &resp->params.bbp_reg; + reg_rw->offset = (u32)le16_to_cpu(r.bbp->offset); + reg_rw->value = (u32)r.bbp->value; + break; + + case HOST_CMD_RF_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.bbp->value; + break; + case HOST_CMD_PMIC_REG_ACCESS: + r.pmic = &resp->params.pmic_reg; + reg_rw->offset = (u32)le16_to_cpu(r.pmic->offset); + reg_rw->value = (u32)r.pmic->value; + break; + case HOST_CMD_CAU_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.rf->value; + break; + case HOST_CMD_802_11_EEPROM_ACCESS: + r.eeprom = &resp->params.eeprom; + pr_debug("info: EEPROM read len=%x\n", + le16_to_cpu(r.eeprom->byte_count)); + if (eeprom->byte_count < le16_to_cpu(r.eeprom->byte_count)) { + eeprom->byte_count = 0; + pr_debug("info: EEPROM read length is too big\n"); + return -ENOMEM; + } + eeprom->offset = le16_to_cpu(r.eeprom->offset); + eeprom->byte_count = le16_to_cpu(r.eeprom->byte_count); + if (eeprom->byte_count > 0) + memcpy(&eeprom->value, &r.eeprom->value, + min((u16)MAX_EEPROM_DATA, eeprom->byte_count)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int +nxpwifi_cmd_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &cmd->params.txp; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_tx_pwr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_RF_TX_PWR); + txp->action = cpu_to_le16(cmd_action); + + return 0; +} + +static int +nxpwifi_ret_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &resp->params.txp; + u16 action = le16_to_cpu(txp->action); + + priv->tx_power_level = le16_to_cpu(txp->cur_level); + + if (action == HOST_ACT_GEN_GET) { + priv->max_tx_power_level = txp->max_power; + priv->min_tx_power_level = txp->min_power; + } + + nxpwifi_dbg(priv->adapter, INFO, + "Current TxPower Level=%d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &cmd->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &cmd->params.ant_siso; + struct nxpwifi_ds_ant_cfg *ant_cfg = + (struct nxpwifi_ds_ant_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_RF_ANTENNA); + + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_SET_TX); + ant_mimo->tx_ant_mode = + cpu_to_le16((u16)ant_cfg->tx_ant); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_SET_RX); + ant_mimo->rx_ant_mode = + cpu_to_le16((u16)ant_cfg->rx_ant); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_SET_BOTH); + ant_siso->ant_mode = cpu_to_le16((u16)ant_cfg->tx_ant); + } + break; + case HOST_ACT_GEN_GET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_GET_TX); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_GET_RX); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_GET_BOTH); + } + break; + } + return 0; +} + +static int +nxpwifi_ret_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &resp->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &resp->params.ant_siso; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + priv->tx_ant = le16_to_cpu(ant_mimo->tx_ant_mode); + priv->rx_ant = le16_to_cpu(ant_mimo->rx_ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: Tx action = 0x%x, Tx Mode = 0x%04x\t" + "Rx action = 0x%x, Rx Mode = 0x%04x\n", + le16_to_cpu(ant_mimo->action_tx), + le16_to_cpu(ant_mimo->tx_ant_mode), + le16_to_cpu(ant_mimo->action_rx), + le16_to_cpu(ant_mimo->rx_ant_mode)); + } else { + priv->tx_ant = le16_to_cpu(ant_siso->ant_mode); + priv->rx_ant = le16_to_cpu(ant_siso->ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: action = 0x%x, Mode = 0x%04x\n", + le16_to_cpu(ant_siso->action), + le16_to_cpu(ant_siso->ant_mode)); + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_deauthenticate *deauth = &cmd->params.deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_DEAUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_deauthenticate) + + S_DS_GEN); + + /* Set AP MAC address */ + memcpy(deauth->mac_addr, mac, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, CMD, "cmd: Deauth: %pM\n", deauth->mac_addr); + + deauth->reason_code = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_cmd_deauth++; + if (!memcmp(resp->params.deauth.mac_addr, + &priv->curr_bss_params.bss_descriptor.mac_address, + sizeof(resp->params.deauth.mac_addr))) + nxpwifi_reset_connect_state(priv, WLAN_REASON_DEAUTH_LEAVING, + false); + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_control(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_control *mac_ctrl = &cmd->params.mac_ctrl; + u32 *action = (u32 *)data_buf; + + if (cmd_action != HOST_ACT_GEN_SET) { + nxpwifi_dbg(priv->adapter, ERROR, + "mac_control: only support set cmd\n"); + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_MAC_CONTROL); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_mac_control) + S_DS_GEN); + mac_ctrl->action = cpu_to_le32(*action); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_MAC_ADDRESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_mac_address) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.mac_addr.action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) + memcpy(cmd->params.mac_addr.mac_addr, priv->curr_addr, + ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_mac_address *cmd_mac_addr; + + cmd_mac_addr = &resp->params.mac_addr; + + memcpy(priv->curr_addr, cmd_mac_addr->mac_addr, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, INFO, + "info: set mac address: %pM\n", priv->curr_addr); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11d_domain_info *domain_info = + &cmd->params.domain_info; + struct nxpwifi_ietypes_domain_param_set *domain = + &domain_info->domain; + struct nxpwifi_ietypes_domain_code *domain_code; + u8 no_of_triplet = adapter->domain_reg.no_of_triplet; + int triplet_size; + + nxpwifi_dbg(adapter, INFO, + "info: 11D: no_of_triplet=0x%x\n", no_of_triplet); + + cmd->command = cpu_to_le16(HOST_CMD_802_11D_DOMAIN_INFO); + cmd->size = cpu_to_le16(S_DS_GEN); + domain_info->action = cpu_to_le16(cmd_action); + le16_unaligned_add_cpu(&cmd->size, sizeof(domain_info->action)); + + if (cmd_action == HOST_ACT_GEN_GET) + return 0; + + triplet_size = no_of_triplet * + sizeof(struct ieee80211_country_ie_triplet); + + domain->header.type = cpu_to_le16(WLAN_EID_COUNTRY); + domain->header.len = + cpu_to_le16(sizeof(domain->country_code) + triplet_size); + memcpy(domain->country_code, adapter->domain_reg.country_code, + sizeof(domain->country_code)); + if (no_of_triplet) + memcpy(domain->triplet, adapter->domain_reg.triplet, + triplet_size); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain) + triplet_size); + + domain_code = (struct nxpwifi_ietypes_domain_code *)((u8 *)cmd + + le16_to_cpu(cmd->size)); + domain_code->header.type = cpu_to_le16(TLV_TYPE_REGION_DOMAIN_CODE); + domain_code->header.len = + cpu_to_le16(sizeof(*domain_code) - + sizeof(struct nxpwifi_ie_types_header)); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain_code)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11d_domain_info_rsp *domain_info = + &resp->params.domain_info_resp; + struct nxpwifi_ietypes_domain_param_set *domain = &domain_info->domain; + u16 action = le16_to_cpu(domain_info->action); + u8 no_of_triplet; + + no_of_triplet = (u8)((le16_to_cpu(domain->header.len) + - IEEE80211_COUNTRY_STRING_LEN) + / sizeof(struct ieee80211_country_ie_triplet)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: 11D Domain Info Resp: no_of_triplet=%d\n", + no_of_triplet); + + if (no_of_triplet > NXPWIFI_MAX_TRIPLET_802_11D) { + nxpwifi_dbg(priv->adapter, FATAL, + "11D: invalid number of triplets %d returned\n", + no_of_triplet); + return -EINVAL; + } + + switch (action) { + case HOST_ACT_GEN_SET: /* Proc Set Action */ + break; + case HOST_ACT_GEN_GET: + break; + default: + nxpwifi_dbg(priv->adapter, ERROR, + "11D: invalid action:%d\n", domain_info->action); + return -EINVAL; + } + + return 0; +} + +static int nxpwifi_set_aes_key(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_ds_encrypt_key *enc_key, + struct host_cmd_ds_802_11_key_material *km) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 size, len = KEY_PARAMS_FIXED_LEN; + u8 key_type, key_type_igtk; + + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + key_type = KEY_TYPE_ID_AES; + key_type_igtk = KEY_TYPE_ID_AES_CMAC; + } else { + key_type = KEY_TYPE_ID_GCMP_256; + key_type_igtk = KEY_TYPE_ID_BIP_GMAC_256; + } + + if (enc_key->is_igtk_key) { + km->key_param_set.key_info &= cpu_to_le16(~KEY_MCAST); + km->key_param_set.key_info |= cpu_to_le16(KEY_IGTK); + km->key_param_set.key_type = key_type_igtk; + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.cmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.cmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.cmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_cmac_aes_param); + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set GMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.gmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.gmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.gmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_gmac_aes_param); + } + } else if (enc_key->is_igtk_def_key) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC default Key index\n", __func__); + km->key_param_set.key_type = key_type_igtk; + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.aes.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = key_type; + km->key_param_set.key_params.aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_aes_param); + } + + km->key_param_set.len = cpu_to_le16(len); + size = len + sizeof(struct nxpwifi_ie_types_header) + + sizeof(km->action) + S_DS_GEN; + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ds_encrypt_key *enc_key = + (struct nxpwifi_ds_encrypt_key *)data_buf; + u8 *mac = enc_key->mac_addr; + u16 key_info, len = KEY_PARAMS_FIXED_LEN; + struct host_cmd_ds_802_11_key_material *km = + &cmd->params.key_material; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_KEY_MATERIAL); + km->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_GET) { + nxpwifi_dbg(adapter, INFO, "%s: Get key\n", __func__); + km->key_param_set.key_idx = + enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_index & NXPWIFI_KEY_INDEX_UNICAST) + key_info = KEY_UNICAST; + else + key_info = KEY_MCAST; + + if (enc_key->is_igtk_key) + key_info |= KEY_IGTK; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + memset(&km->key_param_set, 0, + sizeof(struct nxpwifi_ie_type_key_param_set)); + + if (enc_key->key_disable) { + nxpwifi_dbg(adapter, INFO, "%s: Remove key\n", __func__); + km->action = cpu_to_le16(HOST_ACT_GEN_REMOVE); + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + key_info = KEY_MCAST | KEY_UNICAST; + km->key_param_set.key_info = cpu_to_le16(key_info); + ether_addr_copy(km->key_param_set.mac_addr, mac); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + km->action = cpu_to_le16(HOST_ACT_GEN_SET); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + key_info = KEY_ENABLED; + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_len <= WLAN_KEY_LEN_WEP104) { + nxpwifi_dbg(adapter, INFO, "%s: Set WEP Key\n", __func__); + len += sizeof(struct nxpwifi_wep_param); + km->key_param_set.len = cpu_to_le16(len); + km->key_param_set.key_type = KEY_TYPE_ID_WEP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + key_info |= KEY_MCAST | KEY_UNICAST; + } else { + if (enc_key->is_current_wep_key) { + key_info |= KEY_MCAST | KEY_UNICAST; + if (km->key_param_set.key_idx == + (priv->wep_key_curr_index & KEY_INDEX_MASK)) + key_info |= KEY_DEFAULT; + } else { + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST; + else + key_info |= KEY_UNICAST | KEY_DEFAULT; + } + } + km->key_param_set.key_info = cpu_to_le16(key_info); + + km->key_param_set.key_params.wep.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.wep.key, + enc_key->key_material, enc_key->key_len); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + return 0; + } + + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST | KEY_RX_KEY; + else + key_info |= KEY_UNICAST | KEY_TX_KEY | KEY_RX_KEY; + + /* Enable default key for WPA/WPA2 */ + if (!priv->wpa_is_gtk_set) + key_info |= KEY_DEFAULT; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + if (enc_key->key_cipher != WLAN_CIPHER_SUITE_TKIP && + enc_key->key_len >= WLAN_KEY_LEN_CCMP) + return nxpwifi_set_aes_key(priv, cmd, enc_key, km); + + if (enc_key->key_len == WLAN_KEY_LEN_TKIP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set TKIP Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.tkip.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = KEY_TYPE_ID_TKIP; + km->key_param_set.key_params.tkip.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.tkip.key, + enc_key->key_material, enc_key->key_len); + + len += sizeof(struct nxpwifi_tkip_param); + km->key_param_set.len = cpu_to_le16(len); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + } + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_key_material *key; + int len; + + key = &resp->params.key_material; + + len = le16_to_cpu(key->key_param_set.key_params.aes.key_len); + if (len > sizeof(key->key_param_set.key_params.aes.key)) + return -EINVAL; + + if (le16_to_cpu(key->action) == HOST_ACT_GEN_SET) { + if ((le16_to_cpu(key->key_param_set.key_info) & KEY_MCAST)) { + nxpwifi_dbg(priv->adapter, INFO, + "info: key: GTK is set\n"); + priv->wpa_is_gtk_set = true; + priv->scan_block = false; + priv->port_open = true; + } + } + + if (key->key_param_set.key_type != KEY_TYPE_ID_AES && + key->key_param_set.key_type != KEY_TYPE_ID_GCMP_256) + return 0; + + memset(priv->aes_key.key_param_set.key_params.aes.key, 0, + sizeof(key->key_param_set.key_params.aes.key)); + priv->aes_key.key_param_set.key_params.aes.key_len = cpu_to_le16(len); + memcpy(priv->aes_key.key_param_set.key_params.aes.key, + key->key_param_set.key_params.aes.key, len); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_config(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_query(cmd); +} + +static int +nxpwifi_ret_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + cfg80211_sched_scan_results(priv->wdev.wiphy, 0); + nxpwifi_dbg(adapter, CMD, + "info: CMD_RESP: BG_SCAN result is ready!\n"); + + return ret; +} + +static int +nxpwifi_cmd_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_WMM_GET_STATUS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wmm_get_status) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_wmm_get_status(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_subsc_evt *subsc_evt = &cmd->params.subsc_evt; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt_cfg = + (struct nxpwifi_ds_misc_subsc_evt *)data_buf; + struct nxpwifi_ie_types_rssi_threshold *rssi_tlv; + u16 event_bitmap; + u8 *pos; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SUBSCRIBE_EVENT); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_subsc_evt) + + S_DS_GEN); + + subsc_evt->action = cpu_to_le16(subsc_evt_cfg->action); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: action: %d\n", subsc_evt_cfg->action); + + /* For query requests, no configuration TLV structures are to be added. */ + if (subsc_evt_cfg->action == HOST_ACT_GEN_GET) + return 0; + + subsc_evt->events = cpu_to_le16(subsc_evt_cfg->events); + + event_bitmap = subsc_evt_cfg->events; + nxpwifi_dbg(priv->adapter, CMD, "cmd: event bitmap : %16x\n", + event_bitmap); + + if ((subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR || + subsc_evt_cfg->action == HOST_ACT_BITWISE_SET) && + event_bitmap == 0) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error: No event specified\t" + "for bitwise action type\n"); + return -EINVAL; + } + + /* + * Append TLV structures for each of the specified events for + * subscribing or re-configuring. This is not required for + * bitwise unsubscribing request. + */ + if (subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR) + return 0; + + pos = ((u8 *)subsc_evt) + + sizeof(struct host_cmd_ds_802_11_subsc_evt); + + if (event_bitmap & BITMASK_BCN_RSSI_LOW) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_l_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon Low Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_l_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + if (event_bitmap & BITMASK_BCN_RSSI_HIGH) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_HIGH); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_h_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon High Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_h_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + return 0; +} + +static int +nxpwifi_ret_sta_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_subsc_evt *cmd_sub_event = + &resp->params.subsc_evt; + + /* + * For every subscribe event command (Get/Set/Clear), FW reports the current + * set of subscribed events + */ + nxpwifi_dbg(priv->adapter, EVENT, + "Bitmap of currently subscribed events: %16x\n", + le16_to_cpu(cmd_sub_event->events)); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_TX_RATE_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_tx_rate_query) + + S_DS_GEN); + priv->tx_rate = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->tx_rate = resp->params.tx_rate.tx_rate; + priv->tx_htinfo = resp->params.tx_rate.ht_info; + if (!priv->is_data_rate_auto) + priv->data_rate = + nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + + return 0; +} + +static int +nxpwifi_cmd_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_mem_rw *mem_rw = + (struct nxpwifi_ds_mem_rw *)data_buf; + struct host_cmd_ds_mem_access *mem_access = (void *)&cmd->params.mem; + + cmd->command = cpu_to_le16(HOST_CMD_MEM_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mem_access) + + S_DS_GEN); + + mem_access->action = cpu_to_le16(cmd_action); + mem_access->addr = cpu_to_le32(mem_rw->addr); + mem_access->value = cpu_to_le32(mem_rw->value); + + return 0; +} + +static int +nxpwifi_ret_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_mem_access *mem = (void *)&resp->params.mem; + + priv->mem_rw.addr = le32_to_cpu(mem->addr); + priv->mem_rw.value = le32_to_cpu(mem->value); + + return 0; +} + +static u32 nxpwifi_parse_cal_cfg(u8 *src, size_t len, u8 *dst) +{ + u8 *s = src, *d = dst; + + while (s - src < len) { + if (*s && (isspace(*s) || *s == '\t')) { + s++; + continue; + } + if (isxdigit(*s)) { + if (kstrtou8(s, 16, d)) + return 0; + d++; + s += 2; + } else { + s++; + } + } + + return d - dst; +} + +static int +nxpwifi_cmd_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 len; + u8 *data = (u8 *)cmd + S_DS_GEN; + + if (adapter->cal_data->data && adapter->cal_data->size > 0) { + len = nxpwifi_parse_cal_cfg((u8 *)adapter->cal_data->data, + adapter->cal_data->size, data); + nxpwifi_dbg(adapter, INFO, + "download cfg_data from config file\n"); + } else { + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_CFG_DATA); + cmd->size = cpu_to_le16(S_DS_GEN + len); + + return 0; +} + +static int +nxpwifi_ret_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + if (resp->result != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, "Cal data cmd resp failed\n"); + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.verext.version_str_sel = + (u8)(get_unaligned((u32 *)data_buf)); + memcpy(&cmd->params, data_buf, sizeof(struct host_cmd_ds_version_ext)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_version_ext) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + struct host_cmd_ds_version_ext *version_ext = + (struct host_cmd_ds_version_ext *)data_buf; + + if (test_and_clear_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { + if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", + NXPWIFI_VERSION_STR_LENGTH) == 0) { + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + nxpwifi_dbg(priv->adapter, MSG, + "Bad HW revision detected, disabling deep sleep\n"); + + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Disabling deep sleep failed.\n"); + } + } + + return 0; + } + + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + memcpy(priv->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + + /* Ensure the version string from the firmware is 0-terminated */ + priv->version_str[NXPWIFI_VERSION_STR_LENGTH - 1] = '\0'; + } + return 0; +} + +static int +nxpwifi_cmd_append_rpn_expression(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry, + u8 **buffer) +{ + struct nxpwifi_mef_filter *filter = mef_entry->filter; + int i, byte_len; + u8 *stack_ptr = *buffer; + + for (i = 0; i < NXPWIFI_MEF_MAX_FILTERS; i++) { + filter = &mef_entry->filter[i]; + if (!filter->filt_type) + break; + put_unaligned_le32((u32)filter->repeat, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + byte_len = filter->byte_seq[NXPWIFI_MEF_MAX_BYTESEQ]; + memcpy(stack_ptr, filter->byte_seq, byte_len); + stack_ptr += byte_len; + *stack_ptr = byte_len; + stack_ptr += 1; + *stack_ptr = TYPE_BYTESEQ; + stack_ptr += 1; + put_unaligned_le32((u32)filter->offset, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + *stack_ptr = filter->filt_type; + stack_ptr += 1; + + if (filter->filt_action) { + *stack_ptr = filter->filt_action; + stack_ptr += 1; + } + + if (stack_ptr - *buffer > STACK_NBYTES) + return -ENOMEM; + } + + *buffer = stack_ptr; + return 0; +} + +static int +nxpwifi_cmd_sta_mef_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mef_cfg *mef_cfg = &cmd->params.mef_cfg; + struct nxpwifi_ds_mef_cfg *mef = + (struct nxpwifi_ds_mef_cfg *)data_buf; + struct nxpwifi_fw_mef_entry *mef_entry = NULL; + u8 *pos = (u8 *)mef_cfg; + u16 i; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_MEF_CFG); + + mef_cfg->criteria = cpu_to_le32(mef->criteria); + mef_cfg->num_entries = cpu_to_le16(mef->num_entries); + pos += sizeof(*mef_cfg); + + for (i = 0; i < mef->num_entries; i++) { + mef_entry = (struct nxpwifi_fw_mef_entry *)pos; + mef_entry->mode = mef->mef_entry[i].mode; + mef_entry->action = mef->mef_entry[i].action; + pos += sizeof(*mef_entry); + + ret = nxpwifi_cmd_append_rpn_expression(priv, + &mef->mef_entry[i], + &pos); + if (ret) + return ret; + + mef_entry->exprsize = + cpu_to_le16(pos - mef_entry->expr); + } + cmd->size = cpu_to_le16((u16)(pos - (u8 *)mef_cfg) + S_DS_GEN); + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_RSSI_INFO); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_rssi_info) + + S_DS_GEN); + cmd->params.rssi_info.action = cpu_to_le16(cmd_action); + cmd->params.rssi_info.ndata = cpu_to_le16(priv->data_avg_factor); + cmd->params.rssi_info.nbcn = cpu_to_le16(priv->bcn_avg_factor); + + /* Reset SNR/NF/RSSI values in private structure */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_rssi_info_rsp *rssi_info_rsp = + &resp->params.rssi_info_rsp; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt = + &priv->async_subsc_evt_storage; + + priv->data_rssi_last = le16_to_cpu(rssi_info_rsp->data_rssi_last); + priv->data_nf_last = le16_to_cpu(rssi_info_rsp->data_nf_last); + + priv->data_rssi_avg = le16_to_cpu(rssi_info_rsp->data_rssi_avg); + priv->data_nf_avg = le16_to_cpu(rssi_info_rsp->data_nf_avg); + + priv->bcn_rssi_last = le16_to_cpu(rssi_info_rsp->bcn_rssi_last); + priv->bcn_nf_last = le16_to_cpu(rssi_info_rsp->bcn_nf_last); + + priv->bcn_rssi_avg = le16_to_cpu(rssi_info_rsp->bcn_rssi_avg); + priv->bcn_nf_avg = le16_to_cpu(rssi_info_rsp->bcn_nf_avg); + + if (priv->subsc_evt_rssi_state == EVENT_HANDLED) + return 0; + + memset(subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + + /* Resubscribe low and high rssi events with new thresholds */ + subsc_evt->events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + subsc_evt->action = HOST_ACT_BITWISE_SET; + if (priv->subsc_evt_rssi_state == RSSI_LOW_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg - + priv->cqm_rssi_hyst); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + } else if (priv->subsc_evt_rssi_state == RSSI_HIGH_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg + + priv->cqm_rssi_hyst); + } + subsc_evt->bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt->bcn_h_rssi_cfg.evt_freq = 1; + + priv->subsc_evt_rssi_state = EVENT_HANDLED; + + nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, subsc_evt, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_init(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + if (priv->adapter->hw_status == NXPWIFI_HW_STATUS_RESET) + priv->adapter->hw_status = NXPWIFI_HW_STATUS_READY; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_shutdown(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + priv->adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_req(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_req(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_rsp_gen(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_resp(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_delba(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_delba(priv, resp); +} + +static int +nxpwifi_cmd_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_types_power_group *pg_tlv; + struct host_cmd_ds_txpwr_cfg *cmd_txp_cfg = &cmd->params.txp_cfg; + struct host_cmd_ds_txpwr_cfg *txp = + (struct host_cmd_ds_txpwr_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_TXPWR_CFG); + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_txpwr_cfg)); + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (txp->mode) { + pg_tlv = (struct nxpwifi_types_power_group + *)((unsigned long)txp + + sizeof(struct host_cmd_ds_txpwr_cfg)); + memmove(cmd_txp_cfg, txp, + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + + pg_tlv = (struct nxpwifi_types_power_group *)((u8 *) + cmd_txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + cmd->size = cpu_to_le16(le16_to_cpu(cmd->size) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + } else { + memmove(cmd_txp_cfg, txp, sizeof(*txp)); + } + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + case HOST_ACT_GEN_GET: + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + } + + return 0; +} + +static int nxpwifi_get_power_level(struct nxpwifi_private *priv, void *data_buf) +{ + int length, max_power = -1, min_power = -1; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + + if (!data_buf) + return -ENOMEM; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *)((u8 *)data_buf); + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + sizeof(struct nxpwifi_types_power_group)); + length = le16_to_cpu(pg_tlv_hdr->length); + + /* At least one structure required to update power */ + if (length < sizeof(struct nxpwifi_power_group)) + return 0; + + max_power = pg->power_max; + min_power = pg->power_min; + length -= sizeof(struct nxpwifi_power_group); + + while (length >= sizeof(struct nxpwifi_power_group)) { + pg++; + if (max_power < pg->power_max) + max_power = pg->power_max; + + if (min_power > pg->power_min) + min_power = pg->power_min; + + length -= sizeof(struct nxpwifi_power_group); + } + priv->min_tx_power_level = (u8)min_power; + priv->max_tx_power_level = (u8)max_power; + + return 0; +} + +static int +nxpwifi_ret_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_txpwr_cfg *txp_cfg = &resp->params.txp_cfg; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + u16 action = le16_to_cpu(txp_cfg->action); + u16 tlv_buf_left; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *) + ((u8 *)txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + + sizeof(struct nxpwifi_types_power_group)); + + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*txp_cfg); + if (tlv_buf_left < + le16_to_cpu(pg_tlv_hdr->length) + sizeof(*pg_tlv_hdr)) + return 0; + + switch (action) { + case HOST_ACT_GEN_GET: + if (adapter->hw_status == NXPWIFI_HW_STATUS_INITIALIZING) + nxpwifi_get_power_level(priv, pg_tlv_hdr); + + priv->tx_power_level = (u16)pg->power_min; + break; + + case HOST_ACT_GEN_SET: + if (!le32_to_cpu(txp_cfg->mode)) + break; + + if (pg->power_max == pg->power_min) + priv->tx_power_level = (u16)pg->power_min; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: unknown cmd action %d\n", + action); + return 0; + } + nxpwifi_dbg(adapter, INFO, + "info: Current TxPower Level = %d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &cmd->params.tx_rate_cfg; + u16 *pbitmap_rates = (u16 *)data_buf; + struct nxpwifi_rate_scope *rate_scope; + struct nxpwifi_rate_drop_pattern *rate_drop; + u32 i; + + cmd->command = cpu_to_le16(HOST_CMD_TX_RATE_CFG); + + rate_cfg->action = cpu_to_le16(cmd_action); + rate_cfg->cfg_index = 0; + + rate_scope = (struct nxpwifi_rate_scope *)((u8 *)rate_cfg + + sizeof(struct host_cmd_ds_tx_rate_cfg)); + rate_scope->type = cpu_to_le16(TLV_TYPE_RATE_SCOPE); + rate_scope->length = cpu_to_le16 + (sizeof(*rate_scope) - sizeof(struct nxpwifi_ie_types_header)); + if (pbitmap_rates) { + rate_scope->hr_dsss_rate_bitmap = cpu_to_le16(pbitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = cpu_to_le16(pbitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[10 + i]); + } + } else { + rate_scope->hr_dsss_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[10 + i]); + } + } + + rate_drop = (struct nxpwifi_rate_drop_pattern *)((u8 *)rate_scope + + sizeof(struct nxpwifi_rate_scope)); + rate_drop->type = cpu_to_le16(TLV_TYPE_RATE_DROP_CONTROL); + rate_drop->length = cpu_to_le16(sizeof(rate_drop->rate_drop_mode)); + rate_drop->rate_drop_mode = 0; + + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_tx_rate_cfg) + + sizeof(struct nxpwifi_rate_scope) + + sizeof(struct nxpwifi_rate_drop_pattern)); + + return 0; +} + +static void nxpwifi_ret_rate_scope(struct nxpwifi_private *priv, u8 *tlv_buf) +{ + struct nxpwifi_rate_scope *rate_scope; + int i; + + rate_scope = (struct nxpwifi_rate_scope *)tlv_buf; + priv->bitmap_rates[0] = + le16_to_cpu(rate_scope->hr_dsss_rate_bitmap); + priv->bitmap_rates[1] = + le16_to_cpu(rate_scope->ofdm_rate_bitmap); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + priv->bitmap_rates[2 + i] = + le16_to_cpu(rate_scope->ht_mcs_rate_bitmap[i]); + + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + priv->bitmap_rates[10 + i] = + le16_to_cpu(rate_scope->vht_mcs_rate_bitmap[i]); + } +} + +static int +nxpwifi_ret_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &resp->params.tx_rate_cfg; + struct nxpwifi_ie_types_header *head; + u16 tlv, tlv_buf_len, tlv_buf_left; + u8 *tlv_buf; + + tlv_buf = ((u8 *)rate_cfg) + sizeof(struct host_cmd_ds_tx_rate_cfg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*rate_cfg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_RATE_SCOPE: + nxpwifi_ret_rate_scope(priv, tlv_buf); + break; + /* Add RATE_DROP tlv here */ + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + priv->is_data_rate_auto = nxpwifi_is_rate_auto(priv); + + if (priv->is_data_rate_auto) + priv->data_rate = 0; + else + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_recfg_tx_buf(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (0xffff != (u16)le16_to_cpu(resp->params.tx_buf.buff_size)) { + adapter->tx_buf_size = + (u16)le16_to_cpu(resp->params.tx_buf.buff_size); + adapter->tx_buf_size = + (adapter->tx_buf_size / NXPWIFI_SDIO_BLOCK_SIZE) * + NXPWIFI_SDIO_BLOCK_SIZE; + adapter->curr_tx_buf_size = adapter->tx_buf_size; + nxpwifi_dbg(adapter, CMD, "cmd: curr_tx_buf_size=%d\n", + adapter->curr_tx_buf_size); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = + le16_to_cpu(resp->params.tx_buf.mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, + mp_end_port); + } + } + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_amsdu_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_amsdu_aggr_ctrl(cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_robust_coex *coex = &cmd->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + + cmd->command = cpu_to_le16(HOST_CMD_ROBUST_COEX); + cmd->size = cpu_to_le16(sizeof(*coex) + sizeof(*coex_tlv) + S_DS_GEN); + + coex->action = cpu_to_le16(cmd_action); + coex_tlv = (struct nxpwifi_ie_types_robust_coex *) + ((u8 *)coex + sizeof(*coex)); + coex_tlv->header.type = cpu_to_le16(TLV_TYPE_ROBUST_COEX); + coex_tlv->header.len = cpu_to_le16(sizeof(coex_tlv->mode)); + + if (coex->action == HOST_ACT_GEN_GET) + return 0; + + if (*is_timeshare) + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_TIMESHARE); + else + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_SPATIAL); + + return 0; +} + +static int +nxpwifi_ret_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_robust_coex *coex = &resp->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + u16 action = le16_to_cpu(coex->action); + u32 mode; + + if (!is_timeshare) + return 0; + + coex_tlv = (struct nxpwifi_ie_types_robust_coex + *)((u8 *)coex + sizeof(struct host_cmd_ds_robust_coex)); + if (action == HOST_ACT_GEN_GET) { + mode = le32_to_cpu(coex_tlv->mode); + if (mode == NXPWIFI_COEX_MODE_TIMESHARE) + *is_timeshare = true; + else + *is_timeshare = false; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_ps_mode_enh *psmode_enh = + &cmd->params.psmode_enh; + u16 ps_bitmap = (u16)cmd_type; + struct nxpwifi_ds_auto_ds *auto_ds = + (struct nxpwifi_ds_auto_ds *)data_buf; + u8 *tlv; + u16 cmd_size = 0; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + if (cmd_action == DIS_AUTO_PS) { + psmode_enh->action = cpu_to_le16(DIS_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == GET_PS) { + psmode_enh->action = cpu_to_le16(GET_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == EN_AUTO_PS) { + psmode_enh->action = cpu_to_le16(EN_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd_size = S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap); + tlv = (u8 *)cmd + cmd_size; + if (ps_bitmap & BITMAP_STA_PS) { + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_ps_param *ps_tlv = + (struct nxpwifi_ie_types_ps_param *)tlv; + struct nxpwifi_ps_param *ps_mode = &ps_tlv->param; + + ps_tlv->header.type = cpu_to_le16(TLV_TYPE_PS_PARAM); + ps_tlv->header.len = cpu_to_le16(sizeof(*ps_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*ps_tlv); + tlv += sizeof(*ps_tlv); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter PS\n"); + ps_mode->null_pkt_interval = + cpu_to_le16(adapter->null_pkt_interval); + ps_mode->multiple_dtims = + cpu_to_le16(adapter->multiple_dtim); + ps_mode->bcn_miss_timeout = + cpu_to_le16(adapter->bcn_miss_time_out); + ps_mode->local_listen_interval = + cpu_to_le16(adapter->local_listen_interval); + ps_mode->delay_to_ps = + cpu_to_le16(adapter->delay_to_ps); + ps_mode->mode = cpu_to_le16(adapter->enhanced_ps_mode); + } + if (ps_bitmap & BITMAP_AUTO_DS) { + struct nxpwifi_ie_types_auto_ds_param *auto_ds_tlv = + (struct nxpwifi_ie_types_auto_ds_param *)tlv; + u16 idletime = 0; + + auto_ds_tlv->header.type = + cpu_to_le16(TLV_TYPE_AUTO_DS_PARAM); + auto_ds_tlv->header.len = + cpu_to_le16(sizeof(*auto_ds_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*auto_ds_tlv); + tlv += sizeof(*auto_ds_tlv); + if (auto_ds) + idletime = auto_ds->idle_time; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter Auto Deep Sleep\n"); + auto_ds_tlv->deep_sleep_timeout = cpu_to_le16(idletime); + } + cmd->size = cpu_to_le16(cmd_size); + } + return 0; +} + +static int +nxpwifi_ret_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *ps_mode = + &resp->params.psmode_enh; + struct nxpwifi_ds_pm_cfg *pm_cfg = + (struct nxpwifi_ds_pm_cfg *)data_buf; + u16 action = le16_to_cpu(ps_mode->action); + u16 ps_bitmap = le16_to_cpu(ps_mode->params.ps_bitmap); + u16 auto_ps_bitmap = + le16_to_cpu(ps_mode->params.ps_bitmap); + + nxpwifi_dbg(adapter, INFO, + "info: %s: PS_MODE cmd reply result=%#x action=%#X\n", + __func__, resp->result, action); + if (action == EN_AUTO_PS) { + if (auto_ps_bitmap & BITMAP_AUTO_DS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled auto deep sleep\n"); + priv->adapter->is_deep_sleep = true; + } + if (auto_ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled STA power save\n"); + if (adapter->sleep_period.period) + nxpwifi_dbg(adapter, CMD, + "cmd: set to uapsd/pps mode\n"); + } + } else if (action == DIS_AUTO_PS) { + if (ps_bitmap & BITMAP_AUTO_DS) { + priv->adapter->is_deep_sleep = false; + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled auto deep sleep\n"); + } + if (ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled STA power save\n"); + if (adapter->sleep_period.period) { + adapter->delay_null_pkt = false; + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + } + } + } else if (action == GET_PS) { + if (ps_bitmap & BITMAP_STA_PS) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + + nxpwifi_dbg(adapter, CMD, + "cmd: ps_bitmap=%#x\n", ps_bitmap); + + if (pm_cfg) { + /* This section is for get power save mode */ + if (ps_bitmap & BITMAP_STA_PS) + pm_cfg->param.ps_mode = 1; + else + pm_cfg->param.ps_mode = 0; + } + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = &cmd->params.opt_hs_cfg; + struct nxpwifi_hs_config_param *hscfg_param = + (struct nxpwifi_hs_config_param *)data_buf; + u8 *tlv = (u8 *)hs_cfg + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + struct nxpwifi_ps_param_in_hs *psparam_tlv = NULL; + bool hs_activate = false; + u16 size; + + if (!hscfg_param) + /* New Activate command */ + hs_activate = true; + cmd->command = cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH); + + if (!hs_activate && + hscfg_param->conditions != cpu_to_le32(HS_CFG_CANCEL) && + (adapter->arp_filter_size > 0 && + adapter->arp_filter_size <= ARP_FILTER_MAX_BUF_SIZE)) { + nxpwifi_dbg(adapter, CMD, + "cmd: Attach %d bytes ArpFilter to HSCfg cmd\n", + adapter->arp_filter_size); + memcpy(((u8 *)hs_cfg) + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh), + adapter->arp_filter, adapter->arp_filter_size); + size = adapter->arp_filter_size + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + S_DS_GEN; + tlv = (u8 *)hs_cfg + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + adapter->arp_filter_size; + } else { + size = S_DS_GEN + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + } + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); + + adapter->hs_activated_manually = true; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Activating host sleep manually\n"); + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; + hs_cfg->params.hs_config.gpio = hscfg_param->gpio; + hs_cfg->params.hs_config.gap = hscfg_param->gap; + + size += sizeof(struct nxpwifi_ps_param_in_hs); + psparam_tlv = (struct nxpwifi_ps_param_in_hs *)tlv; + psparam_tlv->header.type = + cpu_to_le16(TLV_TYPE_PS_PARAMS_IN_HS); + psparam_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ps_param_in_hs) + - sizeof(struct nxpwifi_ie_types_header)); + psparam_tlv->hs_wake_int = cpu_to_le32(HS_DEF_WAKE_INTERVAL); + psparam_tlv->hs_inact_timeout = + cpu_to_le32(HS_DEF_INACTIVITY_TIMEOUT); + + nxpwifi_dbg(adapter, CMD, + "cmd: HS_CFG_CMD: condition:0x%x gpio:0x%x gap:0x%x\n", + hs_cfg->params.hs_config.conditions, + hs_cfg->params.hs_config.gpio, + hs_cfg->params.hs_config.gap); + } + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_hs_cfg(priv, resp); +} + +static int +nxpwifi_cmd_sta_set_bss_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + if (priv->bss_mode == NL80211_IFTYPE_STATION) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_INFRA; + else if (priv->bss_mode == NL80211_IFTYPE_AP) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_AP; + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_set_bss_mode) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_802_11_net_monitor *net_mon; + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = + &cmd->params.net_mon; + struct chan_band_param *chan_band = NULL; + u8 sec_chan_offset = 0; + u32 bw_offset = 0; + + net_mon = (struct nxpwifi_802_11_net_monitor *)data_buf; + + cmd->size = cpu_to_le16(S_DS_GEN + + sizeof(struct host_cmd_ds_802_11_net_monitor) + + sizeof(struct chan_band_param)); + cmd->command = cpu_to_le16(cmd_no); + cmd_net_mon->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) { + if (net_mon->enable_net_mon) { + cmd_net_mon->enable_net_mon = cpu_to_le16(0x1); + cmd_net_mon->filter_flag = cpu_to_le16((u16) + net_mon->filter_flag); + } + + if (net_mon->enable_net_mon && net_mon->channel) { + chan_band = &cmd_net_mon->monitor_chan.chan_band_param[0]; + cmd_net_mon->monitor_chan.header.type = + cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + cmd_net_mon->monitor_chan.header.len = + cpu_to_le16(sizeof(struct chan_band_param)); + chan_band->chan_number = (u8)net_mon->channel; + chan_band->band_cfg.chan_band = + nxpwifi_band_to_radio_type((u16)net_mon->band); + + if (net_mon->band & BAND_GN || + net_mon->band & BAND_AN || + net_mon->band & BAND_GAC || + net_mon->band & BAND_AAC) { + bw_offset = net_mon->chan_bandwidth; + if (bw_offset == CHANNEL_BW_40MHZ_ABOVE) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_40MHZ_BELOW) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_80MHZ) { + sec_chan_offset = + nxpwifi_get_sec_chan_offset(net_mon->channel); + if (sec_chan_offset == NXPWIFI_SEC_CHAN_ABOVE) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + else if (sec_chan_offset == NXPWIFI_SEC_CHAN_BELOW) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = CHAN_BW_80MHZ; + } + } + } + } + return 0; +} + +static int +nxpwifi_ret_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = &resp->params.net_mon; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: NET_MONITOR_CMD: action: %d, enable: %d, flag: %d ch: %d band: %d bw: %d offset: %d\n", + le16_to_cpu(cmd_net_mon->action), + le16_to_cpu(cmd_net_mon->enable_net_mon), + le16_to_cpu(cmd_net_mon->filter_flag), + cmd_net_mon->monitor_chan.chan_band_param[0].chan_number, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_band, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_width, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_2O_ffset); + priv->adapter->enable_net_mon = le16_to_cpu(cmd_net_mon->enable_net_mon); + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan_ext(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan_ext(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_coalesce_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_coalesce_cfg *coalesce_cfg = + &cmd->params.coalesce_cfg; + struct nxpwifi_ds_coalesce_cfg *cfg = + (struct nxpwifi_ds_coalesce_cfg *)data_buf; + struct coalesce_filt_field_param *param; + u16 cnt, idx, length; + struct coalesce_receive_filt_rule *rule; + + cmd->command = cpu_to_le16(HOST_CMD_COALESCE_CFG); + cmd->size = cpu_to_le16(S_DS_GEN); + + coalesce_cfg->action = cpu_to_le16(cmd_action); + coalesce_cfg->num_of_rules = cpu_to_le16(cfg->num_of_rules); + rule = (void *)coalesce_cfg->rule_data; + + for (cnt = 0; cnt < cfg->num_of_rules; cnt++) { + rule->header.type = cpu_to_le16(TLV_TYPE_COALESCE_RULE); + rule->max_coalescing_delay = + cpu_to_le16(cfg->rule[cnt].max_coalescing_delay); + rule->pkt_type = cfg->rule[cnt].pkt_type; + rule->num_of_fields = cfg->rule[cnt].num_of_fields; + + length = 0; + + param = rule->params; + for (idx = 0; idx < cfg->rule[cnt].num_of_fields; idx++) { + param->operation = cfg->rule[cnt].params[idx].operation; + param->operand_len = + cfg->rule[cnt].params[idx].operand_len; + param->offset = + cpu_to_le16(cfg->rule[cnt].params[idx].offset); + memcpy(param->operand_byte_stream, + cfg->rule[cnt].params[idx].operand_byte_stream, + param->operand_len); + + length += sizeof(struct coalesce_filt_field_param); + + param++; + } + + /* + * Total rule length is sizeof max_coalescing_delay(u16), + * num_of_fields(u8), pkt_type(u8) and total length of the all + * params + */ + rule->header.len = cpu_to_le16(length + sizeof(u16) + + sizeof(u8) + sizeof(u8)); + + /* Add the rule length to the command size */ + le16_unaligned_add_cpu(&cmd->size, + le16_to_cpu(rule->header.len) + + sizeof(struct nxpwifi_ie_types_header)); + + rule = (void *)((u8 *)rule->params + length); + } + + /* Add sizeof action, num_of_rules to total command length */ + le16_unaligned_add_cpu(&cmd->size, sizeof(u16) + sizeof(u16)); + + return 0; +} + +static int +nxpwifi_cmd_sta_mgmt_frame_reg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.reg_mask.action = cpu_to_le16(cmd_action); + cmd->params.reg_mask.mask = + cpu_to_le32(get_unaligned((u32 *)data_buf)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mgmt_frame_reg) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + memcpy(&cmd->params, data_buf, + sizeof(struct host_cmd_ds_remain_on_chan)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_remain_on_chan) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_remain_on_chan *resp_cfg = &resp->params.roc_cfg; + struct host_cmd_ds_remain_on_chan *roc_cfg = + (struct host_cmd_ds_remain_on_chan *)data_buf; + + if (roc_cfg) + memcpy(roc_cfg, resp_cfg, sizeof(*roc_cfg)); + + return 0; +} + +static int +nxpwifi_cmd_sta_gtk_rekey_offload(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_gtk_rekey_params *rekey = &cmd->params.rekey; + struct cfg80211_gtk_rekey_data *data = + (struct cfg80211_gtk_rekey_data *)data_buf; + u64 rekey_ctr; + + cmd->command = cpu_to_le16(HOST_CMD_GTK_REKEY_OFFLOAD_CFG); + cmd->size = cpu_to_le16(sizeof(*rekey) + S_DS_GEN); + + rekey->action = cpu_to_le16(cmd_action); + if (cmd_action == HOST_ACT_GEN_SET) { + memcpy(rekey->kek, data->kek, NL80211_KEK_LEN); + memcpy(rekey->kck, data->kck, NL80211_KCK_LEN); + rekey_ctr = be64_to_cpup((__be64 *)data->replay_ctr); + rekey->replay_ctr_low = cpu_to_le32((u32)rekey_ctr); + rekey->replay_ctr_high = + cpu_to_le32((u32)((u64)rekey_ctr >> 32)); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ac_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_HS_WAKEUP_REASON); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wakeup_reason) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_wakeup_reason *wakeup_reason = + (struct host_cmd_ds_wakeup_reason *)data_buf; + + if (!wakeup_reason) + return 0; + + wakeup_reason->wakeup_reason = + resp->params.hs_wakeup_reason.wakeup_reason; + + return 0; +} + +static int +nxpwifi_cmd_sta_mc_policy(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_multi_chan_policy *mc_pol = &cmd->params.mc_policy; + const u16 *drcs_info = data_buf; + + mc_pol->action = cpu_to_le16(cmd_action); + mc_pol->policy = cpu_to_le16(*drcs_info); + cmd->command = cpu_to_le16(HOST_CMD_MC_POLICY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_multi_chan_policy) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &cmd->params.sdio_rx_aggr_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_SDIO_SP_RX_AGGR_CFG); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_sdio_sp_rx_aggr_cfg) + + S_DS_GEN); + cfg->action = cmd_action; + if (cmd_action == HOST_ACT_GEN_SET) + cfg->enable = *(u8 *)data_buf; + + return 0; +} + +static int +nxpwifi_ret_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &resp->params.sdio_rx_aggr_cfg; + + adapter->sdio_rx_aggr_enable = cfg->enable; + adapter->sdio_rx_block_size = le16_to_cpu(cfg->block_size); + + return 0; +} + +static int +nxpwifi_cmd_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &cmd->params.sta_cfg; + struct host_cmd_tlv_channel_band *tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + + cmd->command = cpu_to_le16(HOST_CMD_STA_CONFIGURE); + cmd->size = cpu_to_le16(sizeof(*sta_cfg_cmd) + + sizeof(*tlv_band_channel) + S_DS_GEN); + sta_cfg_cmd->action = cpu_to_le16(cmd_action); + memset(tlv_band_channel, 0, sizeof(*tlv_band_channel)); + tlv_band_channel->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + tlv_band_channel->header.len = cpu_to_le16(sizeof(*tlv_band_channel) - + sizeof(struct nxpwifi_ie_types_header)); + + return 0; +} + +static int +nxpwifi_ret_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &resp->params.sta_cfg; + struct nxpwifi_channel_band *channel_band = + (struct nxpwifi_channel_band *)data_buf; + struct host_cmd_tlv_channel_band *tlv_band_channel; + + if (!channel_band) + return 0; + + tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + memcpy(&channel_band->band_config, &tlv_band_channel->band_config, + sizeof(struct nxpwifi_band_config)); + channel_band->channel = tlv_band_channel->channel; + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_chan_region_cfg *reg = &cmd->params.reg_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REGION_CFG); + cmd->size = cpu_to_le16(sizeof(*reg) + S_DS_GEN); + + if (cmd_action == HOST_ACT_GEN_GET) + reg->action = cpu_to_le16(cmd_action); + + return 0; +} + +static struct ieee80211_regdomain * +nxpwifi_create_custom_regdomain(struct nxpwifi_private *priv, + u8 *buf, u16 buf_len) +{ + u16 num_chan = buf_len / 2; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + bool new_rule; + int idx, freq, prev_freq = 0; + u32 bw, prev_bw = 0; + u8 chflags, prev_chflags = 0, valid_rules = 0; + + if (WARN_ON_ONCE(num_chan > NL80211_MAX_SUPP_REG_RULES)) + return ERR_PTR(-EINVAL); + + regd = kzalloc_flex(*regd, reg_rules, num_chan, GFP_KERNEL); + if (!regd) + return ERR_PTR(-ENOMEM); + + for (idx = 0; idx < num_chan; idx++) { + u8 chan; + enum nl80211_band band; + + chan = *buf++; + if (!chan) { + kfree(regd); + return NULL; + } + chflags = *buf++; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + new_rule = false; + + if (chflags & NXPWIFI_CHANNEL_DISABLED) + continue; + + if (band == NL80211_BAND_5GHZ) { + if (!(chflags & NXPWIFI_CHANNEL_NOHT80)) + bw = MHZ_TO_KHZ(80); + else if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } else { + if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } + + if (idx == 0 || prev_chflags != chflags || prev_bw != bw || + freq - prev_freq > 20) { + valid_rules++; + new_rule = true; + } + + rule = ®d->reg_rules[valid_rules - 1]; + + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(freq + 10); + + prev_chflags = chflags; + prev_freq = freq; + prev_bw = bw; + + if (!new_rule) + continue; + + rule->freq_range.start_freq_khz = MHZ_TO_KHZ(freq - 10); + rule->power_rule.max_eirp = DBM_TO_MBM(19); + + if (chflags & NXPWIFI_CHANNEL_PASSIVE) + rule->flags = NL80211_RRF_NO_IR; + + if (chflags & NXPWIFI_CHANNEL_DFS) + rule->flags = NL80211_RRF_DFS; + + rule->freq_range.max_bandwidth_khz = bw; + } + + regd->n_reg_rules = valid_rules; + regd->alpha2[0] = '9'; + regd->alpha2[1] = '9'; + + return regd; +} + +static int +nxpwifi_ret_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_chan_region_cfg *reg = &resp->params.reg_cfg; + u16 action = le16_to_cpu(reg->action); + u16 tlv, tlv_buf_len, tlv_buf_left; + struct nxpwifi_ie_types_header *head; + struct ieee80211_regdomain *regd; + u8 *tlv_buf; + + if (action != HOST_ACT_GEN_GET) + return 0; + + tlv_buf = (u8 *)reg + sizeof(*reg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*reg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_CHAN_ATTR_CFG: + nxpwifi_dbg_dump(priv->adapter, CMD_D, "CHAN:", + (u8 *)head + sizeof(*head), + tlv_buf_len); + regd = nxpwifi_create_custom_regdomain(priv, (u8 *)head + + sizeof(*head), + tlv_buf_len); + if (!IS_ERR(regd)) + priv->adapter->regd = regd; + break; + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.pkt_aggr_ctrl.action = cpu_to_le16(cmd_action); + cmd->params.pkt_aggr_ctrl.enable = cpu_to_le16(*(u16 *)data_buf); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_pkt_aggr_ctrl) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_pkt_aggr_ctrl *pkt_aggr_ctrl = + &resp->params.pkt_aggr_ctrl; + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->bus_aggr.enable = le16_to_cpu(pkt_aggr_ctrl->enable); + if (adapter->bus_aggr.enable) + adapter->intf_hdr_len = INTF_HEADER_LEN; + adapter->bus_aggr.mode = NXPWIFI_BUS_AGGR_MODE_LEN_V2; + adapter->bus_aggr.tx_aggr_max_size = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_size); + adapter->bus_aggr.tx_aggr_max_num = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_num); + adapter->bus_aggr.tx_aggr_align = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_align); + + return 0; +} + +static int +nxpwifi_cmd_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cfg(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cmd(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cmd(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_twt_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_twt_cfg(priv, resp, data_buf); +} + +static const struct nxpwifi_cmd_entry cmd_table_sta[] = { + {.cmd_no = HOST_CMD_GET_HW_SPEC, + .prepare_cmd = nxpwifi_cmd_sta_get_hw_spec, + .cmd_resp = nxpwifi_ret_sta_get_hw_spec}, + {.cmd_no = HOST_CMD_802_11_SCAN, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan, + .cmd_resp = nxpwifi_ret_sta_802_11_scan}, + {.cmd_no = HOST_CMD_802_11_GET_LOG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_get_log, + .cmd_resp = nxpwifi_ret_sta_802_11_get_log}, + {.cmd_no = HOST_CMD_MAC_MULTICAST_ADR, + .prepare_cmd = nxpwifi_cmd_sta_mac_multicast_adr, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_ASSOCIATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_associate, + .cmd_resp = nxpwifi_ret_sta_802_11_associate}, + {.cmd_no = HOST_CMD_802_11_SNMP_MIB, + .prepare_cmd = nxpwifi_cmd_sta_802_11_snmp_mib, + .cmd_resp = nxpwifi_ret_sta_802_11_snmp_mib}, + {.cmd_no = HOST_CMD_MAC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_BBP_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_TX_PWR, + .prepare_cmd = nxpwifi_cmd_sta_rf_tx_pwr, + .cmd_resp = nxpwifi_ret_sta_rf_tx_pwr}, + {.cmd_no = HOST_CMD_RF_ANTENNA, + .prepare_cmd = nxpwifi_cmd_sta_rf_antenna, + .cmd_resp = nxpwifi_ret_sta_rf_antenna}, + {.cmd_no = HOST_CMD_802_11_DEAUTHENTICATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_deauthenticate, + .cmd_resp = nxpwifi_ret_sta_802_11_deauthenticate}, + {.cmd_no = HOST_CMD_MAC_CONTROL, + .prepare_cmd = nxpwifi_cmd_sta_mac_control, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_MAC_ADDRESS, + .prepare_cmd = nxpwifi_cmd_sta_802_11_mac_address, + .cmd_resp = nxpwifi_ret_sta_802_11_mac_address}, + {.cmd_no = HOST_CMD_802_11_EEPROM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_802_11D_DOMAIN_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11d_domain_info, + .cmd_resp = nxpwifi_ret_sta_802_11d_domain_info}, + {.cmd_no = HOST_CMD_802_11_KEY_MATERIAL, + .prepare_cmd = nxpwifi_cmd_sta_802_11_key_material, + .cmd_resp = nxpwifi_ret_sta_802_11_key_material}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_CONFIG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_query, + .cmd_resp = nxpwifi_ret_sta_802_11_bg_scan_query}, + {.cmd_no = HOST_CMD_WMM_GET_STATUS, + .prepare_cmd = nxpwifi_cmd_sta_wmm_get_status, + .cmd_resp = nxpwifi_ret_sta_wmm_get_status}, + {.cmd_no = HOST_CMD_802_11_SUBSCRIBE_EVENT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_subsc_evt, + .cmd_resp = nxpwifi_ret_sta_subsc_evt}, + {.cmd_no = HOST_CMD_802_11_TX_RATE_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_tx_rate_query, + .cmd_resp = nxpwifi_ret_sta_802_11_tx_rate_query}, + {.cmd_no = HOST_CMD_MEM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_mem_access, + .cmd_resp = nxpwifi_ret_sta_mem_access}, + {.cmd_no = HOST_CMD_CFG_DATA, + .prepare_cmd = nxpwifi_cmd_sta_cfg_data, + .cmd_resp = nxpwifi_ret_sta_cfg_data}, + {.cmd_no = HOST_CMD_VERSION_EXT, + .prepare_cmd = nxpwifi_cmd_sta_ver_ext, + .cmd_resp = nxpwifi_ret_sta_ver_ext}, + {.cmd_no = HOST_CMD_MEF_CFG, + .prepare_cmd = nxpwifi_cmd_sta_mef_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_RSSI_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11_rssi_info, + .cmd_resp = nxpwifi_ret_sta_802_11_rssi_info}, + {.cmd_no = HOST_CMD_FUNC_INIT, + .prepare_cmd = nxpwifi_cmd_sta_func_init, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FUNC_SHUTDOWN, + .prepare_cmd = nxpwifi_cmd_sta_func_shutdown, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_PMIC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_11N_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11n_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11N_ADDBA_REQ, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_req, + .cmd_resp = nxpwifi_ret_sta_11n_addba_req}, + {.cmd_no = HOST_CMD_11N_ADDBA_RSP, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_rsp, + .cmd_resp = nxpwifi_ret_sta_11n_addba_rsp}, + {.cmd_no = HOST_CMD_11N_DELBA, + .prepare_cmd = nxpwifi_cmd_sta_11n_delba, + .cmd_resp = nxpwifi_ret_sta_11n_delba}, + {.cmd_no = HOST_CMD_TXPWR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_power_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_power_cfg}, + {.cmd_no = HOST_CMD_TX_RATE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_rate_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_rate_cfg}, + {.cmd_no = HOST_CMD_RECONFIGURE_TX_BUFF, + .prepare_cmd = nxpwifi_cmd_sta_reconfigure_rx_buff, + .cmd_resp = nxpwifi_ret_sta_reconfigure_rx_buff}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_sta_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_AMSDU_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_amsdu_aggr_ctrl, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ROBUST_COEX, + .prepare_cmd = nxpwifi_cmd_sta_robust_coex, + .cmd_resp = nxpwifi_ret_sta_robust_coex}, + {.cmd_no = HOST_CMD_802_11_PS_MODE_ENH, + .prepare_cmd = nxpwifi_cmd_sta_enh_power_mode, + .cmd_resp = nxpwifi_ret_sta_enh_power_mode}, + {.cmd_no = HOST_CMD_802_11_HS_CFG_ENH, + .prepare_cmd = nxpwifi_cmd_sta_802_11_hs_cfg, + .cmd_resp = nxpwifi_ret_sta_802_11_hs_cfg}, + {.cmd_no = HOST_CMD_CAU_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_SET_BSS_MODE, + .prepare_cmd = nxpwifi_cmd_sta_set_bss_mode, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_NET_MONITOR, + .prepare_cmd = nxpwifi_cmd_sta_802_11_net_monitor, + .cmd_resp = nxpwifi_ret_sta_802_11_net_monitor}, + {.cmd_no = HOST_CMD_802_11_SCAN_EXT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan_ext, + .cmd_resp = nxpwifi_ret_sta_802_11_scan_ext}, + {.cmd_no = HOST_CMD_COALESCE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_coalesce_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_MGMT_FRAME_REG, + .prepare_cmd = nxpwifi_cmd_sta_mgmt_frame_reg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_REMAIN_ON_CHAN, + .prepare_cmd = nxpwifi_cmd_sta_remain_on_chan, + .cmd_resp = nxpwifi_ret_sta_remain_on_chan}, + {.cmd_no = HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + .prepare_cmd = nxpwifi_cmd_sta_gtk_rekey_offload, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11AC_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ac_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_HS_WAKEUP_REASON, + .prepare_cmd = nxpwifi_cmd_sta_hs_wakeup_reason, + .cmd_resp = nxpwifi_ret_sta_hs_wakeup_reason}, + {.cmd_no = HOST_CMD_MC_POLICY, + .prepare_cmd = nxpwifi_cmd_sta_mc_policy, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FW_DUMP_EVENT, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_SDIO_SP_RX_AGGR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_sdio_rx_aggr_cfg, + .cmd_resp = nxpwifi_ret_sta_sdio_rx_aggr_cfg}, + {.cmd_no = HOST_CMD_STA_CONFIGURE, + .prepare_cmd = nxpwifi_cmd_sta_get_chan_info, + .cmd_resp = nxpwifi_ret_sta_get_chan_info}, + {.cmd_no = HOST_CMD_CHAN_REGION_CFG, + .prepare_cmd = nxpwifi_cmd_sta_chan_region_cfg, + .cmd_resp = nxpwifi_ret_sta_chan_region_cfg}, + {.cmd_no = HOST_CMD_PACKET_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_pkt_aggr_ctrl, + .cmd_resp = nxpwifi_ret_sta_pkt_aggr_ctrl}, + {.cmd_no = HOST_CMD_11AX_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cfg, + .cmd_resp = nxpwifi_ret_sta_11ax_cfg}, + {.cmd_no = HOST_CMD_11AX_CMD, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cmd, + .cmd_resp = nxpwifi_ret_sta_11ax_cmd}, + {.cmd_no = HOST_CMD_TWT_CFG, + .prepare_cmd = nxpwifi_cmd_sta_twt_cfg, + .cmd_resp = nxpwifi_ret_sta_twt_cfg}, +}; + +/* + * Prepare a command before sending it to firmware by invoking the + * appropriate handler based on the command ID. + */ +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_sta); i++) { + if (cmd_no == cmd_table_sta[i].cmd_no) { + if (cmd_table_sta[i].prepare_cmd) + ret = cmd_table_sta[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + cmd_oid); + cmd_node->cmd_resp = cmd_table_sta[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_sta)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* + * Initialize firmware after download or during virtual interface + * reinitialization to bring the device to a working state. + */ +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct nxpwifi_ds_auto_ds auto_ds; + enum state_11d_t state_11d; + struct nxpwifi_ds_11n_tx_cfg tx_cfg; + u8 sdio_sp_rx_aggr_enable; + + if (first_sta) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_FUNC_INIT, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) + return ret; + + if (adapter->cal_data) + nxpwifi_send_cmd(priv, HOST_CMD_CFG_DATA, + HOST_ACT_GEN_SET, 0, NULL, true); + + /* Read MAC address from HW */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_GET_HW_SPEC, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + /* * Set SDIO Single Port RX Aggr Info */ + if (priv->adapter->iface_type == NXPWIFI_SDIO && + ISSUPP_SDIO_SPA_ENABLED(priv->adapter->fw_cap_info) && + !priv->adapter->host_disable_sdio_rx_aggr) { + sdio_sp_rx_aggr_enable = true; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_SDIO_SP_RX_AGGR_CFG, + HOST_ACT_GEN_SET, 0, + &sdio_sp_rx_aggr_enable, + true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "error while enabling SP aggregation..disable it"); + adapter->sdio_rx_aggr_enable = false; + } + } + + /* Reconfigure tx buf size */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RECONFIGURE_TX_BUFF, + HOST_ACT_GEN_SET, 0, + &priv->adapter->tx_buf_size, true); + if (ret) + return ret; + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable IEEE PS by default */ + priv->adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_STA_PS, NULL, + true); + if (ret) + return ret; + } + + nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REGION_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + } + + /* get tx rate */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + priv->data_rate = 0; + + /* get tx power */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + memset(&amsdu_aggr_ctrl, 0, sizeof(amsdu_aggr_ctrl)); + amsdu_aggr_ctrl.enable = true; + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_AMSDU_AGGR_CTRL, + HOST_ACT_GEN_SET, 0, + &amsdu_aggr_ctrl, true); + if (ret) + return ret; + /* MAC Control must be the last command in init_fw */ + /* set MAC Control */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + if (ret) + return ret; + + if (!disable_auto_ds && first_sta && + priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable auto deep sleep */ + auto_ds.auto_ds = DEEP_SLEEP_ON; + auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_AUTO_DS, + &auto_ds, true); + if (ret) + return ret; + } + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Send cmd to FW to enable/disable 11D function */ + state_11d = ENABLE_11D; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + + /* + * Send cmd to FW to configure 11n specific configuration + * (Short GI, Channel BW, Green field support etc.) for transmit + */ + tx_cfg.tx_htcap = NXPWIFI_FW_DEF_HTTXCFG; + ret = nxpwifi_send_cmd(priv, HOST_CMD_11N_CFG, + HOST_ACT_GEN_SET, 0, &tx_cfg, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_event.c b/drivers/net/wireless/nxp/nxpwifi/sta_event.c new file mode 100644 index 000000000000..355064b1d8f7 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_event.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static int +nxpwifi_sta_event_link_lost(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_event_link_lost++; + if (priv->media_connected) { + adapter->priv_link_lost = priv; + adapter->host_mlme_link_lost = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + + return 0; +} + +static int +nxpwifi_sta_event_link_sensed(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + return 0; +} + +static int +nxpwifi_sta_event_deauthenticated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive deauth event in wps session\n"); + } else { + adapter->dbg.num_event_deauth++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_disassociated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive disassoc event in wps session\n"); + } else { + adapter->dbg.num_event_disassoc++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->port_open && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + + return 0; +} + +static int +nxpwifi_sta_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_multicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_GROUP, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_unicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_PAIRWISE, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_deep_sleep_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->if_ops.wakeup_complete(adapter); + if (adapter->is_deep_sleep) + adapter->is_deep_sleep = false; + + return 0; +} + +static int +nxpwifi_sta_event_wmm_status_change(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_WMM_GET_STATUS, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_bs_scan_report(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_low(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_LOW_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_high(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_HIGH_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_port_release(struct nxpwifi_private *priv) +{ + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_sta_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); +} + +static int +nxpwifi_sta_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bs_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *event = + (struct host_cmd_ds_11n_batimeout *)adapter->event_body; + + nxpwifi_11n_ba_stream_timeout(priv, event); + + return 0; +} + +static int +nxpwifi_sta_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + adapter->tx_buf_size = min_t(u16, adapter->curr_tx_buf_size, ctrl); + + return 0; +} + +static int +nxpwifi_sta_event_hs_act_req(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_HS_CFG_ENH, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_channel_switch_ann(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *bss_desc; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + priv->csa_expire_time = jiffies + msecs_to_jiffies(DFS_CHAN_MOVE_TIME); + priv->csa_chan = bss_desc->channel; + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, + bss_desc->mac_address, false); +} + +static int +nxpwifi_sta_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + /* + * We intend to skip this event during suspend, but handle + * it in interface disabled case + */ + if (adapter->ext_scan && (!priv->scan_aborting || + !netif_running(priv->netdev))) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_sta_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_sta_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + } + + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_sta_event_bg_scan_stopped(struct nxpwifi_private *priv) +{ + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + if (priv->sched_scanning) + priv->sched_scanning = false; + + return 0; +} + +static int +nxpwifi_sta_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->ignore_btcoex_events) + nxpwifi_bt_coex_wlan_param_update_event(priv, + adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_sta[] = { + {.event_cause = EVENT_LINK_LOST, + .event_handler = nxpwifi_sta_event_link_lost}, + {.event_cause = EVENT_LINK_SENSED, + .event_handler = nxpwifi_sta_event_link_sensed}, + {.event_cause = EVENT_DEAUTHENTICATED, + .event_handler = nxpwifi_sta_event_deauthenticated}, + {.event_cause = EVENT_DISASSOCIATED, + .event_handler = nxpwifi_sta_event_disassociated}, + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_sta_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_sta_event_ps_sleep}, + {.event_cause = EVENT_MIC_ERR_MULTICAST, + .event_handler = nxpwifi_sta_event_mic_err_multicast}, + {.event_cause = EVENT_MIC_ERR_UNICAST, + .event_handler = nxpwifi_sta_event_mic_err_unicast}, + {.event_cause = EVENT_DEEP_SLEEP_AWAKE, + .event_handler = nxpwifi_sta_event_deep_sleep_awake}, + {.event_cause = EVENT_WMM_STATUS_CHANGE, + .event_handler = nxpwifi_sta_event_wmm_status_change}, + {.event_cause = EVENT_BG_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_bs_scan_report}, + {.event_cause = EVENT_RSSI_LOW, + .event_handler = nxpwifi_sta_event_rssi_low}, + {.event_cause = EVENT_RSSI_HIGH, + .event_handler = nxpwifi_sta_event_rssi_high}, + {.event_cause = EVENT_PORT_RELEASE, + .event_handler = nxpwifi_sta_event_port_release}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_sta_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_sta_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_sta_event_bs_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_sta_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_HS_ACT_REQ, + .event_handler = nxpwifi_sta_event_hs_act_req}, + {.event_cause = EVENT_CHANNEL_SWITCH_ANN, + .event_handler = nxpwifi_sta_event_channel_switch_ann}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_sta_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_sta_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_sta_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_sta_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_sta_event_remain_on_chan_expired}, + {.event_cause = EVENT_BG_SCAN_STOPPED, + .event_handler = nxpwifi_sta_event_bg_scan_stopped}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_sta_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_sta_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_sta_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_sta_event_vdll_ind}, + {.event_cause = EVENT_DUMMY_HOST_WAKEUP_SIGNAL, + .event_handler = NULL}, + {.event_cause = EVENT_MIB_CHANGED, + .event_handler = NULL}, + {.event_cause = EVENT_INIT_DONE, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_MAX_FAIL, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_LINK_QUALITY, + .event_handler = NULL}, + {.event_cause = EVENT_PRE_BEACON_LOST, + .event_handler = NULL}, + {.event_cause = EVENT_WEP_ICV_ERR, + .event_handler = NULL}, + {.event_cause = EVENT_BW_CHANGE, + .event_handler = NULL}, + {.event_cause = EVENT_HOSTWAKE_STAIE, + .event_handler = NULL}, + {.event_cause = EVENT_UNKNOWN_DEBUG, + .event_handler = NULL}, +}; + +static void nxpwifi_process_uap_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + struct nxpwifi_sta_node *sta_ptr; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "uap tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->netdev->dev_addr)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } else if (is_multicast_ether_addr(tp->peermac)) { + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, tp->tx_pause); + } else { + rcu_read_lock(); + sta_ptr = nxpwifi_get_sta_entry(priv, tp->peermac); + if (sta_ptr && sta_ptr->tx_pause != tp->tx_pause) { + sta_ptr->tx_pause = tp->tx_pause; + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, + tp->tx_pause); + } + rcu_read_unlock(); + } +} + +static void nxpwifi_process_sta_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "sta tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->cfg_bssid)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } +} + +/* + * Reset connection state after a firmware-triggered disconnect. + * Clears link state, queues, RSSI/SNR and security settings, + * saves previous SSID/BSSID for possible reassociation, + * and notifies cfg80211. + */ +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason_code, + bool from_ap) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) + return; + + nxpwifi_dbg(adapter, INFO, + "info: handles disconnect event\n"); + + priv->media_connected = false; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->scan_block = false; + priv->port_open = false; + + /* Free Tx and Rx packets, report disconnect to upper layer */ + nxpwifi_clean_txrx(priv); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wpa_ie_len = 0; + + priv->sec_info.encryption_mode = 0; + + /* Enable auto data rate */ + priv->is_data_rate_auto = true; + priv->data_rate = 0; + + priv->assoc_resp_ht_param = 0; + priv->ht_param_present = false; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && priv->hist_data) + nxpwifi_hist_data_reset(priv); + + /* + * Memorize the previous SSID and BSSID so + * it could be used for re-assoc + */ + + nxpwifi_dbg(adapter, INFO, + "info: previous SSID=%s, SSID len=%u\n", + priv->prev_ssid.ssid, priv->prev_ssid.ssid_len); + + nxpwifi_dbg(adapter, INFO, + "info: current SSID=%s, SSID len=%u\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid, + priv->curr_bss_params.bss_descriptor.ssid.ssid_len); + + memcpy(&priv->prev_ssid, + &priv->curr_bss_params.bss_descriptor.ssid, + sizeof(struct cfg80211_ssid)); + + memcpy(priv->prev_bssid, + priv->curr_bss_params.bss_descriptor.mac_address, ETH_ALEN); + + /* Need to erase the current SSID and BSSID info */ + memset(&priv->curr_bss_params, 0x00, sizeof(priv->curr_bss_params)); + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) && + adapter->curr_cmd) + return; + + priv->media_connected = false; + nxpwifi_dbg(adapter, MSG, + "info: successfully disconnected from %pM: reason code %d\n", + priv->cfg_bssid, reason_code); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (adapter->host_mlme_link_lost) + nxpwifi_host_mlme_disconnect(adapter->priv_link_lost, + reason_code, NULL); + else + cfg80211_disconnected(priv->netdev, reason_code, NULL, + 0, !from_ap, GFP_KERNEL); + } + eth_zero_addr(priv->cfg_bssid); + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + if (!ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + return; + + nxpwifi_send_cmd(priv, HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + HOST_ACT_GEN_REMOVE, 0, NULL, false); +} + +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_private *priv = container_of(work, + struct nxpwifi_private, + reset_conn_state_work); + + nxpwifi_reset_connect_state(priv, priv->last_deauth_reason, true); +} + +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_multi_chan_info *chan_info; + struct nxpwifi_ie_types_mc_group_info *grp_info; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + u16 tlv_buf_left, tlv_type, tlv_len; + int intf_num, bss_type, bss_num, i; + struct nxpwifi_private *intf_priv; + + tlv_buf_left = event_skb->len - sizeof(u32); + chan_info = (void *)event_skb->data + sizeof(u32); + + if (le16_to_cpu(chan_info->header.type) != TLV_TYPE_MULTI_CHAN_INFO || + tlv_buf_left < sizeof(struct nxpwifi_ie_types_multi_chan_info)) { + nxpwifi_dbg(adapter, ERROR, + "unknown TLV in chan_info event\n"); + return; + } + + adapter->usb_mc_status = le16_to_cpu(chan_info->status); + nxpwifi_dbg(adapter, EVENT, "multi chan operation %s\n", + adapter->usb_mc_status ? "started" : "over"); + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_multi_chan_info); + tlv = (struct nxpwifi_ie_types_header *)chan_info->tlv_buffer; + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv: tlvLen=%d,\t" + "tlvBufLeft=%d\n", tlv_len, tlv_buf_left); + break; + } + if (tlv_type != TLV_TYPE_MC_GROUP_INFO) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv type: 0x%x\n", + tlv_type); + break; + } + + grp_info = (struct nxpwifi_ie_types_mc_group_info *)tlv; + intf_num = grp_info->intf_num; + for (i = 0; i < intf_num; i++) { + bss_type = grp_info->bss_type_numlist[i] >> 4; + bss_num = grp_info->bss_type_numlist[i] & BSS_NUM_MASK; + intf_priv = nxpwifi_get_priv_by_id(adapter, bss_num, + bss_type); + if (!intf_priv) { + nxpwifi_dbg(adapter, ERROR, + "Invalid bss_type bss_num\t" + "in multi channel event\n"); + continue; + } + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_header *tlv; + u16 tlv_type, tlv_len; + int tlv_buf_left; + + if (!priv->media_connected) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_pause event while disconnected; bss_role=%d\n", + priv->bss_role); + return; + } + + tlv_buf_left = event_skb->len - sizeof(u32); + tlv = (void *)event_skb->data + sizeof(u32); + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(priv->adapter, ERROR, + "wrong tlv: tlvLen=%d, tlvBufLeft=%d\n", + tlv_len, tlv_buf_left); + break; + } + if (tlv_type == TLV_TYPE_TX_PAUSE) { + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + nxpwifi_process_sta_tx_pause(priv, tlv); + else + nxpwifi_process_uap_tx_pause(priv, tlv); + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +/* + * Handle BT coexistence event. Parse TLVs and update + * coexistence aggregation window and scan timing parameters. + */ +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ie_types_btcoex_aggr_win_size *winsizetlv; + struct nxpwifi_ie_types_btcoex_scan_time *scantlv; + s32 len = event_skb->len - sizeof(u32); + u8 *cur_ptr = event_skb->data + sizeof(u32); + u16 tlv_type, tlv_len; + + while (len >= sizeof(struct nxpwifi_ie_types_header)) { + tlv = (struct nxpwifi_ie_types_header *)cur_ptr; + tlv_len = le16_to_cpu(tlv->len); + tlv_type = le16_to_cpu(tlv->type); + + if ((tlv_len + sizeof(struct nxpwifi_ie_types_header)) > len) + break; + switch (tlv_type) { + case TLV_BTCOEX_WL_AGGR_WINSIZE: + winsizetlv = + (struct nxpwifi_ie_types_btcoex_aggr_win_size *)tlv; + adapter->coex_win_size = winsizetlv->coex_win_size; + adapter->coex_tx_win_size = + winsizetlv->tx_win_size; + adapter->coex_rx_win_size = + winsizetlv->rx_win_size; + nxpwifi_coex_ampdu_rxwinsize(adapter); + nxpwifi_update_ampdu_txwinsize(adapter); + break; + + case TLV_BTCOEX_WL_SCANTIME: + scantlv = + (struct nxpwifi_ie_types_btcoex_scan_time *)tlv; + adapter->coex_scan = scantlv->coex_scan; + adapter->coex_min_scan_time = le16_to_cpu(scantlv->min_scan_time); + adapter->coex_max_scan_time = le16_to_cpu(scantlv->max_scan_time); + break; + + default: + break; + } + + len -= tlv_len + sizeof(struct nxpwifi_ie_types_header); + cur_ptr += tlv_len + + sizeof(struct nxpwifi_ie_types_header); + } + + nxpwifi_dbg(adapter, INFO, "coex_scan=%d min_scan=%d coex_win=%d, tx_win=%d rx_win=%d\n", + adapter->coex_scan, adapter->coex_min_scan_time, + adapter->coex_win_size, adapter->coex_tx_win_size, + adapter->coex_rx_win_size); +} + +/* + * Dispatch station firmware event based on event_cause. + * Looks up the handler in the station event table and invokes it. + */ +int nxpwifi_process_sta_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_sta); evt++) { + if (eventcause == evt_table_sta[evt].event_cause) { + if (evt_table_sta[evt].event_handler) + ret = evt_table_sta[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_sta)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_rx.c b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c new file mode 100644 index 000000000000..d951d21eb41c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop gratuitous IPv4 ARP and IPv6 neighbour advertisements when + * source and destination addresses are identical. + */ +static bool +nxpwifi_discard_gratuitous_arp(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + const struct nxpwifi_arp_eth_header *arp; + struct ethhdr *eth; + struct ipv6hdr *ipv6; + struct icmp6hdr *icmpv6; + + eth = (struct ethhdr *)skb->data; + switch (ntohs(eth->h_proto)) { + case ETH_P_ARP: + arp = (void *)(skb->data + sizeof(struct ethhdr)); + if (arp->hdr.ar_op == htons(ARPOP_REPLY) || + arp->hdr.ar_op == htons(ARPOP_REQUEST)) { + if (!memcmp(arp->ar_sip, arp->ar_tip, 4)) + return true; + } + break; + case ETH_P_IPV6: + ipv6 = (void *)(skb->data + sizeof(struct ethhdr)); + icmpv6 = (void *)(skb->data + sizeof(struct ethhdr) + + sizeof(struct ipv6hdr)); + if (icmpv6->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) { + if (!memcmp(&ipv6->saddr, &ipv6->daddr, + sizeof(struct in6_addr))) + return true; + } + break; + default: + break; + } + + return false; +} + +/* + * Process a received data packet. + * Convert 802.2/LLC/SNAP to Ethernet II when appropriate, trim the + * rxpd and extra headers, optionally drop gratuitous ARP/NA, cache + * RX rate for unicast, then deliver to the stack. + */ +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + int ret; + struct rx_packet_hdr *rx_pkt_hdr; + struct rxpd *local_rx_pd; + int hdr_chop; + struct ethhdr *eth; + u16 rx_pkt_off; + u8 adj_rx_rate = 0; + + local_rx_pd = (struct rxpd *)(skb->data); + + rx_pkt_off = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_off; + + if (sizeof(rx_pkt_hdr->eth803_hdr) + sizeof(rfc1042_header) + + rx_pkt_off > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (sizeof(*rx_pkt_hdr) + rx_pkt_off <= skb->len && + ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX)))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type + * (ethertype). + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + eth = (struct ethhdr *) + ((u8 *)&rx_pkt_hdr->eth803_hdr + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + + memcpy(eth->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(eth->h_source)); + memcpy(eth->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(eth->h_dest)); + + /* + * Chop off the rxpd + the excess memory from the 802.2/llc/snap + * header that was removed. + */ + hdr_chop = (u8 *)eth - (u8 *)local_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)local_rx_pd; + } + + /* + * Chop off the leading header bytes so the it points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdr_chop); + + if (priv->hs2_enabled && + nxpwifi_discard_gratuitous_arp(priv, skb)) { + nxpwifi_dbg(priv->adapter, INFO, "Bypassed Gratuitous ARP\n"); + dev_kfree_skb_any(skb); + return 0; + } + + /* Only stash RX bitrate for unicast packets. */ + if (likely(!is_multicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest))) { + priv->rxpd_rate = local_rx_pd->rx_rate; + priv->rxpd_htinfo = local_rx_pd->rate_info; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + adj_rx_rate = nxpwifi_adjust_data_rate(priv, + local_rx_pd->rx_rate, + local_rx_pd->rate_info); + nxpwifi_hist_data_add(priv, adj_rx_rate, local_rx_pd->snr, + local_rx_pd->nf); + } + + ret = nxpwifi_recv_packet(priv, skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "recv packet failed\n"); + + return ret; +} + +/* + * Process a received buffer on the STA path. + * Validate RxPD and lengths, handle monitor/mgmt frames, fast-path + * non-unicast frames, and feed unicast data into 11n reorder/BA logic. + */ +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct rxpd *local_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ta[ETH_ALEN]; + u16 rx_pkt_type, rx_pkt_offset, rx_pkt_length, seq_num; + + local_rx_pd = (struct rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(local_rx_pd->rx_pkt_type); + rx_pkt_offset = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_length = le16_to_cpu(local_rx_pd->rx_pkt_length); + seq_num = le16_to_cpu(local_rx_pd->seq_num); + + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_offset; + + if ((rx_pkt_offset + rx_pkt_length) > skb->len || + sizeof(rx_pkt_hdr->eth803_hdr) + rx_pkt_offset > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, rx_pkt_offset=%d, rx_pkt_length=%d\n", + skb->len, rx_pkt_offset, rx_pkt_length); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return ret; + } + + if (priv->adapter->enable_net_mon && rx_pkt_type == PKT_TYPE_802DOT11) { + ret = nxpwifi_recv_packet_to_monif(priv, skb); + if (ret) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + /* + * If the packet is not an unicast packet then send the packet + * directly to os. Don't pass thru rx reordering + */ + if (!IS_11N_ENABLED(priv) || + !ether_addr_equal_unaligned(priv->curr_addr, + rx_pkt_hdr->eth803_hdr.h_dest)) { + nxpwifi_process_rx_packet(priv, skb); + return ret; + } + + if (nxpwifi_queuing_ra_based(priv)) { + memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN); + } else { + if (rx_pkt_type != PKT_TYPE_BAR && + local_rx_pd->priority < MAX_NUM_TID) + priv->rx_seq[local_rx_pd->priority] = seq_num; + memcpy(ta, priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + } + + /* Reorder and send to OS */ + ret = nxpwifi_11n_rx_reorder_pkt(priv, seq_num, local_rx_pd->priority, + ta, (u8)rx_pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_tx.c b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c new file mode 100644 index 000000000000..10f963bc4e00 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station TX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" + +/* + * Fill TxPD for TX packets by inserting it before payload and setting required fields. + */ +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + unsigned int pad; + u16 pkt_type, pkt_length, pkt_offset; + int hroom = adapter->intf_hdr_len; + u32 tx_control; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*local_tx_pd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + skb_push(skb, sizeof(*local_tx_pd) + pad); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + pkt_length = (u16)(skb->len - (sizeof(struct txpd) + pad)); + if (pkt_type == PKT_TYPE_MGMT) + pkt_length -= NXPWIFI_MGMT_FRAME_HEADER_SIZE; + local_tx_pd->tx_pkt_length = cpu_to_le16(pkt_length); + + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + local_tx_pd->tx_token_id = tx_info->ack_frame_id; + local_tx_pd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (local_tx_pd->priority < + ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) { + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function + */ + tx_control = + priv->wmm.user_pri_pkt_tx_ctrl[local_tx_pd->priority]; + local_tx_pd->tx_control = cpu_to_le32(tx_control); + } + + if (adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } + + /* Offset of actual data */ + pkt_offset = sizeof(struct txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + local_tx_pd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + local_tx_pd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!local_tx_pd->tx_control) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} + +/* Send a NULL-data frame with TxPD at highest priority. */ +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_tx_param tx_param; +/* sizeof(struct txpd) + Interface specific header */ +#define NULL_PACKET_HDR 64 + u32 data_len = NULL_PACKET_HDR; + struct sk_buff *skb; + int ret; + struct nxpwifi_txinfo *tx_info = NULL; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return -EPERM; + + if (!priv->media_connected) + return -EPERM; + + if (adapter->data_sent) + return -EBUSY; + + skb = dev_alloc_skb(data_len); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = data_len - + (sizeof(struct txpd) + adapter->intf_hdr_len); + skb_reserve(skb, sizeof(struct txpd) + adapter->intf_hdr_len); + skb_push(skb, sizeof(struct txpd)); + + local_tx_pd = (struct txpd *)skb->data; + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + local_tx_pd->flags = flags; + local_tx_pd->priority = WMM_HIGHEST_PRIORITY; + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + skb_push(skb, adapter->intf_hdr_len); + tx_param.next_pkt_len = 0; + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + case 0: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, DATA, + "data: %s: host_to_card succeeded\n", + __func__); + adapter->tx_lock_flag = true; + break; + case -EINPROGRESS: + adapter->tx_lock_flag = true; + break; + default: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + } + + return ret; +} + +/* Check whether a last‑packet indication needs to be sent. */ +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 ret = false; + + if (!adapter->sleep_period.period) + return ret; + if (nxpwifi_wmm_lists_empty(adapter)) + ret = true; + + if (ret && !adapter->cmd_sent && !adapter->curr_cmd && + !nxpwifi_is_command_pending(adapter)) { + adapter->delay_null_pkt = false; + ret = true; + } else { + ret = false; + adapter->delay_null_pkt = true; + } + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/txrx.c b/drivers/net/wireless/nxp/nxpwifi/txrx.c new file mode 100644 index 000000000000..6e8b49138e57 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/txrx.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: generic TX/RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" + +/* + * Parse the RxPD, select the target interface, and dispatch the packet for + * handling. + */ +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct rxpd *local_rx_pd; + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + int ret; + + local_rx_pd = (struct rxpd *)(skb->data); + /* Get the BSS number from rxpd, get corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, local_rx_pd->bss_num & + BSS_NUM_MASK, local_rx_pd->bss_type); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop RX packet\n"); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + nxpwifi_dbg_dump(adapter, DAT_D, "rx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_rx_packet(priv, skb); + else + ret = nxpwifi_process_sta_rx_packet(priv, skb); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_handle_rx_packet); + +/* + * Add TxPD, validate, send the packet to firmware, then run completion + * callback. + */ +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + int hroom, ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd = NULL; + struct nxpwifi_sta_node *dest_node; + struct ethhdr *hdr = (void *)skb->data; + + if (unlikely(!skb->len || + skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN)) { + ret = -EINVAL; + goto out; + } + + hroom = adapter->intf_hdr_len; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + rcu_read_lock(); + dest_node = nxpwifi_get_sta_entry(priv, hdr->h_dest); + if (dest_node) { + dest_node->stats.tx_bytes += skb->len; + dest_node->stats.tx_packets++; + } + rcu_read_unlock(); + nxpwifi_process_uap_txpd(priv, skb); + } else { + nxpwifi_process_sta_txpd(priv, skb); + } + + if ((adapter->data_sent || adapter->tx_lock_flag)) { + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_inc(&adapter->tx_queued); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(skb->data + hroom); + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + nxpwifi_dbg_dump(adapter, DAT_D, "tx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + +out: + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, DATA, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case -EINVAL: + nxpwifi_dbg(adapter, ERROR, + "malformed skb (length: %u, headroom: %u)\n", + skb->len, skb_headroom(skb)); + fallthrough; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", + ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + return ret; +} + +static int nxpwifi_host_to_card(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct txpd *local_tx_pd = NULL; + u8 *head_ptr = skb->data; + int ret = 0; + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop TX packet\n"); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, 0); + return ret; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(head_ptr + adapter->intf_hdr_len); + + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, ERROR, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && + adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + skb_queue_head(&adapter->tx_data_q, skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_add(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_inc(&adapter->tx_queued); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + return ret; +} + +static int +nxpwifi_dequeue_tx_queue(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_tx_param tx_param; + + skb = skb_dequeue(&adapter->tx_data_q); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_sub(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_dec(&adapter->tx_queued); + + if (!skb_queue_empty(&adapter->tx_data_q)) + skb_next = skb_peek(&adapter->tx_data_q); + else + skb_next = NULL; + tx_param.next_pkt_len = ((skb_next) ? skb_next->len : 0); + if (!tx_param.next_pkt_len) { + if (!nxpwifi_wmm_lists_empty(adapter)) + tx_param.next_pkt_len = 1; + } + return nxpwifi_host_to_card(adapter, skb, &tx_param); +} + +void +nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter) +{ + do { + if (adapter->data_sent || adapter->tx_lock_flag) + break; + if (nxpwifi_dequeue_tx_queue(adapter)) + break; + } while (!skb_queue_empty(&adapter->tx_data_q)); +} + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status) +{ + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + struct netdev_queue *txq; + int index; + + if (!skb) + return 0; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) + goto done; + + nxpwifi_set_trans_start(priv->netdev); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) + atomic_dec_return(&adapter->pending_bridged_pkts); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + goto done; + + if (!status) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += tx_info->pkt_len; + if (priv->tx_timeout_cnt) + priv->tx_timeout_cnt = 0; + } else { + priv->stats.tx_errors++; + } + + if (aggr) + /* For skb_aggr, do not wake up tx queue */ + goto done; + + atomic_dec(&adapter->tx_pending); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + if (atomic_dec_return(&priv->wmm_tx_pending[index]) < LOW_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (netif_tx_queue_stopped(txq)) { + netif_tx_wake_queue(txq); + nxpwifi_dbg(adapter, DATA, "wake queue: %d\n", index); + } + } +done: + dev_kfree_skb_any(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_write_data_complete); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body) +{ + struct tx_status_event *tx_status = (void *)priv->adapter->event_body; + struct sk_buff *ack_skb; + struct nxpwifi_txinfo *tx_info; + + if (!tx_status->tx_token_id) + return; + + spin_lock_bh(&priv->ack_status_lock); + ack_skb = xa_erase(&priv->ack_status_frames, tx_status->tx_token_id); + spin_unlock_bh(&priv->ack_status_lock); + + if (ack_skb) { + tx_info = NXPWIFI_SKB_TXCB(ack_skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS) { + /* consumes ack_skb */ + skb_complete_wifi_ack(ack_skb, !tx_status->status); + } else { + /* Remove broadcast address which was added by driver */ + memmove(ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16), + ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN, ack_skb->len - + (sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN)); + ack_skb->len = ack_skb->len - ETH_ALEN; + /* + * Remove driver's proprietary header including 2 bytes + * of packet length and pass actual management frame buffer + * to cfg80211. + */ + cfg80211_mgmt_tx_status(&priv->wdev, tx_info->cookie, + ack_skb->data + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16), ack_skb->len - + (NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16)), + !tx_status->status, GFP_ATOMIC); + dev_kfree_skb_any(ack_skb); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c new file mode 100644 index 000000000000..04551847643f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c @@ -0,0 +1,1256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP specific command handling + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +/* Parse BSS params and append WPA/WPA2 TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wpa(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_pwk_cipher *pwk_cipher; + struct host_cmd_tlv_gwk_cipher *gwk_cipher; + struct host_cmd_tlv_passphrase *passphrase; + struct host_cmd_tlv_akmp *tlv_akmp; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + u16 cmd_size = *param_size; + u8 *tlv = *tlv_buf; + + tlv_akmp = (struct host_cmd_tlv_akmp *)tlv; + tlv_akmp->header.type = cpu_to_le16(TLV_TYPE_UAP_AKMP); + tlv_akmp->header.len = cpu_to_le16(sizeof(struct host_cmd_tlv_akmp) - + sizeof(struct nxpwifi_ie_types_header)); + tlv_akmp->key_mgmt_operation = cpu_to_le16(bss_cfg->key_mgmt_operation); + tlv_akmp->key_mgmt = cpu_to_le16(bss_cfg->key_mgmt); + cmd_size += sizeof(struct host_cmd_tlv_akmp); + tlv += sizeof(struct host_cmd_tlv_akmp); + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa2 & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA2); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa2; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.group_cipher & VALID_CIPHER_BITMAP) { + gwk_cipher = (struct host_cmd_tlv_gwk_cipher *)tlv; + gwk_cipher->header.type = cpu_to_le16(TLV_TYPE_GWK_CIPHER); + gwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_gwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + gwk_cipher->cipher = bss_cfg->wpa_cfg.group_cipher; + cmd_size += sizeof(struct host_cmd_tlv_gwk_cipher); + tlv += sizeof(struct host_cmd_tlv_gwk_cipher); + } + + if (bss_cfg->wpa_cfg.length) { + passphrase = (struct host_cmd_tlv_passphrase *)tlv; + passphrase->header.type = + cpu_to_le16(TLV_TYPE_UAP_WPA_PASSPHRASE); + passphrase->header.len = cpu_to_le16(bss_cfg->wpa_cfg.length); + memcpy(passphrase->passphrase, bss_cfg->wpa_cfg.passphrase, + bss_cfg->wpa_cfg.length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append WEP TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wep(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_wep_key *wep_key; + u16 cmd_size = *param_size; + int i; + u8 *tlv = *tlv_buf; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (bss_cfg->wep_cfg[i].length && + (bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP40 || + bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP104)) { + wep_key = (struct host_cmd_tlv_wep_key *)tlv; + wep_key->header.type = + cpu_to_le16(TLV_TYPE_UAP_WEP_KEY); + wep_key->header.len = + cpu_to_le16(bss_cfg->wep_cfg[i].length + 2); + wep_key->key_index = bss_cfg->wep_cfg[i].key_index; + wep_key->is_default = bss_cfg->wep_cfg[i].is_default; + memcpy(wep_key->key, bss_cfg->wep_cfg[i].key, + bss_cfg->wep_cfg[i].length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + tlv += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + } + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append TLVs to the command buffer. */ +static int nxpwifi_uap_bss_param_prepare(struct nxpwifi_private *priv, u8 *tlv, + void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_mac_addr *mac_tlv; + struct host_cmd_tlv_dtim_period *dtim_period; + struct host_cmd_tlv_beacon_period *beacon_period; + struct host_cmd_tlv_ssid *ssid; + struct host_cmd_tlv_bcast_ssid *bcast_ssid; + struct host_cmd_tlv_channel_band *chan_band; + struct host_cmd_tlv_frag_threshold *frag_threshold; + struct host_cmd_tlv_rts_threshold *rts_threshold; + struct host_cmd_tlv_retry_limit *retry_limit; + struct host_cmd_tlv_encrypt_protocol *encrypt_protocol; + struct host_cmd_tlv_auth_type *auth_type; + struct host_cmd_tlv_rates *tlv_rates; + struct host_cmd_tlv_ageout_timer *ao_timer, *ps_ao_timer; + struct host_cmd_tlv_power_constraint *pwr_ct; + struct nxpwifi_ie_types_htcap *htcap; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + int i; + u16 cmd_size = *param_size; + + mac_tlv = (struct host_cmd_tlv_mac_addr *)tlv; + mac_tlv->header.type = cpu_to_le16(TLV_TYPE_UAP_MAC_ADDRESS); + mac_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(mac_tlv->mac_addr, bss_cfg->mac_addr, ETH_ALEN); + cmd_size += sizeof(struct host_cmd_tlv_mac_addr); + tlv += sizeof(struct host_cmd_tlv_mac_addr); + + if (bss_cfg->ssid.ssid_len) { + ssid = (struct host_cmd_tlv_ssid *)tlv; + ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_SSID); + ssid->header.len = cpu_to_le16((u16)bss_cfg->ssid.ssid_len); + memcpy(ssid->ssid, bss_cfg->ssid.ssid, bss_cfg->ssid.ssid_len); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + + bcast_ssid = (struct host_cmd_tlv_bcast_ssid *)tlv; + bcast_ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_BCAST_SSID); + bcast_ssid->header.len = + cpu_to_le16(sizeof(bcast_ssid->bcast_ctl)); + bcast_ssid->bcast_ctl = bss_cfg->bcast_ssid_ctl; + cmd_size += sizeof(struct host_cmd_tlv_bcast_ssid); + tlv += sizeof(struct host_cmd_tlv_bcast_ssid); + } + if (bss_cfg->rates[0]) { + tlv_rates = (struct host_cmd_tlv_rates *)tlv; + tlv_rates->header.type = cpu_to_le16(TLV_TYPE_UAP_RATES); + + for (i = 0; i < NXPWIFI_SUPPORTED_RATES && bss_cfg->rates[i]; + i++) + tlv_rates->rates[i] = bss_cfg->rates[i]; + + tlv_rates->header.len = cpu_to_le16(i); + cmd_size += sizeof(struct host_cmd_tlv_rates) + i; + tlv += sizeof(struct host_cmd_tlv_rates) + i; + } + if (bss_cfg->channel && + (((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_BG && + bss_cfg->channel <= MAX_CHANNEL_BAND_BG) || + ((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_A && + bss_cfg->channel <= MAX_CHANNEL_BAND_A))) { + chan_band = (struct host_cmd_tlv_channel_band *)tlv; + chan_band->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + chan_band->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_channel_band) - + sizeof(struct nxpwifi_ie_types_header)); + chan_band->band_config = bss_cfg->band_cfg; + chan_band->channel = bss_cfg->channel; + cmd_size += sizeof(struct host_cmd_tlv_channel_band); + tlv += sizeof(struct host_cmd_tlv_channel_band); + } + if (bss_cfg->beacon_period >= NXPWIFI_BEACON_PERIOD_MIN && + bss_cfg->beacon_period <= NXPWIFI_BEACON_PERIOD_MAX) { + beacon_period = (struct host_cmd_tlv_beacon_period *)tlv; + beacon_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_BEACON_PERIOD); + beacon_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_beacon_period) - + sizeof(struct nxpwifi_ie_types_header)); + beacon_period->period = cpu_to_le16(bss_cfg->beacon_period); + cmd_size += sizeof(struct host_cmd_tlv_beacon_period); + tlv += sizeof(struct host_cmd_tlv_beacon_period); + } + if (bss_cfg->dtim_period >= NXPWIFI_MIN_DTIM_PERIOD && + bss_cfg->dtim_period <= NXPWIFI_MAX_DTIM_PERIOD) { + dtim_period = (struct host_cmd_tlv_dtim_period *)tlv; + dtim_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_DTIM_PERIOD); + dtim_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_dtim_period) - + sizeof(struct nxpwifi_ie_types_header)); + dtim_period->period = bss_cfg->dtim_period; + cmd_size += sizeof(struct host_cmd_tlv_dtim_period); + tlv += sizeof(struct host_cmd_tlv_dtim_period); + } + if (bss_cfg->rts_threshold <= NXPWIFI_RTS_THRESHOLD_MAX) { + rts_threshold = (struct host_cmd_tlv_rts_threshold *)tlv; + rts_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_RTS_THRESHOLD); + rts_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_rts_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rts_threshold->rts_thr = cpu_to_le16(bss_cfg->rts_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->frag_threshold >= NXPWIFI_FRAG_THRESHOLD_MIN && + bss_cfg->frag_threshold <= NXPWIFI_FRAG_THRESHOLD_MAX) { + frag_threshold = (struct host_cmd_tlv_frag_threshold *)tlv; + frag_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_FRAG_THRESHOLD); + frag_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_frag_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + frag_threshold->frag_thr = cpu_to_le16(bss_cfg->frag_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->retry_limit <= NXPWIFI_RETRY_LIMIT_MAX) { + retry_limit = (struct host_cmd_tlv_retry_limit *)tlv; + retry_limit->header.type = + cpu_to_le16(TLV_TYPE_UAP_RETRY_LIMIT); + retry_limit->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_retry_limit) - + sizeof(struct nxpwifi_ie_types_header)); + retry_limit->limit = (u8)bss_cfg->retry_limit; + cmd_size += sizeof(struct host_cmd_tlv_retry_limit); + tlv += sizeof(struct host_cmd_tlv_retry_limit); + } + if ((bss_cfg->protocol & PROTOCOL_WPA) || + (bss_cfg->protocol & PROTOCOL_WPA2) || + (bss_cfg->protocol & PROTOCOL_EAP)) + nxpwifi_uap_bss_wpa(&tlv, cmd_buf, &cmd_size); + else + nxpwifi_uap_bss_wep(&tlv, cmd_buf, &cmd_size); + + if (bss_cfg->auth_mode <= WLAN_AUTH_SHARED_KEY || + bss_cfg->auth_mode == NXPWIFI_AUTH_MODE_AUTO) { + auth_type = (struct host_cmd_tlv_auth_type *)tlv; + auth_type->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_type->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_auth_type) - + sizeof(struct nxpwifi_ie_types_header)); + auth_type->auth_type = (u8)bss_cfg->auth_mode; + auth_type->pwe_derivation = 0; + auth_type->transition_disable = 0; + cmd_size += sizeof(struct host_cmd_tlv_auth_type); + tlv += sizeof(struct host_cmd_tlv_auth_type); + } + if (bss_cfg->protocol) { + encrypt_protocol = (struct host_cmd_tlv_encrypt_protocol *)tlv; + encrypt_protocol->header.type = + cpu_to_le16(TLV_TYPE_UAP_ENCRY_PROTOCOL); + encrypt_protocol->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_encrypt_protocol) + - sizeof(struct nxpwifi_ie_types_header)); + encrypt_protocol->proto = cpu_to_le16(bss_cfg->protocol); + cmd_size += sizeof(struct host_cmd_tlv_encrypt_protocol); + tlv += sizeof(struct host_cmd_tlv_encrypt_protocol); + } + + if (bss_cfg->ht_cap.cap_info) { + htcap = (struct nxpwifi_ie_types_htcap *)tlv; + htcap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + htcap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + htcap->ht_cap.cap_info = bss_cfg->ht_cap.cap_info; + htcap->ht_cap.ampdu_params_info = + bss_cfg->ht_cap.ampdu_params_info; + memcpy(&htcap->ht_cap.mcs, &bss_cfg->ht_cap.mcs, + sizeof(struct ieee80211_mcs_info)); + htcap->ht_cap.extended_ht_cap_info = + bss_cfg->ht_cap.extended_ht_cap_info; + htcap->ht_cap.tx_BF_cap_info = bss_cfg->ht_cap.tx_BF_cap_info; + htcap->ht_cap.antenna_selection_info = + bss_cfg->ht_cap.antenna_selection_info; + cmd_size += sizeof(struct nxpwifi_ie_types_htcap); + tlv += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (priv->wmm_enabled) { + struct nxpwifi_ie_types_wmmcap *wmm_cap; + struct nxpwifi_types_wmm_info *fw_wmm; + const struct ieee80211_wmm_param_ie *ie; + + wmm_cap = (struct nxpwifi_ie_types_wmmcap *)tlv; + fw_wmm = &wmm_cap->wmm_info; + ie = &bss_cfg->wmm_element; + + wmm_cap->header.type = cpu_to_le16(WLAN_EID_VENDOR_SPECIFIC); + wmm_cap->header.len = + cpu_to_le16(sizeof(struct nxpwifi_types_wmm_info)); + + /* Map 802.11 WMM IE fields to FW WMM TLV payload */ + fw_wmm->oui[0] = ie->oui[0]; + fw_wmm->oui[1] = ie->oui[1]; + fw_wmm->oui[2] = ie->oui[2]; + fw_wmm->oui[3] = ie->oui_type; + + fw_wmm->subtype = ie->oui_subtype; + fw_wmm->version = ie->version; + fw_wmm->qos_info = ie->qos_info; + fw_wmm->reserved = ie->reserved; + + memcpy(fw_wmm->ac, ie->ac, sizeof(fw_wmm->ac)); + + cmd_size += sizeof(*wmm_cap); + tlv += sizeof(*wmm_cap); + } + + if (bss_cfg->sta_ao_timer) { + ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ao_timer->header.type = cpu_to_le16(TLV_TYPE_UAP_AO_TIMER); + ao_timer->header.len = cpu_to_le16(sizeof(*ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ao_timer->sta_ao_timer = cpu_to_le32(bss_cfg->sta_ao_timer); + cmd_size += sizeof(*ao_timer); + tlv += sizeof(*ao_timer); + } + + if (bss_cfg->power_constraint) { + pwr_ct = (void *)tlv; + pwr_ct->header.type = cpu_to_le16(TLV_TYPE_PWR_CONSTRAINT); + pwr_ct->header.len = cpu_to_le16(sizeof(u8)); + pwr_ct->constraint = bss_cfg->power_constraint; + cmd_size += sizeof(*pwr_ct); + tlv += sizeof(*pwr_ct); + } + + if (bss_cfg->ps_sta_ao_timer) { + ps_ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ps_ao_timer->header.type = + cpu_to_le16(TLV_TYPE_UAP_PS_AO_TIMER); + ps_ao_timer->header.len = cpu_to_le16(sizeof(*ps_ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ps_ao_timer->sta_ao_timer = + cpu_to_le32(bss_cfg->ps_sta_ao_timer); + cmd_size += sizeof(*ps_ao_timer); + tlv += sizeof(*ps_ao_timer); + } + + *param_size = cmd_size; + + return 0; +} + +/* Parse custom IEs and write them to the command buffer. */ +static int nxpwifi_uap_custom_ie_prepare(u8 *tlv, void *cmd_buf, u16 *ie_size) +{ + struct nxpwifi_ie_list *ap_ie = cmd_buf; + struct nxpwifi_ie_types_header *tlv_ie = (void *)tlv; + + if (!ap_ie || !ap_ie->len) + return -EINVAL; + + *ie_size += le16_to_cpu(ap_ie->len) + + sizeof(struct nxpwifi_ie_types_header); + + tlv_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + tlv_ie->len = ap_ie->len; + tlv += sizeof(struct nxpwifi_ie_types_header); + + memcpy(tlv, ap_ie->ie_list, le16_to_cpu(ap_ie->len)); + + return 0; +} + +static int +nxpwifi_cmd_uap_sys_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + u8 *tlv; + u16 cmd_size, param_size, ie_size; + struct host_cmd_ds_sys_config *sys_cfg; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_SYS_CONFIG); + cmd_size = (u16)(sizeof(struct host_cmd_ds_sys_config) + S_DS_GEN); + sys_cfg = &cmd->params.uap_sys_config; + sys_cfg->action = cpu_to_le16(cmd_action); + tlv = sys_cfg->tlv; + + switch (cmd_type) { + case UAP_BSS_PARAMS_I: + param_size = cmd_size; + ret = nxpwifi_uap_bss_param_prepare(priv, tlv, data_buf, ¶m_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(param_size); + break; + case UAP_CUSTOM_IE_I: + ie_size = cmd_size; + ret = nxpwifi_uap_custom_ie_prepare(tlv, data_buf, &ie_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(ie_size); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int +nxpwifi_cmd_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ie_types_host_mlme *tlv; + int size; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_BSS_START); + size = S_DS_GEN; + + tlv = (struct nxpwifi_ie_types_host_mlme *)((u8 *)cmd + size); + tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + tlv->header.len = cpu_to_le16(sizeof(tlv->host_mlme)); + tlv->host_mlme = 1; + size += sizeof(struct nxpwifi_ie_types_host_mlme); + + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + adapter->delay_null_pkt = false; + priv->bss_started = 1; + + return 0; +} + +static int +nxpwifi_ret_uap_bss_stop(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->bss_started = 0; + + return 0; +} + +static int nxpwifi_ret_apcmd_sta_list(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, void *data_buf) +{ + struct host_cmd_ds_sta_list *sta_list = &resp->params.sta_list; + struct nxpwifi_ie_types_sta_info *sta_info; + struct nxpwifi_sta_node *sta_node; + u16 sta_count; + u32 resp_size; + u32 base; + u32 required_size; + int i; + + resp_size = le16_to_cpu(resp->size); + sta_count = le16_to_cpu(sta_list->sta_count); + + /* End of fixed fields before sta_list.tlv[] */ + base = offsetofend(struct host_cmd_ds_command, params.sta_list.sta_count); + + /* At least fixed fields must be present */ + if (resp_size < base) + return -EINVAL; + + required_size = base + sta_count * sizeof(*sta_info); + + /* Verify firmware did not claim more entries than the buffer holds */ + if (resp_size < required_size) + return -EINVAL; + + sta_info = (void *)sta_list->tlv; + + rcu_read_lock(); + for (i = 0; i < sta_count; i++) { + sta_node = nxpwifi_get_sta_entry(priv, sta_info->mac); + if (unlikely(!sta_node)) { + sta_info++; + continue; + } + + sta_node->stats.rssi = sta_info->rssi; + sta_info++; + } + rcu_read_unlock(); + + return 0; +} + +/* Build AP deauth command for the given MAC address. */ +static int nxpwifi_cmd_uap_sta_deauth(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_deauth *sta_deauth = &cmd->params.sta_deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_STA_DEAUTH); + memcpy(sta_deauth->mac, mac, ETH_ALEN); + sta_deauth->reason = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_sta_deauth) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_uap_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +/* Build AP add-station command. */ +static int +nxpwifi_cmd_uap_add_new_station(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_add_station *new_sta = &cmd->params.sta_info; + struct nxpwifi_sta_info *add_sta = (struct nxpwifi_sta_info *)data_buf; + struct station_parameters *params = add_sta->params; + struct nxpwifi_sta_node *sta_ptr; + u16 cmd_size; + u8 *pos, *cmd_end; + u16 tlv_len; + struct nxpwifi_ie_types_sta_flag *sta_flag; + int i; + + cmd->command = cpu_to_le16(HOST_CMD_ADD_NEW_STATION); + new_sta->action = cpu_to_le16(cmd_action); + cmd_size = sizeof(struct host_cmd_ds_add_station) + S_DS_GEN; + + if (cmd_action == HOST_ACT_ADD_STA) + sta_ptr = nxpwifi_add_sta_entry(priv, add_sta->peer_mac); + else + sta_ptr = nxpwifi_get_sta_entry_rcu(priv, add_sta->peer_mac); + + if (!sta_ptr) + return -EINVAL; + + memcpy(new_sta->peer_mac, add_sta->peer_mac, ETH_ALEN); + + if (cmd_action == HOST_ACT_REMOVE_STA) { + cmd->size = cpu_to_le16(cmd_size); + return 0; + } + + new_sta->aid = cpu_to_le16(params->aid); + new_sta->listen_interval = cpu_to_le32(params->listen_interval); + new_sta->cap_info = cpu_to_le16(params->capability); + + pos = new_sta->tlv; + cmd_end = (u8 *)cmd; + cmd_end += (NXPWIFI_SIZE_OF_CMD_BUFFER - 1); + + if (params->sta_flags_set & NL80211_STA_FLAG_WME) + sta_ptr->is_wmm_enabled = 1; + sta_flag = (struct nxpwifi_ie_types_sta_flag *)pos; + sta_flag->header.type = cpu_to_le16(TLV_TYPE_UAP_STA_FLAGS); + sta_flag->header.len = cpu_to_le16(sizeof(__le32)); + sta_flag->sta_flags = cpu_to_le32(params->sta_flags_set); + pos += sizeof(struct nxpwifi_ie_types_sta_flag); + cmd_size += sizeof(struct nxpwifi_ie_types_sta_flag); + + if (params->ext_capab_len) { + u8 *data = (u8 *)params->ext_capab; + u16 len = params->ext_capab_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.supported_rates_len) { + u8 *data = (u8 *)params->link_sta_params.supported_rates; + u16 len = params->link_sta_params.supported_rates_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_SUPP_RATES, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->uapsd_queues || params->max_sp) { + u8 qos_capability = params->uapsd_queues | (params->max_sp << 5); + u8 *data = &qos_capability; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_QOS_CAPA, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_wmm_enabled = 1; + } + + if (params->link_sta_params.ht_capa) { + u8 *data = (u8 *)params->link_sta_params.ht_capa; + u16 len = sizeof(struct ieee80211_ht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_HT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11n_enabled = 1; + sta_ptr->max_amsdu = + le16_to_cpu(params->link_sta_params.ht_capa->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } + + if (params->link_sta_params.vht_capa) { + u8 *data = (u8 *)params->link_sta_params.vht_capa; + u16 len = sizeof(struct ieee80211_vht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_VHT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ac_enabled = 1; + } + + if (params->link_sta_params.opmode_notif_used) { + u8 *data = ¶ms->link_sta_params.opmode_notif; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_OPMODE_NOTIF, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.he_capa_len) { + u8 *data = (u8 *)params->link_sta_params.he_capa; + u16 len = params->link_sta_params.he_capa_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_HE_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ax_enabled = 1; + } + + for (i = 0; i < MAX_NUM_TID; i++) { + if (sta_ptr->is_11n_enabled || sta_ptr->is_11ax_enabled) + sta_ptr->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + sta_ptr->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + + memset(sta_ptr->rx_seq, 0xff, sizeof(sta_ptr->rx_seq)); + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +static const struct nxpwifi_cmd_entry cmd_table_uap[] = { + {.cmd_no = HOST_CMD_APCMD_SYS_RESET, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_SYS_CONFIG, + .prepare_cmd = nxpwifi_cmd_uap_sys_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_BSS_START, + .prepare_cmd = nxpwifi_cmd_uap_bss_start, + .cmd_resp = nxpwifi_ret_uap_bss_start}, + {.cmd_no = HOST_CMD_UAP_BSS_STOP, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_uap_bss_stop}, + {.cmd_no = HOST_CMD_APCMD_STA_LIST, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_apcmd_sta_list}, + {.cmd_no = HOST_CMD_UAP_STA_DEAUTH, + .prepare_cmd = nxpwifi_cmd_uap_sta_deauth, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_uap_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ADD_NEW_STATION, + .prepare_cmd = nxpwifi_cmd_uap_add_new_station, + .cmd_resp = NULL}, +}; + +/* Prepare AP commands and dispatch to per-cmd builders before sending to firmware. */ +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_uap); i++) { + if (cmd_no == cmd_table_uap[i].cmd_no) { + if (cmd_table_uap[i].prepare_cmd) + ret = cmd_table_uap[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + type); + cmd_node->cmd_resp = cmd_table_uap[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_uap)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* Translate cfg80211_ap_settings security into bss_config for firmware. */ +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params) +{ + int i; + struct nxpwifi_wep_key wep_key; + + if (!params->privacy) { + bss_config->protocol = PROTOCOL_NO_SECURITY; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + priv->sec_info.wep_enabled = 0; + priv->sec_info.wpa_enabled = 0; + priv->sec_info.wpa2_enabled = 0; + + return 0; + } + + switch (params->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + bss_config->auth_mode = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + bss_config->auth_mode = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + bss_config->auth_mode = WLAN_AUTH_LEAP; + break; + default: + bss_config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + break; + } + + bss_config->key_mgmt_operation |= KEY_MGMT_ON_HOST; + + bss_config->protocol = 0; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->protocol |= PROTOCOL_WPA; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->protocol |= PROTOCOL_WPA2; + + bss_config->key_mgmt = 0; + for (i = 0; i < params->crypto.n_akm_suites; i++) { + switch (params->crypto.akm_suites[i]) { + case WLAN_AKM_SUITE_8021X: + bss_config->key_mgmt |= KEY_MGMT_EAP; + break; + case WLAN_AKM_SUITE_PSK: + bss_config->key_mgmt |= KEY_MGMT_PSK; + break; + case WLAN_AKM_SUITE_PSK_SHA256: + bss_config->key_mgmt |= KEY_MGMT_PSK_SHA256; + break; + case WLAN_AKM_SUITE_OWE: + bss_config->key_mgmt |= KEY_MGMT_OWE; + break; + case WLAN_AKM_SUITE_SAE: + bss_config->key_mgmt |= KEY_MGMT_SAE; + break; + default: + break; + } + } + + for (i = 0; i < params->crypto.n_ciphers_pairwise; i++) { + switch (params->crypto.ciphers_pairwise[i]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_TKIP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_AES_CCMP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_AES_CCMP; + break; + default: + break; + } + } + + switch (params->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (priv->sec_info.wep_enabled) { + bss_config->protocol = PROTOCOL_STATIC_WEP; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + wep_key = priv->wep_key[i]; + bss_config->wep_cfg[i].key_index = i; + + if (priv->wep_key_curr_index == i) + bss_config->wep_cfg[i].is_default = 1; + else + bss_config->wep_cfg[i].is_default = 0; + + bss_config->wep_cfg[i].length = + wep_key.key_length; + memcpy(&bss_config->wep_cfg[i].key, + &wep_key.key_material, + wep_key.key_length); + } + } + break; + case WLAN_CIPHER_SUITE_TKIP: + bss_config->wpa_cfg.group_cipher = CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + bss_config->wpa_cfg.group_cipher = CIPHER_AES_CCMP; + break; + default: + break; + } + + return 0; +} + +/* Update 11n HT params from beacon and fill bss_config. */ +void +nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *ht_ie; + + if (!ISSUPP_11NENABLED(priv->adapter->fw_cap_info)) + return; + + ht_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (ht_ie) { + memcpy(&bss_cfg->ht_cap, ht_ie + 2, + sizeof(struct ieee80211_ht_cap)); + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + bss_cfg->ht_cap.tx_BF_cap_info = + cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + priv->ap_11n_enabled = 1; + } else { + memset(&bss_cfg->ht_cap, 0, sizeof(struct ieee80211_ht_cap)); + bss_cfg->ht_cap.cap_info = cpu_to_le16(NXPWIFI_DEF_HT_CAP); + bss_cfg->ht_cap.ampdu_params_info = NXPWIFI_DEF_AMPDU; + } +} + +/* Update 11ac VHT params from beacon and fill bss_config. */ +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vht_ie; + + vht_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (vht_ie) { + memcpy(&bss_cfg->vht_cap, vht_ie + 2, + sizeof(struct ieee80211_vht_cap)); + priv->ap_11ac_enabled = 1; + } else { + priv->ap_11ac_enabled = 0; + } +} + +/* Extract TPC request from beacon and set power_constraint. */ +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *tpc_ie; + + tpc_ie = cfg80211_find_ie(WLAN_EID_TPC_REQUEST, params->beacon.tail, + params->beacon.tail_len); + if (tpc_ie) + bss_cfg->power_constraint = *(tpc_ie + 2); + else + bss_cfg->power_constraint = 0; +} + +/* Enable VHT only when VHT IE is present; otherwise disable VHT. */ +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_enable) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_11ac_vht_cfg vht_cfg; + + vht_cfg.band_config = VHT_CFG_5GHZ; + vht_cfg.cap_info = adapter->hw_dot_11ac_dev_cap; + + if (!ap_11ac_enable) { + vht_cfg.mcs_tx_set = DISABLE_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DISABLE_VHT_MCS_SET; + } else { + vht_cfg.mcs_tx_set = DEFAULT_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DEFAULT_VHT_MCS_SET; + } + + vht_cfg.misc_config = VHT_CAP_UAP_ONLY; + + if (ap_11ac_enable && width >= NL80211_CHAN_WIDTH_80) + vht_cfg.misc_config |= VHT_BW_80_160_80P80; + + nxpwifi_send_cmd(priv, HOST_CMD_11AC_CFG, + HOST_ACT_GEN_SET, 0, &vht_cfg, true); +} + +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + + if (band == BAND_2GHZ && + !(adapter->fw_bands & BAND_GAX)) + return false; + + if (band == BAND_5GHZ && + !(adapter->fw_bands & BAND_AAX)) + return false; + + if (params->he_cap) + return true; + else + return false; +} + +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_11ax_he_cfg ax_cfg; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + const struct element *he_cap; + int ret; + + if (band == BAND_2GHZ) + ax_cfg.band = BIT(0); + else if (band == BAND_5GHZ) + ax_cfg.band = BIT(1); + else + return -EINVAL; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_GET, 0, &ax_cfg, true); + if (ret) + return ret; + + he_cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, + params->beacon.tail, + params->beacon.tail_len); + + if (he_cap) { + ax_cfg.he_cap_cfg.id = he_cap->id; + ax_cfg.he_cap_cfg.len = he_cap->datalen; + if (params->twt_responder == 0) { + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg = + (struct nxpwifi_11ax_he_cap_cfg *)he_cap; + + he_cap_cfg->cap_elem.mac_cap_info[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + } + memcpy(ax_cfg.data + 4, + he_cap->data, + he_cap->datalen); + } else { + /* disable */ + if (ax_cfg.he_cap_cfg.len && + ax_cfg.he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + memset(ax_cfg.he_cap_cfg.he_txrx_mcs_support, 0xff, + sizeof(ax_cfg.he_cap_cfg.he_txrx_mcs_support)); + } + } + + return nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_SET, 0, &ax_cfg, true); +} + +/* Copy supported rates from beacon into bss_config. */ +void +nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct element *rate_ie; + int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *var_pos = params->beacon.head + var_offset; + int len = params->beacon.head_len - var_offset; + u8 rate_len = 0; + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES) + return; + memcpy(bss_cfg->rates, rate_ie + 1, rate_ie->datalen); + rate_len = rate_ie->datalen; + } + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, + params->beacon.tail, + params->beacon.tail_len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES - rate_len) + return; + memcpy(bss_cfg->rates + rate_len, + rate_ie + 1, rate_ie->datalen); + } +} + +/* + * Initialize bss_config fields to sentinel values. + * Fields left with sentinel values are treated as unset and will not be + * included in the corresponding firmware command. + */ +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config) +{ + config->radio_ctl = __NXPWIFI_RADIO_CTL_MAX; + config->dtim_period = NXPWIFI_INVALID_DTIM_PERIOD; + config->beacon_period = NXPWIFI_INVALID_BEACON_PERIOD; + config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + config->rts_threshold = NXPWIFI_INVALID_RTS; + config->frag_threshold = NXPWIFI_INVALID_FRAG; + config->retry_limit = NXPWIFI_INVALID_RETRY_LIMI; +} + +/* Parse WMM params from cfg80211_ap_settings and update bss_config. */ +void +nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vendor_ie; + const struct ieee80211_wmm_param_ie *wmm; + u8 ie_len; + + vendor_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + params->beacon.tail, + params->beacon.tail_len); + if (!vendor_ie) + goto no_wmm; + + ie_len = vendor_ie[1]; + + if (ie_len < sizeof(struct ieee80211_wmm_param_ie) - 2) + goto no_wmm; + + wmm = (const struct ieee80211_wmm_param_ie *)vendor_ie; + + if (memcmp(wmm->oui, "\x00\x50\xf2", 3)) + goto no_wmm; + + if (wmm->oui_type != WLAN_OUI_TYPE_MICROSOFT_WMM) + goto no_wmm; + + if (wmm->oui_subtype != 1) + goto no_wmm; + + /* Only WMM version 1 is supported */ + if (wmm->version != 1) + goto no_wmm; + + memcpy(&bss_cfg->wmm_element, wmm, + sizeof(struct ieee80211_wmm_param_ie)); + + priv->wmm_enabled = true; + return; + +no_wmm: + memset(&bss_cfg->wmm_element, 0, sizeof(bss_cfg->wmm_element)); + priv->wmm_enabled = false; +} + +/* Enable 11d when country IE is present. */ +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data) +{ + enum state_11d_t state_11d; + const u8 *country_ie; + + country_ie = cfg80211_find_ie(WLAN_EID_COUNTRY, beacon_data->tail, + beacon_data->tail_len); + if (country_ie) { + /* Send cmd to FW to enable 11D function */ + state_11d = ENABLE_11D; + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + } +} + +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef) +{ + u8 config_bands = 0, old_bands = priv->config_bands; + + priv->bss_chandef = chandef; + + bss_cfg->channel = + ieee80211_frequency_to_channel(chandef.chan->center_freq); + + nxpwifi_convert_chan_to_band_cfg(priv, &bss_cfg->band_cfg, &chandef); + + /* Set appropriate bands */ + if (chandef.chan->band == NL80211_BAND_2GHZ) { + config_bands = BAND_B | BAND_G; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_GN | BAND_GAX; + } else { + config_bands = BAND_A; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_AN; + if (chandef.width > NL80211_CHAN_WIDTH_40) + config_bands |= BAND_AAC | BAND_AAX; + } + + priv->config_bands = config_bands; + + if (old_bands != config_bands) { + if (nxpwifi_band_to_radio_type(priv->config_bands) == + HOST_SCAN_RADIO_TYPE_BG) + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_2GHZ); + else + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_5GHZ); + } +} + +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, bss_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to set AP configuration\n"); + return ret; + } + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_START, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start the BSS\n"); + return ret; + } + + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c new file mode 100644 index 000000000000..9f717a3d7ec5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +#define NXPWIFI_BSS_START_EVT_FIX_SIZE 12 + +static int +nxpwifi_uap_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + + return 0; +} + +static int +nxpwifi_uap_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *deauth_mac; + + deauth_mac = adapter->event_body + + NXPWIFI_UAP_EVENT_EXTRA_HEADER; + cfg80211_del_sta(priv->netdev->ieee80211_ptr, deauth_mac, GFP_KERNEL); + + if (priv->ap_11n_enabled) { + nxpwifi_11n_del_rx_reorder_tbl_by_ta(priv, deauth_mac); + nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); + } + nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + + return 0; +} + +static int +nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct station_info *sinfo; + struct nxpwifi_assoc_event *event; + struct nxpwifi_sta_node *node; + int len, i; + + sinfo = kzalloc_obj(*sinfo, GFP_KERNEL); + if (!sinfo) + return -ENOMEM; + + event = (struct nxpwifi_assoc_event *) + (adapter->event_body + NXPWIFI_UAP_EVENT_EXTRA_HEADER); + if (le16_to_cpu(event->type) == TLV_TYPE_UAP_MGMT_FRAME) { + len = -1; + + if (ieee80211_is_assoc_req(event->frame_control)) + len = 0; + else if (ieee80211_is_reassoc_req(event->frame_control)) + /* + * There will be ETH_ALEN bytes of + * current_ap_addr before the re-assoc ies. + */ + len = ETH_ALEN; + + if (len != -1) { + u16 evt_len = le16_to_cpu(event->len); + + sinfo->assoc_req_ies = &event->data[len]; + len = (u8 *)sinfo->assoc_req_ies - + (u8 *)&event->frame_control; + + /* + * event->len is reported by the device firmware + * and is not otherwise validated. Reject a length + * that underflows the header or that would place + * the association request IEs outside the fixed + * event_body[] buffer. + */ + if (evt_len < len || + (u8 *)&event->frame_control + evt_len > + adapter->event_body + MAX_EVENT_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "invalid STA assoc event length\n"); + kfree(sinfo); + return -EINVAL; + } + + sinfo->assoc_req_ies_len = evt_len - (u16)len; + } + } + cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, sinfo, + GFP_KERNEL); + + node = nxpwifi_add_sta_entry(priv, event->sta_addr); + if (!node) { + nxpwifi_dbg(adapter, ERROR, + "could not create station entry!\n"); + kfree(sinfo); + return -ENOENT; + } + + if (!priv->ap_11n_enabled) { + kfree(sinfo); + return 0; + } + + nxpwifi_set_sta_ht_cap(priv, sinfo->assoc_req_ies, + sinfo->assoc_req_ies_len, node); + + for (i = 0; i < MAX_NUM_TID; i++) { + if (node->is_11n_enabled || node->is_11ax_enabled) + node->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + node->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + memset(node->rx_seq, 0xff, sizeof(node->rx_seq)); + kfree(sinfo); + + return 0; +} + +static int +nxpwifi_check_uap_capabilities(struct nxpwifi_private *priv, + struct sk_buff *event) +{ + int evt_len; + u8 *curr; + u16 tlv_len; + struct nxpwifi_ie_types_data *tlv_hdr; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + int mask = IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK; + + priv->wmm_enabled = false; + skb_pull(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + evt_len = event->len; + curr = event->data; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "uap capabilities:", + event->data, event->len); + + skb_push(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + + while ((evt_len >= sizeof(tlv_hdr->header))) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (evt_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case WLAN_EID_HT_CAPABILITY: + priv->ap_11n_enabled = true; + break; + + case WLAN_EID_VHT_CAPABILITY: + priv->ap_11ac_enabled = true; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* + * Point the regular IEEE element 2 bytes into the NXP element + * and setup the IEEE element type and length byte fields + */ + wmm_param_ie = (void *)(curr + 2); + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = + WLAN_EID_VENDOR_SPECIFIC; + nxpwifi_dbg(priv->adapter, EVENT, + "info: check uap capabilities:\t" + "wmm parameter set count: %d\n", + wmm_param_ie->qos_info & mask); + + nxpwifi_wmm_setup_ac_downgrade(priv); + priv->wmm_enabled = true; + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + break; + + default: + break; + } + + curr += (tlv_len + sizeof(tlv_hdr->header)); + evt_len -= (tlv_len + sizeof(tlv_hdr->header)); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_start(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + priv->port_open = false; + eth_hw_addr_set(priv->netdev, adapter->event_body + 2); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + return nxpwifi_check_uap_capabilities(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); + + return 0; +} + +static int +nxpwifi_uap_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_ba_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *ba_timeout; + + if (priv->media_connected) { + ba_timeout = (void *)adapter->event_body; + nxpwifi_11n_ba_stream_timeout(priv, ba_timeout); + } + + return 0; +} + +static int +nxpwifi_uap_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + nxpwifi_dbg(adapter, EVENT, + "event: AMSDU_AGGR_CTRL %d\n", ctrl); + + if (priv->media_connected) { + adapter->tx_buf_size = + min_t(u16, adapter->curr_tx_buf_size, ctrl); + nxpwifi_dbg(adapter, EVENT, + "event: tx_buf_size %d\n", + adapter->tx_buf_size); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_idle(struct nxpwifi_private *priv) +{ + priv->media_connected = false; + priv->port_open = false; + nxpwifi_clean_txrx(priv); + nxpwifi_del_all_sta_list(priv); + + return 0; +} + +static int +nxpwifi_uap_event_bss_active(struct nxpwifi_private *priv) +{ + priv->media_connected = true; + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_uap_event_mic_countermeasures(struct nxpwifi_private *priv) +{ + /* For future development */ + + return 0; +} + +static int +nxpwifi_uap_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + if (adapter->ext_scan) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_uap_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_uap_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_uap_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_bt_coex_wlan_param_update_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_uap[] = { + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_uap_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_uap_event_ps_sleep}, + {.event_cause = EVENT_UAP_STA_DEAUTH, + .event_handler = nxpwifi_uap_event_sta_deauth}, + {.event_cause = EVENT_UAP_STA_ASSOC, + .event_handler = nxpwifi_uap_event_sta_assoc}, + {.event_cause = EVENT_UAP_BSS_START, + .event_handler = nxpwifi_uap_event_bss_start}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_uap_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_uap_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_uap_event_ba_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_uap_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_UAP_BSS_IDLE, + .event_handler = nxpwifi_uap_event_bss_idle}, + {.event_cause = EVENT_UAP_BSS_ACTIVE, + .event_handler = nxpwifi_uap_event_bss_active}, + {.event_cause = EVENT_UAP_MIC_COUNTERMEASURES, + .event_handler = nxpwifi_uap_event_mic_countermeasures}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_uap_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_uap_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_uap_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_uap_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_uap_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_uap_event_remain_on_chan_expired}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_uap_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_uap_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_uap_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_uap_event_vdll_ind}, +}; + +/* Handle AP‑interface events by dispatching them to event‑specific routines. */ +int nxpwifi_process_uap_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_uap); evt++) { + if (eventcause == evt_table_uap[evt].event_cause) { + if (evt_table_uap[evt].event_handler) + ret = evt_table_uap[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_uap)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c new file mode 100644 index 000000000000..840dddfc4f5a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP TX and RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "wmm.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop bridged pkts from RA list until pending <= low threshold; return true if + * any. + */ +static bool +nxpwifi_uap_del_tx_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head, + int tid) +{ + struct nxpwifi_ra_list_tbl *ra_list; + struct sk_buff *skb, *tmp; + bool pkt_deleted = false; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_adapter *adapter = priv->adapter; + + list_for_each_entry(ra_list, ra_list_head, list) { + if (skb_queue_empty(&ra_list->skb_head)) + continue; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) { + __skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, + -1); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid]--; + else + atomic_dec(&priv->wmm.tx_pkts_queued); + pkt_deleted = true; + } + if ((atomic_read(&adapter->pending_bridged_pkts) <= + NXPWIFI_BRIDGED_PKTS_THR_LOW)) + break; + } + } + + return pkt_deleted; +} + +/* Delete bridged pkts from one RA list; rotate index to keep fairness. */ +static void nxpwifi_uap_cleanup_tx_queues(struct nxpwifi_private *priv) +{ + struct list_head *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; i++, priv->del_list_idx++) { + if (priv->del_list_idx == MAX_NUM_TID) + priv->del_list_idx = 0; + ra_list = &priv->wmm.tid_tbl_ptr[priv->del_list_idx].ra_list; + if (nxpwifi_uap_del_tx_pkts_in_ralist(priv, ra_list, i)) { + priv->del_list_idx++; + break; + } + } + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +static void +nxpwifi_uap_queue_bridged_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + int hdr_chop; + struct ethhdr *p_ethhdr; + struct nxpwifi_sta_node *src_node; + int index; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + kfree_skb(skb); + nxpwifi_uap_cleanup_tx_queues(priv); + return; + } + + if (sizeof(*rx_pkt_hdr) + + le16_to_cpu(uap_rx_pd->rx_pkt_offset) > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return; + } + + if ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with + * an Ethernet II header, keep the src/dst and snap_type + * (ethertype). + * + * The firmware only passes up SNAP frames converting all RX + * data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + p_ethhdr = (struct ethhdr *) + ((u8 *)(&rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + memcpy(p_ethhdr->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(p_ethhdr->h_source)); + memcpy(p_ethhdr->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(p_ethhdr->h_dest)); + /* + * Chop off the rxpd + the excess memory from + * 802.2/llc/snap header that was removed. + */ + hdr_chop = (u8 *)p_ethhdr - (u8 *)uap_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)uap_rx_pd; + } + + /* + * Chop off the leading header bytes so that it points + * to the start of either the reconstructed EthII frame + * or the 802.2/llc/snap frame. + */ + skb_pull(skb, hdr_chop); + + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(adapter, ERROR, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: cannot allocate new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return; + } + + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(adapter, INFO, + "info: new skb headroom %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, rx_pkt_hdr->eth803_hdr.h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + src_node->stats.last_tx_rate = uap_rx_pd->rx_rate; + src_node->stats.last_tx_htinfo = uap_rx_pd->ht_info; + } + rcu_read_unlock(); + + if (is_unicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest)) { + /* + * Update bridge packet statistics as the + * packet is not going to kernel/upper layer. + */ + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + /* + * Sending bridge packet to TX queue, so save the packet + * length in TXCB to update statistics in TX complete. + */ + tx_info->pkt_len = skb->len; + } + + __net_timestamp(skb); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + atomic_inc(&priv->wmm_tx_pending[index]); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* AP fwd: mcast/bcast -> up + bridge; unicast -> bridge if RA assoc, else up. */ +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ra[ETH_ALEN]; + struct sk_buff *skb_uap; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + /* don't do packet forwarding in disconnected state */ + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "drop packet in disconnected state.\n"); + dev_kfree_skb_any(skb); + return 0; + } + + memcpy(ra, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN); + + if (is_multicast_ether_addr(ra)) { + skb_uap = skb_copy(skb, GFP_ATOMIC); + if (likely(skb_uap)) { + nxpwifi_uap_queue_bridged_pkt(priv, skb_uap); + } else { + nxpwifi_dbg(adapter, ERROR, + "failed to copy skb for uAP\n"); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -ENOMEM; + } + } else { + node = nxpwifi_get_sta_entry_rcu(priv, ra); + if (node) { + /* Requeue Intra-BSS packet */ + nxpwifi_uap_queue_bridged_pkt(priv, skb); + return 0; + } + } + + /* Forward unicat/Inter-BSS packets to kernel. */ + return nxpwifi_process_rx_packet(priv, skb); +} + +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *src_node, *dst_node; + struct ethhdr *p_ethhdr; + struct sk_buff *skb_uap; + struct nxpwifi_txinfo *tx_info; + + if (!skb) + return -ENOMEM; + + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + dst_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_dest); + rcu_read_unlock(); + + if (is_multicast_ether_addr(p_ethhdr->h_dest) || dst_node) { + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) + skb_uap = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + else + skb_uap = skb_copy(skb, GFP_ATOMIC); + + if (likely(skb_uap)) { + tx_info = NXPWIFI_SKB_TXCB(skb_uap); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + __net_timestamp(skb_uap); + nxpwifi_wmm_add_buf_txqueue(priv, skb_uap); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + nxpwifi_uap_cleanup_tx_queues(priv); + } + + } else { + nxpwifi_dbg(adapter, ERROR, "failed to allocate skb_uap"); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); + /* Don't forward Intra-BSS unicast packet to upper layer*/ + + if (dst_node) + return 0; + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* Forward multicast/broadcast packet to upper layer*/ + netif_rx(skb); + return 0; +} + +/* Process AP RX: check RxPD/len, handle mgmt or 11n reorder/AMSDU, then forward. */ +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u16 rx_pkt_type; + u8 ta[ETH_ALEN], pkt_type; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(uap_rx_pd->rx_pkt_type); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if (le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + sizeof(rx_pkt_hdr->eth803_hdr) > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet for struct ethhdr: len=%d, offset=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset)); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return 0; + } + + ether_addr_copy(ta, rx_pkt_hdr->eth803_hdr.h_source); + + if ((le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + le16_to_cpu(uap_rx_pd->rx_pkt_length)) > (u16)skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, offset=%d, length=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset), + le16_to_cpu(uap_rx_pd->rx_pkt_length)); + priv->stats.rx_dropped++; + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->stats.tx_failed++; + rcu_read_unlock(); + + dev_kfree_skb_any(skb); + return 0; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type != PKT_TYPE_BAR && uap_rx_pd->priority < MAX_NUM_TID) { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->rx_seq[uap_rx_pd->priority] = + le16_to_cpu(uap_rx_pd->seq_num); + rcu_read_unlock(); + } + + if (!priv->ap_11n_enabled || + (!nxpwifi_11n_get_rx_reorder_tbl(priv, uap_rx_pd->priority, ta) && + (le16_to_cpu(uap_rx_pd->rx_pkt_type) != PKT_TYPE_AMSDU))) { + ret = nxpwifi_handle_uap_rx_forward(priv, skb); + return ret; + } + + /* Reorder and send to kernel */ + pkt_type = (u8)le16_to_cpu(uap_rx_pd->rx_pkt_type); + ret = nxpwifi_11n_rx_reorder_pkt(priv, le16_to_cpu(uap_rx_pd->seq_num), + uap_rx_pd->priority, ta, pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} + +/* + * Build TxPD for AP TX: push aligned TxPD; set bss, len/off, prio, delay, txctl, + * flags. + */ +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_txpd *txpd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + int pad; + u16 pkt_type, pkt_offset; + int hroom = adapter->intf_hdr_len; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*txpd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + + skb_push(skb, sizeof(*txpd) + pad); + + txpd = (struct uap_txpd *)skb->data; + memset(txpd, 0, sizeof(*txpd)); + txpd->bss_num = priv->bss_num; + txpd->bss_type = priv->bss_type; + txpd->tx_pkt_length = cpu_to_le16((u16)(skb->len - (sizeof(*txpd) + + pad))); + txpd->priority = (u8)skb->priority; + + txpd->pkt_delay_2ms = nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + txpd->tx_token_id = tx_info->ack_frame_id; + txpd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (txpd->priority < ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function. + */ + txpd->tx_control = + cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[txpd->priority]); + + /* Offset of actual data */ + pkt_offset = sizeof(*txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + txpd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + txpd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!txpd->tx_control) + /* TxCtrl set by user or default */ + txpd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c new file mode 100644 index 000000000000..bbfefb81d8d3 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include +#include +#include +#include +#include + +#define RX_RATE_FORMAT_MASK GENMASK(1, 0) +#define RX_RATE_BW_MASK GENMASK(3, 2) +#define RX_RATE_GI_MASK BIT(4) +#define RX_RATE_STBC_MASK BIT(5) +#define RX_RATE_LDPC_MASK BIT(6) + +static struct nxpwifi_debug_data items[] = { + {"debug_mask", item_size(debug_mask), + item_addr(debug_mask), 1}, + {"int_counter", item_size(int_counter), + item_addr(int_counter), 1}, + {"wmm_ac_vo", item_size(packets_out[WMM_AC_VO]), + item_addr(packets_out[WMM_AC_VO]), 1}, + {"wmm_ac_vi", item_size(packets_out[WMM_AC_VI]), + item_addr(packets_out[WMM_AC_VI]), 1}, + {"wmm_ac_be", item_size(packets_out[WMM_AC_BE]), + item_addr(packets_out[WMM_AC_BE]), 1}, + {"wmm_ac_bk", item_size(packets_out[WMM_AC_BK]), + item_addr(packets_out[WMM_AC_BK]), 1}, + {"tx_buf_size", item_size(tx_buf_size), + item_addr(tx_buf_size), 1}, + {"curr_tx_buf_size", item_size(curr_tx_buf_size), + item_addr(curr_tx_buf_size), 1}, + {"ps_mode", item_size(ps_mode), + item_addr(ps_mode), 1}, + {"ps_state", item_size(ps_state), + item_addr(ps_state), 1}, + {"is_deep_sleep", item_size(is_deep_sleep), + item_addr(is_deep_sleep), 1}, + {"wakeup_dev_req", item_size(pm_wakeup_card_req), + item_addr(pm_wakeup_card_req), 1}, + {"wakeup_tries", item_size(pm_wakeup_fw_try), + item_addr(pm_wakeup_fw_try), 1}, + {"hs_configured", item_size(is_hs_configured), + item_addr(is_hs_configured), 1}, + {"hs_activated", item_size(hs_activated), + item_addr(hs_activated), 1}, + {"num_tx_timeout", item_size(num_tx_timeout), + item_addr(num_tx_timeout), 1}, + {"is_cmd_timedout", item_size(is_cmd_timedout), + item_addr(is_cmd_timedout), 1}, + {"timeout_cmd_id", item_size(timeout_cmd_id), + item_addr(timeout_cmd_id), 1}, + {"timeout_cmd_act", item_size(timeout_cmd_act), + item_addr(timeout_cmd_act), 1}, + {"last_cmd_id", item_size(last_cmd_id), + item_addr(last_cmd_id), DBG_CMD_NUM}, + {"last_cmd_act", item_size(last_cmd_act), + item_addr(last_cmd_act), DBG_CMD_NUM}, + {"last_cmd_index", item_size(last_cmd_index), + item_addr(last_cmd_index), 1}, + {"last_cmd_resp_id", item_size(last_cmd_resp_id), + item_addr(last_cmd_resp_id), DBG_CMD_NUM}, + {"last_cmd_resp_index", item_size(last_cmd_resp_index), + item_addr(last_cmd_resp_index), 1}, + {"last_event", item_size(last_event), + item_addr(last_event), DBG_CMD_NUM}, + {"last_event_index", item_size(last_event_index), + item_addr(last_event_index), 1}, + {"last_mp_wr_bitmap", item_size(last_mp_wr_bitmap), + item_addr(last_mp_wr_bitmap), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_ports", item_size(last_mp_wr_ports), + item_addr(last_mp_wr_ports), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_len", item_size(last_mp_wr_len), + item_addr(last_mp_wr_len), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_curr_wr_port", item_size(last_mp_curr_wr_port), + item_addr(last_mp_curr_wr_port), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_sdio_mp_index", item_size(last_sdio_mp_index), + item_addr(last_sdio_mp_index), 1}, + {"num_cmd_h2c_fail", item_size(num_cmd_host_to_card_failure), + item_addr(num_cmd_host_to_card_failure), 1}, + {"num_cmd_sleep_cfm_fail", + item_size(num_cmd_sleep_cfm_host_to_card_failure), + item_addr(num_cmd_sleep_cfm_host_to_card_failure), 1}, + {"num_tx_h2c_fail", item_size(num_tx_host_to_card_failure), + item_addr(num_tx_host_to_card_failure), 1}, + {"num_evt_deauth", item_size(num_event_deauth), + item_addr(num_event_deauth), 1}, + {"num_evt_disassoc", item_size(num_event_disassoc), + item_addr(num_event_disassoc), 1}, + {"num_evt_link_lost", item_size(num_event_link_lost), + item_addr(num_event_link_lost), 1}, + {"num_cmd_deauth", item_size(num_cmd_deauth), + item_addr(num_cmd_deauth), 1}, + {"num_cmd_assoc_ok", item_size(num_cmd_assoc_success), + item_addr(num_cmd_assoc_success), 1}, + {"num_cmd_assoc_fail", item_size(num_cmd_assoc_failure), + item_addr(num_cmd_assoc_failure), 1}, + {"cmd_sent", item_size(cmd_sent), + item_addr(cmd_sent), 1}, + {"data_sent", item_size(data_sent), + item_addr(data_sent), 1}, + {"cmd_resp_received", item_size(cmd_resp_received), + item_addr(cmd_resp_received), 1}, + {"event_received", item_size(event_received), + item_addr(event_received), 1}, + + /* variables defined in struct nxpwifi_adapter */ + {"cmd_pending", adapter_item_size(cmd_pending), + adapter_item_addr(cmd_pending), 1}, + {"tx_pending", adapter_item_size(tx_pending), + adapter_item_addr(tx_pending), 1}, + {"rx_pending", adapter_item_size(rx_pending), + adapter_item_addr(rx_pending), 1}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/* Send init or shutdown command to firmware. */ +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown) +{ + u16 cmd; + + if (func_init_shutdown == NXPWIFI_FUNC_INIT) { + cmd = HOST_CMD_FUNC_INIT; + } else if (func_init_shutdown == NXPWIFI_FUNC_SHUTDOWN) { + cmd = HOST_CMD_FUNC_SHUTDOWN; + } else { + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported parameter\n"); + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd, HOST_ACT_GEN_SET, 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_init_shutdown_fw); + +/* Handle IOCTL get/set of debug info across driver structures. */ +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (info) { + info->debug_mask = adapter->debug_mask; + memcpy(info->packets_out, + priv->wmm.packets_out, + sizeof(priv->wmm.packets_out)); + info->curr_tx_buf_size = (u32)adapter->curr_tx_buf_size; + info->tx_buf_size = (u32)adapter->tx_buf_size; + info->rx_tbl_num = nxpwifi_get_rx_reorder_tbl(priv, + info->rx_tbl); + info->tx_tbl_num = nxpwifi_get_tx_ba_stream_tbl(priv, + info->tx_tbl); + info->ps_mode = adapter->ps_mode; + info->ps_state = adapter->ps_state; + info->is_deep_sleep = adapter->is_deep_sleep; + info->pm_wakeup_card_req = adapter->pm_wakeup_card_req; + info->pm_wakeup_fw_try = adapter->pm_wakeup_fw_try; + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->hs_activated = adapter->hs_activated; + info->is_cmd_timedout = test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags); + info->num_cmd_host_to_card_failure = + adapter->dbg.num_cmd_host_to_card_failure; + info->num_cmd_sleep_cfm_host_to_card_failure = + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure; + info->num_tx_host_to_card_failure = + adapter->dbg.num_tx_host_to_card_failure; + info->num_event_deauth = adapter->dbg.num_event_deauth; + info->num_event_disassoc = adapter->dbg.num_event_disassoc; + info->num_event_link_lost = adapter->dbg.num_event_link_lost; + info->num_cmd_deauth = adapter->dbg.num_cmd_deauth; + info->num_cmd_assoc_success = + adapter->dbg.num_cmd_assoc_success; + info->num_cmd_assoc_failure = + adapter->dbg.num_cmd_assoc_failure; + info->num_tx_timeout = adapter->dbg.num_tx_timeout; + info->timeout_cmd_id = adapter->dbg.timeout_cmd_id; + info->timeout_cmd_act = adapter->dbg.timeout_cmd_act; + memcpy(info->last_cmd_id, adapter->dbg.last_cmd_id, + sizeof(adapter->dbg.last_cmd_id)); + memcpy(info->last_cmd_act, adapter->dbg.last_cmd_act, + sizeof(adapter->dbg.last_cmd_act)); + info->last_cmd_index = adapter->dbg.last_cmd_index; + memcpy(info->last_cmd_resp_id, adapter->dbg.last_cmd_resp_id, + sizeof(adapter->dbg.last_cmd_resp_id)); + info->last_cmd_resp_index = adapter->dbg.last_cmd_resp_index; + memcpy(info->last_event, adapter->dbg.last_event, + sizeof(adapter->dbg.last_event)); + info->last_event_index = adapter->dbg.last_event_index; + memcpy(info->last_mp_wr_bitmap, adapter->dbg.last_mp_wr_bitmap, + sizeof(adapter->dbg.last_mp_wr_bitmap)); + memcpy(info->last_mp_wr_ports, adapter->dbg.last_mp_wr_ports, + sizeof(adapter->dbg.last_mp_wr_ports)); + memcpy(info->last_mp_curr_wr_port, + adapter->dbg.last_mp_curr_wr_port, + sizeof(adapter->dbg.last_mp_curr_wr_port)); + memcpy(info->last_mp_wr_len, adapter->dbg.last_mp_wr_len, + sizeof(adapter->dbg.last_mp_wr_len)); + info->last_sdio_mp_index = adapter->dbg.last_sdio_mp_index; + info->data_sent = adapter->data_sent; + info->cmd_sent = adapter->cmd_sent; + info->cmd_resp_received = adapter->cmd_resp_received; + } + + return 0; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info) +{ + char *p = buf; + struct nxpwifi_debug_data *d = &items[0]; + size_t size, addr; + long val; + int i, j; + + if (!info) + return 0; + + for (i = 0; i < num_of_items; i++) { + p += sprintf(p, "%s=", d[i].name); + + size = d[i].size / d[i].num; + + if (i < (num_of_items - 3)) + addr = d[i].addr + (size_t)info; + else /* The last 3 items are struct nxpwifi_adapter variables */ + addr = d[i].addr + (size_t)priv->adapter; + + for (j = 0; j < d[i].num; j++) { + switch (size) { + case 1: + val = *((u8 *)addr); + break; + case 2: + val = get_unaligned((u16 *)addr); + break; + case 4: + val = get_unaligned((u32 *)addr); + break; + case 8: + val = get_unaligned((long long *)addr); + break; + default: + val = -1; + break; + } + + p += sprintf(p, "%#lx ", val); + addr += size; + } + + p += sprintf(p, "\n"); + } + + if (info->tx_tbl_num) { + p += sprintf(p, "Tx BA stream table:\n"); + for (i = 0; i < info->tx_tbl_num; i++) + p += sprintf(p, "tid = %d, ra = %pM\n", + info->tx_tbl[i].tid, info->tx_tbl[i].ra); + } + + if (info->rx_tbl_num) { + p += sprintf(p, "Rx reorder table:\n"); + for (i = 0; i < info->rx_tbl_num; i++) { + p += sprintf(p, "tid = %d, ta = %pM, ", + info->rx_tbl[i].tid, + info->rx_tbl[i].ta); + p += sprintf(p, "start_win = %d, ", + info->rx_tbl[i].start_win); + p += sprintf(p, "win_size = %d, buffer: ", + info->rx_tbl[i].win_size); + + for (j = 0; j < info->rx_tbl[i].win_size; j++) + p += sprintf(p, "%c ", + info->rx_tbl[i].buffer[j] ? + '1' : '0'); + + p += sprintf(p, "\n"); + } + } + + return p - buf; +} + +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int i; + struct nxpwifi_private *tmp_priv; + u8 bss_role = GET_BSS_ROLE(priv); + struct ieee80211_channel *set_chan; + + for (i = 0; i < adapter->priv_num; i++) { + tmp_priv = adapter->priv[i]; + if (tmp_priv == priv) + continue; + + set_chan = NULL; + if (bss_role == NXPWIFI_BSS_ROLE_STA) { + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_UAP && + netif_carrier_ok(tmp_priv->netdev) && + cfg80211_chandef_valid(&tmp_priv->bss_chandef)) + set_chan = tmp_priv->bss_chandef.chan; + } else if (bss_role == NXPWIFI_BSS_ROLE_UAP) { + struct nxpwifi_current_bss_params *bss_params = + &tmp_priv->curr_bss_params; + int channel = bss_params->bss_descriptor.channel; + enum nl80211_band band = + nxpwifi_band_to_radio_type(bss_params->band); + int freq = + ieee80211_channel_to_frequency(channel, band); + + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_STA && + tmp_priv->media_connected) + set_chan = ieee80211_get_channel(adapter->wiphy, freq); + } + + if (set_chan && !ieee80211_channel_equal(check_chan, set_chan)) { + nxpwifi_dbg(adapter, ERROR, + "AP/STA must run on the same channel\n"); + return false; + } + } + + return true; +} + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def) +{ + u8 chan_band = 0, chan_width = 0, chan2_offset = 0; + + switch (chan_def->chan->band) { + case NL80211_BAND_2GHZ: + chan_band = BAND_2GHZ; + break; + case NL80211_BAND_5GHZ: + chan_band = BAND_5GHZ; + break; + default: + break; + } + + switch (chan_def->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chan_width = CHAN_BW_20MHZ; + break; + case NL80211_CHAN_WIDTH_40: + chan_width = CHAN_BW_40MHZ; + if (chan_def->center_freq1 > chan_def->chan->center_freq) + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case NL80211_CHAN_WIDTH_80: + chan2_offset = + nxpwifi_get_sec_chan_offset(chan_def->chan->hw_value); + chan_width = CHAN_BW_80MHZ; + break; + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_160: + default: + nxpwifi_dbg(priv->adapter, + WARN, "Unknown channel width: %d\n", + chan_def->width); + break; + } + + *band_cfg = ((chan2_offset << BAND_CFG_CHAN2_SHIFT_BIT) & + BAND_CFG_CHAN2_OFFSET_MASK) | + ((chan_width << BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK) | + ((chan_band << BAND_CFG_CHAN_BAND_SHIFT_BIT) & + BAND_CFG_CHAN_BAND_MASK); +} + +static int +nxpwifi_parse_mgmt_packet(struct nxpwifi_private *priv, u8 *payload, u16 len, + struct rxpd *rx_pd) +{ + u16 stype; + u8 category; + struct ieee80211_hdr *ieee_hdr = (void *)payload; + + stype = (le16_to_cpu(ieee_hdr->frame_control) & IEEE80211_FCTL_STYPE); + + switch (stype) { + case IEEE80211_STYPE_ACTION: + category = *(payload + sizeof(struct ieee80211_hdr)); + switch (category) { + case WLAN_CATEGORY_BACK: + /*we dont indicate BACK action frames to cfg80211*/ + nxpwifi_dbg(priv->adapter, INFO, + "drop BACK action frames"); + return -EINVAL; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown public action frame category %d\n", + category); + } + break; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown mgmt frame subtype %#x\n", stype); + return 0; + } + + return 0; +} + +/* Send deauth frame to cfg80211. */ +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa) +{ + u8 frame_buf[100]; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)frame_buf; + + memset(frame_buf, 0, sizeof(frame_buf)); + mgmt->frame_control = cpu_to_le16(IEEE80211_STYPE_DEAUTH); + mgmt->duration = 0; + mgmt->seq_ctrl = 0; + mgmt->u.deauth.reason_code = cpu_to_le16(reason_code); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + eth_broadcast_addr(mgmt->da); + memcpy(mgmt->sa, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + memcpy(mgmt->bssid, priv->cfg_bssid, ETH_ALEN); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + memcpy(mgmt->da, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->sa, sa, ETH_ALEN); + memcpy(mgmt->bssid, priv->curr_addr, ETH_ALEN); + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + cfg80211_rx_mlme_mgmt(priv->netdev, frame_buf, 26); + } else { + cfg80211_rx_mgmt(&priv->wdev, + priv->bss_chandef.chan->center_freq, + 0, frame_buf, 26, 0); + } +} + +/* Parse and forward received management packet to cfg80211. */ +int +nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct rxpd *rx_pd; + u16 pkt_len; + struct ieee80211_hdr *ieee_hdr; + int ret; + + if (!skb) + return -ENOMEM; + + if (!priv->mgmt_frame_mask || + priv->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(adapter, ERROR, + "do not receive mgmt frames on uninitialized intf"); + return -EINVAL; + } + + rx_pd = (struct rxpd *)skb->data; + pkt_len = le16_to_cpu(rx_pd->rx_pkt_length); + if (pkt_len < sizeof(struct ieee80211_hdr) + sizeof(pkt_len)) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_pkt_length"); + return -EINVAL; + } + + skb_pull(skb, le16_to_cpu(rx_pd->rx_pkt_offset)); + skb_pull(skb, sizeof(pkt_len)); + pkt_len -= sizeof(pkt_len); + + ieee_hdr = (void *)skb->data; + if (ieee80211_is_mgmt(ieee_hdr->frame_control)) { + ret = nxpwifi_parse_mgmt_packet(priv, (u8 *)ieee_hdr, + pkt_len, rx_pd); + if (ret) + return ret; + } + /* Remove address4 */ + memmove(skb->data + sizeof(struct ieee80211_hdr_3addr), + skb->data + sizeof(struct ieee80211_hdr), + pkt_len - sizeof(struct ieee80211_hdr)); + + pkt_len -= ETH_ALEN; + rx_pd->rx_pkt_length = cpu_to_le16(pkt_len); + + if (priv->host_mlme_reg && + (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) && + (ieee80211_is_auth(ieee_hdr->frame_control) || + ieee80211_is_deauth(ieee_hdr->frame_control) || + ieee80211_is_disassoc(ieee_hdr->frame_control))) { + struct nxpwifi_rxinfo *rx_info; + + if (ieee80211_is_auth(ieee_hdr->frame_control)) { + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + if (priv->auth_alg != WLAN_AUTH_SAE) { + priv->auth_flag &= + ~HOST_MLME_AUTH_PENDING; + priv->auth_flag |= + HOST_MLME_AUTH_DONE; + } + } else { + return 0; + } + + nxpwifi_dbg(adapter, MSG, + "auth: receive authentication from %pM\n", + ieee_hdr->addr3); + } else { + if (!priv->wdev.connected) + return 0; + + if (ieee80211_is_deauth(ieee_hdr->frame_control)) { + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr3); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr3); + } + } + + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->pkt_len = pkt_len; + skb_queue_tail(&adapter->rx_mlme_q, skb); + nxpwifi_queue_wiphy_work(adapter, &adapter->host_mlme_work); + return -EINPROGRESS; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive auth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_deauth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_disassoc(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_assoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive assoc req from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_reassoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive reassoc req from %pM\n", + ieee_hdr->addr2); + } + + cfg80211_rx_mgmt(&priv->wdev, priv->roc_cfg.chan.center_freq, + CAL_RSSI(rx_pd->snr, rx_pd->nf), skb->data, pkt_len, + 0); + + return 0; +} + +#define RTAP_MAX_LEN 128 + +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *rxpd; + struct rxpd_extra_info ext; + struct ieee80211_hdr *dot11; + struct ieee80211_radiotap_header *hdr; + int freq, band; + bool has_ext = false, has_ts = false, use_tsft = false; + u16 rx_flags = 0, off, chan_flags, rx_pkt_offset; + __le16 freq_le, flags_le; + u8 rthdr[RTAP_MAX_LEN] = {0}; + s8 signal, noise; + u8 flags = 0, chan, ant, format, bw, gi, ldpc, mcs, nss, stbc; + + if (!skb) + return -EINVAL; + + rxpd = (struct rxpd *)skb->data; + rx_pkt_offset = le16_to_cpu(rxpd->rx_pkt_offset); + + if (rx_pkt_offset > skb->len || rx_pkt_offset < sizeof(struct rxpd)) + return -EINVAL; + + if (skb->len - rx_pkt_offset < sizeof(struct ieee80211_hdr)) + return -EINVAL; + + has_ext = rxpd->flags & RXPD_FLAG_EXTRA_HEADER; + + if (has_ext) + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + dot11 = (void *)skb->data + rx_pkt_offset; + memset(rthdr, 0, sizeof(rthdr)); + hdr = (void *)rthdr; + hdr->it_version = 0; + hdr->it_pad = 0; + hdr->it_present = 0; + off = sizeof(*hdr); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_FLAGS)); + + if (has_ext) { + has_ts = true; + use_tsft = (ext.timestamp.position == 0); + + if (has_ts) { + if (use_tsft) + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TSFT)); + else + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + } + + flags = ext.flags & ~IEEE80211_RADIOTAP_F_BADFCS; + /* reverse fail fcs, 1 means pass FCS in FW, + * but means fail FCS in radiotap + */ + flags &= ~((~ext.flags) & IEEE80211_RADIOTAP_F_BADFCS); + + if (ext.plcp_crc_failed) + rx_flags |= IEEE80211_RADIOTAP_F_RX_BADPLCP; + } + + if (has_ts && use_tsft) { + off = ALIGN(off, 8); + memcpy(rthdr + off, &ext.timestamp.device_timestamp, 8); + off += 8; + } + + if (ieee80211_has_morefrags(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_FRAG; + + if (ieee80211_has_protected(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_WEP; + + rthdr[off++] = flags; + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_CHANNEL)); + chan = (le32_to_cpu(rxpd->rx_info) >> 5) & 0xff; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + + if (has_ext) + chan_flags = ext.channel_flags; + else if (band == NL80211_BAND_2GHZ) + chan_flags = IEEE80211_CHAN_2GHZ; + else + chan_flags = IEEE80211_CHAN_5GHZ; + + freq_le = cpu_to_le16(freq); + flags_le = cpu_to_le16(chan_flags); + memcpy(rthdr + off, &freq_le, 2); + off += 2; + memcpy(rthdr + off, &flags_le, 2); + off += 2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL)); + signal = -(rxpd->nf - rxpd->snr); + rthdr[off++] = signal; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTNOISE)); + noise = -rxpd->nf; + rthdr[off++] = noise; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_ANTENNA)); + ant = rxpd->antenna >> 1; + rthdr[off++] = ant; + + if (rx_flags) { + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_RX_FLAGS)); + off = ALIGN(off, 2); + memcpy(rthdr + off, &rx_flags, 2); + off += 2; + } + + format = FIELD_GET(RX_RATE_FORMAT_MASK, rxpd->rate_info); + bw = FIELD_GET(RX_RATE_BW_MASK, rxpd->rate_info); + ldpc = FIELD_GET(RX_RATE_LDPC_MASK, rxpd->rate_info) ? 1 : 0; + stbc = FIELD_GET(RX_RATE_STBC_MASK, rxpd->rate_info) ? 1 : 0; + + if (format == NXPWIFI_RATE_FORMAT_HE) { + gi = ((rxpd->rate_info >> 7) & 0x1) << 1 | + ((rxpd->rate_info >> 4) & 0x1); + } else { + gi = FIELD_GET(RX_RATE_GI_MASK, rxpd->rate_info); + } + + mcs = rxpd->rx_rate & 0xf; + nss = ((rxpd->rx_rate >> 4) & 0xf) + 1; + + if (format == NXPWIFI_RATE_FORMAT_HT) { + u8 mcs_flags = 0; + + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_MCS)); + rthdr[off++] = IEEE80211_RADIOTAP_MCS_HAVE_MCS | + IEEE80211_RADIOTAP_MCS_HAVE_BW | + IEEE80211_RADIOTAP_MCS_HAVE_GI; + + if (bw == 1) + mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_40; + + if (gi) + mcs_flags |= IEEE80211_RADIOTAP_MCS_SGI; + + if (ldpc) + mcs_flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; + + if (stbc) + mcs_flags |= (1 << IEEE80211_RADIOTAP_MCS_STBC_SHIFT); + + rthdr[off++] = mcs_flags; + rthdr[off++] = mcs; + } + + if (format == NXPWIFI_RATE_FORMAT_VHT && has_ext) { + struct ieee80211_radiotap_vht vht = {0}; + u32 vht_sig1 = 0, vht_sig2 = 0; + __le16 partial_aid = 0; + u8 bw_field = 0; + + vht_sig1 = ext.vht_he_sig1; + vht_sig2 = ext.vht_he_sig2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_VHT)); + vht.known = cpu_to_le16(IEEE80211_RADIOTAP_VHT_KNOWN_GI | + IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH | + IEEE80211_RADIOTAP_VHT_KNOWN_STBC | + IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED | + IEEE80211_RADIOTAP_VHT_KNOWN_GROUP_ID); + + if (stbc) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_STBC; + + if (gi) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + + if (vht_sig2 & BIT(1)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI_NSYM_M10_9; + + if (vht_sig2 & BIT(8)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED; + + switch (bw) { + case 1: + bw_field = 1; /* 40 MHz */ + break; + case 2: + bw_field = 4; /* 80 MHz */ + break; + case 3: + bw_field = 11; /* 160 MHz */ + break; + default: + bw_field = 0; /* 20 MHz */ + } + + vht.bandwidth = bw_field; + vht.mcs_nss[0] = (nss & 0xf) | (mcs << 4); + + if (vht_sig2 & BIT(2)) + vht.coding |= IEEE80211_RADIOTAP_CODING_LDPC_USER0; + + vht.group_id = (vht_sig1 >> 4) & 0x3f; + memcpy(&vht.partial_aid, &partial_aid, 2); + off = ALIGN(off, 2); + memcpy(rthdr + off, &vht, sizeof(vht)); + off += sizeof(vht); + } + + /* TIMESTAMP */ + if (has_ts && !use_tsft) { + u64 ts; + __le64 ts_le; + u16 accuracy = 0; + __le16 acc_le; + u8 flags = 0; + + hdr->it_present |= + cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + off = ALIGN(off, 8); + + if (ext.timestamp.flags & 0x01) { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_32BIT; + ts = (u32)ext.timestamp.device_timestamp; + } else { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_64BIT; + ts = ext.timestamp.device_timestamp; + } + + ts_le = cpu_to_le64(ts); + memcpy(rthdr + off, &ts_le, sizeof(ts_le)); + off += sizeof(ts_le); + + if (ext.timestamp.flags & 0x02) { + accuracy = ext.timestamp.accuracy; + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_ACCURACY; + } + + acc_le = cpu_to_le16(accuracy); + memcpy(rthdr + off, &acc_le, sizeof(acc_le)); + off += sizeof(acc_le); + rthdr[off++] = (ext.timestamp.unit & 0x0f) | + ((ext.timestamp.position & 0x0f) << 4); + rthdr[off++] = flags; + } + + if (format == NXPWIFI_RATE_FORMAT_HE && has_ext) { + struct ieee80211_radiotap_he he = {0}; + u16 data1 = 0, data2 = 0, data3 = 0, data5 = 0, data6 = 0; + u8 bw_val = 0; + + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_HE)); + data1 |= IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN; + data2 |= IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN; + data3 = mcs << 8; + data3 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA3_DATA_MCS, mcs); + + if (stbc) + data3 |= IEEE80211_RADIOTAP_HE_DATA3_STBC; + + switch (bw) { + case 0: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ; + break; + case 1: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ; + break; + case 2: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ; + break; + case 3: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ; + break; + } + + data5 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC, + bw_val); + + switch (gi) { + case 0: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_0_8; + break; + case 1: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_1_6; + break; + case 2: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_3_2; + break; + } + + data6 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA6_NSTS, nss); + he.data1 = cpu_to_le16(data1); + he.data2 = cpu_to_le16(data2); + he.data3 = cpu_to_le16(data3); + he.data5 = cpu_to_le16(data5); + he.data6 = cpu_to_le16(data6); + memcpy(rthdr + off, &he, sizeof(he)); + off += sizeof(he); + } + + hdr->it_len = cpu_to_le16(off); + + if (off > sizeof(rthdr)) + return -EINVAL; + + /* Remove RXPD */ + skb_pull(skb, rx_pkt_offset); + + /* Ensure enough headroom */ + if (skb_cow_head(skb, off)) + return -ENOMEM; + + /* Push radiotap header */ + skb_push(skb, off); + memcpy(skb->data, rthdr, off); + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_NONE; + skb->pkt_type = PACKET_OTHERHOST; + skb->dev = priv->netdev; + skb->protocol = htons(ETH_P_802_2); + netif_rx(skb); + return 0; +} + +/* Process received packet and pass to net stack; reuse or build skb as needed. */ +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_sta_node *src_node; + struct ethhdr *p_ethhdr; + + if (!skb) + return -ENOMEM; + + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + rcu_read_unlock(); + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + netif_rx(skb); + return 0; +} + +/* IOCTL completion callback: wake waiters or process response as needed. */ +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + WARN_ON(!cmd_node->wait_q_enabled); + nxpwifi_dbg(adapter, CMD, "cmd completed: status=%d\n", + adapter->cmd_wait_q.status); + + *cmd_node->condition = true; + wake_up_interruptible(&adapter->cmd_wait_q.wait); + + return 0; +} + +/* Find STA entry by MAC under rcu_read_lock(); return NULL if not found. */ +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + + if (!mac) + return NULL; + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + found = node; + break; + } + } + + return found; +} + +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + + return node; +} + +/* Add STA entry by MAC; return existing entry or NULL on invalid MAC. */ +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + if (!mac) + return NULL; + + spin_lock_bh(&priv->sta_list_spinlock); + node = nxpwifi_get_sta_entry_rcu(priv, mac); + + if (node) + goto done; + + node = kzalloc_obj(*node, GFP_ATOMIC); + if (!node) + goto done; + + memcpy(node->mac_addr, mac, ETH_ALEN); + list_add_tail_rcu(&node->list, &priv->sta_list); + +done: + spin_unlock_bh(&priv->sta_list_spinlock); + return node; +} + +/* Parse HT cap IE from association IEs and set STA HT parameters. */ +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node) +{ + struct element *ht_cap_ie; + const struct ieee80211_ht_cap *ht_cap; + + if (!ies) + return; + + ht_cap_ie = (void *)cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, + ies_len); + if (ht_cap_ie) { + ht_cap = (void *)(ht_cap_ie + 1); + node->is_11n_enabled = 1; + node->max_amsdu = le16_to_cpu(ht_cap->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } else { + node->is_11n_enabled = 0; + } +} + +/* Delete a station from list; called under cfg80211 mutex. */ + +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) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + break; + } + } +} + +/* Delete all stations from list. */ +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv) +{ + struct nxpwifi_sta_node *node, *tmp; + + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry_safe(node, tmp, &priv->sta_list, list) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + } + + INIT_LIST_HEAD(&priv->sta_list); + spin_unlock_bh(&priv->sta_list_spinlock); +} + +/* Add one histogram sample. */ +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + if (atomic_read(&phist_data->num_samples) > NXPWIFI_HIST_MAX_SAMPLES) + nxpwifi_hist_data_reset(priv); + nxpwifi_hist_data_set(priv, rx_rate, snr, nflr); +} + +/* function to add histogram record */ +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + s8 nf = -nflr; + s8 rssi = snr - nflr; + + atomic_inc(&phist_data->num_samples); + atomic_inc(&phist_data->rx_rate[rx_rate]); + atomic_inc(&phist_data->snr[snr + 128]); + atomic_inc(&phist_data->noise_flr[nf + 128]); + atomic_inc(&phist_data->sig_str[rssi + 128]); +} + +/* function to reset histogram data during init/reset */ +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv) +{ + int ix; + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + atomic_set(&phist_data->num_samples, 0); + for (ix = 0; ix < NXPWIFI_MAX_AC_RX_RATES; ix++) + atomic_set(&phist_data->rx_rate[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SNR; ix++) + atomic_set(&phist_data->snr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_NOISE_FLR; ix++) + atomic_set(&phist_data->noise_flr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SIG_STRENGTH; ix++) + atomic_set(&phist_data->sig_str[ix], 0); +} + +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags) +{ + struct sk_buff *skb; + int buf_len, pad; + + buf_len = rx_len + NXPWIFI_RX_HEADROOM + NXPWIFI_DMA_ALIGN_SZ; + + skb = __dev_alloc_skb(buf_len, flags); + + if (!skb) + return NULL; + + skb_reserve(skb, NXPWIFI_RX_HEADROOM); + + pad = NXPWIFI_ALIGN_ADDR(skb->data, NXPWIFI_DMA_ALIGN_SZ) - + (long)skb->data; + + skb_reserve(skb, pad); + + return skb; +} +EXPORT_SYMBOL_GPL(nxpwifi_alloc_dma_align_buf); + +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv) +{ + nxpwifi_send_cmd(priv, HOST_CMD_FW_DUMP_EVENT, HOST_ACT_GEN_SET, + 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_fw_dump_event); + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end) +{ + struct nxpwifi_ie_types_data *tlv; + u16 header_len = sizeof(struct nxpwifi_ie_types_header); + + tlv = (struct nxpwifi_ie_types_data *)pos; + tlv->header.len = cpu_to_le16(len); + + if (id == WLAN_EID_EXT_HE_CAPABILITY) { + if ((pos + header_len + len + 1) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(WLAN_EID_EXTENSION); + tlv->data[0] = WLAN_EID_EXT_HE_CAPABILITY; + memcpy(tlv->data + 1, data, len); + } else { + if ((pos + header_len + len) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(id); + memcpy(tlv->data, data, len); + } + + return (header_len + len); +} + +static int nxpwifi_get_vdll_image(struct nxpwifi_adapter *adapter, u32 vdll_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + bool req_fw = false; + u32 offset; + + if (ctrl->vdll_mem) { + nxpwifi_dbg(adapter, EVENT, + "VDLL mem is not empty: %p old_len=%d new_len=%d\n", + ctrl->vdll_mem, ctrl->vdll_len, vdll_len); + vfree(ctrl->vdll_mem); + ctrl->vdll_mem = NULL; + ctrl->vdll_len = 0; + } + + ctrl->vdll_mem = vmalloc(vdll_len); + if (!ctrl->vdll_mem) + return -ENOMEM; + + if (!adapter->firmware) { + req_fw = true; + if (request_firmware(&adapter->firmware, adapter->fw_name, + adapter->dev)) + return -ENOENT; + } + + if (adapter->firmware) { + if (vdll_len < adapter->firmware->size) { + offset = adapter->firmware->size - vdll_len; + memcpy(ctrl->vdll_mem, adapter->firmware->data + offset, + vdll_len); + } else { + nxpwifi_dbg(adapter, ERROR, + "Invalid VDLL length = %d, fw_len=%d\n", + vdll_len, (int)adapter->firmware->size); + return -EINVAL; + } + if (req_fw) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + } + + ctrl->vdll_len = vdll_len; + nxpwifi_dbg(adapter, MSG, "VDLL image: len=%d\n", ctrl->vdll_len); + + return 0; +} + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + struct host_cmd_ds_command *host_cmd; + u16 msg_len = block_len + S_DS_GEN; + int ret = 0; + + skb_trim(ctrl->skb, 0); + skb_put_zero(ctrl->skb, msg_len); + + host_cmd = (struct host_cmd_ds_command *)(ctrl->skb->data); + + host_cmd->command = cpu_to_le16(HOST_CMD_VDLL); + host_cmd->seq_num = cpu_to_le16(0xFF00); + host_cmd->size = cpu_to_le16(msg_len); + memcpy(ctrl->skb->data + S_DS_GEN, block, block_len); + + skb_push(ctrl->skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_VDLL, + ctrl->skb, NULL); + skb_pull(ctrl->skb, adapter->intf_hdr_len); + + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Fail to download VDLL: block: %p, len: %d\n", + block, block_len); + + return ret; +} + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct vdll_ind_event *vdll_evt = + (struct vdll_ind_event *)(skb->data + sizeof(u32)); + u16 type = le16_to_cpu(vdll_evt->type); + u16 vdll_id = le16_to_cpu(vdll_evt->vdll_id); + u32 offset = le32_to_cpu(vdll_evt->offset); + u16 block_len = le16_to_cpu(vdll_evt->block_len); + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + int ret = 0; + + switch (type) { + case VDLL_IND_TYPE_REQ: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (REG): ID: %d, offset: %#x, len: %d\n", + vdll_id, offset, block_len); + if (offset <= ctrl->vdll_len) { + block_len = + min((u32)block_len, ctrl->vdll_len - offset); + if (!adapter->cmd_sent) { + ret = nxpwifi_download_vdll_block(adapter, + ctrl->vdll_mem + + offset, + block_len); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Download VDLL failed\n"); + } else { + nxpwifi_dbg(adapter, EVENT, + "Delay download VDLL block\n"); + ctrl->pending_block_len = block_len; + ctrl->pending_block = ctrl->vdll_mem + offset; + } + } else { + nxpwifi_dbg(adapter, ERROR, + "Err Req: offset=%#x, len=%d, vdll_len=%d\n", + offset, block_len, ctrl->vdll_len); + ret = -EINVAL; + } + break; + case VDLL_IND_TYPE_OFFSET: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (OFFSET): offset: %#x\n", offset); + ret = nxpwifi_get_vdll_image(adapter, offset); + break; + case VDLL_IND_TYPE_ERR_SIG: + case VDLL_IND_TYPE_ERR_ID: + case VDLL_IND_TYPE_SEC_ERR_ID: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: error: %d\n", type); + break; + case VDLL_IND_TYPE_INTF_RESET: + nxpwifi_dbg(adapter, EVENT, "VDLL IND: interface reset\n"); + break; + default: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: unknown type: %d", type); + ret = -EINVAL; + break; + } + + return ret; +} + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter) +{ + adapter->roc_cookie_counter++; + + /* wow, you wrapped 64 bits ... more likely a bug */ + if (WARN_ON(adapter->roc_cookie_counter == 0)) + adapter->roc_cookie_counter++; + + return adapter->roc_cookie_counter; +} + +static bool nxpwifi_can_queue_work(struct nxpwifi_adapter *adapter) +{ + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) || + test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, WARN, + "queueing nxpwifi work while going to suspend\n"); + return false; + } + + return true; +} + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_work(adapter->workqueue, work); +} +EXPORT_SYMBOL(nxpwifi_queue_work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_delayed_work(adapter->workqueue, dwork, delay); +} +EXPORT_SYMBOL(nxpwifi_queue_delayed_work); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_work_queue(adapter->wiphy, work); +} + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_delayed_work_queue(adapter->wiphy, dwork, delay); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.h b/drivers/net/wireless/nxp/nxpwifi/util.h new file mode 100644 index 000000000000..1a47c8c5b530 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_UTIL_H_ +#define _NXPWIFI_UTIL_H_ +#include "fw.h" + +struct nxpwifi_adapter; + +struct nxpwifi_private; + +struct nxpwifi_dma_mapping { + dma_addr_t addr; + size_t len; +}; + +struct nxpwifi_cb { + struct nxpwifi_dma_mapping dma_mapping; + union { + struct nxpwifi_rxinfo rx_info; + struct nxpwifi_txinfo tx_info; + }; +}; + +/* size/addr for nxpwifi_debug_info */ +#define item_size(n) (sizeof_field(struct nxpwifi_debug_info, n)) +#define item_addr(n) (offsetof(struct nxpwifi_debug_info, n)) + +/* size/addr for struct nxpwifi_adapter */ +#define adapter_item_size(n) (sizeof_field(struct nxpwifi_adapter, n)) +#define adapter_item_addr(n) (offsetof(struct nxpwifi_adapter, n)) + +struct nxpwifi_debug_data { + char name[32]; /* variable/array name */ + u32 size; /* size of the variable/array */ + size_t addr; /* address of the variable/array */ + int num; /* number of variables in an array */ +}; + +static inline struct nxpwifi_rxinfo *NXPWIFI_SKB_RXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + BUILD_BUG_ON(sizeof(struct nxpwifi_cb) > sizeof(skb->cb)); + return &cb->rx_info; +} + +static inline struct nxpwifi_txinfo *NXPWIFI_SKB_TXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + return &cb->tx_info; +} + +static inline void nxpwifi_store_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(&cb->dma_mapping, mapping, sizeof(*mapping)); +} + +static inline void nxpwifi_get_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(mapping, &cb->dma_mapping, sizeof(*mapping)); +} + +static inline dma_addr_t NXPWIFI_SKB_DMA_ADDR(struct sk_buff *skb) +{ + struct nxpwifi_dma_mapping mapping; + + nxpwifi_get_mapping(skb, &mapping); + + return mapping.addr; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info); + +static inline void le16_unaligned_add_cpu(__le16 *var, u16 val) +{ + put_unaligned_le16(get_unaligned_le16(var) + val, var); +} + +/* + * Iterate over TLVs safely. + * Ensures no out-of-bound access even if firmware sends malformed data. + */ +#define nxpwifi_for_each_tlv(tlv, buf, buf_len) \ + for (tlv = (const struct nxpwifi_tlv *)(buf); \ + (u8 *)(tlv) + sizeof(*tlv) <= (u8 *)(buf) + (buf_len) && \ + (u8 *)(tlv) + sizeof(*tlv) + le16_to_cpu(tlv->len) <= \ + (u8 *)(buf) + (buf_len); \ + tlv = (const struct nxpwifi_tlv *)((u8 *)(tlv) + sizeof(*tlv) + \ + le16_to_cpu(tlv->len))) + +/* Return first TLV matching @type in given buffer. */ +static inline const struct nxpwifi_tlv * +nxpwifi_find_tlv(u16 type, const u8 *buf, u32 buf_len) +{ + const struct nxpwifi_tlv *tlv; + + nxpwifi_for_each_tlv(tlv, buf, buf_len) { + if (le16_to_cpu(tlv->type) == type) + return tlv; + } + + return NULL; +} + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end); + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len); + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb); + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work); + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay); + +/* + * Firmware cannot run AP and STA on different channels simultaneously, + * and doing so may trigger a crash. Check whether check_chan can be set + * safely; return true if allowed, false if another channel is already + * active in firmware. + */ +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan); + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def); + +#endif /* !_NXPWIFI_UTIL_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.c b/drivers/net/wireless/nxp/nxpwifi/wmm.c new file mode 100644 index 000000000000..bb4bb724b6fb --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.c @@ -0,0 +1,1318 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + +#define WMM_QUEUED_PACKET_LOWER_LIMIT 180 + +#define WMM_QUEUED_PACKET_UPPER_LIMIT 200 + +/* Offset for TOS field in the IP header */ +#define IPTOS_OFFSET 5 + +static bool disable_tx_amsdu; + +/* + * This table inverses the tos_to_tid operation to get a priority + * which is in sequential order, and can be compared. + * Use this to compare the priority of two different TIDs. + */ +static const u8 tos_to_tid_inv[] = { + 0x02, /* from tos_to_tid[2] = 0 */ + 0x00, /* from tos_to_tid[0] = 1 */ + 0x01, /* from tos_to_tid[1] = 2 */ + 0x03, + 0x04, + 0x05, + 0x06, + 0x07 +}; + +/* WMM information element */ +static const u8 wmm_info_ie[] = { WLAN_EID_VENDOR_SPECIFIC, 0x07, + 0x00, 0x50, 0xf2, 0x02, + 0x00, 0x01, 0x00 +}; + +static const u8 wmm_aci_to_qidx_map[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_VI, + WMM_AC_VO +}; + +static u8 tos_to_tid[] = { + /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x01, /* 0 1 0 AC_BK */ + 0x02, /* 0 0 0 AC_BK */ + 0x00, /* 0 0 1 AC_BE */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ +}; + +static u8 ac_to_tid[4][2] = { {1, 2}, {0, 3}, {4, 5}, {6, 7} }; + +/* Debug prints the priority parameters for a WMM AC. */ +static void +nxpwifi_wmm_ac_debug_print(const struct ieee80211_wmm_ac_param *ac_param) +{ + static const char * const ac_str[] = { "BK", "BE", "VI", "VO" }; + + pr_debug("info: WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, ", + ac_str[wmm_aci_to_qidx_map[(ac_param->aci_aifsn + & NXPWIFI_ACI) >> 5]], + (ac_param->aci_aifsn & NXPWIFI_ACI) >> 5, + (ac_param->aci_aifsn & NXPWIFI_ACM) >> 4, + ac_param->aci_aifsn & NXPWIFI_AIFSN); + pr_debug("EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + ac_param->cw & NXPWIFI_ECW_MIN, + (ac_param->cw & NXPWIFI_ECW_MAX) >> 4, + le16_to_cpu(ac_param->txop_limit)); +} + +/* Allocates a route address list. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_allocate_ralist_node(struct nxpwifi_adapter *adapter, const u8 *ra) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = kzalloc_obj(*ra_list, GFP_ATOMIC); + if (!ra_list) + return NULL; + + INIT_LIST_HEAD(&ra_list->list); + skb_queue_head_init(&ra_list->skb_head); + + memcpy(ra_list->ra, ra, ETH_ALEN); + + ra_list->total_pkt_count = 0; + + nxpwifi_dbg(adapter, INFO, "info: allocated ra_list %p\n", ra_list); + + return ra_list; +} + +/* + * Returns random no between 16 and 32 to be used as threshold for no of + * packets after which BA setup is initiated. + */ +static u8 nxpwifi_get_random_ba_threshold(void) +{ + u64 ns; + /* + * setup ba_packet_threshold here random number between + * [BA_SETUP_PACKET_OFFSET, + * BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1] + */ + ns = ktime_get_ns(); + ns += (ns >> 32) + (ns >> 16); + + return ((u8)ns % BA_SETUP_MAX_PACKET_THRESHOLD) + BA_SETUP_PACKET_OFFSET; +} + +/* Allocates and adds a RA list for all TIDs with the given RA. */ +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra) +{ + int i; + struct nxpwifi_ra_list_tbl *ra_list; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *node; + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_allocate_ralist_node(adapter, ra); + nxpwifi_dbg(adapter, INFO, + "info: created ra_list %p\n", ra_list); + + if (!ra_list) + break; + + ra_list->is_11n_enabled = 0; + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + if (!nxpwifi_queuing_ra_based(priv)) { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ra); + if (node) + ra_list->tx_paused = node->tx_pause; + ra_list->is_11n_enabled = + nxpwifi_is_sta_11n_enabled(priv, node); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = node->max_amsdu; + rcu_read_unlock(); + } + + nxpwifi_dbg(adapter, DATA, "data: ralist %p: is_11n_enabled=%d\n", + ra_list, ra_list->is_11n_enabled); + + if (ra_list->is_11n_enabled) { + ra_list->ba_pkt_count = 0; + ra_list->ba_packet_thr = + nxpwifi_get_random_ba_threshold(); + } + list_add_tail(&ra_list->list, + &priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +/* Sets the WMM queue priorities to their default values. */ +static void nxpwifi_wmm_default_queue_priorities(struct nxpwifi_private *priv) +{ + /* Default queue priorities: VO->VI->BE->BK */ + priv->wmm.queue_priority[0] = WMM_AC_VO; + priv->wmm.queue_priority[1] = WMM_AC_VI; + priv->wmm.queue_priority[2] = WMM_AC_BE; + priv->wmm.queue_priority[3] = WMM_AC_BK; +} + +/* Map ACs to TIDs. */ +static void +nxpwifi_wmm_queue_priorities_tid(struct nxpwifi_private *priv) +{ + struct nxpwifi_wmm_desc *wmm = &priv->wmm; + u8 *queue_priority = wmm->queue_priority; + int i; + + for (i = 0; i < 4; ++i) { + tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1]; + tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0]; + } + + for (i = 0; i < MAX_NUM_TID; ++i) + priv->tos_to_tid_inv[tos_to_tid[i]] = (u8)i; + + atomic_set(&wmm->highest_queued_prio, HIGH_PRIO_TID); +} + +/* Initializes WMM priority queues. */ +void +nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie) +{ + u16 cw_min, avg_back_off, tmp[4]; + u32 i, j, num_ac; + u8 ac_idx; + + if (!wmm_ie || !priv->wmm_enabled) { + /* WMM is not enabled, just set the defaults and return */ + nxpwifi_wmm_default_queue_priorities(priv); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM Parameter element: version=%d,\t" + "qos_info Parameter Set Count=%d, Reserved=%#x\n", + wmm_ie->version, wmm_ie->qos_info & + IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK, + wmm_ie->reserved); + + for (num_ac = 0; num_ac < ARRAY_SIZE(wmm_ie->ac); num_ac++) { + u8 ecw = wmm_ie->ac[num_ac].cw; + u8 aci_aifsn = wmm_ie->ac[num_ac].aci_aifsn; + + cw_min = (1 << (ecw & NXPWIFI_ECW_MIN)) - 1; + avg_back_off = (cw_min >> 1) + (aci_aifsn & NXPWIFI_AIFSN); + + ac_idx = wmm_aci_to_qidx_map[(aci_aifsn & NXPWIFI_ACI) >> 5]; + priv->wmm.queue_priority[ac_idx] = ac_idx; + tmp[ac_idx] = avg_back_off; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + (1 << ((ecw & NXPWIFI_ECW_MAX) >> 4)) - 1, + cw_min, avg_back_off); + nxpwifi_wmm_ac_debug_print(&wmm_ie->ac[num_ac]); + } + + /* Bubble sort */ + for (i = 0; i < num_ac; i++) { + for (j = 1; j < num_ac - i; j++) { + if (tmp[j - 1] > tmp[j]) { + swap(tmp[j - 1], tmp[j]); + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (priv->wmm.queue_priority[j - 1] + < priv->wmm.queue_priority[j]) + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } + } + } + + nxpwifi_wmm_queue_priorities_tid(priv); +} + +/* Evaluates whether or not an AC is to be downgraded. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_eval_downgrade_ac(struct nxpwifi_private *priv, + enum nxpwifi_wmm_ac_e eval_ac) +{ + int down_ac; + enum nxpwifi_wmm_ac_e ret_ac; + struct nxpwifi_wmm_ac_status *ac_status; + + ac_status = &priv->wmm.ac_status[eval_ac]; + + if (!ac_status->disabled) + /* Okay to use this AC, its enabled */ + return eval_ac; + + /* Setup a default return value of the lowest priority */ + ret_ac = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require + * admission control. The spec disallows downgrading to an AC, + * which is enabled due to a completed admission control. + * Unadmitted traffic is not to be sent on an AC with admitted + * traffic. + */ + for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) { + ac_status = &priv->wmm.ac_status[down_ac]; + + if (!ac_status->disabled && !ac_status->flow_required) + /* + * AC is enabled and does not require admission + * control + */ + ret_ac = (enum nxpwifi_wmm_ac_e)down_ac; + } + + return ret_ac; +} + +/* Downgrades WMM priority queue. */ +void +nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv) +{ + int ac_val; + + nxpwifi_dbg(priv->adapter, INFO, "info: WMM: AC Priorities:\t" + "BK(0), BE(1), VI(2), VO(3)\n"); + + if (!priv->wmm_enabled) { + /* WMM is not enabled, default priorities */ + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) + priv->wmm.ac_down_graded_vals[ac_val] = + (enum nxpwifi_wmm_ac_e)ac_val; + } else { + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] = + nxpwifi_wmm_eval_downgrade_ac + (priv, (enum nxpwifi_wmm_ac_e)ac_val); + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: AC PRIO %d maps to %d\n", + ac_val, + priv->wmm.ac_down_graded_vals[ac_val]); + } + } +} + +/* Converts the IP TOS field to an WMM AC Queue assignment. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_convert_tos_to_ac(struct nxpwifi_adapter *adapter, u32 tos) +{ + /* Map of TOS UP values to WMM AC */ + static const enum nxpwifi_wmm_ac_e tos_to_ac[] = { + WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO + }; + + if (tos >= ARRAY_SIZE(tos_to_ac)) + return WMM_AC_BE; + + return tos_to_ac[tos]; +} + +/* + * Evaluates a given TID and downgrades it to a lower TID if the WMM Parameter + * element received from the AP indicates that the AP is disabled (due to call + * admission control (ACM bit). + */ +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid) +{ + enum nxpwifi_wmm_ac_e ac, ac_down; + u8 new_tid; + + ac = nxpwifi_wmm_convert_tos_to_ac(priv->adapter, tid); + ac_down = priv->wmm.ac_down_graded_vals[ac]; + + /* + * Send the index to tid array, picking from the array will be + * taken care by dequeuing function + */ + new_tid = ac_to_tid[ac_down][tid % 2]; + + return new_tid; +} + +/* Initializes the WMM state information and the WMM data path queues. */ +void +nxpwifi_wmm_init(struct nxpwifi_adapter *adapter) +{ + int i, j; + struct nxpwifi_private *priv; + + for (j = 0; j < adapter->priv_num; ++j) { + priv = adapter->priv[j]; + + for (i = 0; i < MAX_NUM_TID; ++i) { + if (!disable_tx_amsdu && + adapter->tx_buf_size > NXPWIFI_TX_DATA_BUF_SIZE_2K) + priv->aggr_prio_tbl[i].amsdu = + priv->tos_to_tid_inv[i]; + else + priv->aggr_prio_tbl[i].amsdu = + BA_STREAM_NOT_ALLOWED; + priv->aggr_prio_tbl[i].ampdu_ap = + priv->tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_user = + priv->tos_to_tid_inv[i]; + } + + priv->aggr_prio_tbl[6].amsdu = + priv->aggr_prio_tbl[6].ampdu_ap = + priv->aggr_prio_tbl[6].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + priv->aggr_prio_tbl[7].amsdu = + priv->aggr_prio_tbl[7].ampdu_ap = + priv->aggr_prio_tbl[7].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + nxpwifi_set_ba_params(priv); + nxpwifi_reset_11n_rx_seq_num(priv); + + priv->wmm.drv_pkt_delay_max = NXPWIFI_WMM_DRV_DELAY_MAX; + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } +} + +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (!skb_queue_empty(&priv->bypass_txq)) + return false; + } + + return true; +} + +/* Checks if WMM Tx queue is empty. */ +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter) +{ + int i; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + if (!priv->port_open) + continue; + if (atomic_read(&priv->wmm.tx_pkts_queued)) + return false; + } + + return true; +} + +/* Deletes all packets in an RA list node. */ +static void +nxpwifi_wmm_del_pkts_in_ralist_node(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + } +} + +/* Deletes all packets in an RA list. */ +static void +nxpwifi_wmm_del_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, ra_list_head, list) + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); +} + +/* Deletes all packets in all RA lists. */ +static void nxpwifi_wmm_cleanup_queues(struct nxpwifi_private *priv) +{ + int i; + + for (i = 0; i < MAX_NUM_TID; i++) + nxpwifi_wmm_del_pkts_in_ralist + (priv, &priv->wmm.tid_tbl_ptr[i].ra_list); + + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); +} + +/* Deletes all route addresses from all RA lists. */ +static void nxpwifi_wmm_delete_all_ralist(struct nxpwifi_private *priv) +{ + struct nxpwifi_ra_list_tbl *ra_list, *tmp_node; + int i; + + for (i = 0; i < MAX_NUM_TID; ++i) { + nxpwifi_dbg(priv->adapter, INFO, + "info: ra_list: freeing buf for tid %d\n", i); + list_for_each_entry_safe(ra_list, tmp_node, + &priv->wmm.tid_tbl_ptr[i].ra_list, + list) { + list_del(&ra_list->list); + kfree(ra_list); + } + + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +static int nxpwifi_free_ack_frame(int id, void *p, void *data) +{ + pr_warn("Have pending ack frames!\n"); + kfree_skb(p); + return 0; +} + +/* Cleans up the Tx and Rx queues. */ +void +nxpwifi_clean_txrx(struct nxpwifi_private *priv) +{ + struct sk_buff *skb, *tmp; + unsigned long index; + void *entry; + + nxpwifi_11n_cleanup_reorder_tbl(priv); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + nxpwifi_wmm_cleanup_queues(priv); + nxpwifi_11n_delete_all_tx_ba_stream_tbl(priv); + + if (priv->adapter->if_ops.cleanup_mpa_buf) + priv->adapter->if_ops.cleanup_mpa_buf(priv->adapter); + + nxpwifi_wmm_delete_all_ralist(priv); + memcpy(tos_to_tid, ac_to_tid, sizeof(tos_to_tid)); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + atomic_set(&priv->adapter->bypass_tx_pending, 0); + + xa_for_each(&priv->ack_status_frames, index, entry) { + nxpwifi_free_ack_frame(index, entry, NULL); + xa_erase(&priv->ack_status_frames, index); + } + + xa_destroy(&priv->ack_status_frames); +} + +/* Retrieves a particular RA list node, matching with the given TID and RA address. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_ralist_node(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, &priv->wmm.tid_tbl_ptr[tid].ra_list, + list) { + if (!memcmp(ra_list->ra, ra_addr, ETH_ALEN)) + return ra_list; + } + + return NULL; +} + +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause) +{ + struct nxpwifi_ra_list_tbl *ra_list; + u32 pkt_cnt = 0, tx_pkts_queued; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, mac); + if (ra_list && ra_list->tx_paused != tx_pause) { + pkt_cnt += ra_list->total_pkt_count; + ra_list->tx_paused = tx_pause; + if (tx_pause) + priv->wmm.pkts_paused[i] += + ra_list->total_pkt_count; + else + priv->wmm.pkts_paused[i] -= + ra_list->total_pkt_count; + } + } + + if (pkt_cnt) { + tx_pkts_queued = atomic_read(&priv->wmm.tx_pkts_queued); + if (tx_pause) + tx_pkts_queued -= pkt_cnt; + else + tx_pkts_queued += pkt_cnt; + + atomic_set(&priv->wmm.tx_pkts_queued, tx_pkts_queued); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Retrieves an RA list node for a given TID and RA address pair. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); + if (ra_list) + return ra_list; + nxpwifi_ralist_add(priv, ra_addr); + + return nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); +} + +/* Deletes RA list nodes for given mac for all TIDs. */ +void +nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, ra_addr); + + if (!ra_list) + continue; + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[i] -= ra_list->total_pkt_count; + else + atomic_sub(ra_list->total_pkt_count, + &priv->wmm.tx_pkts_queued); + list_del(&ra_list->list); + kfree(ra_list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if a particular RA list node exists in a given TID table index. */ +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int ptr_index) +{ + struct nxpwifi_ra_list_tbl *rlist; + + list_for_each_entry(rlist, &priv->wmm.tid_tbl_ptr[ptr_index].ra_list, + list) { + if (rlist == ra_list) + return true; + } + + return false; +} + +/* Adds a packet to bypass TX queue. */ +void +nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + skb_queue_tail(&priv->bypass_txq, skb); +} + +/* Adds a packet to WMM queue. */ +void +nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 tid; + struct nxpwifi_ra_list_tbl *ra_list = NULL; + struct list_head list_head; + u8 ra[ETH_ALEN], tid_down; + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + memcpy(ra, eth_hdr->h_dest, ETH_ALEN); + + if (!priv->media_connected && !nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(adapter, DATA, "data: drop packet in disconnect\n"); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + tid = skb->priority; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + + /* + * In case of infra as we have already created the list during + * association we just don't have to call get_queue_raptr, we will + * have only 1 raptr for a tid in case of infra + */ + if (!nxpwifi_queuing_ra_based(priv) && + !nxpwifi_is_skb_mgmt_frame(skb)) { + list_head = priv->wmm.tid_tbl_ptr[tid_down].ra_list; + ra_list = list_first_entry_or_null(&list_head, + struct nxpwifi_ra_list_tbl, + list); + } else { + memcpy(ra, skb->data, ETH_ALEN); + if (is_multicast_ether_addr(ra) || + nxpwifi_is_skb_mgmt_frame(skb)) + eth_broadcast_addr(ra); + ra_list = nxpwifi_wmm_get_queue_raptr(priv, tid_down, ra); + } + + if (!ra_list) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ra_list->skb_head, skb); + + ra_list->ba_pkt_count++; + ra_list->total_pkt_count++; + + if (atomic_read(&priv->wmm.highest_queued_prio) < + priv->tos_to_tid_inv[tid_down]) + atomic_set(&priv->wmm.highest_queued_prio, + priv->tos_to_tid_inv[tid_down]); + + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid_down]++; + else + atomic_inc(&priv->wmm.tx_pkts_queued); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Processes the get WMM status command response from firmware. */ +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp) +{ + u8 *curr; + u16 resp_len = le16_to_cpu(resp->size), tlv_len; + bool valid = true; + + struct nxpwifi_ie_types_data *tlv_hdr; + struct nxpwifi_ie_types_wmm_queue_status *wmm_qs; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + struct nxpwifi_wmm_ac_status *ac_status; + u32 base; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: WMM_GET_STATUS cmdresp received: %d\n", + resp_len); + + base = offsetofend(struct host_cmd_ds_command, params.get_wmm_status); + + if (resp_len < base) + return -EINVAL; + + curr = (u8 *)&resp->params.get_wmm_status; + resp_len -= base; + + while (resp_len >= sizeof(tlv_hdr->header) && valid) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (resp_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case TLV_TYPE_WMMQSTATUS: + if (tlv_len < + sizeof(struct nxpwifi_ie_types_wmm_queue_status) - + sizeof(tlv_hdr->header)) + break; + + wmm_qs = + (struct nxpwifi_ie_types_wmm_queue_status *)tlv_hdr; + + if (wmm_qs->queue_index >= IEEE80211_NUM_ACS) + break; + + ac_status = &priv->wmm.ac_status[wmm_qs->queue_index]; + ac_status->disabled = wmm_qs->disabled; + ac_status->flow_required = wmm_qs->flow_required; + ac_status->flow_created = wmm_qs->flow_created; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* Need at least OUI(4) + WMM fixed fields */ + if (tlv_len + sizeof(tlv_hdr->header) < + offsetofend(struct ieee80211_wmm_param_ie, + qos_info)) + break; + + wmm_param_ie = + (struct ieee80211_wmm_param_ie *)(curr + 2); + + if (tlv_len + 2 > sizeof(struct ieee80211_wmm_param_ie)) + break; + + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = WLAN_EID_VENDOR_SPECIFIC; + + memcpy(&priv->curr_bss_params.bss_descriptor.wmm_ie, + wmm_param_ie, wmm_param_ie->len + 2); + break; + + default: + valid = false; + break; + } + + curr += sizeof(tlv_hdr->header) + tlv_len; + resp_len -= sizeof(tlv_hdr->header) + tlv_len; + } + + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + nxpwifi_wmm_setup_ac_downgrade(priv); + + return 0; +} + +/* + * Callback handler from the command module to allow insertion of a WMM TLV. + * + * If the BSS we are associating to supports WMM, this function adds the + * required WMM Information element to the association request command buffer in + * the form of a NXP extended IEEE element. + */ +u32 +nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmm_ie, + struct ieee80211_ht_cap *ht_cap) +{ + struct nxpwifi_ie_types_wmm_param_set *wmm_tlv; + u32 ret_len = 0; + + /* Null checks */ + if (!assoc_buf) + return 0; + if (!(*assoc_buf)) + return 0; + + if (!wmm_ie) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: process assoc req: bss->wmm_ie=%#x\n", + wmm_ie->element_id); + + if ((priv->wmm_required || + (ht_cap && (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN))) && + wmm_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + wmm_tlv = (struct nxpwifi_ie_types_wmm_param_set *)*assoc_buf; + wmm_tlv->header.type = cpu_to_le16((u16)wmm_info_ie[0]); + wmm_tlv->header.len = cpu_to_le16((u16)wmm_info_ie[1]); + memcpy(wmm_tlv->wmm_ie, &wmm_info_ie[2], + le16_to_cpu(wmm_tlv->header.len)); + if (wmm_ie->qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD) + memcpy((u8 *)(wmm_tlv->wmm_ie + + le16_to_cpu(wmm_tlv->header.len) + - sizeof(priv->wmm_qosinfo)), + &priv->wmm_qosinfo, sizeof(priv->wmm_qosinfo)); + + ret_len = sizeof(wmm_tlv->header) + + le16_to_cpu(wmm_tlv->header.len); + + *assoc_buf += ret_len; + } + + return ret_len; +} + +/* Computes the time delay in the driver queues for a given packet. */ +u8 +nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb) +{ + u32 queue_delay = ktime_to_ms(net_timedelta(skb->tstamp)); + u8 ret_val; + + /* + * Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + ret_val = (u8)(min(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1); + + nxpwifi_dbg(priv->adapter, DATA, "data: WMM: Pkt Delay: %d ms,\t" + "%d ms sent to FW\n", queue_delay, ret_val); + + return ret_val; +} + +/* Retrieves the highest priority RA list table pointer. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_highest_priolist_ptr(struct nxpwifi_adapter *adapter, + struct nxpwifi_private **priv, int *tid) +{ + struct nxpwifi_private *priv_tmp; + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_tid_tbl *tid_ptr; + atomic_t *hqp; + int i, j; + u8 to_tid; + + /* check the BSS with highest priority first */ + for (j = adapter->priv_num - 1; j >= 0; --j) { + /* iterate over BSS with the equal priority */ + list_for_each_entry(adapter->bss_prio_tbl[j].bss_prio_cur, + &adapter->bss_prio_tbl[j].bss_prio_head, + list) { +try_again: + priv_tmp = adapter->bss_prio_tbl[j].bss_prio_cur->priv; + + if (!priv_tmp->port_open || + (atomic_read(&priv_tmp->wmm.tx_pkts_queued) == 0)) + continue; + + /* iterate over the WMM queues of the BSS */ + hqp = &priv_tmp->wmm.highest_queued_prio; + for (i = atomic_read(hqp); i >= LOW_PRIO_TID; --i) { + spin_lock_bh(&priv_tmp->wmm.ra_list_spinlock); + + to_tid = tos_to_tid[i]; + tid_ptr = &(priv_tmp)->wmm.tid_tbl_ptr[to_tid]; + + /* iterate over receiver addresses */ + list_for_each_entry(ptr, &tid_ptr->ra_list, + list) { + if (!ptr->tx_paused && + !skb_queue_empty(&ptr->skb_head)) + /* holds both locks */ + goto found; + } + + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + } + + if (atomic_read(&priv_tmp->wmm.tx_pkts_queued) != 0) { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + HIGH_PRIO_TID); + /* + * Iterate current private once more, since + * there still exist packets in data queue + */ + goto try_again; + } else { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + NO_PKT_PRIO_TID); + } + } + } + + return NULL; + +found: + /* holds ra_list_spinlock */ + if (atomic_read(hqp) > i) + atomic_set(hqp, i); + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + + *priv = priv_tmp; + *tid = tos_to_tid[i]; + + return ptr; +} + +/* Rotates ra and bss lists so packets are picked round robin. */ +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, + int tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + struct nxpwifi_tid_tbl *tid_ptr = &priv->wmm.tid_tbl_ptr[tid]; + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + /* + * dirty trick: we remove 'head' temporarily and reinsert it after + * curr bss node. imagine list to stay fixed while head is moved + */ + list_move(&tbl[priv->bss_priority].bss_prio_head, + &tbl[priv->bss_priority].bss_prio_cur->list); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (nxpwifi_is_ralist_valid(priv, ra, tid)) { + priv->wmm.packets_out[tid]++; + /* same as above */ + list_move(&tid_ptr->ra_list, &ra->list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if 11n aggregation is possible. */ +static bool +nxpwifi_is_11n_aggragation_possible(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int max_buf_size) +{ + int count = 0, total_size = 0; + struct sk_buff *skb, *tmp; + int max_amsdu_size; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && priv->ap_11n_enabled && + ptr->is_11n_enabled) + max_amsdu_size = min_t(int, ptr->max_amsdu, max_buf_size); + else + max_amsdu_size = max_buf_size; + + skb_queue_walk_safe(&ptr->skb_head, skb, tmp) { + total_size += skb->len; + if (total_size >= max_amsdu_size) + break; + if (++count >= MIN_NUM_AMSDU) + return true; + } + + return false; +} + +/* Sends a single packet to firmware for transmission. */ +static void +nxpwifi_send_single_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) +__releases(&priv->wmm.ra_list_spinlock) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, DATA, "data: nothing to send\n"); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + nxpwifi_dbg(adapter, DATA, + "data: dequeuing the packet %p %p\n", ptr, skb); + + ptr->total_pkt_count--; + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + /* Queue the packet back at the head */ + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + ptr->total_pkt_count++; + ptr->ba_pkt_count++; + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } else { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + } +} + +/* Checks if the first packet in the given RA list is already processed or not. */ +static bool +nxpwifi_is_ptr_processed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) + return false; + + skb = skb_peek(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_REQUEUED_PKT) + return true; + + return false; +} + +/* Sends a single processed packet to firmware for transmission. */ +static void +nxpwifi_send_processed_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + if (adapter->data_sent || adapter->tx_lock_flag) { + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_dec(&priv->wmm.tx_pkts_queued); + atomic_inc(&adapter->tx_queued); + return; + } + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + tx_info = NXPWIFI_SKB_TXCB(skb); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = + ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "host_to_card failed: %#x\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + if (ret != -EBUSY) { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } +} + +/* Dequeues a packet from the highest priority list and transmits it. */ +static int +nxpwifi_dequeue_tx_packet(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_private *priv = NULL; + int ptr_index = 0; + u8 ra[ETH_ALEN]; + int tid_del = 0, tid = 0; + + ptr = nxpwifi_wmm_get_highest_priolist_ptr(adapter, &priv, &ptr_index); + if (!ptr) + return -ENOENT; + + tid = nxpwifi_get_tid(ptr); + + nxpwifi_dbg(adapter, DATA, "data: tid=%d\n", tid); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -EINVAL; + } + + if (nxpwifi_is_ptr_processed(priv, ptr)) { + nxpwifi_send_processed_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_processed_packet() + */ + return 0; + } + + if (!ptr->is_11n_enabled || + ptr->ba_status || + priv->wps.session_enable) { + if (ptr->is_11n_enabled && + ptr->ba_status && + ptr->amsdu_in_ampdu && + nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } else { + if (nxpwifi_is_ampdu_allowed(priv, ptr, tid) && + ptr->ba_pkt_count > ptr->ba_packet_thr) { + if (nxpwifi_space_avail_for_new_ba_stream(adapter)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_addba(priv, tid, ptr->ra); + } else if (nxpwifi_find_stream_to_delete + (priv, tid, &tid_del, ra)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_delba(priv, tid_del, ra, 1); + } + } + if (nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } + return 0; +} + +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_tx_param tx_param; + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv; + int i; + + if (adapter->data_sent || adapter->tx_lock_flag) + return; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + + if (skb_queue_empty(&priv->bypass_txq)) + continue; + + skb = skb_dequeue(&priv->bypass_txq); + tx_info = NXPWIFI_SKB_TXCB(skb); + + /* no aggregation for bypass packets */ + tx_param.next_pkt_len = 0; + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + skb_queue_head(&priv->bypass_txq, skb); + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + } else { + atomic_dec(&adapter->bypass_tx_pending); + } + } +} + +/* Transmits the highest priority packet awaiting in the WMM Queues. */ +void +nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter) +{ + do { + if (nxpwifi_dequeue_tx_packet(adapter)) + break; + if (adapter->iface_type != NXPWIFI_SDIO) { + if (adapter->data_sent || + adapter->tx_lock_flag) + break; + } else { + if (atomic_read(&adapter->tx_queued) >= + NXPWIFI_MAX_PKTS_TXQ) + break; + } + } while (!nxpwifi_wmm_lists_empty(adapter)); +} + +void nxpwifi_wmm_init_tos_to_tid_inv(struct nxpwifi_private *priv) +{ + memcpy(priv->tos_to_tid_inv, tos_to_tid_inv, sizeof(priv->tos_to_tid_inv)); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.h b/drivers/net/wireless/nxp/nxpwifi/wmm.h new file mode 100644 index 000000000000..d7f4a29bc301 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_WMM_H_ +#define _NXPWIFI_WMM_H_ + +enum ieee_types_wmm_aciaifsn_bitmasks { + NXPWIFI_AIFSN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ACM = BIT(4), + NXPWIFI_ACI = (BIT(5) | BIT(6)), +}; + +enum ieee_types_wmm_ecw_bitmasks { + NXPWIFI_ECW_MIN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ECW_MAX = (BIT(4) | BIT(5) | BIT(6) | BIT(7)), +}; + +extern const u16 nxpwifi_1d_to_wmm_queue[]; + +/* Retrieve the TID of the given RA list. */ +static inline int +nxpwifi_get_tid(struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + + if (skb_queue_empty(&ptr->skb_head)) + return 0; + + skb = skb_peek(&ptr->skb_head); + + return skb->priority; +} + +void nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra); +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, int tid); + +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter); +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter); +void nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter); +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter); +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int tid); + +u8 nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb); +void nxpwifi_wmm_init(struct nxpwifi_adapter *adapter); + +u32 nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmmie, + struct ieee80211_ht_cap *htcap); + +void nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie); +void nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv); +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp); +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr); +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid); +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause); + +struct nxpwifi_ra_list_tbl *nxpwifi_wmm_get_ralist_node(struct nxpwifi_private + *priv, u8 tid, const u8 *ra_addr); +void nxpwifi_wmm_init_tos_to_tid_inv(struct nxpwifi_private *priv); +#endif /* !_NXPWIFI_WMM_H_ */ diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c index 9e44c85d2051..45e2b7ae9633 100644 --- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c +++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c @@ -446,7 +446,7 @@ qtnf_update_mgmt_frame_registrations(struct wiphy *wiphy, static int qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev); const struct ieee80211_mgmt *mgmt_frame = (void *)params->buf; @@ -454,8 +454,6 @@ qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, u16 flags = 0; u16 freq; - *cookie = short_cookie; - if (params->offchan) flags |= QLINK_FRAME_TX_FLAG_OFFCHAN; diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c index 070c0431c482..4a5989172a5e 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c @@ -1652,7 +1652,7 @@ static void rtl8180_eeprom_register_write(struct eeprom_93cx6 *eeprom) static void rtl8180_eeprom_read(struct rtl8180_priv *priv) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; int eeprom_cck_table_adr; u16 eeprom_val; int i; diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c index 1d21c468a236..a766712187f7 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c @@ -1445,7 +1445,7 @@ static int rtl8187_probe(struct usb_interface *intf, struct usb_device *udev = interface_to_usbdev(intf); struct ieee80211_hw *dev; struct rtl8187_priv *priv; - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; struct ieee80211_channel *channel; const char *chip_name; u16 txpwr, reg; diff --git a/drivers/net/wireless/realtek/rtl8xxxu/8723b.c b/drivers/net/wireless/realtek/rtl8xxxu/8723b.c index e314ef991b38..24c6d8ae76ec 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/8723b.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/8723b.c @@ -483,16 +483,7 @@ static int rtl8723bu_parse_efuse(struct rtl8xxxu_priv *priv) static int rtl8723bu_load_firmware(struct rtl8xxxu_priv *priv) { - const char *fw_name; - int ret; - - if (priv->enable_bluetooth) - fw_name = "rtlwifi/rtl8723bu_bt.bin"; - else - fw_name = "rtlwifi/rtl8723bu_nic.bin"; - - ret = rtl8xxxu_load_firmware(priv, fw_name); - return ret; + return rtl8xxxu_load_firmware(priv, "rtlwifi/rtl8723bu_nic.bin"); } static void rtl8723bu_init_phy_bb(struct rtl8xxxu_priv *priv) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 646fe76b086e..bddbd0990de7 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -37,7 +37,6 @@ MODULE_FIRMWARE("rtlwifi/rtl8192cufw_B.bin"); MODULE_FIRMWARE("rtlwifi/rtl8192cufw_TMSC.bin"); MODULE_FIRMWARE("rtlwifi/rtl8192eu_nic.bin"); MODULE_FIRMWARE("rtlwifi/rtl8723bu_nic.bin"); -MODULE_FIRMWARE("rtlwifi/rtl8723bu_bt.bin"); MODULE_FIRMWARE("rtlwifi/rtl8188fufw.bin"); MODULE_FIRMWARE("rtlwifi/rtl8710bufw_SMIC.bin"); MODULE_FIRMWARE("rtlwifi/rtl8710bufw_UMC.bin"); @@ -5838,14 +5837,19 @@ static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, { struct sk_buff *skb; unsigned long flags; - int pending = 0; spin_lock_irqsave(&priv->rx_urb_lock, flags); if (!priv->shutdown) { list_add_tail(&rx_urb->list, &priv->rx_urb_pending_list); priv->rx_urb_pending_count++; - pending = priv->rx_urb_pending_count; + /* + * Arm the worker under rx_urb_lock so this is atomic with the + * shutdown check: moving it out of the lock would let a + * completion arm the work after rtl8xxxu_stop() canceled it. + */ + if (priv->rx_urb_pending_count > RTL8XXXU_RX_URB_PENDING_WATER) + schedule_work(&priv->rx_urb_wq); } else { skb = (struct sk_buff *)rx_urb->urb.context; dev_kfree_skb_irq(skb); @@ -5853,9 +5857,6 @@ static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, } spin_unlock_irqrestore(&priv->rx_urb_lock, flags); - - if (pending > RTL8XXXU_RX_URB_PENDING_WATER) - schedule_work(&priv->rx_urb_wq); } static void rtl8xxxu_rx_urb_work(struct work_struct *work) @@ -7507,6 +7508,13 @@ static void rtl8xxxu_stop(struct ieee80211_hw *hw, bool suspend) priv->shutdown = true; spin_unlock_irqrestore(&priv->rx_urb_lock, flags); + /* + * Cancel before killing rx_anchor: the worker re-anchors every URB + * it drained via rtl8xxxu_submit_rx_urb(), so a worker still running + * after the kill could submit a URB that escapes it. + */ + cancel_work_sync(&priv->rx_urb_wq); + usb_kill_anchored_urbs(&priv->rx_anchor); usb_kill_anchored_urbs(&priv->tx_anchor); if (priv->usb_interrupts) diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c index 73018a0498b4..d3b79d0d3eb6 100644 --- a/drivers/net/wireless/realtek/rtlwifi/pci.c +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c @@ -31,6 +31,14 @@ static const u8 ac_to_hwq[] = { BK_QUEUE }; +static const struct pci_device_id rtl_aspm_quirks[] = { + /* ASUSTek F441U/X555UQ */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_REALTEK, 0xb723, 0x11ad, 0x1723) }, + /* Razer Blade 14 2017 */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_REALTEK, 0xb723, 0x17aa, 0xb736) }, + {} +}; + static u8 _rtl_mac_to_hwqueue(struct ieee80211_hw *hw, struct sk_buff *skb) { struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); @@ -156,15 +164,6 @@ static void _rtl_pci_update_default_setting(struct ieee80211_hw *hw) PCI_EXP_LNKCTL_ASPM_L1 | PCI_EXP_LNKCTL_CCC)) ppsc->support_aspm = false; - /* RTL8723BE found on some ASUSTek laptops, such as F441U and - * X555UQ with subsystem ID 11ad:1723 are known to output large - * amounts of PCIe AER errors during and after boot up, causing - * heavy lags, poor network throughput, and occasional lock-ups. - */ - if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE && - (rtlpci->pdev->subsystem_vendor == 0x11ad && - rtlpci->pdev->subsystem_device == 0x1723)) - ppsc->support_aspm = false; } static bool _rtl_pci_platform_switch_device_pci_aspm( @@ -203,7 +202,7 @@ static void _rtl_pci_switch_clk_req(struct ieee80211_hw *hw, u16 value) } /*Disable RTL8192SE ASPM & Disable Pci Bridge ASPM*/ -static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) +static void __rtl_pci_disable_aspm(struct ieee80211_hw *hw) { struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); @@ -215,9 +214,6 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) u16 aspmlevel = 0; u16 tmp_u1b = 0; - if (!ppsc->support_aspm) - return; - if (pcibridge_vendor == PCI_BRIDGE_VENDOR_UNKNOWN) { rtl_dbg(rtlpriv, COMP_POWER, DBG_TRACE, "PCI(Bridge) UNKNOWN\n"); @@ -240,6 +236,16 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) _rtl_pci_platform_switch_device_pci_aspm(hw, linkctrl_reg); } +static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if (!ppsc->support_aspm) + return; + + __rtl_pci_disable_aspm(hw); +} + /*Enable RTL8192SE ASPM & Enable Pci Bridge ASPM for *power saving We should follow the sequence to enable *RTL8192SE first then enable Pci Bridge ASPM @@ -330,10 +336,21 @@ static void rtl_pci_parse_configuration(struct pci_dev *pdev, static void rtl_pci_init_aspm(struct ieee80211_hw *hw) { + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); _rtl_pci_update_default_setting(hw); + /* + * Certain pci devices are known to output large + * amounts of PCIe AER errors during and after boot up, causing + * heavy lags, poor network throughput, and occasional lock-ups. + */ + if (pci_match_id(rtl_aspm_quirks, rtlpci->pdev)) { + __rtl_pci_disable_aspm(hw); + ppsc->support_aspm = false; + } + if (ppsc->reg_rfps_level & RT_RF_PS_LEVEL_ALWAYS_ASPM) { /*Always enable ASPM & Clock Req. */ rtl_pci_enable_aspm(hw); @@ -2227,13 +2244,17 @@ int rtl_pci_probe(struct pci_dev *pdev, rtl_dbg(rtlpriv, COMP_INIT, DBG_DMESG, "%s: failed to register IRQ handler\n", wiphy_name(hw->wiphy)); - goto fail3; + goto fail6; } rtlpci->irq_alloc = 1; set_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status); return 0; +fail6: + rtl_deinit_rfkill(hw); + rtl_debug_remove_one(hw); + ieee80211_unregister_hw(hw); fail5: rtl_pci_deinit(hw); fail4: diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c index 97e0d9c01e0a..cfefbe86380f 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c @@ -775,7 +775,6 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_phy *rtlphy = &rtlpriv->phy; enum wireless_mode wirelessmode; - u8 mimo_ps = IEEE80211_SMPS_OFF; u8 curtxbw_40mhz = mac->bw_40; u8 nmode = mac->ht_enable; u8 curshortgi_40mhz; @@ -784,6 +783,7 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, u8 ratr_index = 0; u16 shortgi_rate; u32 ratr_value; + u32 ratr_mask; curshortgi_40mhz = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_40); curshortgi_20mhz = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_20); @@ -811,20 +811,15 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, case WIRELESS_MODE_N_24G: case WIRELESS_MODE_N_5G: nmode = 1; - if (mimo_ps == IEEE80211_SMPS_STATIC) { - ratr_value &= 0x0007F005; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) { + ratr_mask = 0x000ff005; } else { - u32 ratr_mask; - - if (get_rf_type(rtlphy) == RF_1T2R || - get_rf_type(rtlphy) == RF_1T1R) { - ratr_mask = 0x000ff005; - } else { - ratr_mask = 0x0f0ff005; - } - - ratr_value &= ratr_mask; + ratr_mask = 0x0f0ff005; } + + ratr_value &= ratr_mask; break; default: if (rtlphy->rf_type == RF_1T2R) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c index 7bafb051d5ec..88e8cc590c54 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c @@ -145,8 +145,10 @@ static int rtl92du_init_sw_vars(struct ieee80211_hw *hw) /* for firmware buf */ rtlpriv->rtlhal.pfirmware = kmalloc(0x8000, GFP_KERNEL); - if (!rtlpriv->rtlhal.pfirmware) - return -ENOMEM; + if (!rtlpriv->rtlhal.pfirmware) { + err = -ENOMEM; + goto error; + } rtlpriv->max_fw_size = 0x8000; pr_info("Driver for Realtek RTL8192DU WLAN interface\n"); @@ -160,10 +162,14 @@ static int rtl92du_init_sw_vars(struct ieee80211_hw *hw) pr_err("Failed to request firmware!\n"); kfree(rtlpriv->rtlhal.pfirmware); rtlpriv->rtlhal.pfirmware = NULL; - return err; + goto error; } return 0; + +error: + rtl92du_deinit_shared_data(hw); + return err; } static void rtl92du_deinit_sw_vars(struct ieee80211_hw *hw) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c index 743ce0cfffe6..c608c51f1b78 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c @@ -106,7 +106,8 @@ void rtl92du_tx_fill_desc(struct ieee80211_hw *hw, if (sta) { sta_entry = (struct rtl_sta_info *)sta->drv_priv; tid = ieee80211_get_tid(hdr); - agg_state = sta_entry->tids[tid].agg.agg_state; + if (tid < MAX_TID_COUNT) + agg_state = sta_entry->tids[tid].agg.agg_state; ampdu_density = sta->deflink.ht_cap.ampdu_density; } diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c index e1f811218894..bf7b5a32adaa 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c @@ -1333,11 +1333,6 @@ int rtl8723be_hw_init(struct ieee80211_hw *hw) bool rtstatus = true; int err; u8 tmp_u1b; - unsigned long flags; - - /* reenable interrupts to not interfere with other devices */ - local_save_flags(flags); - local_irq_enable(); rtlhal->fw_ready = false; rtlpriv->rtlhal.being_init_adapter = true; @@ -1443,7 +1438,6 @@ int rtl8723be_hw_init(struct ieee80211_hw *hw) rtl8723be_dm_init(hw); exit: - local_irq_restore(flags); rtlpriv->rtlhal.being_init_adapter = false; return err; } diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c index 9b119a51bc30..1ba53e671207 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c @@ -1462,12 +1462,7 @@ static bool _rtl8821ae_init_llt_table(struct ieee80211_hw *hw, u32 boundary) return status; } - status = _rtl8821ae_llt_write(hw, last_entry_of_txpktbuf, - txpktbuf_bndy); - if (!status) - return status; - - return status; + return _rtl8821ae_llt_write(hw, last_entry_of_txpktbuf, txpktbuf_bndy); } static bool _rtl8821ae_dynamic_rqpn(struct ieee80211_hw *hw, u32 boundary, diff --git a/drivers/net/wireless/realtek/rtw88/bf.c b/drivers/net/wireless/realtek/rtw88/bf.c index 0d0ccbc7d00c..7313ecc5c82a 100644 --- a/drivers/net/wireless/realtek/rtw88/bf.c +++ b/drivers/net/wireless/realtek/rtw88/bf.c @@ -137,7 +137,8 @@ void rtw_bf_cfg_sounding(struct rtw_dev *rtwdev, struct rtw_vif *vif, rtw_write8_mask(rtwdev, REG_SND_PTCL_CTRL, BIT_MASK_BEAMFORM, RTW_SND_CTRL_SOUNDING); rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3, 0x26); - rtw_write8_clr(rtwdev, REG_RXFLTMAP1, BIT_RXFLTMAP1_BF_REPORT_POLL); + rtwdev->hal.rxfltmap1 &= ~BIT_RXFLTMAP1_BF_REPORT_POLL; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write8_clr(rtwdev, REG_RXFLTMAP4, BIT_RXFLTMAP4_BF_REPORT_POLL); if (vif->net_type == RTW_NET_AP_MODE) @@ -269,7 +270,8 @@ void rtw_bf_enable_bfee_mu(struct rtw_dev *rtwdev, struct rtw_vif *vif, rtw_write16_set(rtwdev, REG_RXFLTMAP0, BIT_RXFLTMAP0_ACTIONNOACK); /* accept NDPA and BF report poll */ - rtw_write16_set(rtwdev, REG_RXFLTMAP1, BIT_RXFLTMAP1_BF); + rtwdev->hal.rxfltmap1 |= BIT_RXFLTMAP1_BF; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); } EXPORT_SYMBOL(rtw_bf_enable_bfee_mu); diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c index 766f22d31079..b01b98d24b0a 100644 --- a/drivers/net/wireless/realtek/rtw88/mac80211.c +++ b/drivers/net/wireless/realtek/rtw88/mac80211.c @@ -281,12 +281,18 @@ static void rtw_ops_configure_filter(struct ieee80211_hw *hw, struct rtw_dev *rtwdev = hw->priv; *new_flags &= FIF_ALLMULTI | FIF_OTHER_BSS | FIF_FCSFAIL | - FIF_BCN_PRBRESP_PROMISC; + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; mutex_lock(&rtwdev->mutex); rtw_leave_lps_deep(rtwdev); + if (changed_flags & FIF_CONTROL) { + if (*new_flags & FIF_CONTROL) + rtw_write16(rtwdev, REG_RXFLTMAP1, 0xffff); + else + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); + } if (changed_flags & FIF_ALLMULTI) { if (*new_flags & FIF_ALLMULTI) rtwdev->hal.rcr |= BIT_AM; diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 9c0b746540b0..c6e981ba7986 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -1963,6 +1963,7 @@ struct rtw_sar { struct rtw_hal { u32 rcr; + u16 rxfltmap1; u32 chip_version; u8 cut_version; diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c index a30467228912..5b00c1e1eec6 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.c +++ b/drivers/net/wireless/realtek/rtw88/pci.c @@ -1779,6 +1779,16 @@ static const struct dmi_system_id rtw_pci_quirks[] = { .driver_data = (void *)(BIT(QUIRK_DIS_CAP_PCI_ASPM) | BIT(QUIRK_DIS_CAP_LPS_DEEP)), }, + { + .callback = rtw_pci_disable_caps, + .ident = "ASUS TUF Gaming A15 FA506II", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_BOARD_NAME, "FA506II"), + }, + .driver_data = (void *)(BIT(QUIRK_DIS_CAP_PCI_ASPM) | + BIT(QUIRK_DIS_CAP_LPS_DEEP)), + }, {} }; @@ -1834,7 +1844,7 @@ int rtw_pci_probe(struct pci_dev *pdev, ret = rtw_pci_napi_init(rtwdev); if (ret) { rtw_err(rtwdev, "failed to setup NAPI\n"); - goto err_pci_declaim; + goto err_destroy_rsrc; } ret = rtw_chip_info_setup(rtwdev); @@ -1866,6 +1876,8 @@ int rtw_pci_probe(struct pci_dev *pdev, err_destroy_pci: rtw_pci_napi_deinit(rtwdev); + +err_destroy_rsrc: rtw_pci_destroy(rtwdev, pdev); err_pci_declaim: diff --git a/drivers/net/wireless/realtek/rtw88/rtw8723x.c b/drivers/net/wireless/realtek/rtw88/rtw8723x.c index 3f3e9b0c44e8..97e6f6a62c0d 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8723x.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8723x.c @@ -356,7 +356,8 @@ static int __rtw8723x_mac_init(struct rtw_dev *rtwdev) rtw_write32(rtwdev, REG_TCR, BIT_TCR_CFG); rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); - rtw_write16(rtwdev, REG_RXFLTMAP1, WLAN_RX_FILTER1); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c index ca1079e12023..fa02aa299b5e 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c @@ -400,7 +400,8 @@ static int rtw8814a_mac_init(struct rtw_dev *rtwdev) rtw_write16(rtwdev, REG_RETRY_LIMIT, 0x3030); rtw_write16(rtwdev, REG_RXFLTMAP0, 0xffff); - rtw_write16(rtwdev, REG_RXFLTMAP1, 0x0400); + rtwdev->hal.rxfltmap1 = 0x0400; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, 0xffff); rtw_write8(rtwdev, REG_MAX_AGGR_NUM, 0x36); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c index 246046da4f13..ee689fa3d87f 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c @@ -248,7 +248,9 @@ static int rtw8821c_mac_init(struct rtw_dev *rtwdev) rtw_write8_clr(rtwdev, REG_TX_PTCL_CTRL + 1, BIT_SIFS_BK_EN >> 8); /* WMAC configuration */ - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.h b/drivers/net/wireless/realtek/rtw88/rtw8821c.h index 954e93c8020d..ea85b7e73050 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821c.h +++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.h @@ -139,7 +139,8 @@ extern const struct rtw_chip_info rtw8821c_hw_spec; #define WLAN_DRV_EARLY_INT 0x04 #define WLAN_BCN_DMA_TIME 0x02 -#define WLAN_RX_FILTER0 0x0FFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0x0FFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c index e9e8a7f3f382..a56c8befa077 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c @@ -202,7 +202,8 @@ static void rtw8822b_phy_set_param(struct rtw_dev *rtwdev) #define WLAN_DRV_EARLY_INT 0x04 #define WLAN_BCN_DMA_TIME 0x02 -#define WLAN_RX_FILTER0 0x0FFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0x0FFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 @@ -273,7 +274,9 @@ static int rtw8822b_mac_init(struct rtw_dev *rtwdev) rtw_write8(rtwdev, REG_BCNDMATIM, WLAN_BCN_DMA_TIME); rtw_write8_clr(rtwdev, REG_TX_PTCL_CTRL + 1, BIT_SIFS_BK_EN >> 8); /* WMAC configuration */ - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); @@ -896,7 +899,7 @@ static void query_phy_status_page1(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->cfo_tail[RF_PATH_A] = GET_PHY_STAT_P1_CFO_TAIL_A(phy_status); pkt_stat->cfo_tail[RF_PATH_B] = GET_PHY_STAT_P1_CFO_TAIL_B(phy_status); - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; dm_info->rx_snr[path] = pkt_stat->rx_snr[path] >> 1; diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index 244c8026479c..32d9709f2c24 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -1942,7 +1942,8 @@ static void rtw8822c_phy_set_param(struct rtw_dev *rtwdev) #define WLAN_EDCA_BE_PARAM 0x005EA42B #define WLAN_EDCA_BK_PARAM 0x0000A44F -#define WLAN_RX_FILTER0 0xFFFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0xFFFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 @@ -2093,7 +2094,9 @@ static int rtw8822c_mac_init(struct rtw_dev *rtwdev) rtw_write16(rtwdev, REG_EIFS, WLAN_EIFS_DUR_TUNE); rtw_write8(rtwdev, REG_NAV_CTRL + 2, WLAN_NAV_MAX); rtw_write8(rtwdev, REG_WMAC_TRXPTCL_CTL_H + 2, WLAN_BAR_ACK_TYPE); - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); @@ -2584,7 +2587,7 @@ static void query_phy_status_page0(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->rx_power[RF_PATH_A] = rx_power[RF_PATH_A]; pkt_stat->rx_power[RF_PATH_B] = rx_power[RF_PATH_B]; - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; } @@ -2644,7 +2647,7 @@ static void query_phy_status_page1(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->cfo_tail[RF_PATH_A] = GET_PHY_STAT_P1_CFO_TAIL_A(phy_status); pkt_stat->cfo_tail[RF_PATH_B] = GET_PHY_STAT_P1_CFO_TAIL_B(phy_status); - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; if (path == RF_PATH_A) { @@ -3405,7 +3408,7 @@ static u8 rtw8822c_dpk_one_shot(struct rtw_dev *rtwdev, u8 path, u8 action) rtw_write32_mask(rtwdev, REG_DPD_CTL0, BIT(12), 0x1); rtw_write32_mask(rtwdev, REG_DPD_CTL0, BIT(12), 0x0); rtw_write32_mask(rtwdev, REG_RXSRAM_CTL, BIT_RPT_SEL, 0x0); - msleep(10); + fsleep(10000); if (!check_hw_ready(rtwdev, REG_STAT_RPT, BIT(31), 0x1)) { result = 1; rtw_dbg(rtwdev, RTW_DBG_RFK, "[DPK] one-shot over 20ms\n"); @@ -3418,7 +3421,7 @@ static u8 rtw8822c_dpk_one_shot(struct rtw_dev *rtwdev, u8 path, u8 action) dpk_cmd = rtw8822c_dpk_get_cmd(rtwdev, action, path); rtw_write32(rtwdev, REG_NCTL0, dpk_cmd); rtw_write32(rtwdev, REG_NCTL0, dpk_cmd + 1); - msleep(10); + fsleep(10000); if (!check_hw_ready(rtwdev, 0x2d9c, 0xff, 0x55)) { result = 1; rtw_dbg(rtwdev, RTW_DBG_RFK, "[DPK] one-shot over 20ms\n"); diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c index 0fa943271fb6..2eaadcfec4cb 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c +++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c @@ -512,7 +512,8 @@ static int rtw88xxau_init_queue_priority(struct rtw_dev *rtwdev) static void rtw88xxa_init_wmac_setting(struct rtw_dev *rtwdev) { rtw_write16(rtwdev, REG_RXFLTMAP0, 0xffff); - rtw_write16(rtwdev, REG_RXFLTMAP1, 0x0400); + rtwdev->hal.rxfltmap1 = 0x0400; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, 0xffff); rtw_write32(rtwdev, REG_MAR, 0xffffffff); diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c index 9d747a060b98..8786bbb421c2 100644 --- a/drivers/net/wireless/realtek/rtw88/tx.c +++ b/drivers/net/wireless/realtek/rtw88/tx.c @@ -619,6 +619,7 @@ static int rtw_txq_push_skb(struct rtw_dev *rtwdev, ret = rtw_hci_tx_write(rtwdev, &pkt_info, skb); if (ret) { rtw_err(rtwdev, "failed to write TX skb to HCI\n"); + ieee80211_free_txskb(rtwdev->hw, skb); return ret; } return 0; diff --git a/drivers/net/wireless/realtek/rtw89/Kconfig b/drivers/net/wireless/realtek/rtw89/Kconfig index 43e3b0ef44da..7c678dd1f6b3 100644 --- a/drivers/net/wireless/realtek/rtw89/Kconfig +++ b/drivers/net/wireless/realtek/rtw89/Kconfig @@ -41,6 +41,9 @@ config RTW89_8852C config RTW89_8922A tristate +config RTW89_8922D + tristate + config RTW89_8851BE tristate "Realtek 8851BE PCI wireless network (Wi-Fi 6) adapter" depends on PCI @@ -169,6 +172,18 @@ config RTW89_8922AU 802.11be USB wireless network (Wi-Fi 7) adapter supporting 2x2 2GHz/5GHz/6GHz 4096-QAM 160MHz channels. +config RTW89_8922DE + tristate "Realtek 8922DE/8922DE-VS PCI wireless network (Wi-Fi 7) adapter" + depends on PCI + select RTW89_CORE + select RTW89_PCI + select RTW89_8922D + help + Select this option will enable support for 8922DE/8922DE-VS chipset + + 802.11be PCIe wireless network (Wi-Fi 7) adapter + supporting 2x2 2GHz/5GHz/6GHz 4096/1024-QAM 160MHz channels. + config RTW89_DEBUG bool @@ -190,4 +205,18 @@ config RTW89_DEBUGFS If unsure, say Y to simplify debug problems +config RTW89_LEDS + bool + depends on RTW89_CORE + depends on LEDS_CLASS=y || LEDS_CLASS=MAC80211 + imply MAC80211_LEDS + default y + +config RTW89_LEDS_MC + bool + depends on RTW89_LEDS + depends on LEDS_CLASS_MULTICOLOR + imply LEDS_TRIGGER_TIMER + default y + endif diff --git a/drivers/net/wireless/realtek/rtw89/Makefile b/drivers/net/wireless/realtek/rtw89/Makefile index 475bad743d75..a85b7785330c 100644 --- a/drivers/net/wireless/realtek/rtw89/Makefile +++ b/drivers/net/wireless/realtek/rtw89/Makefile @@ -91,7 +91,16 @@ rtw89_8922ae-objs := rtw8922ae.o obj-$(CONFIG_RTW89_8922AU) += rtw89_8922au.o rtw89_8922au-objs := rtw8922au.o +obj-$(CONFIG_RTW89_8922D) += rtw89_8922d.o +rtw89_8922d-objs := rtw8922d.o \ + rtw8922d_rfk.o + +obj-$(CONFIG_RTW89_8922DE) += rtw89_8922de.o +rtw89_8922de-objs := rtw8922de.o + rtw89_core-$(CONFIG_RTW89_DEBUG) += debug.o +rtw89_core-$(CONFIG_RTW89_LEDS) += led.o +rtw89_core-$(CONFIG_RTW89_LEDS_MC) += led_mc.o obj-$(CONFIG_RTW89_PCI) += rtw89_pci.o rtw89_pci-y := pci.o pci_be.o diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 0f7ae572ef91..5a9dc4d8a00b 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -11,7 +11,7 @@ #include "ps.h" #include "reg.h" -#define RTW89_COEX_VERSION 0x09000013 +#define RTW89_COEX_VERSION 0x09180113 #define FCXDEF_STEP 50 /* MUST <= FCXMAX_STEP and match with wl fw*/ #define BTC_E2G_LIMIT_DEF 80 @@ -133,125 +133,203 @@ static const u32 cxtbl[] = { static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { /* firmware version must be in decreasing order for each chip */ + {RTL8922D, RTW89_FW_VER_CODE(0, 35, 111, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 11, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, + }, + {RTL8922D, RTW89_FW_VER_CODE(0, 35, 94, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 10, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, + }, + {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 5, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, + }, {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, }, {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 90, 0), .fcxbtcrpt = 7, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, + }, + {RTL8922A, RTW89_FW_VER_CODE(0, 35, 111, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 11, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 71, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 3, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, + .fcxtrx = 7, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 63, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 2, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, + .fcxtrx = 0, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 8, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 1, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 1, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, + }, + {RTL8851B, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 5, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, }, {RTL8851B, RTW89_FW_VER_CODE(0, 29, 29, 0), .fcxbtcrpt = 105, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 5, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, + }, + {RTL8852C, RTW89_FW_VER_CODE(0, 27, 97, 0), + .fcxbtcrpt = 4, .fcxtdma = 4, .fcxslots = 2, .fcxcysta = 105, + .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, + .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, + .fwlrole = 101, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, + .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 107, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 57, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 42, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 0, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, + }, + {RTL8852B, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 105, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 29, 0), .fcxbtcrpt = 105, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 5, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 14, 0), .fcxbtcrpt = 5, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 4, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 27, 0, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 1, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852A, RTW89_FW_VER_CODE(0, 13, 37, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852A, RTW89_FW_VER_CODE(0, 13, 0, 0), .fcxbtcrpt = 1, .fcxtdma = 1, .fcxslots = 1, .fcxcysta = 2, .fcxstep = 2, .fcxnullsta = 1, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1024, + .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_ver = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, /* keep it to be the last as default entry */ @@ -260,8 +338,9 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 2, .fcxnullsta = 1, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1024, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, }; @@ -270,10 +349,11 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { static const union rtw89_btc_wl_state_map btc_scanning_map = { .map = { .scan = 1, - .connecting = 1, + .dhcp = 1, .roaming = 1, - .dbccing = 1, + .transacting = 1, ._4way = 1, + .handshake = 1, }, }; @@ -288,6 +368,7 @@ static u32 chip_id_to_bt_rom_code_id(u32 id) case RTL8851B: return 0x8851; case RTL8922A: + case RTL8922D: return 0x8922; default: return 0; @@ -322,6 +403,8 @@ static char *chip_id_str(u32 id) return "RTL8851B"; case RTL8922A: return "RTL8922A"; + case RTL8922D: + return "RTL8922D"; default: return "UNKNOWN"; } @@ -408,11 +491,6 @@ union rtw89_fbtc_set_mon_reg { struct rtw89_btc_btf_set_mon_reg_v7 v7; } __packed; -struct _wl_rinfo_now { - u8 link_mode; - u32 dbcc_2g_phy: 2; -}; - enum btc_btf_set_cx_policy { CXPOLICY_TDMA = 0x0, CXPOLICY_SLOT = 0x1, @@ -433,7 +511,16 @@ enum btc_b2w_scoreboard { BTC_BSCB_BT_LNAB1 = BIT(10), BTC_BSCB_WLRFK = BIT(11), BTC_BSCB_BT_HILNA = BIT(13), + BTC_BSCB_BT_HILNA_56G = BIT(14), + BTC_BSCB_CTCODE = BIT(15), BTC_BSCB_BT_CONNECT = BIT(16), + BTC_BSCB_BT_CONNECT_56G = BIT(17), + BTC_BSCB_BT_LNAB0_56G = BIT(18), + BTC_BSCB_BT_LNAB1_56G = BIT(19), + BTC_BSCB_BT_15DOT4 = BIT(24), + BTC_BSCB_BT_PROTECT = BIT(27), + BTC_BSCB_PAN_ACT = BIT(28), + BTC_BSCB_HFP_ACT = BIT(29), BTC_BSCB_PATCH_CODE = BIT(30), BTC_BSCB_ALL = GENMASK(30, 0), }; @@ -459,12 +546,12 @@ enum btc_ant_phase { BTC_ANT_WONLY, BTC_ANT_WOFF, BTC_ANT_W2G, - BTC_ANT_W5G, + BTC_ANT_FDD, BTC_ANT_W25G, BTC_ANT_FREERUN, BTC_ANT_WRFK, BTC_ANT_WRFK2, - BTC_ANT_BRFK, + BTC_ANT_PTA, /* for Multi-PTA, each hw-band has its own PTA */ BTC_ANT_MAX }; @@ -493,6 +580,14 @@ enum btc_cx_poicy_main_type { BTC_CXP_MAIN_MAX }; +enum btc_bslot_length { + BTC_BSLOT_A2DP_HID = 60, + BTC_BSLOT_A2DP = 50, + BTC_BSLOT_A2DP_2 = 40, + BTC_BSLOT_INQ = 30, + BTC_BSLOT_IDLE = 20, +}; + enum btc_cx_poicy_type { /* TDMA off + pri: BT > WL */ BTC_CXP_OFF_BT = (BTC_CXP_OFF << 8) | 0, @@ -533,9 +628,21 @@ enum btc_cx_poicy_type { /* TDMA off + pri: WL > BT, Block-BT*/ BTC_CXP_OFF_WL2 = (BTC_CXP_OFF << 8) | 12, - /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx > BT_Hi_Rx, BT_Hi > WL > BT_Lo*/ + /* TDMA off+Bcn-Protect + pri: BT_Hi > WL > BT_Lo */ BTC_CXP_OFFB_BWB0 = (BTC_CXP_OFFB << 8) | 0, + /* TDMA off+Bcn-Protect + pri: BT_Hi_Tx = WL_Hi-Tx > BT_Hi_Rx, BT_Hi > WL > BT_Lo */ + BTC_CXP_OFFB_BWB1 = (BTC_CXP_OFFB << 8) | 1, + + /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx > BT, BT_Hi > other-WL > BT_Lo */ + BTC_CXP_OFFB_BWB2 = (BTC_CXP_OFFB << 8) | 2, + + /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx = BT*/ + BTC_CXP_OFFB_BWB3 = (BTC_CXP_OFFB << 8) | 3, + + /* TDMA off+Bcn-Protect + pri: BT > WL*/ + BTC_CXP_OFFB_BWB4 = (BTC_CXP_OFFB << 8) | 4, + /* TDMA off + Ext-Ctrl + pri: default */ BTC_CXP_OFFE_DEF = (BTC_CXP_OFFE << 8) | 0, @@ -700,6 +807,7 @@ enum btc_w2b_scoreboard { BTC_WSCB_SCAN = BIT(2), BTC_WSCB_UNDERTEST = BIT(3), BTC_WSCB_RXGAIN = BIT(4), + BTC_WSCB_5GHICH = BIT(6), BTC_WSCB_WLBUSY = BIT(7), BTC_WSCB_EXTFEM = BIT(8), BTC_WSCB_TDMA = BIT(9), @@ -708,22 +816,37 @@ enum btc_w2b_scoreboard { BTC_WSCB_RXSCAN_PRI = BIT(12), BTC_WSCB_BT_HILNA = BIT(13), BTC_WSCB_BTLOG = BIT(14), + BTC_WSCB_CTCODE = BIT(15), + BTC_WSCB_RXGAIN_56G = BIT(16), + BTC_WSCB_BT_HILNA_56G = BIT(17), BTC_WSCB_ALL = GENMASK(23, 0), }; -enum btc_wl_link_mode { +enum btc_wl_link_mode_v0 { + BTC_WLINK_V0_NOLINK = 0x0, + BTC_WLINK_V0_2G_STA, + BTC_WLINK_V0_2G_AP, + BTC_WLINK_V0_2G_GO, + BTC_WLINK_V0_2G_GC, + BTC_WLINK_V0_2G_SCC, + BTC_WLINK_V0_2G_MCC, + BTC_WLINK_V0_25G_MCC, + BTC_WLINK_V0_25G_DBCC, + BTC_WLINK_V0_5G, + BTC_WLINK_V0_2G_NAN, + BTC_WLINK_V0_OTHER, + BTC_WLINK_V0_MAX +}; + +enum btc_wl_link_mode_v1 { BTC_WLINK_NOLINK = 0x0, - BTC_WLINK_2G_STA, - BTC_WLINK_2G_AP, - BTC_WLINK_2G_GO, - BTC_WLINK_2G_GC, - BTC_WLINK_2G_SCC, - BTC_WLINK_2G_MCC, - BTC_WLINK_25G_MCC, - BTC_WLINK_25G_DBCC, - BTC_WLINK_5G, - BTC_WLINK_2G_NAN, - BTC_WLINK_OTHER, + BTC_WLINK_STA, + BTC_WLINK_AP, + BTC_WLINK_GO, + BTC_WLINK_GC, + BTC_WLINK_SCC, + BTC_WLINK_SB_MCC, + BTC_WLINK_DB_MCC, /* 2+0/0+2 Dual-RF-band MCC */ BTC_WLINK_MAX }; @@ -733,16 +856,13 @@ static const char *id_to_linkmode(u8 id) { switch (id) { CASE_BTC_WL_LINK_MODE(NOLINK); - CASE_BTC_WL_LINK_MODE(2G_STA); - CASE_BTC_WL_LINK_MODE(2G_AP); - CASE_BTC_WL_LINK_MODE(2G_GO); - CASE_BTC_WL_LINK_MODE(2G_GC); - CASE_BTC_WL_LINK_MODE(2G_SCC); - CASE_BTC_WL_LINK_MODE(2G_MCC); - CASE_BTC_WL_LINK_MODE(25G_MCC); - CASE_BTC_WL_LINK_MODE(25G_DBCC); - CASE_BTC_WL_LINK_MODE(5G); - CASE_BTC_WL_LINK_MODE(OTHER); + CASE_BTC_WL_LINK_MODE(STA); + CASE_BTC_WL_LINK_MODE(AP); + CASE_BTC_WL_LINK_MODE(GO); + CASE_BTC_WL_LINK_MODE(GC); + CASE_BTC_WL_LINK_MODE(SCC); + CASE_BTC_WL_LINK_MODE(SB_MCC); + CASE_BTC_WL_LINK_MODE(DB_MCC); default: return "unknown"; } @@ -774,6 +894,8 @@ enum btc_reset_module { BTC_RESET_CXDM = BIT(0) | BIT(1), BTC_RESET_BTINFO = BIT(3), BTC_RESET_MDINFO = BIT(4), + BTC_RESET_BT_PSD_DM = BIT(5), + BTC_RESET_BTINFO2 = BIT(6), BTC_RESET_ALL = GENMASK(7, 0), }; @@ -781,7 +903,9 @@ enum btc_gnt_state { BTC_GNT_HW = 0, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_GNT_MAX + BTC_GNT_MAX, + + BTC_GNT_SET_SKIP = 0xff, }; enum btc_ctr_path { @@ -827,6 +951,7 @@ enum btc_reason_and_action { BTC_RSN_ACT1_WORK, BTC_RSN_BT_DEVINFO_WORK, BTC_RSN_RFK_CHK_WORK, + BTC_RSN_UPDATE_BT_LEAUDINFO, BTC_RSN_NUM, BTC_ACT_NONE = 100, BTC_ACT_WL_ONLY, @@ -851,6 +976,8 @@ enum btc_reason_and_action { BTC_ACT_BT_A2DP_PAN, BTC_ACT_BT_PAN_HID, BTC_ACT_BT_A2DP_PAN_HID, + BTC_ACT_BT_BIS, + BTC_ACT_BT_CIS, BTC_ACT_WL_25G_MCC, BTC_ACT_WL_2G_MCC, BTC_ACT_WL_2G_SCC, @@ -871,8 +998,17 @@ enum btc_reason_and_action { static void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason); -static void _write_scbd(struct rtw89_dev *rtwdev, u32 val, bool state); -static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update); +static void _write_scbd(struct rtw89_dev *rtwdev, u8 bid, u32 val, bool state); +static u8 _sned_h2c_w2bscbd(struct rtw89_dev *rtwdev, bool force_exec, u8 bid); +static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid); +static const char *id_to_h2c(u32 id); + +static void _reset_h2c_macro(struct rtw89_btc *btc) +{ + btc->hbuf_len = 0; + btc->hbuf_cnt = 0; + memset(btc->hbuf, 0, BTC_H2C_MAXLEN); +} static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, void *param, u16 len) @@ -882,6 +1018,8 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_dm *dm = &btc->dm; + u8 h2c_func_mask, bid = BTC_BT_1ST; + u8 *buf = param; int ret; if (len > BTC_H2C_MAXLEN || len == 0) { @@ -903,7 +1041,70 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, return -EINVAL; } - ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, param, len, + h2c_func_mask = h2c_func & (~BT_H2C_FUNC_BT2ND); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + bid = !!(h2c_func & BT_H2C_FUNC_BT2ND); + + if (btc->io_oflld_type != BTC_IO_OFLD_BTC_H2C || btc->cli_h2c_cmd || + h2c_func_mask == SET_H2C_MACRO || + (h2c_func_mask == SET_DRV_INFO && buf[0] == CXDRVINFO_TRX) || + (h2c_func_mask == SET_IOFLD_SCBD && dm->scbd_write_instant) || + h2c_func_mask == SET_BT_LNA_CONSTRAIN) { + h2c_class = BTFC_SET; + + ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, + buf, len, false, true); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():class=%d, bt%d-func=%s, len=%d\n", + __func__, h2c_class, bid, + id_to_h2c(h2c_func_mask), len); + + if (h2c_func_mask == SET_H2C_MACRO) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():Send H2C-MACRO cnt=%d, len=%d\n", + __func__, btc->hbuf_cnt, btc->hbuf_len); + _reset_h2c_macro(btc); /* clear H2C MACRO buffer */ + } + + if (ret != 0) { /* Send H2C fail */ + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():return by rtw_hal_mac_send_h2c\n", + __func__); + btc->fwinfo.cnt_h2c_fail++; + return 0; + } + + btc->fwinfo.cnt_h2c++; + return 0; + } else { /* Fill H2C MACRO buffer(TLV format) temporarily */ + if (btc->hbuf_cnt == 0) + _reset_h2c_macro(btc); + + /* Type:1 byte, Length:2 Bytes, Data:len bytes */ + if (btc->hbuf_len + len + 3 >= BTC_H2C_MAXLEN) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():return by MACRO buf full(%d)\n", + __func__, btc->hbuf_len + len + 3); + btc->fwinfo.cnt_h2c_fail++; + return 0; + } + + btc->hbuf[btc->hbuf_len] = h2c_func; + btc->hbuf[btc->hbuf_len + 1] = len & GENMASK(7, 0); + btc->hbuf[btc->hbuf_len + 2] = (len & GENMASK(15, 8)) >> 8; + + memcpy(&btc->hbuf[btc->hbuf_len + 3], buf, len); + btc->hbuf_len = btc->hbuf_len + len + 3; + btc->hbuf_cnt++; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():Buffer H2C-MACRO cnt=%d/bt%d-func=%s/len=%d\n", + __func__, btc->hbuf_cnt, bid, + id_to_h2c(h2c_func_mask), len); + } + + ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, buf, len, false, true); if (ret) pfwinfo->cnt_h2c_fail++; @@ -913,51 +1114,57 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, return ret; } -#define BTC_BT_DEF_BR_TX_PWR 4 -#define BTC_BT_DEF_LE_TX_PWR 4 +#define BTC_DEFAULT_SIR_THRES 51 /* dB, BT Pin - This = TDD/FDD swtch thres */ +#define BTC_FDDR_TP_SETUP_TIME 1 /* in second */ +#define BTC_FDDR_TP_HOLD_TIME 3 /* in second */ +#define BTC_FDDR_RX_LOW_RATE_THRES 4 /* low rate for TDD swotch */ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - struct rtw89_btc_wl_link_info *wl_linfo; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; u8 i; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); - if (type & BTC_RESET_CX) + if (type & BTC_RESET_CX) { memset(cx, 0, sizeof(*cx)); + cx->bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR; + cx->bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } - if (type & BTC_RESET_BTINFO) /* only for BT enable */ - memset(bt, 0, sizeof(*bt)); + if (type & BTC_RESET_BTINFO) {/* only for BT enable */ + memset(bt0, 0, sizeof(*bt0)); + bt0->link_info.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt0->link_info_56g.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt0->link_info.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt0->link_info_56g.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt0->ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } else if (type & BTC_RESET_BTINFO2) { + memset(bt1, 0, sizeof(*bt1)); + bt1->link_info.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt1->link_info_56g.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt1->link_info.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt1->link_info_56g.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt1->ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } if (type & BTC_RESET_CTRL) { memset(&btc->ctrl, 0, sizeof(btc->ctrl)); - btc->manual_ctrl = false; - if (ver->fcxctrl != 7) - btc->ctrl.ctrl.trace_step = FCXDEF_STEP; + btc->ctrl.trace_step = FCXDEF_STEP; } /* Init Coex variables that are not zero */ if (type & BTC_RESET_DM) { memset(&btc->dm, 0, sizeof(btc->dm)); - memset(bt_linfo->rssi_state, 0, sizeof(bt_linfo->rssi_state)); - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (btc->ver->fwlrole == 8) - wl_linfo = &wl->rlink_info[i][0]; - else - wl_linfo = &wl->link_info[i]; - memset(wl_linfo->rssi_state, 0, sizeof(wl_linfo->rssi_state)); - } /* set the slot_now table to original */ btc->dm.tdma_now = t_def[CXTD_OFF]; btc->dm.tdma = t_def[CXTD_OFF]; - if (ver->fcxslots >= 7) { + if (ver->fcxslots >= 2) { for (i = 0; i < ARRAY_SIZE(s_def); i++) { btc->dm.slot.v7[i].dur = s_def[i].dur; btc->dm.slot.v7[i].cxtype = s_def[i].cxtype; @@ -973,20 +1180,28 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) } btc->policy_len = 0; - btc->bt_req_len = 0; + btc->bt_req_len[RTW89_PHY_0] = 0; + btc->bt_req_len[RTW89_PHY_1] = 0; + btc->hubmsg_cnt = 0; btc->dm.coex_info_map = BTC_COEX_INFO_ALL; btc->dm.wl_tx_limit.tx_time = BTC_MAX_TX_TIME_DEF; btc->dm.wl_tx_limit.tx_retry = BTC_MAX_TX_RETRY_DEF; + btc->dm.bt_slot_flood = BTC_B1_MAX; + btc->dm.sir_thres = BTC_DEFAULT_SIR_THRES; + btc->dm.fddr_info.tp_setup_time = BTC_FDDR_TP_SETUP_TIME; + btc->dm.fddr_info.tp_hold_time = BTC_FDDR_TP_HOLD_TIME; + btc->dm.fddr_info.wl_rx_rate_thres = BTC_FDDR_RX_LOW_RATE_THRES; + btc->dm.fddt_info.type = BTC_FDDT_TYPE_AUTO; + btc->dm.wl_pre_agc_rb = BTC_PREAGC_NOTFOUND; btc->dm.wl_btg_rx_rb = BTC_BTGCTRL_BB_GNT_NOTFOUND; } - if (type & BTC_RESET_MDINFO) + if (type & BTC_RESET_MDINFO) { memset(&btc->mdinfo, 0, sizeof(btc->mdinfo)); - - bt->link_info.bt_txpwr_desc.br_dbm = BTC_BT_DEF_BR_TX_PWR; - bt->link_info.bt_txpwr_desc.le_dbm = BTC_BT_DEF_LE_TX_PWR; + btc->mdinfo.ant.isolation = RTW89_BTC_DEFAULT_ANISO; + } } static u8 _search_reg_index(struct rtw89_dev *rtwdev, u8 mreg_num, u16 reg_type, u32 target) @@ -1006,27 +1221,22 @@ static void _get_reg_status(struct rtw89_dev *rtwdev, u8 type, u8 *val) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - union rtw89_btc_module_info *md = &btc->mdinfo; + struct rtw89_btc_module *md = &btc->mdinfo; union rtw89_btc_fbtc_mreg_val *pmreg; u32 pre_agc_addr = R_BTC_BB_PRE_AGC_S1; u32 reg_val; - u8 idx, switch_type; - - if (ver->fcxinit == 7) - switch_type = md->md_v7.switch_type; - else - switch_type = md->md.switch_type; + u8 idx; if (btc->btg_pos == RF_PATH_A) pre_agc_addr = R_BTC_BB_PRE_AGC_S0; switch (type) { case BTC_CSTATUS_TXDIV_POS: - if (switch_type == BTC_SWITCH_INTERNAL) + if (md->bt0_sw_type == BTC_SWITCH_INTERNAL) *val = BTC_ANT_DIV_MAIN; break; case BTC_CSTATUS_RXDIV_POS: - if (switch_type == BTC_SWITCH_INTERNAL) + if (md->bt0_sw_type == BTC_SWITCH_INTERNAL) *val = BTC_ANT_DIV_MAIN; break; case BTC_CSTATUS_BB_GNT_MUX: @@ -1104,7 +1314,7 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_dm *dm = &btc->dm; @@ -1216,7 +1426,7 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.slot_no_sync = false; break; case BTC_DCNT_BTTX_HANG: - cnt = cx->cnt_bt[BTC_BCNT_LOPRI_TX]; + cnt = bt->bcnt[BTC_BCNT_LOPRI_TX]; if (cnt == 0 && bt->link_info.slave_role) dm->cnt_dm[BTC_DCNT_BTTX_HANG]++; @@ -1229,20 +1439,15 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.bt_tx_hang = false; break; case BTC_DCNT_BTCNT_HANG: - cnt = cx->cnt_bt[BTC_BCNT_HIPRI_RX] + - cx->cnt_bt[BTC_BCNT_HIPRI_TX] + - cx->cnt_bt[BTC_BCNT_LOPRI_RX] + - cx->cnt_bt[BTC_BCNT_LOPRI_TX]; + cnt = bt->bcnt[BTC_BCNT_HIPRI_RX] + + bt->bcnt[BTC_BCNT_HIPRI_TX] + + bt->bcnt[BTC_BCNT_LOPRI_RX] + + bt->bcnt[BTC_BCNT_LOPRI_TX]; if (cnt == 0) dm->cnt_dm[BTC_DCNT_BTCNT_HANG]++; else dm->cnt_dm[BTC_DCNT_BTCNT_HANG] = 0; - - if ((dm->cnt_dm[BTC_DCNT_BTCNT_HANG] >= BTC_CHK_HANG_MAX && - bt->enable.now) || (!dm->cnt_dm[BTC_DCNT_BTCNT_HANG] && - !bt->enable.now)) - _update_bt_scbd(rtwdev, false); break; case BTC_DCNT_WL_SLOT_DRIFT: if (cnt >= BTC_CHK_WLSLOT_DRIFT_MAX) @@ -1267,6 +1472,40 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.bt_slot_drift = false; break; + case BTC_DCNT_WL_STA_NTFY: + cnt = dm->cnt_notify[BTC_NCNT_WL_STA] - + dm->cnt_notify[BTC_NCNT_WL_STA_LAST]; + + dm->cnt_notify[BTC_NCNT_WL_STA_LAST] = + dm->cnt_notify[BTC_NCNT_WL_STA]; + + if (cnt == 0 && + (dm->tdd_bind.wl_link_mode != BTC_WLINK_NOLINK || + dm->fdd_bind.wl_link_mode != BTC_WLINK_NOLINK)) + dm->cnt_dm[BTC_DCNT_WL_STA_NTFY]++; + else + dm->cnt_dm[BTC_DCNT_WL_STA_NTFY] = 0; + + if (dm->cnt_dm[BTC_DCNT_WL_STA_NTFY] >= BTC_CHK_HANG_MAX) + dm->error.map.wl_no_sta_ntfy = true; + else + dm->error.map.wl_no_sta_ntfy = false; + break; + case BTC_DCNT_W2B_SCBD_NOSYNC: + if (!(rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA)) + return; /* return if no-support W2B scbd readback */ + + if (wl->scbd_rb[BTC_BT_1ST] != wl->scbd[BTC_BT_1ST] || + wl->scbd_rb[BTC_BT_2ND] != wl->scbd[BTC_BT_2ND]) + dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC]++; + else + dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC] = 0; + + if (dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC] >= BTC_CHK_HANG_MAX) + dm->error.map.w2b_scbd_no_sync = true; + else + dm->error.map.w2b_scbd_no_sync = false; + break; } } @@ -1274,7 +1513,7 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_a2dp_desc *a2dp = &bt_linfo->a2dp_desc; union rtw89_btc_fbtc_btver *pver = &btc->fwinfo.rpt_fbtc_btver.finfo; @@ -1294,7 +1533,14 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) switch (rpt_type) { case BTC_RPT_TYPE_BT_VER: - if (ver->fcxbtver == 7) { + if (ver->fcxbtver == 8) { + pver->v8 = *(struct rtw89_btc_fbtc_btver_v8 *)pfinfo; + bt = pver->v8.bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + bt->ver_info.fw = le32_to_cpu(pver->v8.fw_ver); + bt->ver_info.fw_coex = le32_get_bits(pver->v8.coex_ver, + GENMASK(7, 0)); + bt->feature = le32_to_cpu(pver->v8.feature); + } else if (ver->fcxbtver == 7) { pver->v7 = *(struct rtw89_btc_fbtc_btver_v7 *)pfinfo; bt->ver_info.fw = le32_to_cpu(pver->v7.fw_ver); bt->ver_info.fw_coex = le32_get_bits(pver->v7.coex_ver, @@ -1335,6 +1581,19 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) pscan_v7->para[i].intvl == 0) scan_update = false; } + } else if (ver->fcxbtscan == 8) { + struct rtw89_btc_fbtc_btscan_v8 *pscan_v8 = + (struct rtw89_btc_fbtc_btscan_v8 *)pfinfo; + struct rtw89_btc_bt_info *tbt = + pscan_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + + for (i = 0; i < CXSCAN_MAX; i++) { + tbt->scan_info_v2[i] = pscan_v8->para[i]; + if ((pscan_v8->type & BIT(i)) && + pscan_v8->para[i].win == 0 && + pscan_v8->para[i].intvl == 0) + scan_update = false; + } } if (scan_update) bt->scan_info_update = 1; @@ -1362,6 +1621,22 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) memcpy(&bt_linfo->afh_map_le[0], pafh_v7->afh_le_a, 4); memcpy(&bt_linfo->afh_map_le[4], pafh_v7->afh_le_b, 1); } + } else if (ver->fcxbtafh == 8) { + struct rtw89_btc_fbtc_btafh_v8 *pafh_v8 = + (struct rtw89_btc_fbtc_btafh_v8 *)pfinfo; + struct rtw89_btc_bt_info *tbt = + pafh_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + struct rtw89_btc_bt_link_info *tbt_linfo = &tbt->link_info; + + if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LEGACY) { + memcpy(&tbt_linfo->afh_map[0], pafh_v8->afh_l, 4); + memcpy(&tbt_linfo->afh_map[4], pafh_v8->afh_m, 4); + memcpy(&tbt_linfo->afh_map[8], pafh_v8->afh_h, 2); + } + if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LE) { + memcpy(&tbt_linfo->afh_map_le[0], pafh_v8->afh_le_a, 4); + memcpy(&tbt_linfo->afh_map_le[4], pafh_v8->afh_le_b, 1); + } } else if (ver->fcxbtafh == 1) { pafh_v1 = (struct rtw89_btc_fbtc_btafh *)pfinfo; memcpy(&bt_linfo->afh_map[0], pafh_v1->afh_l, 4); @@ -1414,8 +1689,9 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; union rtw89_btc_fbtc_rpt_ctrl_ver_info *prpt = NULL; union rtw89_btc_fbtc_cysta_info *pcysta = NULL; struct rtw89_btc_prpt *btc_prpt = NULL; @@ -1424,7 +1700,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, u16 wl_slot_set = 0, wl_slot_real = 0, val16; u32 trace_step = 0, rpt_len = 0, diff_t = 0; u32 cnt_leak_slot, bt_slot_real, bt_slot_set, cnt_rx_imr; - u8 i, val = 0, val1, val2; + u8 i, j, val = 0, val1, val2; + u32 *bt_cnt, start_idx; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): index:%d\n", @@ -1476,6 +1753,14 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_ctrl.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v7); fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v7.fver; + } else if (ver->fcxbtcrpt == 9) { + pfinfo = &pfwinfo->rpt_ctrl.finfo.v9; + pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v9); + fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v9.fver; + } else if (ver->fcxbtcrpt == 11) { + pfinfo = &pfwinfo->rpt_ctrl.finfo.v11; + pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v11); + fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v11.fver; } else { goto err; } @@ -1487,7 +1772,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v1; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v1); fwsubver->fcxtdma = 0; - } else if (ver->fcxtdma == 3 || ver->fcxtdma == 7) { + } else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || + ver->fcxtdma == 7 || ver->fcxtdma == 8) { pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v3; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v3); fwsubver->fcxtdma = pfwinfo->rpt_fbtc_tdma.finfo.v3.fver; @@ -1502,6 +1788,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v1; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v1); fwsubver->fcxslots = pfwinfo->rpt_fbtc_slots.finfo.v1.fver; + } else if (ver->fcxslots == 2) { + pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v2; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v2); + fwsubver->fcxslots = pfwinfo->rpt_fbtc_slots.finfo.v2.fver; } else if (ver->fcxslots == 7) { pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v7); @@ -1534,6 +1824,11 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pcysta->v5 = pfwinfo->rpt_fbtc_cysta.finfo.v5; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_cysta.finfo.v5); fwsubver->fcxcysta = pfwinfo->rpt_fbtc_cysta.finfo.v5.fver; + } else if (ver->fcxcysta == 105) { + pfinfo = &pfwinfo->rpt_fbtc_cysta.finfo.v105; + pcysta->v105 = pfwinfo->rpt_fbtc_cysta.finfo.v105; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_cysta.finfo.v105); + fwsubver->fcxcysta = pfwinfo->rpt_fbtc_cysta.finfo.v105.fver; } else if (ver->fcxcysta == 7) { pfinfo = &pfwinfo->rpt_fbtc_cysta.finfo.v7; pcysta->v7 = pfwinfo->rpt_fbtc_cysta.finfo.v7; @@ -1547,7 +1842,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, case BTC_RPT_TYPE_STEP: pcinfo = &pfwinfo->rpt_fbtc_step.cinfo; if (ver->fcxctrl != 7) - trace_step = btc->ctrl.ctrl.trace_step; + trace_step = btc->ctrl.trace_step; if (ver->fcxstep == 2) { pfinfo = &pfwinfo->rpt_fbtc_step.finfo.v2; @@ -1627,6 +1922,12 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v7); fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v7.fver; + } else if (ver->fcxbtver == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v8); + fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v8.fver; + } else { + goto err; } pcinfo->req_fver = ver->fcxbtver; break; @@ -1644,6 +1945,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v7); fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v7.fver; + } else if (ver->fcxbtscan == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v8); + fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v8.fver; } else { goto err; } @@ -1663,6 +1968,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v7); fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v7.fver; + } else if (ver->fcxbtafh == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v8); + fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v8.fver; } else { goto err; } @@ -1721,7 +2030,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, rtwdev->chip->ops->btc_update_bt_cnt(rtwdev); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = rtw89_mac_get_plt_cnt(rtwdev, RTW89_MAC_0); } @@ -1733,18 +2042,18 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = !!le32_to_cpu(prpt->v4.wl_fw_info.cx_offload); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v4.gnt_val[i], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_val[i], &prpt->v4.gnt_val[i], + sizeof(dm->gnt_val[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_TX]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_RX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_TX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_RX]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1765,18 +2074,18 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v5.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_val[i], &prpt->v5.gnt_val[i], + sizeof(dm->gnt_val[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_TX]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_RX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_TX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_RX]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1792,18 +2101,18 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v105.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_val[i], &prpt->v105.gnt_val[i], + sizeof(dm->gnt_val[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_POLLUTED_V105]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1818,24 +2127,24 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v7.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v7.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_val[i], &prpt->v7.gnt_val[i], + sizeof(dm->gnt_val[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_LO_RX_V105]); val1 = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_POLLUTED_V105]); - if (val1 > btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]) - val1 -= btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]; /* diff */ + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ - btc->cx.cnt_bt[BTC_BCNT_POLUT_DIFF] = val1; - btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW] = + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_POLLUTED_V105]); val1 = pfwinfo->event[BTF_EVNT_RPT]; @@ -1850,24 +2159,24 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v8.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v8.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_val[i], &prpt->v8.gnt_val[i], + sizeof(dm->gnt_val[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_LO_RX_V105]); val1 = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_POLLUTED_V105]); - if (val1 > btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]) - val1 -= btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]; /* diff */ + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ - btc->cx.cnt_bt[BTC_BCNT_POLUT_DIFF] = val1; - btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW] = + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_POLLUTED_V105]); val1 = pfwinfo->event[BTF_EVNT_RPT]; @@ -1881,6 +2190,105 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, _chk_btc_err(rtwdev, BTC_DCNT_RPT_HANG, val1); _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + } else if (ver->fcxbtcrpt == 9) { + prpt->v9 = pfwinfo->rpt_ctrl.finfo.v9; + pfwinfo->rpt_en_map = le32_to_cpu(prpt->v9.rpt_info.en); + wl->ver_info.fw_coex = le32_to_cpu(prpt->v9.rpt_info.cx_ver); + wl->ver_info.fw = le32_to_cpu(prpt->v9.rpt_info.fw_ver); + + memcpy(wl->ver_info.build_time, + prpt->v9.build_time, + sizeof(wl->ver_info.build_time)); + memcpy(wl->ver_info.build_date, + prpt->v9.build_date, + sizeof(wl->ver_info.build_date)); + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) + memcpy(&dm->gnt_val[i], &prpt->v9.gnt_val[i], + sizeof(dm->gnt_val[i])); + + bt->bcnt[BTC_BCNT_HIPRI_TX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_HI_TX_V105]); + bt->bcnt[BTC_BCNT_HIPRI_RX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_HI_RX_V105]); + bt->bcnt[BTC_BCNT_LOPRI_TX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_LO_TX_V105]); + bt->bcnt[BTC_BCNT_LOPRI_RX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_LO_RX_V105]); + + val1 = le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_POLLUTED_V105]); + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ + + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_POLLUTED_V105]); + + dm->pta_owner = prpt->v9.pta_owner; + + _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_RPT_HANG, val1); + _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); + _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + } else if (ver->fcxbtcrpt == 11) { + prpt->v11 = pfwinfo->rpt_ctrl.finfo.v11; + pfwinfo->rpt_en_map = le32_to_cpu(prpt->v11.rpt_info.en); + wl->ver_info.fw_coex = le32_to_cpu(prpt->v11.rpt_info.cx_ver); + wl->ver_info.fw = le32_to_cpu(prpt->v11.rpt_info.fw_ver); + + memcpy(wl->ver_info.build_time, + prpt->v11.build_time, + sizeof(wl->ver_info.build_time)); + memcpy(wl->ver_info.build_date, + prpt->v11.build_date, + sizeof(wl->ver_info.build_date)); + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) + memcpy(&dm->gnt_val[i], &prpt->v11.gnt_val[i][0], + sizeof(dm->gnt_val[i])); + + for (i = BTC_BT_1ST; i <= BTC_BT_EXT; i++) { + if (i == BTC_BT_EXT) { + if (cx->bt_ext.func_type == BTC_BTF_NONE) + continue; + bt_cnt = cx->bt_ext.bcnt; + } else if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + bt_cnt = cx->bt1.bcnt; + wl->scbd_rb[i] = le32_to_cpu(prpt->v11.scbd_w2b[i]); + cx->bt1.scbd_rb = le32_to_cpu(prpt->v11.scbd_b2w[i]); + } else { + bt_cnt = cx->bt0.bcnt; + wl->scbd_rb[i] = le32_to_cpu(prpt->v11.scbd_w2b[i]); + cx->bt0.scbd_rb = le32_to_cpu(prpt->v11.scbd_b2w[i]); + } + + start_idx = BTC_BCNT_HIPRI_TX; + for (j = BTC_BCNT_HI_TX_V105; j <= BTC_BCNT_LO_RX_V105 ; j++) + bt_cnt[start_idx++] = le16_to_cpu(prpt->v11.bt_cnt[i][j]); + + /* check if polut counter increase */ + val1 = le16_to_cpu(prpt->v11.bt_cnt[i][BTC_BCNT_POLLUTED_V105]); + if (val1 > bt_cnt[BTC_BCNT_POLUT_NOW]) /* cnt increase*/ + val2 = val1 - bt_cnt[BTC_BCNT_POLUT_NOW]; + else + val2 = val1; /* overflow */ + + bt_cnt[BTC_BCNT_POLUT_DIFF] = val2; + bt_cnt[BTC_BCNT_POLUT_NOW] = val1; + + if (i <= BTC_BT_2ND) + bt_cnt[BTC_BCNT_SCBDREAD]++; + } + dm->scbd_w2b_update = true; + dm->scbd_b2w_update = true; + + _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); + _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_WL_STA_NTFY, 0); + _chk_btc_err(rtwdev, BTC_DCNT_W2B_SCBD_NOSYNC, 0); } else { goto err; } @@ -1895,7 +2303,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcmp(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.v1, sizeof(dm->tdma_now))); - else if (ver->fcxtdma == 3 || ver->fcxtdma == 7) + else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || + ver->fcxtdma == 7 || ver->fcxtdma == 8) _chk_btc_err(rtwdev, BTC_DCNT_TDMA_NONSYNC, memcmp(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.v3.tdma, @@ -1913,6 +2322,15 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcmp(dm->slot_now.v7, pfwinfo->rpt_fbtc_slots.finfo.v7.slot, sizeof(dm->slot_now.v7))); + } else if (ver->fcxslots == 2) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): check %d %zu\n", + __func__, BTC_DCNT_SLOT_NONSYNC, + sizeof(dm->slot_now.v7)); + _chk_btc_err(rtwdev, BTC_DCNT_SLOT_NONSYNC, + memcmp(dm->slot_now.v7, + pfwinfo->rpt_fbtc_slots.finfo.v2.slot, + sizeof(dm->slot_now.v7))); } else if (ver->fcxslots == 1) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): check %d %zu\n", @@ -1992,10 +2410,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, /* Check diff time between real BT slot and EBT/E5G slot */ if (dm->tdma_now.type == CXTDMA_OFF && dm->tdma_now.ext_ctrl == CXECTL_EXT && - btc->bt_req_len != 0) { + btc->bt_req_len[RTW89_PHY_0] != 0) { bt_slot_real = le16_to_cpu(pcysta->v3.cycle_time.tavg[CXT_BT]); - if (btc->bt_req_len > bt_slot_real) { - diff_t = btc->bt_req_len - bt_slot_real; + if (btc->bt_req_len[RTW89_PHY_0] > bt_slot_real) { + diff_t = btc->bt_req_len[RTW89_PHY_0] - bt_slot_real; _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); } } @@ -2036,11 +2454,11 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, /* Check diff time between real BT slot and EBT/E5G slot */ if (dm->tdma_now.type == CXTDMA_OFF && dm->tdma_now.ext_ctrl == CXECTL_EXT && - btc->bt_req_len != 0) { + btc->bt_req_len[RTW89_PHY_0] != 0) { bt_slot_real = le16_to_cpu(pcysta->v4.cycle_time.tavg[CXT_BT]); - if (btc->bt_req_len > bt_slot_real) { - diff_t = btc->bt_req_len - bt_slot_real; + if (btc->bt_req_len[RTW89_PHY_0] > bt_slot_real) { + diff_t = btc->bt_req_len[RTW89_PHY_0] - bt_slot_real; _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); } } @@ -2081,7 +2499,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, _chk_btc_err(rtwdev, BTC_DCNT_WL_SLOT_DRIFT, diff_t); /* Check diff time between real BT slot and EBT/E5G slot */ - bt_slot_set = btc->bt_req_len; + bt_slot_set = btc->bt_req_len[RTW89_PHY_0]; bt_slot_real = le16_to_cpu(pcysta->v5.cycle_time.tavg[CXT_BT]); diff_t = 0; if (dm->tdma_now.type == CXTDMA_OFF && @@ -2102,6 +2520,58 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, le16_to_cpu(pcysta->v5.slot_cnt[CXST_B1])); _chk_btc_err(rtwdev, BTC_DCNT_CYCLE_HANG, le16_to_cpu(pcysta->v5.cycles)); + } else if (ver->fcxcysta == 105) { + if (dm->fddt_train == BTC_FDDT_ENABLE) + break; + + cnt_leak_slot = le16_to_cpu(pcysta->v105.slot_cnt[CXST_LK]); + cnt_rx_imr = le32_to_cpu(pcysta->v105.leak_slot.cnt_rximr); + + /* Check Leak-AP */ + if (cnt_leak_slot != 0 && cnt_rx_imr != 0 && + dm->tdma_now.rxflctrl) { + if (le16_to_cpu(pcysta->v5.cycles) >= BTC_CYSTA_CHK_PERIOD && + cnt_leak_slot < BTC_LEAK_AP_TH * cnt_rx_imr) + dm->leak_ap = 1; + } + + /* Check diff time between real WL slot and W1 slot */ + if (dm->tdma_now.type == CXTDMA_OFF) { + if (ver->fcxslots == 1) + wl_slot_set = le16_to_cpu(dm->slot_now.v1[CXST_W1].dur); + else if (ver->fcxslots == 7) + wl_slot_set = le16_to_cpu(dm->slot_now.v7[CXST_W1].dur); + wl_slot_real = le16_to_cpu(pcysta->v105.cycle_time.tavg[CXT_WL]); + + if (wl_slot_real > wl_slot_set) + diff_t = wl_slot_real - wl_slot_set; + else + diff_t = wl_slot_set - wl_slot_real; + } + _chk_btc_err(rtwdev, BTC_DCNT_WL_SLOT_DRIFT, diff_t); + + /* Check diff time between real BT slot and EBT/E5G slot */ + bt_slot_set = btc->bt_req_len[RTW89_PHY_0]; + bt_slot_real = le16_to_cpu(pcysta->v105.cycle_time.tavg[CXT_BT]); + diff_t = 0; + if (dm->tdma_now.type == CXTDMA_OFF && + dm->tdma_now.ext_ctrl == CXECTL_EXT && + bt_slot_set != 0) { + if (bt_slot_set > bt_slot_real) + diff_t = bt_slot_set - bt_slot_real; + else + diff_t = bt_slot_real - bt_slot_set; + } + + _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); + _chk_btc_err(rtwdev, BTC_DCNT_E2G_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_E2G])); + _chk_btc_err(rtwdev, BTC_DCNT_W1_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_W1])); + _chk_btc_err(rtwdev, BTC_DCNT_B1_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_B1])); + _chk_btc_err(rtwdev, BTC_DCNT_CYCLE_HANG, + le16_to_cpu(pcysta->v105.cycles)); } else if (ver->fcxcysta == 7) { if (dm->fddt_train == BTC_FDDT_ENABLE) break; @@ -2242,7 +2712,7 @@ static void _append_tdma(struct rtw89_dev *rtwdev) tlv->len = sizeof(*v); *v = dm->tdma; btc->policy_len += BTC_TLV_HDR_LEN + sizeof(*v); - } else if (ver->fcxtdma == 7) { + } else if (ver->fcxtdma == 7 || ver->fcxtdma == 4) { tlv_v7 = (struct rtw89_btc_btf_tlv_v7 *)&btc->policy[len]; tlv_v7->len = sizeof(dm->tdma); tlv_v7->ver = ver->fcxtdma; @@ -2258,9 +2728,9 @@ static void _append_tdma(struct rtw89_dev *rtwdev) } rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): type:%d, rxflctrl=%d, txpause=%d, wtgle_n=%d, leak_n=%d, ext_ctrl=%d\n", + "[BTC], %s(): type:%d, rxflctrl=%d, txflctrl=%d, bind=%d, leak_n=%d, ext_ctrl=%d\n", __func__, dm->tdma.type, dm->tdma.rxflctrl, - dm->tdma.txpause, dm->tdma.wtgle_n, dm->tdma.leak_n, + dm->tdma.txflctrl, dm->tdma.bind, dm->tdma.leak_n, dm->tdma.ext_ctrl); } @@ -2368,7 +2838,7 @@ static void _append_slot(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - if (btc->ver->fcxslots == 7) + if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) _append_slot_v7(rtwdev); else _append_slot_v1(rtwdev); @@ -2405,6 +2875,8 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: + case 105: bit_map = BIT(5); break; default: @@ -2420,6 +2892,8 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: + case 105: bit_map = BIT(6); break; default: @@ -2433,6 +2907,8 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: + case 105: bit_map = BIT(7); break; default: @@ -2445,8 +2921,10 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 1: case 2: case 3: + case 105: break; case 4: + case 5: bit_map = BIT(8); break; default: @@ -2460,9 +2938,11 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = BIT(7); break; case 3: + case 105: bit_map = BIT(8); break; case 4: + case 5: bit_map = BIT(9); break; default: @@ -2470,7 +2950,12 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) } break; case RPT_EN_TEST: - bit_map = BIT(31); + if (ver->frptmap == 5) + bit_map = BIT(10); + else if (ver->frptmap == 105) + bit_map = BIT(9); + else + bit_map = BIT(31); break; case RPT_EN_WL_ALL: switch (ver->frptmap) { @@ -2480,9 +2965,11 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(2, 0); break; case 3: + case 105: bit_map = GENMASK(2, 0) | BIT(8); break; case 4: + case 5: bit_map = GENMASK(2, 0) | BIT(9); break; default: @@ -2499,9 +2986,11 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(6, 3) | BIT(8); break; case 3: + case 105: bit_map = GENMASK(7, 3); break; case 4: + case 5: bit_map = GENMASK(8, 3); break; default: @@ -2521,6 +3010,8 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(8, 0); break; case 4: + case 5: + case 105: bit_map = GENMASK(9, 0); break; default: @@ -2537,9 +3028,11 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(6, 2) | BIT(8); break; case 3: + case 105: bit_map = GENMASK(8, 2); break; case 4: + case 5: bit_map = GENMASK(9, 2); break; default: @@ -2560,7 +3053,7 @@ static void rtw89_btc_fw_set_slots(struct rtw89_dev *rtwdev) struct rtw89_btc_dm *dm = &btc->dm; u16 n, len; - if (ver->fcxslots == 7) { + if (ver->fcxslots == 2 || ver->fcxslots == 7) { len = sizeof(*tlv_v7) + sizeof(dm->slot.v7); tlv_v7 = kmalloc(len, GFP_KERNEL); if (!tlv_v7) @@ -2591,37 +3084,28 @@ static void rtw89_btc_fw_set_slots(struct rtw89_dev *rtwdev) } static void rtw89_btc_fw_en_rpt(struct rtw89_dev *rtwdev, - u32 rpt_map, bool rpt_state) + bool force_exec, u32 rpt_map) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_smap *wl_smap = &btc->cx.wl.status.map; struct rtw89_btc_btf_fwinfo *fwinfo = &btc->fwinfo; union rtw89_fbtc_rtp_ctrl r; - u32 val, bit_map; int ret; + u32 i; - if ((wl_smap->rf_off || wl_smap->lps != BTC_LPS_OFF) && rpt_state != 0) + if (!force_exec && rpt_map == fwinfo->rpt_en_map) return; - bit_map = rtw89_btc_fw_rpt_ver(rtwdev, rpt_map); + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): rpt_map=%x\n", + __func__, fwinfo->rpt_en_map); - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): rpt_map=%x, rpt_state=%x\n", - __func__, rpt_map, rpt_state); - - if (rpt_state) - val = fwinfo->rpt_en_map | bit_map; - else - val = fwinfo->rpt_en_map & ~bit_map; - - if (val == fwinfo->rpt_en_map) - return; - - if (btc->ver->fcxbtcrpt == 7 || btc->ver->fcxbtcrpt == 8) { + if (btc->ver->fcxbtcrpt == 7 || + btc->ver->fcxbtcrpt == 8 || + btc->ver->fcxbtcrpt == 9 || + btc->ver->fcxbtcrpt == 11) { r.v8.type = SET_REPORT_EN; r.v8.fver = btc->ver->fcxbtcrpt; r.v8.len = sizeof(r.v8.map); - r.v8.map = cpu_to_le32(val); + r.v8.map = cpu_to_le32(rpt_map); ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, &r.v8, sizeof(r.v8)); } else { @@ -2629,14 +3113,21 @@ static void rtw89_btc_fw_en_rpt(struct rtw89_dev *rtwdev, r.v1.fver = 5; else r.v1.fver = btc->ver->fcxbtcrpt; - r.v1.enable = cpu_to_le32(val); - r.v1.para = cpu_to_le32(rpt_state); - ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, &r.v1, - sizeof(r.v1)); + + for (i = 0; i < RPT_EN_MONITER; i++) { + r.v1.enable = cpu_to_le32(i); + if (rpt_map & BIT(i)) + r.v1.para = cpu_to_le32(1); + else + r.v1.para = 0; + + ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, + &r.v1, sizeof(r.v1)); + } } if (!ret) - fwinfo->rpt_en_map = val; + fwinfo->rpt_en_map = rpt_map; } static void btc_fw_set_monreg(struct rtw89_dev *rtwdev) @@ -2698,8 +3189,6 @@ static void btc_fw_set_monreg(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): sz=%d ulen=%d n=%d\n", __func__, sz, ulen, n); - - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, 1); } static void _update_dm_step(struct rtw89_dev *rtwdev, @@ -2745,18 +3234,18 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, if (dm->tdma.rxflctrl == CXFLC_NULLP || dm->tdma.rxflctrl == CXFLC_QOSNULL) - btc->lps = 1; + btc->btc_ctrl_lps = 1; else - btc->lps = 0; + btc->btc_ctrl_lps = dm->lps_ctrl_scbd; - if (btc->lps == 1) - rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); + if (btc->btc_ctrl_lps == 1) + rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_CX_POLICY, btc->policy, btc->policy_len); if (!ret) { memcpy(&dm->tdma_now, &dm->tdma, sizeof(dm->tdma_now)); - if (btc->ver->fcxslots == 7) + if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) memcpy(&dm->slot_now.v7, &dm->slot.v7, sizeof(dm->slot_now.v7)); else memcpy(&dm->slot_now.v1, &dm->slot.v1, sizeof(dm->slot_now.v1)); @@ -2765,81 +3254,323 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, if (btc->update_policy_force) btc->update_policy_force = false; - if (btc->lps == 0) - rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); + if (btc->btc_ctrl_lps == 0) + rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); } -static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) +static u8 _get_gpiosig_for_ver(struct rtw89_dev *rtwdev, u8 sig_v8) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + + /* v8: gpio_ver >= 8, directly use v8 signal ID */ + if (ver->fcxgpiodbg >= 8) + return sig_v8; + + /* v7: gpio_ver < 8, convert v8 signal ID to v7 signal ID */ + if (sig_v8 <= BTC_DBG_GNT_WL) { + return sig_v8; + } else if (sig_v8 <= BTC_DBG_GNT_WL1) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): sig %d not supported in v7\n", + __func__, sig_v8); + return BTC_DBG_NUM; + } else if (sig_v8 >= BTC_DBG_NUM) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): sig %d not available\n", + __func__, sig_v8); + return BTC_DBG_NUM; + } else { + return sig_v8 - 2; + } +} + +static u32 _convert_gpio_enmap_to_ver(struct rtw89_dev *rtwdev, u32 en_map_v8) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + u32 en_map_v7 = 0; + u32 bit; + + /* v8: gpio_ver >= 8, directly use v8 en_map */ + if (ver->fcxgpiodbg >= 8) + return en_map_v8; + + /* v7: gpio_ver < 8, convert v8 en_map bitmap to v7 en_map bitmap */ + /* bit 0-1: GNT_BT, GNT_WL - same position in both versions */ + en_map_v7 |= en_map_v8 & GENMASK(1, 0); + + /* bit 2-3: GNT_BT1, GNT_WL1 - not supported in v7, skip these bits */ + + /* bit 4-31: shift right by 2 positions (become bit 2-29 in v7) */ + for (bit = BTC_DBG_BCN_EARLY; bit < BTC_DBG_NUM; bit++) { + if (en_map_v8 & BIT(bit)) + en_map_v7 |= BIT(bit - 2); + } + + return en_map_v7; +} + +static u32 _convert_gpio_enmap_from_ver(struct rtw89_dev *rtwdev, u32 en_map) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + u32 en_map_v8 = 0; + u32 bit; + + if (ver->fcxgpiodbg >= 8) + return en_map; + + /* bit 0-1: GNT_BT, GNT_WL - same position in both versions */ + en_map_v8 |= en_map & GENMASK(1, 0); + + /* bit 2-29: shift left by 2 positions (become bit 4-31 in v8) */ + for (bit = BTC_DBG_GNT_BT1; bit < BTC_DBG_NUM - 2; bit++) { + if (en_map & BIT(bit)) + en_map_v8 |= BIT(bit + 2); + } + + return en_map_v8; +} + +static void _fw_set_gpio(struct rtw89_dev *rtwdev, u8 type, u32 val) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_fbtc_h2c_set_gpio *gpio = &btc->gpio; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_fbtc_h2c_set_gpio_2b l2_h2c; + struct rtw89_fbtc_h2c_set_gpio_4b l4_h2c; + u8 gpio_ver = ver->fcxgpiodbg; + u8 buf[sizeof(l4_h2c)]; + u8 len; + + if (gpio_ver < 8 && type > CXDGPIO_MUX_MAP) + return; + + if (type >= CXDGPIO_MAX) + return; + + switch (type) { + case CXDGPIO_EN_MAP: /* GPIO debug signal en-map 0~31 */ + val = _convert_gpio_enmap_to_ver(rtwdev, val); + gpio->en_map.data.type = CXDGPIO_EN_MAP; + gpio->en_map.data.fver = gpio_ver; + gpio->en_map.data.dlen = CXDGPIO_SET_L4; + gpio->en_map.data.en_map = val; + l4_h2c = gpio->en_map.fmt; + put_unaligned_le32(val, (u32 *)l4_h2c.data); + memcpy(buf, &l4_h2c, sizeof(l4_h2c)); + len = sizeof(l4_h2c); + break; + case CXDGPIO_MUX_MAP: /* GPIO dbg: Signal to GPIO Mux */ + gpio->mux.data.type = CXDGPIO_MUX_MAP; + gpio->mux.data.fver = gpio_ver; + gpio->mux.data.dlen = CXDGPIO_SET_L2; + gpio->mux.data.sig = _get_gpiosig_for_ver(rtwdev, + FIELD_GET(GENMASK(7, 0), val)); + if (gpio->mux.data.sig == 0xff) + return; + gpio->mux.data.gpio = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->mux.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_HPTA: /* GPIO config for Ext HW-PTA */ + gpio->ext_pta.data.type = CXDGPIO_EXT_HPTA; + gpio->ext_pta.data.fver = gpio_ver; + gpio->ext_pta.data.dlen = CXDGPIO_SET_L2; + gpio->ext_pta.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_pta.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_pta.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_HMBX: /* GPIO config for Ext HW-mailbox */ + gpio->ext_mb.data.type = CXDGPIO_EXT_HMBX; + gpio->ext_mb.data.fver = gpio_ver; + gpio->ext_mb.data.dlen = CXDGPIO_SET_L2; + gpio->ext_mb.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_mb.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_mb.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_SWOUT: /* GPIO config for Ext SW output (wlan_act) */ + gpio->ext_swout.data.type = CXDGPIO_EXT_SWOUT; + gpio->ext_swout.data.fver = gpio_ver; + gpio->ext_swout.data.dlen = CXDGPIO_SET_L2; + gpio->ext_swout.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_swout.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_swout.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_SWIN: /* GPIO config for Ext SW input control */ + gpio->ext_swin.data.type = CXDGPIO_EXT_SWIN; + gpio->ext_swin.data.fver = gpio_ver; + gpio->ext_swin.data.dlen = CXDGPIO_SET_L4; + gpio->ext_swin.data.in_map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_swin.data.in_map_high = FIELD_GET(GENMASK(15, 8), val); + gpio->ext_swin.data.int_map_low = FIELD_GET(GENMASK(23, 16), val); + gpio->ext_swin.data.int_map_high = FIELD_GET(GENMASK(31, 24), val); + l4_h2c = gpio->ext_swin.fmt; + put_unaligned_le32(val, (u32 *)l4_h2c.data); + memcpy(buf, &l4_h2c, sizeof(l4_h2c)); + len = sizeof(l4_h2c); + break; + default: + return; + } + + _send_fw_cmd(rtwdev, BTFC_SET, SET_GPIO_DBG, buf, len); +} + +static void _set_ext_interface(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + u8 bt1_sw_type = BTC_SWITCH_INTERNAL; + u32 val; + + if (btc->ver->fcxgpiodbg < 7) + return; + + /* if BT1+WL-S0, route DBG_GNT_BT1 to control SPDT */ + if ((rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && + btc->ver->fcxinit >= 10) { + if (btc->ver->fcxinit >= 10) + bt1_sw_type = btc->mdinfo.bt1_sw_type; + else + return; + + if (bt1_sw_type > BTC_SWITCH_INTERNAL) { + _fw_set_gpio(rtwdev, CXDGPIO_EN_MAP, + BIT(BTC_DBG_GNT_BT1)); + + val = (bt1_sw_type << 8) + BTC_DBG_GNT_BT1; + _fw_set_gpio(rtwdev, CXDGPIO_MUX_MAP, val); + } + } + + /* set GPIO as Ext-PTA interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_PTA) + _fw_set_gpio(rtwdev, CXDGPIO_EXT_HPTA, cx->bt_ext.hpta_cfg); + + /* set GPIO as Ext-mailbox interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_MBX) + _fw_set_gpio(rtwdev, CXDGPIO_EXT_HMBX, cx->bt_ext.hmbx_cfg); + + /* set GPIO as Ext-SWIO interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_SWIO) { + _fw_set_gpio(rtwdev, CXDGPIO_EXT_SWOUT, cx->bt_ext.swout_cfg); + _fw_set_gpio(rtwdev, CXDGPIO_EXT_SWIN, cx->bt_ext.swin_cfg); + } +} + +static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_rf_trx_para rf_para = dm->rf_trx_para; - switch (type) { + switch (index) { case CXDRVINFO_INIT: - if (ver->fcxinit == 7) - rtw89_fw_h2c_cxdrv_init_v7(rtwdev, type); + if (ver->fcxinit == 7 || ver->fcxinit == 107) + rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index); + else if (ver->fcxinit == 10) + rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index); + else if (ver->fcxinit == 11) + rtw89_fw_h2c_cxdrv_init_v11(rtwdev, index); else - rtw89_fw_h2c_cxdrv_init(rtwdev, type); + rtw89_fw_h2c_cxdrv_init(rtwdev, index); break; case CXDRVINFO_ROLE: if (ver->fwlrole == 0) - rtw89_fw_h2c_cxdrv_role(rtwdev, type); + rtw89_fw_h2c_cxdrv_role(rtwdev, index); else if (ver->fwlrole == 1) - rtw89_fw_h2c_cxdrv_role_v1(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v1(rtwdev, index); + else if (ver->fwlrole == 101) + rtw89_fw_h2c_cxdrv_role_v101(rtwdev, index); else if (ver->fwlrole == 2) - rtw89_fw_h2c_cxdrv_role_v2(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v2(rtwdev, index); else if (ver->fwlrole == 7) - rtw89_fw_h2c_cxdrv_role_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v7(rtwdev, index); else if (ver->fwlrole == 8) - rtw89_fw_h2c_cxdrv_role_v8(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v8(rtwdev, index); + else if (ver->fwlrole == 10) + rtw89_fw_h2c_cxdrv_role_v10(rtwdev, index); break; case CXDRVINFO_CTRL: - if (ver->drvinfo_type == 1) - type = 2; + if (ver->drvinfo_ver != 0) + index = 2; if (ver->fcxctrl == 7) - rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, index); + else if (ver->fcxctrl == 9) + rtw89_fw_h2c_cxdrv_ctrl_v9(rtwdev, index); else - rtw89_fw_h2c_cxdrv_ctrl(rtwdev, type); + rtw89_fw_h2c_cxdrv_ctrl(rtwdev, index); break; case CXDRVINFO_TRX: - if (ver->drvinfo_type == 1) - type = 3; + if (ver->drvinfo_ver > 1) + index = 3; - dm->trx_info.tx_power = u32_get_bits(rf_para.wl_tx_power, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.rx_gain = u32_get_bits(rf_para.wl_rx_gain, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.bt_tx_power = u32_get_bits(rf_para.bt_tx_power, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.bt_rx_gain = u32_get_bits(rf_para.bt_rx_gain, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.cn = wl->cn_report; - dm->trx_info.nhm = wl->nhm.pwr; - rtw89_fw_h2c_cxdrv_trx(rtwdev, type); + if (ver->fcxtrx == 0) + return; + + if (ver->fcxtrx == 7) + rtw89_fw_h2c_cxdrv_trx_v7(rtwdev, index); + else if (ver->fcxtrx == 9) + rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, index); + else if (ver->fcxtrx == 107) + rtw89_fw_h2c_cxdrv_trx_v107(rtwdev, index); break; case CXDRVINFO_RFK: - if (ver->drvinfo_type == 1) + if (ver->drvinfo_ver != 0) return; - rtw89_fw_h2c_cxdrv_rfk(rtwdev, type); + rtw89_fw_h2c_cxdrv_rfk(rtwdev, index); break; case CXDRVINFO_TXPWR: - case CXDRVINFO_FDDT: - case CXDRVINFO_MLO: - case CXDRVINFO_OSI: - if (!ver->fcxosi) - return; + if (ver->drvinfo_ver == 3 || ver->drvinfo_ver == 103) + index = 4; - if (ver->drvinfo_type == 2) - type = 7; + if (ver->fcxtrx == 7 || ver->fcxtrx == 107) + rtw89_fw_h2c_cxtxpwr_v7(rtwdev, index); + else if (ver->fcxtrx == 9) + rtw89_fw_h2c_cxtxpwr_v9(rtwdev, index); + break; + case CXDRVINFO_FDDT: + if (ver->drvinfo_ver == 3 || ver->drvinfo_ver == 103) + index = 5; else return; - rtw89_fw_h2c_cxdrv_osi_info(rtwdev, type); + break; + case CXDRVINFO_MLO: + if (!ver->fcxmlo || ver->drvinfo_ver == 103) + return; + + if (ver->drvinfo_ver == 3) + index = 6; + else + return; + + if (ver->fcxmlo == 2) + rtw89_fw_h2c_cxdrv_mlo_v2(rtwdev, index); + + break; + case CXDRVINFO_OSI: + if (!ver->fcxosi || ver->drvinfo_ver == 103) + return; + + if (ver->drvinfo_ver == 3) + index = 7; + else + return; + + if (ver->fcxosi == 1) + rtw89_fw_h2c_cxdrv_osi_info(rtwdev, index); + else if (ver->fcxosi == 6) + rtw89_fw_h2c_cxdrv_osi_info_v6(rtwdev, index); + else + return; break; default: break; @@ -2851,28 +3582,45 @@ void btc_fw_event(struct rtw89_dev *rtwdev, u8 evt_id, void *data, u32 len) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + u8 i; - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): evt_id:%d len:%d\n", - __func__, evt_id, len); + _parse_btc_report(rtwdev, pfwinfo, data, len); - if (!len || !data) + if (!rtwdev->chip->scbd) return; - switch (evt_id) { - case BTF_EVNT_RPT: - _parse_btc_report(rtwdev, pfwinfo, data, len); - break; - default: - break; + if (!btc->dm.scbd_b2w_update) + return; /* skip if init or no-update */ + + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) { + bt = (i == BTC_BT_1ST) ? &btc->cx.bt0 : &btc->cx.bt1; + + if (bt->scbd_c2h != bt->scbd_rb) { + /* if btx b2w scbd non-sync */ + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s() bt%d:c2h->0x%08x, rb->0x%08x\n", + __func__, i, bt->scbd_c2h, bt->scbd_rb); + bt->scbd_c2h = bt->scbd_rb; + _update_bt_scbd(rtwdev, i); + btc->dm.scbd_b2w_update = false; + } } + + if (!btc->dm.scbd_b2w_update) + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); + else + btc->dm.scbd_b2w_update = 0; } -static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, u8 wl_state, u8 bt_state) +static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, + u8 wl_state, u8 bt_state, u8 wlact_state) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; + struct rtw89_btc_fbtc_outsrc_set_info *o = &dm->ost_info; + struct rtw89_mac_ax_wl_act *wlact_set; + struct rtw89_mac_ax_coex_gnt mg; u8 i; if (phy_map > BTC_PHY_ALL) @@ -2884,122 +3632,118 @@ static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, u8 wl_state, u8 bt_st switch (wl_state) { case BTC_GNT_HW: - g[i].gnt_wl_sw_en = 0; - g[i].gnt_wl = 0; + dm->gnt_set[i].gnt_wl_sw_en = 0; + dm->gnt_set[i].gnt_wl = 0; break; case BTC_GNT_SW_LO: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + dm->gnt_set[i].gnt_wl_sw_en = 1; + dm->gnt_set[i].gnt_wl = 0; break; case BTC_GNT_SW_HI: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 1; + dm->gnt_set[i].gnt_wl_sw_en = 1; + dm->gnt_set[i].gnt_wl = 1; + break; + case BTC_GNT_SET_SKIP: + default: break; } switch (bt_state) { case BTC_GNT_HW: - g[i].gnt_bt_sw_en = 0; - g[i].gnt_bt = 0; + dm->gnt_set[i].gnt_bt0_sw_en = 0; + dm->gnt_set[i].gnt_bt0 = 0; + dm->gnt_set[i].gnt_bt1_sw_en = 0; + dm->gnt_set[i].gnt_bt1 = 0; + dm->gnt_set[i].gnt_zb_sw_en = 0; + dm->gnt_set[i].gnt_zb = 0; break; case BTC_GNT_SW_LO: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 0; + dm->gnt_set[i].gnt_bt0_sw_en = 1; + dm->gnt_set[i].gnt_bt0 = 0; + dm->gnt_set[i].gnt_bt1_sw_en = 1; + dm->gnt_set[i].gnt_bt1 = 0; + dm->gnt_set[i].gnt_zb_sw_en = 1; + dm->gnt_set[i].gnt_zb = 0; break; case BTC_GNT_SW_HI: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; + dm->gnt_set[i].gnt_bt0_sw_en = 1; + dm->gnt_set[i].gnt_bt0 = 1; + dm->gnt_set[i].gnt_bt1_sw_en = 1; + dm->gnt_set[i].gnt_bt1 = 1; + dm->gnt_set[i].gnt_zb_sw_en = 1; + dm->gnt_set[i].gnt_zb = 1; + break; + case BTC_GNT_SET_SKIP: + default: break; } } - rtw89_chip_mac_cfg_gnt(rtwdev, &dm->gnt); -} - -static void _set_gnt_v1(struct rtw89_dev *rtwdev, u8 phy_map, - u8 wl_state, u8 bt_state, u8 wlact_state) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_fbtc_outsrc_set_info *osi = &dm->ost_info; - struct rtw89_mac_ax_wl_act *b = dm->gnt.bt; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; - u8 i, bt_idx = dm->bt_select + 1; - - if (phy_map > BTC_PHY_ALL) - return; - - for (i = 0; i < RTW89_PHY_NUM; i++) { - if (!(phy_map & BIT(i))) - continue; - - switch (wl_state) { - case BTC_GNT_HW: - g[i].gnt_wl_sw_en = 0; - g[i].gnt_wl = 0; + for (i = 0; i < BTC_ALL_BT_EZL; i++) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_WLAN_ACT_MUX)) break; - case BTC_GNT_SW_LO: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + + switch (wlact_state) { + case BTC_WLACT_HW: + dm->wlact_set[i].wlan_act_en = 0; + dm->wlact_set[i].wlan_act = 0; break; - case BTC_GNT_SW_HI: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 1; + case BTC_WLACT_SW_LO: + dm->wlact_set[i].wlan_act_en = 1; + dm->wlact_set[i].wlan_act = 0; + break; + case BTC_WLACT_SW_HI: + dm->wlact_set[i].wlan_act_en = 1; + dm->wlact_set[i].wlan_act = 1; + break; + default: break; } - switch (bt_state) { - case BTC_GNT_HW: - g[i].gnt_bt_sw_en = 0; - g[i].gnt_bt = 0; + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) break; - case BTC_GNT_SW_LO: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 0; - break; - case BTC_GNT_SW_HI: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - break; - } } - if (rtwdev->chip->para_ver & BTC_FEAT_WLAN_ACT_MUX) { - for (i = 0; i < 2; i++) { - if (!(bt_idx & BIT(i))) - continue; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): phy_map=0x%x, gnt_wl:%d, gnt_bt:%d, wl_act:%d\n", + __func__, phy_map, wl_state, bt_state, wlact_state); - switch (wlact_state) { - case BTC_WLACT_HW: - b[i].wlan_act_en = 0; - b[i].wlan_act = 0; - break; - case BTC_WLACT_SW_LO: - b[i].wlan_act_en = 1; - b[i].wlan_act = 0; - break; - case BTC_WLACT_SW_HI: - b[i].wlan_act_en = 1; - b[i].wlan_act = 1; - break; + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { + if (o->rf_band[BTC_RF_S0] == 1 && o->rf_band[BTC_RF_S1] == 0) + o->rf_gbt_source = BTC_RF_S1; + else + o->rf_gbt_source = BTC_RF_S0; + + memcpy(o->wlact_set, dm->wlact_set, sizeof(o->wlact_set)); + + if (btc->ver->fcxosi == 6) { + memcpy(o->gnt_set_be, dm->gnt_set, sizeof(o->gnt_set_be)); + return; + } else if (btc->ver->fcxosi == 1) { + for (i = 0; i < RTW89_PHY_NUM; i++) { /* gnt[MAC0/MAC1] */ + mg.band[i].gnt_bt_sw_en = dm->gnt_set[i].gnt_bt0_sw_en; + mg.band[i].gnt_bt = dm->gnt_set[i].gnt_bt0; + mg.band[i].gnt_wl_sw_en = dm->gnt_set[i].gnt_wl_sw_en; + mg.band[i].gnt_wl = dm->gnt_set[i].gnt_wl; + memcpy(o->gnt_set, mg.band, sizeof(o->gnt_set)); } + return; } } - if (!btc->ver->fcxosi) { - rtw89_mac_cfg_gnt_v2(rtwdev, &dm->gnt); - return; + for (i = 0; i < RTW89_PHY_NUM; i++) { /* gnt[MAC0/MAC1] */ + mg.band[i].gnt_bt_sw_en = dm->gnt_set[i].gnt_bt0_sw_en; + mg.band[i].gnt_bt = dm->gnt_set[i].gnt_bt0; + mg.band[i].gnt_wl_sw_en = dm->gnt_set[i].gnt_wl_sw_en; + mg.band[i].gnt_wl = dm->gnt_set[i].gnt_wl; + } + for (i = 0; i < BTC_ALL_BT; i++) { /* wlact[BT0/BT1] */ + wlact_set = &dm->wlact_set[i]; + mg.bt[i].wlan_act_en = wlact_set->wlan_act_en; + mg.bt[i].wlan_act = wlact_set->wlan_act; } - memcpy(osi->gnt_set, dm->gnt.band, sizeof(osi->gnt_set)); - memcpy(osi->wlact_set, dm->gnt.bt, sizeof(osi->wlact_set)); - - /* GBT source should be GBT_S1 in 1+1 (HWB0:5G + HWB1:2G) case */ - if (osi->rf_band[BTC_RF_S0] == 1 && - osi->rf_band[BTC_RF_S1] == 0) - osi->rf_gbt_source = BTC_RF_S1; - else - osi->rf_gbt_source = BTC_RF_S0; + rtw89_chip_mac_cfg_gnt(rtwdev, &mg); } #define BTC_TDMA_WLROLE_MAX 3 @@ -3020,111 +3764,293 @@ static void _set_bt_ignore_wlan_act(struct rtw89_dev *rtwdev, u8 enable) #define WL_TX_POWER_FRA_PART GENMASK(1, 0) #define B_BTC_WL_TX_POWER_SIGN BIT(7) #define B_TSSI_WL_TX_POWER_SIGN BIT(8) +#define SET_RF_PARA_AX_LEN 1 -static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level) +static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_dm *dm = &btc->dm; + bool level_chg = false; u32 pwr_val; - if (wl->rf_para.tx_pwr_freerun == level) + if (phy_map & BIT(RTW89_PHY_0)) + level_chg = !(dm->rf_trx_para.wl_tx_power[RTW89_PHY_0] == level); + + if (phy_map & BIT(RTW89_PHY_1)) + level_chg |= !(dm->rf_trx_para.wl_tx_power[RTW89_PHY_1] == level); + + if (!level_chg && !btc->cli_h2c_cmd) return; - wl->rf_para.tx_pwr_freerun = level; - btc->dm.rf_trx_para.wl_tx_power = level; + if (phy_map & BIT(RTW89_PHY_0)) + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0] = level; + if (phy_map & BIT(RTW89_PHY_1)) + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1] = level; + + dm->wl_tx_pwr_phy_map = phy_map; rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + "[BTC], %s(): phy_map=0x%x, wl_tx_pwr-> phy0=%d, phy1=%d\n", + __func__, phy_map, + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1]); - if (level == RTW89_BTC_WL_DEF_TX_PWR) { - pwr_val = WL_TX_POWER_NO_BTC_CTRL; - } else { /* only apply "force tx power" */ - pwr_val = FIELD_PREP(WL_TX_POWER_INT_PART, level); - if (pwr_val > RTW89_BTC_WL_DEF_TX_PWR) - pwr_val = RTW89_BTC_WL_DEF_TX_PWR; + if ((ver->fcxtrx == 7 && chip->chip_id == RTL8922A) || + ver->fcxtrx == 107) { + _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); + } else if (ver->fcxtrx == 9) { + _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); + } else { + if (level == RTW89_BTC_WL_DEF_TX_PWR) { + pwr_val = WL_TX_POWER_NO_BTC_CTRL; + } else { /* only apply "force tx power" */ + pwr_val = FIELD_PREP(WL_TX_POWER_INT_PART, level); + if (pwr_val > RTW89_BTC_WL_DEF_TX_PWR) + pwr_val = RTW89_BTC_WL_DEF_TX_PWR; - if (level & B_BTC_WL_TX_POWER_SIGN) - pwr_val |= B_TSSI_WL_TX_POWER_SIGN; - pwr_val |= WL_TX_POWER_WITH_BT; + if (level & B_BTC_WL_TX_POWER_SIGN) + pwr_val |= B_TSSI_WL_TX_POWER_SIGN; + pwr_val |= WL_TX_POWER_WITH_BT; + } + chip->ops->btc_set_wl_txpwr_ctrl(rtwdev, pwr_val); } - - chip->ops->btc_set_wl_txpwr_ctrl(rtwdev, pwr_val); } -static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level) +static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + bool level_chg = false; - if (wl->rf_para.rx_gain_freerun == level) + if (phy_map & BIT(RTW89_PHY_0)) + level_chg = !(dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0] == level); + + if (phy_map & BIT(RTW89_PHY_1)) + level_chg |= !(dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1] == level); + + if (!level_chg && !btc->cli_h2c_cmd) return; - wl->rf_para.rx_gain_freerun = level; - btc->dm.rf_trx_para.wl_rx_gain = level; + if (phy_map & BIT(RTW89_PHY_0)) + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0] = level; + + if (phy_map & BIT(RTW89_PHY_1)) + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1] = level; rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + "[BTC], %s(): phy_map=0x%x, wl_rx_gain-> phy0=%d, phy1=%d\n", + __func__, phy_map, + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1]); chip->ops->btc_set_wl_rx_gain(rtwdev, level); } -static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) +static void _set_bt_tx_power(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, + u8 rf_band, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - int ret; - u8 buf; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + u8 h2c_func = SET_BT_TX_PWR; + u8 i, id_start, id_stop; + u8 buf[2] = {}; + u8 len = sizeof(*buf); - if (btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0 || !rf_band) return; if (bt->rf_para.tx_pwr_freerun == level) return; - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - buf = (s8)(-level); - ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_TX_PWR, &buf, 1); - if (!ret) { - bt->rf_para.tx_pwr_freerun = level; - btc->dm.rf_trx_para.bt_tx_power = level; + for (i = id_start; i <= id_stop; i++) { + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + + bt = &btc->cx.bt1; + h2c_func |= BT_H2C_FUNC_BT2ND; + } + + buf[0] = (s8)(-level); + buf[1] = rf_band; /* bit-map: bit1->5GHz/6Ghz, bit0->2.4GHz */ + + if (!force_exec && !btc->cli_h2c_cmd) { + if (rf_band == RTW89_BAND_2G && + bt->tx_power_now == level) + continue; + else if (rf_band != RTW89_BAND_2G && + bt->tx_power_now_6g == level) + continue; + } + + if (rtwdev->chip->chip_gen == RTW89_CHIP_AX) + len = SET_RF_PARA_AX_LEN; + + if (!_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { + btc->dm.rf_trx_para.bt_tx_power[i] = level; + if (rf_band == RTW89_BAND_2G) + bt->tx_power_now = level; + else + bt->tx_power_now_6g = level; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): bt%d_tx_power_level = %d\n", + __func__, i, level); + } } } #define BTC_BT_RX_NORMAL_LVL 7 - -static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, u8 level) +static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, + u8 rf_band, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt = &cx->bt0; + u8 h2c_func = SET_BT_LNA_CONSTRAIN; + u8 i, id_start, id_stop; + bool state = false; + u32 scbd_bit = 0; + u8 buf[2] = {}; + u8 len = sizeof(*buf); - if (btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0 || !rf_band) return; - if ((bt->rf_para.rx_gain_freerun == level || - level > BTC_BT_RX_NORMAL_LVL) && - (!rtwdev->chip->scbd || bt->lna_constrain == level)) + if (bt->rf_para.rx_gain_freerun == level || + level > BTC_BT_RX_NORMAL_LVL || !rtwdev->chip->scbd || + bid > BTC_ALL_BT) return; - bt->rf_para.rx_gain_freerun = level; - btc->dm.rf_trx_para.bt_rx_gain = level; + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); - - if (level == BTC_BT_RX_NORMAL_LVL) - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, false); + if (rf_band == RTW89_BAND_2G) + scbd_bit |= BTC_WSCB_RXGAIN; else - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + scbd_bit |= BTC_WSCB_RXGAIN_56G; - _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_LNA_CONSTRAIN, &level, sizeof(level)); + for (i = id_start; i <= id_stop; i++) { + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + + bt = &cx->bt1; + h2c_func |= BT_H2C_FUNC_BT2ND; + } + + /* return if same setup */ + if (!force_exec && !btc->cli_h2c_cmd) { + if (rf_band == RTW89_BAND_2G && + bt->lna_constrain == level) + continue; + else if (rf_band != RTW89_BAND_2G && + bt->lna_constrain_6g == level) + continue; + } + + buf[0] = level; + buf[1] = rf_band; + + if (rtwdev->chip->chip_gen == RTW89_CHIP_AX) + len = SET_RF_PARA_AX_LEN; + + if (_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): bt%d_rx_gain_level = %d\n", + __func__, i, level); + } + + btc->dm.rf_trx_para.bt_rx_gain[i] = level; + + if (buf[0] != BTC_BT_RX_NORMAL_LVL) + state = true; + + _write_scbd(rtwdev, bid, scbd_bit, state); + } +} + +static void _set_bt_corx_table(struct rtw89_dev *rtwdev, bool en) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt = &cx->bt0; + struct rtw89_btc_dm *dm = &btc->dm; + u8 is_24g, is_56g, i; + u32 scbd_bit; + + /* + * true: bt use Hi-LNA rx gain table (f/e/3/2) in -3x~-9xdBm for co-rx + * false: bt use original rx gain table (f/b/7/3/2) + */ + + if (btc->dm.fdd_bind.bt_sel == BIT(BTC_BT_EXT)) + return; + + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) { + scbd_bit = 0; + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + bt = &cx->bt1; + } + + is_24g = (dm->corx_map[BTC_RF_S0][i] || + dm->corx_map[BTC_RF_S1][i]) & BIT(RTW89_BAND_2G); + is_56g = (dm->corx_map[BTC_RF_S0][i] || + dm->corx_map[BTC_RF_S1][i]) & BIT(RTW89_BAND_5G); + if (is_24g) + scbd_bit |= BTC_WSCB_BT_HILNA; + if (is_56g) + scbd_bit |= BTC_WSCB_BT_HILNA_56G; + + if ((is_24g && (en != (!!bt->hi_lna_rx))) || + (is_56g && (en != (!!bt->hi_lna_rx_6g)))) + _write_scbd(rtwdev, i, scbd_bit, en); + } +} + +static void _set_rf_trx_para_v9(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_smap *wl_smap = &cx->wl.status.map; + struct rtw89_btc_dm *dm = &btc->dm; + bool bt_2r = false; + u8 wl_stb_chg = 0; + + if (wl_smap->rf_off || wl_smap->lps == BTC_LPS_RF_OFF) + return; + + /* must call after _set_halbb_btg_ctrl() */ + if (dm->tdd_bind.wl_link_mode != BTC_WLINK_NOLINK) { + wl_stb_chg = 1; + if (dm->wl_btg_rx) + bt_2r = true; + } + + _set_bt_corx_table(rtwdev, bt_2r); + + if (wl_stb_chg != dm->wl_stb_chg) { + dm->wl_stb_chg = wl_stb_chg; + dm->ost_info.wl_btg_standby_chg = wl_stb_chg; + rtwdev->chip->ops->btc_wl_s1_standby(rtwdev, dm->wl_stb_chg); + } } static void _set_rf_trx_para(struct rtw89_dev *rtwdev) @@ -3134,31 +4060,49 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; - struct rtw89_btc_rf_trx_para para; - u32 wl_stb_chg = 0; - u8 level_id = 0, link_mode = 0, i, dbcc_2g_phy = 0; + struct rtw89_btc_rf_trx_para_v9 para; + u8 bmode = dm->tdd_bind.wl_link_mode; + u8 rf_band = dm->tdd_bind.rf_band; + u8 i, ul_para_num, dl_para_num; + u8 mode = BTC_WLINK_NOLINK; + u8 bid = BTC_BT_1ST, lv; + u32 wl_stb_chg; - if (ver->fwlrole == 0) { - link_mode = wl->role_info.link_mode; - for (i = 0; i < RTW89_PHY_NUM; i++) { - if (wl->dbcc_info.real_band[i] == RTW89_BAND_2G) - dbcc_2g_phy = i; + if (ver->fcxtrx == 9) { + /* Early v9 need to assign UL/DL RF para at driver */ + if (chip->rf_para_ulink_v9) { + ul_para_num = chip->rf_para_ulink_num_v9; + dl_para_num = chip->rf_para_dlink_num_v9; + } else { + _set_rf_trx_para_v9(rtwdev); + return; } - } else if (ver->fwlrole == 1) { - link_mode = wl->role_info_v1.link_mode; - dbcc_2g_phy = wl->role_info_v1.dbcc_2g_phy; - } else if (ver->fwlrole == 2) { - link_mode = wl->role_info_v2.link_mode; - dbcc_2g_phy = wl->role_info_v2.dbcc_2g_phy; + } else if ((ver->fcxtrx == 0 || ver->fcxtrx == 7 || ver->fcxtrx == 107) && + chip->rf_para_ulink_v0) { + ul_para_num = chip->rf_para_ulink_num_v0; + dl_para_num = chip->rf_para_dlink_num_v0; + } else { + rtw89_warn(rtwdev, "[BTC]%s(), No rf_para for verseion %d\n", + __func__, ver->fcxtrx); + goto next; + } + + if (bmode == BTC_WLINK_NOLINK) { + return; + } else if (rf_band == RTW89_BAND_5G) { + mode = BTC_WLINK_V0_5G; + } else if (rf_band == RTW89_BAND_2G && bmode == BTC_WLINK_STA) { + mode = BTC_WLINK_V0_2G_STA; } /* decide trx_para_level */ if (btc->ant_type == BTC_ANT_SHARED) { /* fix LNA2 + TIA gain not change by GNT_BT */ - if ((btc->dm.wl_btg_rx && b->profile_cnt.now != 0) || + if ((btc->dm.wl_btg_rx && b->link_cnt.now != 0) || dm->bt_only == 1) dm->trx_para_level = 1; /* for better BT ACI issue */ else @@ -3166,8 +4110,8 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) } else { /* non-shared antenna */ dm->trx_para_level = 5; /* modify trx_para if WK 2.4G-STA-DL + bt link */ - if (b->profile_cnt.now != 0 && - link_mode == BTC_WLINK_2G_STA && + if (b->link_cnt.now != 0 && + mode == BTC_WLINK_V0_2G_STA && wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { /* uplink */ if (wl->rssi_level == 4 && bt->rssi_level > 2) dm->trx_para_level = 6; @@ -3176,35 +4120,57 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) } } - level_id = dm->trx_para_level; - if (level_id >= chip->rf_para_dlink_num || - level_id >= chip->rf_para_ulink_num) { + lv = dm->trx_para_level; + if (lv >= ul_para_num || + lv >= dl_para_num) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): invalid level_id: %d\n", - __func__, level_id); + __func__, lv); return; } - if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) - para = chip->rf_para_ulink[level_id]; - else - para = chip->rf_para_dlink[level_id]; - - if (dm->fddt_train) { - _set_wl_rx_gain(rtwdev, 1); - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { + if (ver->fcxtrx == 9) { + para = chip->rf_para_ulink_v9[lv]; + } else { + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + para.wl_tx_power[i] = chip->rf_para_ulink_v0[lv].wl_tx_power; + para.wl_rx_gain[i] = chip->rf_para_ulink_v0[lv].wl_rx_gain; + } + for (i = BTC_BT_1ST; i < BTC_ALL_BT; i++) { + para.bt_tx_power[i] = chip->rf_para_ulink_v0[lv].bt_tx_power; + para.bt_rx_gain[i] = chip->rf_para_ulink_v0[lv].bt_rx_gain; + } + } } else { - _set_wl_tx_power(rtwdev, para.wl_tx_power); - _set_wl_rx_gain(rtwdev, para.wl_rx_gain); - _set_bt_tx_power(rtwdev, para.bt_tx_power); - _set_bt_rx_gain(rtwdev, para.bt_rx_gain); + if (ver->fcxtrx == 9) { + para = chip->rf_para_dlink_v9[lv]; + } else { + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + para.wl_tx_power[i] = chip->rf_para_dlink_v0[lv].wl_tx_power; + para.wl_rx_gain[i] = chip->rf_para_dlink_v0[lv].wl_rx_gain; + } + for (i = BTC_BT_1ST; i < BTC_ALL_BT; i++) { + para.bt_tx_power[i] = chip->rf_para_dlink_v0[lv].bt_tx_power; + para.bt_rx_gain[i] = chip->rf_para_dlink_v0[lv].bt_rx_gain; + } + } } + if (dm->fddt_train) { + _set_wl_rx_gain(rtwdev, 1, RTW89_PHY_0); + _write_scbd(rtwdev, bid, BTC_WSCB_RXGAIN, true); + } else { + _set_wl_tx_power(rtwdev, para.wl_tx_power[RTW89_PHY_0], RTW89_PHY_0); + _set_wl_rx_gain(rtwdev, para.wl_rx_gain[RTW89_PHY_0], RTW89_PHY_0); + _set_bt_tx_power(rtwdev, true, bid, rf_band, para.bt_tx_power[BTC_BT_1ST]); + _set_bt_rx_gain(rtwdev, true, bid, rf_band, para.bt_rx_gain[BTC_BT_1ST]); + } + +next: if (!bt->enable.now || dm->wl_only || wl_smap->rf_off || - wl_smap->lps == BTC_LPS_RF_OFF || - link_mode == BTC_WLINK_5G || - link_mode == BTC_WLINK_NOLINK || - (rtwdev->dbcc_en && dbcc_2g_phy != RTW89_PHY_1)) + wl_smap->lps == BTC_LPS_RF_OFF || mode == BTC_WLINK_V0_5G || + (rtwdev->dbcc_en && r->dbcc_2g_phy != RTW89_PHY_1)) wl_stb_chg = 0; else wl_stb_chg = 1; @@ -3220,11 +4186,12 @@ static void _update_btc_state_map(struct rtw89_dev *rtwdev) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - if (wl->status.map.connecting || wl->status.map._4way || - wl->status.map.roaming || wl->status.map.dbccing) { + if (wl->status.map.dhcp || wl->status.map._4way || + wl->status.map.roaming || wl->status.map.transacting || + wl->status.map.handshake) { cx->state_map = BTC_WLINKING; } else if (wl->status.map.scan) { /* wl scan */ if (bt_linfo->status.map.inq_pag) @@ -3241,315 +4208,383 @@ static void _update_btc_state_map(struct rtw89_dev *rtwdev) } } -static void _set_bt_afh_info_v0(struct rtw89_dev *rtwdev) +static u8 _get_wl_guard_bw(struct rtw89_dev *rtwdev, u8 index) { - const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_bt_link_info *b = &bt->link_info; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_wl_active_role *r; - struct rtw89_btc_wl_active_role_v1 *r1; - struct rtw89_btc_wl_active_role_v2 *r2; - struct rtw89_btc_wl_active_role_v7 *r7; - struct rtw89_btc_wl_rlink *rlink; - u8 en = 0, i, ch = 0, bw = 0; - u8 mode, connect_cnt; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + u8 guard_ch = rtwdev->chip->afh_guard_ch; + u8 bw = 0; - if (btc->manual_ctrl || wl->status.map.scan) - return; - - if (ver->fwlrole == 0) { - mode = wl_rinfo->link_mode; - connect_cnt = wl_rinfo->connect_cnt; - } else if (ver->fwlrole == 1) { - mode = wl_rinfo_v1->link_mode; - connect_cnt = wl_rinfo_v1->connect_cnt; - } else if (ver->fwlrole == 2) { - mode = wl_rinfo_v2->link_mode; - connect_cnt = wl_rinfo_v2->connect_cnt; - } else if (ver->fwlrole == 7) { - mode = wl_rinfo_v7->link_mode; - connect_cnt = wl_rinfo_v7->connect_cnt; - } else if (ver->fwlrole == 8) { - mode = wl_rinfo_v8->link_mode; - connect_cnt = wl_rinfo_v8->connect_cnt; - } else { - return; - } - - if (wl->status.map.rf_off || bt->whql_test || - mode == BTC_WLINK_NOLINK || mode == BTC_WLINK_5G || - connect_cnt > BTC_TDMA_WLROLE_MAX) { - en = false; - } else if (mode == BTC_WLINK_2G_MCC || mode == BTC_WLINK_2G_SCC) { - en = true; - /* get p2p channel */ - for (i = 0; i < RTW89_PORT_NUM; i++) { - r = &wl_rinfo->active_role[i]; - r1 = &wl_rinfo_v1->active_role_v1[i]; - r2 = &wl_rinfo_v2->active_role_v2[i]; - r7 = &wl_rinfo_v7->active_role[i]; - rlink = &wl_rinfo_v8->rlink[i][0]; - - if (ver->fwlrole == 0 && - (r->role == RTW89_WIFI_ROLE_P2P_GO || - r->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r->ch; - bw = r->bw; - break; - } else if (ver->fwlrole == 1 && - (r1->role == RTW89_WIFI_ROLE_P2P_GO || - r1->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r1->ch; - bw = r1->bw; - break; - } else if (ver->fwlrole == 2 && - (r2->role == RTW89_WIFI_ROLE_P2P_GO || - r2->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r2->ch; - bw = r2->bw; - break; - } else if (ver->fwlrole == 7 && - (r7->role == RTW89_WIFI_ROLE_P2P_GO || - r7->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r7->ch; - bw = r7->bw; - break; - } else if (ver->fwlrole == 8 && - (rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = rlink->ch; - bw = rlink->bw; - break; - } - } - } else { - en = true; - /* get 2g channel */ - for (i = 0; i < RTW89_PORT_NUM; i++) { - r = &wl_rinfo->active_role[i]; - r1 = &wl_rinfo_v1->active_role_v1[i]; - r2 = &wl_rinfo_v2->active_role_v2[i]; - r7 = &wl_rinfo_v7->active_role[i]; - rlink = &wl_rinfo_v8->rlink[i][0]; - - if (ver->fwlrole == 0 && - r->connected && r->band == RTW89_BAND_2G) { - ch = r->ch; - bw = r->bw; - break; - } else if (ver->fwlrole == 1 && - r1->connected && r1->band == RTW89_BAND_2G) { - ch = r1->ch; - bw = r1->bw; - break; - } else if (ver->fwlrole == 2 && - r2->connected && r2->band == RTW89_BAND_2G) { - ch = r2->ch; - bw = r2->bw; - break; - } else if (ver->fwlrole == 7 && - r7->connected && r7->band == RTW89_BAND_2G) { - ch = r7->ch; - bw = r7->bw; - break; - } else if (ver->fwlrole == 8 && - rlink->connected && rlink->rf_band == RTW89_BAND_2G) { - ch = rlink->ch; - bw = rlink->bw; - break; - } - } - } - - switch (bw) { + /* default AFH channel sapn = center-ch +- 6MHz */ + switch (index) { case RTW89_CHANNEL_WIDTH_20: - bw = 20 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_40: - bw = 40 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_5: - bw = 5 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_10: - bw = 10 + chip->afh_guard_ch * 2; - break; - default: - bw = 0; - en = false; /* turn off AFH info if BW > 40 */ - break; - } - - if (wl->afh_info.en == en && - wl->afh_info.ch == ch && - wl->afh_info.bw == bw && - b->profile_cnt.last == b->profile_cnt.now) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return because no change!\n", - __func__); - return; - } - - wl->afh_info.en = en; - wl->afh_info.ch = ch; - wl->afh_info.bw = bw; - - _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_WL_CH_INFO, &wl->afh_info, 3); - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): en=%d, ch=%d, bw=%d\n", - __func__, en, ch, bw); - btc->cx.cnt_wl[BTC_WCNT_CH_UPDATE]++; -} - -static void _set_bt_afh_info_v1(struct rtw89_dev *rtwdev) -{ - const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; - struct rtw89_btc_wl_afh_info *wl_afh = &wl->afh_info; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_wl_rlink *rlink; - u8 en = 0, ch = 0, bw = 0, buf[3] = {}; - u8 i, j, link_mode; - - if (btc->manual_ctrl || wl->status.map.scan) - return; - - link_mode = wl_rinfo->link_mode; - - for (i = 0; i < btc->ver->max_role_num; i++) { - for (j = RTW89_MAC_0; j < RTW89_MAC_NUM; j++) { - if (wl->status.map.rf_off || bt->whql_test || - link_mode == BTC_WLINK_NOLINK || - link_mode == BTC_WLINK_5G) - break; - - rlink = &wl_rinfo->rlink[i][j]; - - /* Don't care no-connected/non-2G-band role */ - if (!rlink->connected || !rlink->active || - rlink->rf_band != RTW89_BAND_2G) - continue; - - en = 1; - ch = rlink->ch; - bw = rlink->bw; - - if (link_mode == BTC_WLINK_2G_MCC && - (rlink->role == RTW89_WIFI_ROLE_AP || - rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - /* for 2.4G MCC, take role = ap/go/gc */ - break; - } else if (link_mode != BTC_WLINK_2G_SCC || - rlink->bw == RTW89_CHANNEL_WIDTH_40) { - /* for 2.4G scc, take bw = 40M */ - break; - } - } - } - - /* default AFH channel sapn = center-ch +- 6MHz */ - switch (bw) { - case RTW89_CHANNEL_WIDTH_20: - if (btc->dm.freerun || btc->dm.fddt_train) + if (dm->freerun || dm->fddt_train) bw = 48; else - bw = 20 + chip->afh_guard_ch * 2; + bw = 20 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_40: - if (btc->dm.freerun) - bw = 40 + chip->afh_guard_ch * 2; - else - bw = 40; + bw = 40 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_80: + bw = 80 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_160: + case RTW89_CHANNEL_WIDTH_80_80: + bw = 160 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_320: + bw = 320 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_5: - bw = 5 + chip->afh_guard_ch * 2; + bw = 5 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_10: - bw = 10 + chip->afh_guard_ch * 2; + bw = 10 + guard_ch * 2; break; default: - en = false; /* turn off AFH info if invalid BW */ bw = 0; - ch = 0; break; } - if (!en || ch > 14 || ch == 0) { - en = false; - bw = 0; - ch = 0; + return bw; +} + +static void _set_wl_ch_map(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_afh_info *afh; + u16 ch_start, ch_end, i; + u8 b_group, b_pos; /* byte-group, bit-position */ + u8 hwb; + + if (wl->afh_info[RTW89_MAC_0][RTW89_BAND_2G].en && + wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].en) { + /* select wilder bw if 1+1 */ + if (wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].bw > + wl->afh_info[RTW89_MAC_0][RTW89_BAND_2G].bw) + hwb = RTW89_MAC_1; + else + hwb = RTW89_MAC_0; + } else if (wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].en) { + hwb = RTW89_MAC_1; + } else { + hwb = RTW89_MAC_0; } - if (wl_afh->en == en && - wl_afh->ch == ch && - wl_afh->bw == bw && - (!bt->enable.now || bt->enable.last)) + afh = &wl->afh_info[hwb][RTW89_BAND_2G]; + + /* focus on WL 2.4GHz link */ + if (!afh->en || afh->band != RTW89_BAND_2G) return; - wl_afh->en = buf[0]; - wl_afh->ch = buf[1]; - wl_afh->bw = buf[2]; + memset(wl->ch_map, 0x0, sizeof(wl->ch_map)); + memset(wl->ch_map_le, 0x0, sizeof(wl->ch_map_le)); - if (_send_fw_cmd(rtwdev, BTFC_SET, SET_BT_WL_CH_INFO, &wl->afh_info, 3)) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): en=%d, ch=%d, bw=%d\n", - __func__, en, ch, bw); + ch_start = afh->ch * 5 + 2407 - afh->bw / 2; + ch_end = ch_start + afh->bw; - btc->cx.cnt_wl[BTC_WCNT_CH_UPDATE]++; + for (i = ch_start; i < ch_end; i++) { + if (i < 2402 || i > 2480) + continue; + b_group = (i - 2402) / 8; + b_pos = 7 - ((i - 2402) % 8); + wl->ch_map[b_group] |= BIT(b_pos); + + if (i % 2) /* BLE ch = even-freq (ex:2402,2404) */ + continue; + + b_group = (i - 2402) / 16; + b_pos = 7 - (((i - 2402) / 2) % 8); + wl->ch_map_le[b_group] |= BIT(b_pos); + } +} + +static u8 _get_w2b_fbd_ch(struct rtw89_dev *rtwdev, + u8 bt_fb_band, u32 wl_vco_freq, + u32 *ch_lo_bound, u32 *ch_up_bound) +{ + u32 ch_lo, ch_pup, ch_min, ch_max; + u8 out_of_range = 0; + s32 val_lo, val_up; + u32 vco_lo_in_KHz; /* VCO pulling lower bound */ + u32 vco_up_in_KHz; /* VCO pulling Upper bound */ + u32 freq_start; + + vco_lo_in_KHz = wl_vco_freq - BTC_VCO_GUARD_W2B * 1000; /* KHz */ + vco_up_in_KHz = wl_vco_freq + BTC_VCO_GUARD_W2B * 1000; + + switch (bt_fb_band) { + default: + case BTC_BT_B2G: /* BT 2GHz forbiddened Channel */ + val_lo = vco_lo_in_KHz * BTC_LO_2G_DNM / BTC_LO_2G_NMR; + val_up = vco_up_in_KHz * BTC_LO_2G_DNM / BTC_LO_2G_NMR; + freq_start = BTC_FREQ_B2G * 1000; /* KHz */ + ch_min = BTC_CH_B2G_MIN; + ch_max = BTC_CH_B2G_MAX; + break; + case BTC_BT_B5G: /* BT 5/6GHz forbiddened Channel */ + val_lo = vco_lo_in_KHz * BTC_LO_6G_DNM / BTC_LO_6G_NMR; + val_up = vco_up_in_KHz * BTC_LO_6G_DNM / BTC_LO_6G_NMR; + freq_start = BTC_FREQ_B6G * 1000; /* KHz */ + ch_min = BTC_CH_B5G_MIN; + ch_max = BTC_CH_B5G_MAX; + break; + } + + /* Search forbiddened Channel (un-process range)*/ + ch_lo = (val_lo - freq_start) / 1000; + if ((val_lo - freq_start) % 1000 != 0) /* Round-UP */ + ch_lo++; + + ch_pup = (val_up - freq_start) / 1000; /* Round-down */ + + /* Search forbiddened Channel */ + if (ch_lo > ch_max || ch_pup < ch_min) { /* out-of-range -> NA */ + out_of_range = 1; + *ch_lo_bound = 0; + *ch_up_bound = 0; + } + + if (ch_lo < ch_min) + *ch_lo_bound = ch_min; + else if (ch_lo <= ch_max) + *ch_lo_bound = ch_lo; + + if (ch_pup > ch_max) + *ch_up_bound = ch_max; + else if (ch_pup >= ch_min) + *ch_up_bound = ch_pup; + + if (bt_fb_band == BTC_BT_B5G) { /* BT ch is 8MHz-spacing for 5/6GHz */ + *ch_lo_bound = *ch_lo_bound / 8; /* low-bound -> Round-down */ + + if (*ch_up_bound % 8 != 0) /* Up-bound -> cover wider range */ + *ch_up_bound = *ch_up_bound / 8 + 1; + else + *ch_up_bound = *ch_up_bound / 8; + } + + return out_of_range; +} + +static void _set_wl_ch_info(struct rtw89_dev *rtwdev) +{ + static const u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 vco_freq, ch_lo = 0, ch_up = 0; + u16 rf_band, freq, f_diff; + u8 i, j, ch; + + if (!(rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA)) /* only new-PTA require setup */ + return; + + for (i = RTW89_PHY_0; i <= RTW89_PHY_1; i++) { + if (wl->mlo_info.wmode[i] == RTW89_MR_WMODE_NONE) { /* no-connect */ + dm->ost_info.rf_center_freq[i] = 0; + dm->ost_info.fbd_group_en[i][0] = 0; + dm->ost_info.fbd_group_en[i][1] = 0; + dm->ost_info.fbd_group_bound[i][0] = 0; + dm->ost_info.fbd_group_bound[i][1] = 0; + continue; + } + + ch = wl->rf_ch_info[i].center_ch; + switch (wl->rf_ch_info[i].band) { + case RTW89_BAND_2G: + default: + rf_band = 0; + if (ch == 14) + freq = BTC_FREQ_W2G_CH14; + else + freq = BTC_FREQ_W2G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_2G_NMR / BTC_LO_2G_DNM; + break; + case RTW89_BAND_5G: + rf_band = BIT(15); + freq = BTC_FREQ_W5G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_5G_NMR / BTC_LO_5G_DNM; + break; + case RTW89_BAND_6G: + rf_band = BIT(15); + freq = BTC_FREQ_W6G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_6G_NMR / BTC_LO_6G_DNM; + break; + } + + dm->ost_info.rf_center_freq[i] = rf_band + freq; + + for (j = BTC_BT_B2G; j <= BTC_BT_B5G; j++) { + /* no WL->BT forbidden channel */ + if (_get_w2b_fbd_ch(rtwdev, j, vco_freq, &ch_lo, &ch_up)) { + dm->ost_info.fbd_group_en[i][j] = j << 1; + dm->ost_info.fbd_group_bound[i][j] = 0; + continue; + } + + dm->ost_info.fbd_group_en[i][j] = (j << 1) + 0x1; + dm->ost_info.fbd_group_bound[i][j] = (ch_up << 8) + ch_lo; + } + } + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) { + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) { + /* + * If WL & BT share same ANT/Path, set freq-diff > 2GHz, + * because TDD will always run in 5/6GHz band. + * Otherwise, the frq-diff thres is decided by the RF + * out-of-channel rejection capability. + */ + if (dm->ant_xmap[i][j]) + f_diff = 0x7ff; /* 2047MHz */ + else + f_diff = rtwdev->chip->fdd_iso_freq; + + dm->ost_info.freq_diff_thres[path_hwb[i]][j] = f_diff; + } } } static void _set_bt_afh_info(struct rtw89_dev *rtwdev) { - if (rtwdev->chip->chip_id == RTL8922A) - _set_bt_afh_info_v1(rtwdev); - else - _set_bt_afh_info_v0(rtwdev); + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + struct rtw89_btc_wl_rlink *rlink; + u8 h2c_func, hwb, bt_link_chg; + u8 i, j, sz, bw; + u8 buf[4]; + + if (btc->manual_ctrl || wl->status.map.rf_off || + (wl->status.val & btc_scanning_map.val)) + return; + + memset(wl->afh_info, 0, sizeof(wl->afh_info)); + + for (j = RTW89_MAC_0; j < RTW89_MAC_NUM; j++) { + /* skip HW-Band if no fdd/fdd bind */ + if ((!(btc->dm.tdd_bind.wl_hwb_sel & BIT(j))) && + (!(btc->dm.fdd_bind.wl_hwb_sel & BIT(j)))) + continue; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rlink = &wl_rinfo->rlink[i][j]; + + /* Skip no-active case */ + if (!rlink->connected || !rlink->active || + rlink->rf_band >= RTW89_BAND_NUM) + continue; + + /* In addition to AP/P2P, skip if same-rf-band*/ + if (wl->afh_info[j][rlink->rf_band].en && + (rlink->role != RTW89_WIFI_ROLE_AP && + rlink->role != RTW89_WIFI_ROLE_P2P_GO && + rlink->role != RTW89_WIFI_ROLE_P2P_CLIENT)) + continue; + + bw = _get_wl_guard_bw(rtwdev, rlink->bw); + wl->afh_info[j][rlink->rf_band].en = 1; + wl->afh_info[j][rlink->rf_band].ch = rlink->ch; + wl->afh_info[j][rlink->rf_band].bw = bw; + wl->afh_info[j][rlink->rf_band].band = rlink->rf_band; + } + } + + for (j = RTW89_BAND_2G; j < RTW89_BAND_NUM; j++) { + /* skip non-2.4GHz if BT is 2.4GHz only*/ + if (!(rtwdev->chip->para_ver & BTC_FEAT_BT_6G)) { + if (j != RTW89_BAND_2G) + continue; + sz = 3; + } else { + sz = 4; + } + + if (wl->afh_info[RTW89_MAC_0][j].en && + wl->afh_info[RTW89_MAC_1][j].en) { + /* select wilder bw if 1+1 */ + if (wl->afh_info[RTW89_MAC_1][j].bw > + wl->afh_info[RTW89_MAC_0][j].bw) + hwb = RTW89_MAC_1; + else + hwb = RTW89_MAC_0; + } else if (wl->afh_info[RTW89_MAC_1][j].en) { + hwb = RTW89_MAC_1; + } else { + hwb = RTW89_MAC_0; + } + + if (j == RTW89_BAND_2G) { + if (cx->bt0.link_info.link_cnt.chg || + cx->bt1.link_info.link_cnt.chg) + bt_link_chg = 1; + else + bt_link_chg = 0; + } else { + if (cx->bt0.link_info_56g.link_cnt.chg || + cx->bt1.link_info_56g.link_cnt.chg) + bt_link_chg = 1; + else + bt_link_chg = 0; + } + + /* return if no-afh-info-change/no wl-bt link change */ + if (!memcmp(&wl->afh_info_last[hwb][j], + &wl->afh_info[hwb][j], + sizeof(struct rtw89_btc_wl_afh_info)) && + !bt_link_chg && + !wl->link_mode_chg) + continue; + + memcpy(&wl->afh_info_last[hwb][j], + &wl->afh_info[hwb][j], + sizeof(struct rtw89_btc_wl_afh_info)); + + buf[0] = wl->afh_info[hwb][j].en; + buf[1] = wl->afh_info[hwb][j].ch; + buf[2] = wl->afh_info[hwb][j].bw; + buf[3] = wl->afh_info[hwb][j].band; + + h2c_func = SET_BT_WL_CH_INFO; + if (cx->bt0.enable.now) + _send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, sz); + + /* send mailbox to 2nd-BT if exist */ + if (cx->bt1.enable.now) { + h2c_func |= BT_H2C_FUNC_BT2ND; + _send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, sz); + } + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): en=%d, ch=%d, bw=%d, rf_band=%d\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + + wl->wcnt[BTC_WCNT_CH_UPDATE]++; + + /* ToDo: Remove if BT update new-define to support 6GHz */ + if (j == RTW89_BAND_2G) + break; + } + + /* for AFH map display in coex log */ + _set_wl_ch_map(rtwdev); + + /* set HWB center-ch/forbidden group to ost_info for CR setup in FW */ + _set_wl_ch_info(rtwdev); } static bool _check_freerun(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hid_desc *hid = &bt_linfo->hid_desc; - union rtw89_btc_module_info *md = &btc->mdinfo; - const struct rtw89_btc_ver *ver = btc->ver; - u8 isolation, connect_cnt = 0; - - if (ver->fcxinit == 7) - isolation = md->md_v7.ant.isolation; - else - isolation = md->md.ant.isolation; - - if (ver->fwlrole == 0) - connect_cnt = wl_rinfo->connect_cnt; - else if (ver->fwlrole == 1) - connect_cnt = wl_rinfo_v1->connect_cnt; - else if (ver->fwlrole == 2) - connect_cnt = wl_rinfo_v2->connect_cnt; - else if (ver->fwlrole == 7) - connect_cnt = wl_rinfo_v7->connect_cnt; - else if (ver->fwlrole == 8) - connect_cnt = wl_rinfo_v8->connect_cnt; + struct rtw89_btc_module *md = &btc->mdinfo; if (btc->ant_type == BTC_ANT_SHARED) { btc->dm.trx_para_level = 0; @@ -3557,12 +4592,12 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) } /* The below is dedicated antenna case */ - if (connect_cnt > BTC_TDMA_WLROLE_MAX) { + if (wl_rinfo->connect_cnt > BTC_TDMA_WLROLE_MAX) { btc->dm.trx_para_level = 5; return true; } - if (bt_linfo->profile_cnt.now == 0) { + if (bt_linfo->link_cnt.now == 0) { btc->dm.trx_para_level = 5; return true; } @@ -3573,7 +4608,7 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) } /* TODO get isolation by BT psd */ - if (isolation >= BTC_FREERUN_ANTISO_MIN) { + if (md->ant.isolation >= BTC_FREERUN_ANTISO_MIN) { btc->dm.trx_para_level = 5; return true; } @@ -3607,7 +4642,8 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) #define _tdma_set_flctrl(btc, flc) ({(btc)->dm.tdma.rxflctrl = flc; }) #define _tdma_set_flctrl_role(btc, role) ({(btc)->dm.tdma.rxflctrl_role = role; }) -#define _tdma_set_tog(btc, wtg) ({(btc)->dm.tdma.wtgle_n = wtg; }) +#define _tdma_set_rxflctrl(btc, rxflc) ({(btc)->dm.tdma.rxflctrl = rxflc; }) +#define _tdma_set_txflctrl(btc, txflc) ({(btc)->dm.tdma.txflctrl = txflc; }) #define _tdma_set_lek(btc, lek) ({(btc)->dm.tdma.leak_n = lek; }) struct btc_btinfo_lb2 { @@ -3678,15 +4714,58 @@ static void _set_policy(struct rtw89_dev *rtwdev, u16 policy_type, _fw_set_policy(rtwdev, policy_type, action); } +static void _set_tdma_bind(struct rtw89_dev *rtwdev, bool tdma_on) +{ + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_bind_info *bind; + u8 null_role = RTW89_WIFI_ROLE_STATION; + u8 bt_sel; + + if (dm->tdd_en) + bind = &dm->tdd_bind; /* tdd = 1 && fdd = 0 or 1 */ + else + bind = &dm->fdd_bind; /* tdd = 0 && fdd = 1 */ + + if (bind->bt_sel >= (BIT(BTC_BT_1ST) | BIT(BTC_BT_2ND))) + bt_sel = BTC_ALL_BT; + else if (bind->bt_sel == BIT(BTC_BT_2ND)) + bt_sel = BTC_BT_2ND; + else + bt_sel = BTC_BT_1ST; + + /* notify BT TDMA on/off by scoreboard for ACL/Scan schedule */ + _write_scbd(rtwdev, bt_sel, BTC_WSCB_TDMA, tdma_on); + + /* + * set hwb/bt bind to TDMA policy parameter + * FW-BTC will setup related hwbx vs. BTx coex tables by slot toggle + * ex: HWb0 + BT0 + BT1, HWB0-BT0/HWB0-BT1 coex by WL/BT slot toggle + */ + dm->tdma.bind = ((bind->bt_sel & 0xf) << 4) + (bind->wl_hwb_sel & 0x3); + if (dm->tdd_en) + dm->tdma.bind |= BIT(2); + if (dm->fdd_en) + dm->tdma.bind |= BIT(3); + if (rtwdev->btc.ver->fcxtdma != 8) + dm->tdma.bind |= 0; + + /* set Null-tx role for 2 HW-BAND TDMA (MLMR) */ + if (!dm->eslot_ctrl.en && dm->tdma.rxflctrl && + (bind->wl_hwb_sel == (BIT(RTW89_PHY_1) | BIT(RTW89_PHY_0)))) { + null_role = (null_role << 4) + null_role; + _tdma_set_flctrl_role(&rtwdev->btc, null_role); + } +} + #define BTC_B1_MAX 250 /* unit ms */ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_tdma *t = &dm->tdma; - struct rtw89_btc_fbtc_slot *s = dm->slot.v1; u8 type; u32 tbl_w1, tbl_b1, tbl_b4; + bool tdma_on = false; if (btc->ant_type == BTC_ANT_SHARED) { if (btc->cx.wl.status.map._4way) @@ -3707,14 +4786,16 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) switch (type) { case BTC_CXP_USERDEF0: *t = t_def[CXTD_OFF]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); btc->update_policy_force = true; break; case BTC_CXP_OFF: /* TDMA off */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); switch (policy_type) { case BTC_CXP_OFF_BT: @@ -3747,36 +4828,58 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_OFFB: /* TDMA off + beacon protect */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF_B2]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); + _slot_set(btc, CXST_B2, 10, cxtbl[1], SLOT_ISO); + switch (policy_type) { case BTC_CXP_OFFB_BWB0: + _slot_set_tbl(btc, CXST_OFF, cxtbl[5]); + break; + case BTC_CXP_OFFB_BWB1: _slot_set_tbl(btc, CXST_OFF, cxtbl[8]); break; + case BTC_CXP_OFFB_BWB2: + _slot_set_tbl(btc, CXST_OFF, cxtbl[7]); + break; + case BTC_CXP_OFFB_BWB3: + _slot_set_tbl(btc, CXST_OFF, cxtbl[6]); + break; + case BTC_CXP_OFFB_BWB4: + _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); + break; } break; case BTC_CXP_OFFE: /* TDMA off + beacon protect + Ext_control */ btc->bt_req_en = true; - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_OFF_EXT]; switch (policy_type) { case BTC_CXP_OFFE_DEF: - s[CXST_E2G] = s_def[CXST_E2G]; - s[CXST_E5G] = s_def[CXST_E5G]; - s[CXST_EBT] = s_def[CXST_EBT]; - s[CXST_ENULL] = s_def[CXST_ENULL]; + _slot_set_le(btc, CXST_E2G, s_def[CXST_E2G].dur, + s_def[CXST_E2G].cxtbl, s_def[CXST_E2G].cxtype); + _slot_set_le(btc, CXST_E5G, s_def[CXST_E5G].dur, + s_def[CXST_E5G].cxtbl, s_def[CXST_E5G].cxtype); + _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, + s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); + _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, + s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); break; case BTC_CXP_OFFE_DEF2: _slot_set(btc, CXST_E2G, 20, cxtbl[1], SLOT_ISO); - s[CXST_E5G] = s_def[CXST_E5G]; - s[CXST_EBT] = s_def[CXST_EBT]; - s[CXST_ENULL] = s_def[CXST_ENULL]; + _slot_set_le(btc, CXST_E5G, s_def[CXST_E5G].dur, + s_def[CXST_E5G].cxtbl, s_def[CXST_E5G].cxtype); + _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, + s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); + _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, + s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); break; } break; case BTC_CXP_FIX: /* TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_FIX]; switch (policy_type) { case BTC_CXP_FIX_TD3030: @@ -3832,9 +4935,9 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PFIX: /* PS-TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PFIX]; - if (btc->cx.wl.role_info.role_map.role.ap) + if (btc->cx.wl.role_info.role_map & BIT(RTW89_WIFI_ROLE_AP)) _tdma_set_flctrl(btc, CXFLC_QOSNULL); switch (policy_type) { @@ -3865,7 +4968,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO: /* TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO]; switch (policy_type) { case BTC_CXP_AUTO_TD50B1: @@ -3889,7 +4992,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO: /* PS-TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO]; switch (policy_type) { case BTC_CXP_PAUTO_TD50B1: @@ -3913,7 +5016,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO2: /* TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO2]; switch (policy_type) { case BTC_CXP_AUTO2_TD3050: @@ -3950,7 +5053,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO2: /* PS-TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO2]; switch (policy_type) { case BTC_CXP_PAUTO2_TD3050: @@ -3987,6 +5090,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; } + _set_tdma_bind(rtwdev, tdma_on); } EXPORT_SYMBOL(rtw89_btc_set_policy); @@ -3995,14 +5099,14 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_tdma *t = &dm->tdma; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &btc->cx.wl.role_info_v1; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; - struct rtw89_btc_bt_hid_desc *hid = &btc->cx.bt.link_info.hid_desc; - struct rtw89_btc_bt_hfp_desc *hfp = &btc->cx.bt.link_info.hfp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; + struct rtw89_btc_bt_hid_desc *hid = &btc->cx.bt0.link_info.hid_desc; + struct rtw89_btc_bt_hfp_desc *hfp = &btc->cx.bt0.link_info.hfp_desc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u8 type, null_role; u32 tbl_w1, tbl_b1, tbl_b4; - u16 dur_2; + bool tdma_on = false; + u16 dur_1 = 0, dur_2; if (wl->status.map.lps) { _slot_set_le(btc, CXST_E2G, s_def[CXST_E2G].dur, @@ -4056,7 +5160,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); break; case BTC_CXP_OFF: /* TDMA off */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF]; _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); @@ -4108,38 +5212,60 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_OFFB: /* TDMA off + beacon protect */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF_B2]; _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); + _slot_set(btc, CXST_B2, 10, cxtbl[1], SLOT_ISO); switch (policy_type) { case BTC_CXP_OFFB_BWB0: + _slot_set_tbl(btc, CXST_OFF, cxtbl[5]); + break; + case BTC_CXP_OFFB_BWB1: _slot_set_tbl(btc, CXST_OFF, cxtbl[8]); break; - default: + case BTC_CXP_OFFB_BWB2: + _slot_set_tbl(btc, CXST_OFF, cxtbl[7]); + break; + case BTC_CXP_OFFB_BWB3: + _slot_set_tbl(btc, CXST_OFF, cxtbl[6]); + break; + case BTC_CXP_OFFB_BWB4: + _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); break; } break; case BTC_CXP_OFFE: /* TDMA off + beacon protect + Ext_control */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_OFF_EXT]; /* To avoid wl-s0 tx break by hid/hfp tx */ if (hid->exist || hfp->exist) tbl_w1 = cxtbl[16]; + if (dm->eslot_ctrl.en) { + null_role = u8_encode_bits(dm->eslot_ctrl.nulltx_role1, 0x0f) | + u8_encode_bits(dm->eslot_ctrl.nulltx_role2, 0xf0); + _tdma_set_flctrl_role(btc, null_role); + _tdma_set_rxflctrl(btc, 1); + _tdma_set_txflctrl(btc, 1); + /* Set Null-Tx time-tick by E2G duration */ + dm->eslot_ctrl.nulltx_pre_time = 5; /* TODO: BIS COEX */ + dur_1 = dm->eslot_ctrl.nulltx_pre_time; + } + dur_2 = dm->e2g_slot_limit; switch (policy_type) { case BTC_CXP_OFFE_2GBWISOB: /* for normal-case */ - _slot_set(btc, CXST_E2G, 5, tbl_w1, SLOT_ISO); + _slot_set(btc, CXST_E2G, dur_1, tbl_w1, SLOT_ISO); _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_2GISOB: /* for bt no-link */ - _slot_set(btc, CXST_E2G, 5, cxtbl[1], SLOT_ISO); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[1], SLOT_ISO); _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); @@ -4167,16 +5293,16 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) break; case BTC_CXP_OFFE_2GBWMIXB: if (a2dp->exist) - _slot_set(btc, CXST_E2G, 5, cxtbl[2], SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[2], SLOT_MIX); else - _slot_set(btc, CXST_E2G, 5, tbl_w1, SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, tbl_w1, SLOT_MIX); _slot_set_le(btc, CXST_EBT, cpu_to_le16(40), s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_WL: /* for 4-way */ - _slot_set(btc, CXST_E2G, 5, cxtbl[1], SLOT_MIX); - _slot_set(btc, CXST_EBT, 5, cxtbl[1], SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[1], SLOT_MIX); + _slot_set(btc, CXST_EBT, dur_1, cxtbl[1], SLOT_MIX); break; default: break; @@ -4187,7 +5313,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); break; case BTC_CXP_FIX: /* TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_FIX]; switch (policy_type) { @@ -4246,7 +5372,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PFIX: /* PS-TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PFIX]; switch (policy_type) { @@ -4285,7 +5411,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO: /* TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO]; switch (policy_type) { @@ -4312,7 +5438,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO: /* PS-TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO]; switch (policy_type) { @@ -4339,7 +5465,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO2: /* TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO2]; switch (policy_type) { @@ -4381,7 +5507,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO2: /* PS-TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO2]; switch (policy_type) { @@ -4424,20 +5550,22 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) break; } - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC && dm->tdma.rxflctrl) { - null_role = FIELD_PREP(0x0f, dm->wl_scc.null_role1) | - FIELD_PREP(0xf0, dm->wl_scc.null_role2); - _tdma_set_flctrl_role(btc, null_role); - } - /* enter leak_slot after each null-1 */ if (dm->leak_ap && dm->tdma.leak_n > 1) _tdma_set_lek(btc, 1); - if (dm->tdma_instant_excute) { + if (dm->tdma_instant_excute || + dm->error.map.tdma_no_sync || + dm->error.map.slot_no_sync || + dm->lps_ctrl_change) { btc->dm.tdma.option_ctrl |= BIT(0); btc->update_policy_force = true; } + + if (btc->cli_h2c_cmd) + btc->update_policy_force = true; + + _set_tdma_bind(rtwdev, tdma_on); } EXPORT_SYMBOL(rtw89_btc_set_policy_v1); @@ -4472,154 +5600,25 @@ static void _set_bt_plut(struct rtw89_dev *rtwdev, u8 phy_map, } } -static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) +static void _set_ant(struct rtw89_dev *rtwdev, bool force_exec, + u8 phy_map, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &cx->bt; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 gnt_wl_ctrl, gnt_bt_ctrl, plt_ctrl, i, b2g = 0; - bool dbcc_chg = false; - u32 ant_path_type; - - ant_path_type = ((phy_map << 8) + type); - - if (btc->ver->fwlrole == 1) - dbcc_chg = wl->role_info_v1.dbcc_chg; - else if (btc->ver->fwlrole == 2) - dbcc_chg = wl->role_info_v2.dbcc_chg; - else if (btc->ver->fwlrole == 7) - dbcc_chg = wl->role_info_v7.dbcc_chg; - else if (btc->ver->fwlrole == 8) - dbcc_chg = wl->role_info_v8.dbcc_chg; - - if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || - btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || - btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || dbcc_chg) - force_exec = FC_EXEC; - - if (!force_exec && ant_path_type == dm->set_ant_path) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by no change!!\n", - __func__); - return; - } else if (bt->rfk_info.map.run) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by bt rfk!!\n", __func__); - return; - } else if (btc->dm.run_reason != BTC_RSN_NTFY_WL_RFK && - wl->rfk_info.state != BTC_WRFK_STOP) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by wl rfk!!\n", __func__); - return; - } - - dm->set_ant_path = ant_path_type; - - rtw89_debug(rtwdev, - RTW89_DBG_BTC, - "[BTC], %s(): path=0x%x, set_type=0x%x\n", - __func__, phy_map, dm->set_ant_path & 0xff); - - switch (type) { - case BTC_ANT_WPOWERON: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - break; - case BTC_ANT_WINIT: - if (bt->enable.now) - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI); - else - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_BT, BTC_PLT_BT); - break; - case BTC_ANT_WONLY: - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_WOFF: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_W2G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - if (rtwdev->dbcc_en) { - for (i = 0; i < RTW89_PHY_NUM; i++) { - b2g = (wl_dinfo->real_band[i] == RTW89_BAND_2G); - - gnt_wl_ctrl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; - gnt_bt_ctrl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; - /* BT should control by GNT_BT if WL_2G at S0 */ - if (i == 1 && - wl_dinfo->real_band[0] == RTW89_BAND_2G && - wl_dinfo->real_band[1] == RTW89_BAND_5G) - gnt_bt_ctrl = BTC_GNT_HW; - _set_gnt(rtwdev, BIT(i), gnt_wl_ctrl, gnt_bt_ctrl); - plt_ctrl = b2g ? BTC_PLT_BT : BTC_PLT_NONE; - _set_bt_plut(rtwdev, BIT(i), - plt_ctrl, plt_ctrl); - } - } else { - _set_gnt(rtwdev, phy_map, BTC_GNT_HW, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, - BTC_PLT_BT, BTC_PLT_BT); - } - break; - case BTC_ANT_W5G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_W25G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_HW, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, - BTC_PLT_GNT_WL, BTC_PLT_GNT_WL); - break; - case BTC_ANT_FREERUN: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_HI); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_WRFK: - case BTC_ANT_WRFK2: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_BRFK: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI); - _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); - break; - default: - break; - } -} - -static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; u32 ant_path_type = rtw89_get_antpath_type(phy_map, type); - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; struct rtw89_btc_dm *dm = &btc->dm; - u8 gwl = BTC_GNT_HW; + u8 gwl = BTC_GNT_HW, gwl0, gwl1, gbt; + bool cx_ctrl; - if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || - btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || - btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || wl_rinfo->dbcc_chg) + if (btc->cli_h2c_cmd || wl_rinfo->dbcc_chg || + dm->run_reason == BTC_RSN_NTFY_POWEROFF || + dm->run_reason == BTC_RSN_NTFY_RADIO_STATE || + dm->run_reason == BTC_RSN_CMD_SET_COEX) force_exec = FC_EXEC; - if (wl_rinfo->link_mode != BTC_WLINK_25G_MCC && + if (wl_rinfo->link_mode != BTC_WLINK_DB_MCC && btc->dm.wl_btg_rx == 2) force_exec = FC_EXEC; @@ -4628,10 +5627,6 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, "[BTC], %s(): return by no change!!\n", __func__); return; - } else if (bt->rfk_info.map.run) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by bt rfk!!\n", __func__); - return; } else if (btc->dm.run_reason != BTC_RSN_NTFY_WL_RFK && wl->rfk_info.state != BTC_WRFK_STOP) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -4646,73 +5641,77 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, __func__, phy_map, dm->set_ant_path & 0xff); switch (type) { + case BTC_ANT_WPOWERON: + cx_ctrl = BTC_CTRL_BY_BT; + break; case BTC_ANT_WINIT: + cx_ctrl = BTC_CTRL_BY_WL; /* To avoid BT MP driver case (bt_enable but no mailbox) */ - if (bt->enable.now && bt->run_patch_code) - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); + if ((cx->bt0.enable.now && cx->bt0.run_patch_code) || + (cx->bt1.enable.now && cx->bt1.run_patch_code)) + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_LO, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); else - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); break; case BTC_ANT_WONLY: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); break; case BTC_ANT_WOFF: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); + cx_ctrl = BTC_CTRL_BY_BT; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_LO, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); break; - case BTC_ANT_W2G: - case BTC_ANT_W25G: - if (wl_rinfo->dbcc_en) { - if (wl_dinfo->real_band[RTW89_PHY_0] == RTW89_BAND_2G) - gwl = BTC_GNT_HW; - else - gwl = BTC_GNT_SW_HI; - _set_gnt_v1(rtwdev, BTC_PHY_0, gwl, BTC_GNT_HW, BTC_WLACT_HW); - - if (wl_dinfo->real_band[RTW89_PHY_1] == RTW89_BAND_2G) - gwl = BTC_GNT_HW; - else - gwl = BTC_GNT_SW_HI; - _set_gnt_v1(rtwdev, BTC_PHY_1, gwl, BTC_GNT_HW, BTC_WLACT_HW); - } else { + case BTC_ANT_PTA: + default: + cx_ctrl = BTC_CTRL_BY_WL; + gbt = BTC_GNT_HW; + if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || + !wl_rinfo->dbcc_en) { gwl = BTC_GNT_HW; - _set_gnt_v1(rtwdev, phy_map, gwl, BTC_GNT_HW, BTC_WLACT_HW); + _set_gnt(rtwdev, BTC_PHY_ALL, gwl, gbt, BTC_WLACT_HW); + } else { + /* for DBCC Only-1-PTA */ + if (wl_rinfo->dbcc_2g_phy == RTW89_PHY_0) { + gwl0 = BTC_GNT_HW; + gwl1 = BTC_GNT_SW_HI; + } else { + gwl0 = BTC_GNT_SW_HI; + gwl1 = BTC_GNT_HW; + } + _set_gnt(rtwdev, BTC_PHY_0, gwl0, gbt, BTC_WLACT_HW); + _set_gnt(rtwdev, BTC_PHY_1, gwl1, gbt, BTC_WLACT_HW); } break; - case BTC_ANT_W5G: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_HW, BTC_WLACT_HW); + case BTC_ANT_FDD: + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_HW, + BTC_WLACT_HW); break; case BTC_ANT_FREERUN: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); break; case BTC_ANT_WRFK: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_HW); + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_HW); break; case BTC_ANT_WRFK2: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); /* no BT-Tx */ + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); /* no BT-Tx */ break; - default: - return; } + rtw89_chip_cfg_ctrl_path(rtwdev, cx_ctrl); _set_bt_plut(rtwdev, phy_map, BTC_PLT_GNT_WL, BTC_PLT_GNT_WL); } -static void _set_ant(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) -{ - if (rtwdev->chip->chip_id == RTL8922A) - _set_ant_v1(rtwdev, force_exec, phy_map, type); - else - _set_ant_v0(rtwdev, force_exec, phy_map, type); -} - static void _action_wl_only(struct rtw89_dev *rtwdev) { _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_WONLY); @@ -4730,29 +5729,24 @@ static void _action_wl_init(struct rtw89_dev *rtwdev) static void _action_wl_off(struct rtw89_dev *rtwdev, u8 mode) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): !!\n", __func__); - if (wl->status.map.rf_off || btc->dm.bt_only) { + if (wl->status.map.rf_off || btc->dm.bt_only) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_WOFF); - } else if (wl->status.map.lps == BTC_LPS_RF_ON) { - if (mode == BTC_WLINK_5G) - _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_W5G); - else - _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - } + else if (wl->status.map.lps == BTC_LPS_RF_ON || + cx->bt0.whql_test || cx->bt1.whql_test) + _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); - if (mode == BTC_WLINK_5G) { + if (dm->out_of_band) _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_OFF); - } else if (wl->status.map.lps == BTC_LPS_RF_ON) { - if (btc->cx.bt.link_info.a2dp_desc.active) - _set_policy(rtwdev, BTC_CXP_OFF_BT, BTC_ACT_WL_OFF); - else - _set_policy(rtwdev, BTC_CXP_OFF_BWB1, BTC_ACT_WL_OFF); - } else { + else if (cx->bt0.whql_test || cx->bt1.whql_test) + _set_policy(rtwdev, BTC_CXP_OFFB_BWB4, BTC_ACT_WL_OFF); + else _set_policy(rtwdev, BTC_CXP_OFF_BT, BTC_ACT_WL_OFF); - } } static void _action_freerun(struct rtw89_dev *rtwdev) @@ -4786,7 +5780,7 @@ static void _action_bt_off(struct rtw89_dev *rtwdev) static void _action_bt_idle(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *b = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *b = &btc->cx.bt0.link_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); @@ -4834,7 +5828,7 @@ static void _action_bt_hfp(struct rtw89_dev *rtwdev) if (btc->cx.wl.status.map._4way) { _set_policy(rtwdev, BTC_CXP_OFF_WL, BTC_ACT_BT_HFP); } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { - btc->cx.bt.scan_rx_low_pri = true; + btc->cx.bt0.scan_rx_low_pri = true; _set_policy(rtwdev, BTC_CXP_OFF_BWB2, BTC_ACT_BT_HFP); } else { _set_policy(rtwdev, BTC_CXP_OFF_BWB1, BTC_ACT_BT_HFP); @@ -4854,7 +5848,7 @@ static void _action_bt_hid(struct rtw89_dev *rtwdev) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_hid_desc *hid = &bt->link_info.hid_desc; u16 policy_type = BTC_CXP_OFF_BT; @@ -4864,7 +5858,7 @@ static void _action_bt_hid(struct rtw89_dev *rtwdev) if (wl->status.map._4way) { policy_type = BTC_CXP_OFF_WL; } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { - btc->cx.bt.scan_rx_low_pri = true; + btc->cx.bt0.scan_rx_low_pri = true; if (hid->type & BTC_HID_BLE) policy_type = BTC_CXP_OFF_BWB0; else @@ -4956,7 +5950,7 @@ static void _action_bt_a2dpsink(struct rtw89_dev *rtwdev) static void _action_bt_pan(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; struct rtw89_btc_bt_pan_desc pan = bt_linfo->pan_desc; @@ -5078,6 +6072,43 @@ static void _action_bt_pan_hid(struct rtw89_dev *rtwdev) } } +static void _action_bt_bis(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + u16 policy_type; + + if (dm->bis_tdma) + policy_type = BTC_CXP_OFFE_2GBWISOB; + else + policy_type = BTC_CXP_OFF_EQ0; + + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); + _set_policy(rtwdev, policy_type, BTC_ACT_BT_BIS); +} + +static void _action_bt_cis(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + u16 policy_type; + + if (bt_linfo->a2dp_desc.active) { + dm->slot_dur[CXST_W1] = 80; + dm->slot_dur[CXST_B1] = 20; + policy_type = BTC_CXP_PFIX_TDW1B1; + } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { + policy_type = BTC_CXP_OFF_BWB0; + } else { + policy_type = BTC_CXP_OFF_BWB1; + } + + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); + _set_policy(rtwdev, policy_type, BTC_ACT_BT_CIS); +} + static void _action_bt_a2dp_pan_hid(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; @@ -5111,7 +6142,7 @@ static void _action_bt_a2dp_pan_hid(struct rtw89_dev *rtwdev) static void _action_wl_5g(struct rtw89_dev *rtwdev) { - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W5G); + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_FDD); _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_5G); } @@ -5159,48 +6190,30 @@ static void _action_wl_rfk(struct rtw89_dev *rtwdev) static void _set_btg_ctrl(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_fbtc_outsrc_set_info *o_info = &btc->dm.ost_info; - struct rtw89_btc_wl_role_info *wl_rinfo_v0 = &wl->role_info; - const struct rtw89_chip_info *chip = rtwdev->chip; - const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; struct rtw89_btc_dm *dm = &btc->dm; - struct _wl_rinfo_now wl_rinfo; + struct rtw89_btc_fbtc_outsrc_set_info *o_info = &dm->ost_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u32 is_btg = BTC_BTGCTRL_DISABLE; if (btc->manual_ctrl) return; - if (ver->fwlrole == 0) - wl_rinfo.link_mode = wl_rinfo_v0->link_mode; - else if (ver->fwlrole == 1) - wl_rinfo.link_mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - wl_rinfo.link_mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - wl_rinfo.link_mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - wl_rinfo.link_mode = wl_rinfo_v8->link_mode; - else - return; - /* notify halbb ignore GNT_BT or not for WL BB Rx-AGC control */ if (btc->ant_type == BTC_ANT_SHARED) { if (!(bt->run_patch_code && bt->enable.now)) is_btg = BTC_BTGCTRL_DISABLE; - else if (wl_rinfo.link_mode != BTC_WLINK_5G) + else if (wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G) || + wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G)) is_btg = BTC_BTGCTRL_ENABLE; else is_btg = BTC_BTGCTRL_DISABLE; /* bb call ctrl_btg() in WL FW by slot */ - if (!ver->fcxosi && - wl_rinfo.link_mode == BTC_WLINK_25G_MCC) + if (!btc->ver->fcxosi && + wl_rinfo->link_mode == BTC_WLINK_DB_MCC) is_btg = BTC_BTGCTRL_BB_GNT_FWCTRL; } @@ -5210,7 +6223,7 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) dm->wl_btg_rx = is_btg; /* skip setup if btg_ctrl set by wl fw */ - if (!ver->fcxosi && is_btg > BTC_BTGCTRL_ENABLE) + if (!btc->ver->fcxosi && is_btg > BTC_BTGCTRL_ENABLE) return; /* Below flow is for BTC_FEAT_NEW_BBAPI_FLOW = 1 */ @@ -5229,7 +6242,7 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) o_info->btg_rx[BTC_RF_S1] = is_btg; } - if (ver->fcxosi) + if (btc->ver->fcxosi) return; chip->ops->ctrl_btg_bt_rx(rtwdev, o_info->btg_rx[BTC_RF_S0], @@ -5245,50 +6258,30 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_fbtc_outsrc_set_info *o_info = &btc->dm.ost_info; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; - u8 is_preagc, val, link_mode, dbcc_2g_phy; - u8 role_ver = rtwdev->btc.ver->fwlrole; - bool dbcc_en; + u8 is_preagc, val; if (btc->manual_ctrl) return; - if (role_ver == 2) { - dbcc_en = rinfo_v2->dbcc_en; - link_mode = rinfo_v2->link_mode; - dbcc_2g_phy = rinfo_v2->dbcc_2g_phy; - } else if (role_ver == 7) { - dbcc_en = rinfo_v7->dbcc_en; - link_mode = rinfo_v7->link_mode; - dbcc_2g_phy = rinfo_v7->dbcc_2g_phy; - } else if (role_ver == 8) { - dbcc_en = rinfo_v8->dbcc_en; - link_mode = rinfo_v8->link_mode; - dbcc_2g_phy = rinfo_v7->dbcc_2g_phy; - } else { - return; - } - if (!(bt->run_patch_code && bt->enable.now)) { is_preagc = BTC_PREAGC_DISABLE; - } else if (link_mode == BTC_WLINK_5G) { + } else if (dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G)) { is_preagc = BTC_PREAGC_DISABLE; - } else if (link_mode == BTC_WLINK_NOLINK || - btc->cx.bt.link_info.profile_cnt.now == 0) { + } else if (rinfo->link_mode == BTC_WLINK_NOLINK || + btc->cx.bt0.link_info.link_cnt.now == 0) { is_preagc = BTC_PREAGC_DISABLE; } else if (dm->tdma_now.type != CXTDMA_OFF && !bt_linfo->hfp_desc.exist && !bt_linfo->hid_desc.exist && dm->fddt_train == BTC_FDDT_DISABLE) { is_preagc = BTC_PREAGC_DISABLE; - } else if (dbcc_en && (dbcc_2g_phy != RTW89_PHY_1)) { + } else if (rinfo->dbcc_en && (rinfo->dbcc_2g_phy != RTW89_PHY_1)) { is_preagc = BTC_PREAGC_DISABLE; } else if (btc->ant_type == BTC_ANT_SHARED) { is_preagc = BTC_PREAGC_DISABLE; @@ -5296,7 +6289,7 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) is_preagc = BTC_PREAGC_ENABLE; } - if (!btc->ver->fcxosi && link_mode == BTC_WLINK_25G_MCC) + if (!btc->ver->fcxosi && rinfo->link_mode == BTC_WLINK_DB_MCC) is_preagc = BTC_PREAGC_BB_FWCTRL; if (dm->wl_pre_agc_rb != dm->wl_pre_agc && @@ -5367,10 +6360,7 @@ static void __rtw89_tx_time_iter(struct rtw89_vif_link *rtwvif_link, u16 enable = iter_data->enable; bool reenable = iter_data->reenable; - if (btc->ver->fwlrole == 8) - plink = &wl->rlink_info[port][0]; - else - plink = &wl->link_info[port]; + plink = &wl->rlink_info[port][rtwsta_link->link_id]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): port = %d\n", __func__, port); @@ -5427,48 +6417,26 @@ static void rtw89_tx_time_iter(void *data, struct ieee80211_sta *sta) static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; struct rtw89_txtime_data data = {.rtwdev = rtwdev}; - u8 mode, igno_bt, tx_retry; + bool reenable = false; + u8 tx_retry; u32 tx_time; u16 enable; - bool reenable = false; if (btc->manual_ctrl) return; - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; - - if (ver->fcxctrl == 7) - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - else - igno_bt = btc->ctrl.ctrl.igno_bt; - - if (btc->dm.freerun || igno_bt || b->profile_cnt.now == 0 || - mode == BTC_WLINK_5G || mode == BTC_WLINK_NOLINK) { + if (btc->dm.freerun || btc->ctrl.igno_bt || b->link_cnt.now == 0 || + dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G) || + wl_rinfo->link_mode == BTC_WLINK_NOLINK) { enable = 0; tx_time = BTC_MAX_TX_TIME_DEF; tx_retry = BTC_MAX_TX_RETRY_DEF; @@ -5508,48 +6476,91 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) &data); } +static void _set_phl_bt_slot_req(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_dm *dm = &btc->dm; + u8 len = 0; + u8 i; + + /* don't change bt slot req state during RFK for p2p/mcc case */ + if (dm->run_reason == BTC_RSN_NTFY_WL_RFK || + wl->status.map.transacting) + return; + + /* enable bt-slot req if ext-slot-control */ + if (dm->tdma_now.type == CXTDMA_OFF && + dm->tdma_now.ext_ctrl == CXECTL_EXT && + dm->tdd_bind.bt_sel != 0) { + if (btc->cx.wl.status.val & btc_scanning_map.val) + btc->bt_req_en = false; + else + btc->bt_req_en = true; + } else { + btc->bt_req_en = false; + } + + if (dm->slot_req_more) { + len = BTC_BSLOT_A2DP_HID; + } else { + if (dm->tdd_bind.bt_link_weight >= BTC_BSLOT_A2DP_HID) + len = BTC_BSLOT_A2DP_HID; + else if (dm->tdd_bind.bt_link_weight <= BTC_BSLOT_IDLE) + len = BTC_BSLOT_IDLE; + else + len = dm->tdd_bind.bt_link_weight; + } + + if (dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G)) + len = 100 - len; + + if (!btc->bt_req_en) + len = 0; + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + if (!(dm->tdd_bind.wl_hwb_sel & BIT(i))) + continue; + + if (len == btc->bt_req_len[i]) + continue; + + btc->bt_req_len[i] = len; + + rtw89_core_ntfy_btc_event(rtwdev, + RTW89_BTC_HMSG_SET_BT_REQ_SLOT, i); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): HWB%d bt_req_len = %d\n", + __func__, i, btc->bt_req_len[i]); + } +} + static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; bool bt_hi_lna_rx = false; - u8 mode; - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; - - if (mode != BTC_WLINK_NOLINK && btc->dm.wl_btg_rx) + if (wl_rinfo->link_mode != BTC_WLINK_NOLINK && btc->dm.wl_btg_rx) bt_hi_lna_rx = true; if (bt_hi_lna_rx == bt->hi_lna_rx) return; - _write_scbd(rtwdev, BTC_WSCB_BT_HILNA, bt_hi_lna_rx); + _write_scbd(rtwdev, BTC_BT_1ST, BTC_WSCB_BT_HILNA, bt_hi_lna_rx); } static void _set_bt_rx_scan_pri(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - _write_scbd(rtwdev, BTC_WSCB_RXSCAN_PRI, (bool)(!!bt->scan_rx_low_pri)); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_RXSCAN_PRI, + (bool)(!!bt->scan_rx_low_pri)); } static void _wl_req_mac(struct rtw89_dev *rtwdev, u8 mac) @@ -5580,54 +6591,118 @@ static void _wl_req_mac(struct rtw89_dev *rtwdev, u8 mac) rtw89_write32_set(rtwdev, add, B_AX_WL_SRC); } +static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + u32 zb_tbl0 = 0xda5a5a5a, zb_tbl1 = 0xda5a5a5a; + u8 link_mode_chg = btc->cx.wl.link_mode_chg; + u8 mode = btc->cx.wl.role_info.link_mode; + + if (btc->dm.run_reason != BTC_RSN_NTFY_INIT && !link_mode_chg) + return; + + if (!(btc->mdinfo.wa_type & BTC_WA_HFP_ZB)) + return; + + if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_5G) || + rtwdev->btc.dm.freerun) { + zb_tbl0 = 0xffffffff; + zb_tbl1 = 0xffffffff; + } else if (mode == BTC_WLINK_DB_MCC) { + zb_tbl0 = 0xffffffff; /* for E5G slot */ + zb_tbl1 = 0xda5a5a5a; /* for E2G slot */ + } + rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_0, zb_tbl0); + rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, zb_tbl1); +} + +static void _set_fw_report_map(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_smap *wl_smap = &btc->cx.wl.status.map; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + u32 rpt_map = btc->fwinfo.rpt_en_map; + u32 bt_rom_code_id, bt_fw_ver; + bool bt_ver_unknown = false; + u32 bitmap; + + if (wl_smap->rf_off == 1 || wl_smap->lps != BTC_LPS_OFF) { + rtw89_btc_fw_en_rpt(rtwdev, false, 0); + return; + } + + rpt_map |= rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_MREG); + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_SCAN_INFO); + if ((bt0->run_patch_code && bt0->enable.now) || + (bt1->run_patch_code && bt1->enable.now)) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bt_rom_code_id = chip_id_to_bt_rom_code_id(rtwdev->btc.ver->chip_id); + bt_fw_ver = bt0->ver_info.fw & 0xffff; + if (bt_fw_ver == 0 || + (bt_fw_ver == bt_rom_code_id && bt0->run_patch_code)) + bt_ver_unknown = true; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_VER_INFO); + if (bt0->enable.now && bt_ver_unknown) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_AFH_MAP) | + rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_AFH_MAP_LE) | + rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_TX_PWR_LVL); + if (bt0->link_info.link_cnt.now || + bt0->link_info_56g.link_cnt.now || + bt1->link_info.link_cnt.now || + bt1->link_info_56g.link_cnt.now) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_DEVICE_INFO); + if ((bt0->link_info.a2dp_desc.exist && + bt0->link_info.a2dp_desc.play_latency) || + (bt1->link_info.a2dp_desc.exist && + bt1->link_info.a2dp_desc.play_latency)) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + rtw89_btc_fw_en_rpt(rtwdev, false, rpt_map); +} + static void _action_common(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; struct rtw89_btc_dm *dm = &btc->dm; - u32 bt_rom_code_id, bt_fw_ver; - - if (btc->ver->fwlrole == 8) - _wl_req_mac(rtwdev, rinfo_v8->pta_req_band); + u8 i; + _wl_req_mac(rtwdev, rinfo->pta_req_band); + _update_zb_coex_tbl(rtwdev); _set_btg_ctrl(rtwdev); _set_wl_preagc_ctrl(rtwdev); _set_wl_tx_limit(rtwdev); + _set_phl_bt_slot_req(rtwdev); _set_bt_afh_info(rtwdev); _set_bt_rx_agc(rtwdev); _set_rf_trx_para(rtwdev); _set_bt_rx_scan_pri(rtwdev); - - bt_rom_code_id = chip_id_to_bt_rom_code_id(rtwdev->btc.ver->chip_id); - bt_fw_ver = bt->ver_info.fw & 0xffff; - if (bt->enable.now && - (bt_fw_ver == 0 || - (bt_fw_ver == bt_rom_code_id && bt->run_patch_code && rtwdev->chip->scbd))) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, 1); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, 0); + _set_fw_report_map(rtwdev); if (dm->run_reason == BTC_RSN_NTFY_INIT || dm->run_reason == BTC_RSN_NTFY_RADIO_STATE || - dm->run_reason == BTC_RSN_NTFY_POWEROFF) { + dm->run_reason == BTC_RSN_NTFY_POWEROFF) _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); - if (wl_smap->rf_off == 1 || wl_smap->lps != BTC_LPS_OFF) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, 0); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, 1); - } - - if (wl->scbd_change) { - rtw89_mac_cfg_sb(rtwdev, wl->scbd); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], write scbd: 0x%08x\n", - wl->scbd); - wl->scbd_change = false; - btc->cx.cnt_wl[BTC_WCNT_SCBDUPDATE]++; - } + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) + _sned_h2c_w2bscbd(rtwdev, false, i); if (btc->ver->fcxosi) { if (memcmp(&dm->ost_info_last, &dm->ost_info, @@ -5638,15 +6713,20 @@ static void _action_common(struct rtw89_dev *rtwdev) _fw_set_drv_info(rtwdev, CXDRVINFO_OSI); } } - btc->dm.tdma_instant_excute = 0; + dm->tdma_instant_excute = 0; + dm->lps_ctrl_change = false; + wl->role_info.link_mode_chg = 0; wl->pta_reg_mac_chg = false; + dm->pre_agc_chg = false; + wl->dbcc_chg = 0; } static void _action_by_bt(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; + struct rtw89_btc_bt_leaudio_desc leaudio = bt_linfo->leaudio_desc; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; struct rtw89_btc_bt_pan_desc pan = bt_linfo->pan_desc; @@ -5670,6 +6750,12 @@ static void _action_by_bt(struct rtw89_dev *rtwdev) if (bt_linfo->pan_desc.exist) profile_map |= BTC_BT_PAN; + if (leaudio.bis_exist) + profile_map |= BTC_BT_BIS; + + if (leaudio.cis_exist) + profile_map |= BTC_BT_CIS; + switch (profile_map) { case BTC_BT_NOPROFILE: if (pan.active) @@ -5695,6 +6781,13 @@ static void _action_by_bt(struct rtw89_dev *rtwdev) case BTC_BT_PAN: _action_bt_pan(rtwdev); break; + case BTC_BT_BIS: + _action_bt_bis(rtwdev); + break; + case BTC_BT_CIS: + case BTC_BT_CIS | BTC_BT_HID: + _action_bt_cis(rtwdev); + break; case BTC_BT_A2DP | BTC_BT_HFP: case BTC_BT_A2DP | BTC_BT_HID: case BTC_BT_A2DP | BTC_BT_HFP | BTC_BT_HID: @@ -5742,7 +6835,7 @@ static void _action_wl_25g_mcc(struct rtw89_dev *rtwdev) policy_type = BTC_CXP_OFFE_WL; else if (btc->cx.wl.status.val & btc_scanning_map.val) policy_type = BTC_CXP_OFFE_2GBWMIXB; - else if (btc->cx.bt.link_info.status.map.connect == 0) + else if (btc->cx.bt0.link_info.status.map.connect == 0) policy_type = BTC_CXP_OFFE_2GISOB; else policy_type = BTC_CXP_OFFE_2GBWISOB; @@ -5763,7 +6856,7 @@ static void _action_wl_scan(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; if (btc->cx.state_map != BTC_WLINKING && - RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD, &rtwdev->fw)) { + btc->ctrl.wl_ctrl_info.fw_scan) { _action_wl_25g_mcc(rtwdev); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], Scan offload!\n"); } else if (rtwdev->dbcc_en) { @@ -5786,7 +6879,7 @@ static void _action_wl_2g_mcc(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_MCC); else @@ -5800,178 +6893,15 @@ static void _action_wl_2g_mcc(struct rtw89_dev *rtwdev) static void _action_wl_2g_scc(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - - if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) - _set_policy(rtwdev, - BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_SCC); - else - _set_policy(rtwdev, - BTC_CXP_OFFE_DEF, BTC_ACT_WL_2G_SCC); - } else { /* dedicated-antenna */ - _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_2G_SCC); - } -} - -static void _action_wl_2g_scc_v1(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &wl->role_info_v1; - u16 policy_type = BTC_CXP_OFF_BT; - u32 dur; + u16 policy_type; - if (btc->ant_type == BTC_ANT_DEDICATED) { - policy_type = BTC_CXP_OFF_EQ0; - } else { - /* shared-antenna */ - switch (wl_rinfo->mrole_type) { - case BTC_WLMROLE_STA_GC: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_P2P_CLIENT; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_STA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_GC_NOA: - case BTC_WLMROLE_STA_GO: - case BTC_WLMROLE_STA_GO_NOA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_NONE; - dur = wl_rinfo->mrole_noa_duration; + if (!dm->tdd_bind.bt_smap.connect) + policy_type = BTC_CXP_OFFE_2GISOB; + else + policy_type = BTC_CXP_OFFE_2GBWISOB; - if (wl->status.map._4way) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_WL; - } else if (bt->link_info.status.map.connect == 0) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GISOB; - } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWMIXB2; - } else if (bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GBWISOB; - } - break; - default: - break; - } - } - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); -} - -static void _action_wl_2g_scc_v2(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - u32 dur, mrole_type, mrole_noa_duration; - u16 policy_type = BTC_CXP_OFF_BT; - - if (btc->ver->fwlrole == 2) { - mrole_type = rinfo_v2->mrole_type; - mrole_noa_duration = rinfo_v2->mrole_noa_duration; - } else if (btc->ver->fwlrole == 7) { - mrole_type = rinfo_v7->mrole_type; - mrole_noa_duration = rinfo_v7->mrole_noa_duration; - } else { - return; - } - - if (btc->ant_type == BTC_ANT_DEDICATED) { - policy_type = BTC_CXP_OFF_EQ0; - } else { - /* shared-antenna */ - switch (mrole_type) { - case BTC_WLMROLE_STA_GC: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_P2P_CLIENT; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_STA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_GC_NOA: - case BTC_WLMROLE_STA_GO: - case BTC_WLMROLE_STA_GO_NOA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_NONE; - dur = mrole_noa_duration; - - if (wl->status.map._4way) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_WL; - } else if (bt->link_info.status.map.connect == 0) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GISOB; - } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWMIXB2; - } else if (bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GBWISOB; - } - break; - default: - break; - } - } - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); -} - -static void _action_wl_2g_scc_v8(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_dm *dm = &btc->dm; - u16 policy_type = BTC_CXP_OFF_BT; - - if (btc->ant_type == BTC_ANT_SHARED) { - if (wl->status.map._4way) - policy_type = BTC_CXP_OFFE_WL; - else if (bt->link_info.status.map.connect == 0) - policy_type = BTC_CXP_OFFE_2GISOB; - else - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - policy_type = BTC_CXP_OFF_EQ0; - } - - dm->e2g_slot_limit = BTC_E2G_LIMIT_DEF; - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); } @@ -5982,7 +6912,7 @@ static void _action_wl_2g_ap(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_AP); else @@ -5999,7 +6929,7 @@ static void _action_wl_2g_go(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_GO); else @@ -6030,7 +6960,7 @@ static void _action_wl_2g_nan(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_NAN); else @@ -6041,42 +6971,97 @@ static void _action_wl_2g_nan(struct rtw89_dev *rtwdev) } } -static u32 _read_scbd(struct rtw89_dev *rtwdev) +static u8 _sned_h2c_w2bscbd(struct rtw89_dev *rtwdev, bool force_exec, u8 bid) { - const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - u32 scbd_val = 0; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + u8 cnt_idx = BTC_WCNT_SCBDUPDATE; + u8 h2c_func = SET_IOFLD_SCBD; + u8 buf[4]; - if (!chip->scbd) - return 0; + if (bid == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + return 0; - scbd_val = rtw89_mac_get_sb(rtwdev); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], read scbd: 0x%08x\n", - scbd_val); + h2c_func |= BT_H2C_FUNC_BT2ND; + cnt_idx = BTC_WCNT_SCBDUPDATE2; + } - btc->cx.cnt_bt[BTC_BCNT_SCBDREAD]++; - return scbd_val; + if (wl->scbd_chg[bid] || force_exec) { + /* Add delay to avoid BT FW loss information */ + + btc_dw2b(buf, 0, (wl->scbd[bid] | BIT(31))); /* trig */ + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { + if (!_send_fw_cmd(rtwdev, BTFC_SET, + h2c_func, buf, sizeof(buf))) + return 0; + } else { + rtw89_mac_cfg_sb(rtwdev, wl->scbd[bid]); + } + wl->scbd_chg[bid] = 0; + wl->wcnt[cnt_idx]++; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s() write BT%d scbd:0x%08x\n", + __func__, bid, wl->scbd[bid]); + } + return 1; } -static void _write_scbd(struct rtw89_dev *rtwdev, u32 val, bool state) +static void _write_scbd(struct rtw89_dev *rtwdev, u8 bid, u32 val, bool state) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - u32 scbd_val = 0; - u8 force_exec = false; + struct rtw89_btc_dm *dm = &btc->dm; + u8 force_exec, id, id_start, id_stop; + u32 scbd = 0; if (!chip->scbd) return; - scbd_val = state ? wl->scbd | val : wl->scbd & ~val; + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - if (val & BTC_WSCB_ACTIVE || val & BTC_WSCB_ON) - force_exec = true; + for (id = id_start; id <= id_stop; id++) { + force_exec = false; + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && + id == BTC_BT_2ND) + continue; - if (scbd_val != wl->scbd || force_exec) { - wl->scbd = scbd_val; - wl->scbd_change = true; + if (state) + scbd = wl->scbd[id] | val; + else + scbd = wl->scbd[id] & (~val); + + if (val & BTC_WSCB_CTCODE || + val & BTC_WSCB_RXGAIN || + val & BTC_WSCB_RXGAIN_56G) { /* instant exec */ + force_exec = true; + wl->scbd[id] = scbd; + dm->scbd_write_instant = 1; + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) + _sned_h2c_w2bscbd(rtwdev, force_exec, id); + else + rtw89_mac_cfg_sb(rtwdev, wl->scbd[id]); + dm->scbd_write_instant = 0; + } else if ((val & BTC_WSCB_ACTIVE || + val & BTC_WSCB_ON || + val & BTC_WSCB_WLRFK) || + scbd != wl->scbd[id]) { + /* + * Just update wl->scbd[] and set wl->scbd_chg[], + * moved "Write scoreboard I/O" to _action_common() + * _write_scbd will be executed if run_coex() + */ + wl->scbd[id] = scbd; + wl->scbd_chg[id] = 1; + } } } @@ -6113,457 +7098,6 @@ void _update_dbcc_band(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) btc->cx.wl.dbcc_info.op_band[phy_idx]; } -static void _update_wl_info(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 i, cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {0}, wl_5g_ch[2] = {0}; - bool b2g = false, b5g = false, client_joined = false; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - /* check if role active? */ - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - /* check dbcc role */ - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static void _update_wl_info_v1(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &wl->role_info_v1; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {}, wl_5g_ch[2] = {}; - bool b2g = false, b5g = false, client_joined = false; - u8 i; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role_v1[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role_v1[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role_v1[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role_v1[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role_v1[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role_v1[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role_v1[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role_v1[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role_v1[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static void _update_wl_info_v2(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo = &wl->role_info_v2; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {}, wl_5g_ch[2] = {}; - bool b2g = false, b5g = false, client_joined = false; - u8 i; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role_v2[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role_v2[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role_v2[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role_v2[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role_v2[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role_v2[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role_v2[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role_v2[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role_v2[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - #define BTC_CHK_HANG_MAX 3 #define BTC_SCB_INV_VALUE GENMASK(31, 0) @@ -6571,15 +7105,14 @@ static u8 _get_role_link_mode(u8 role) { switch (role) { case RTW89_WIFI_ROLE_STATION: - return BTC_WLINK_2G_STA; - case RTW89_WIFI_ROLE_P2P_GO: - return BTC_WLINK_2G_GO; - case RTW89_WIFI_ROLE_P2P_CLIENT: - return BTC_WLINK_2G_GC; - case RTW89_WIFI_ROLE_AP: - return BTC_WLINK_2G_AP; default: - return BTC_WLINK_OTHER; + return BTC_WLINK_STA; + case RTW89_WIFI_ROLE_P2P_GO: + return BTC_WLINK_GO; + case RTW89_WIFI_ROLE_P2P_CLIENT: + return BTC_WLINK_GC; + case RTW89_WIFI_ROLE_AP: + return BTC_WLINK_AP; } } @@ -6600,10 +7133,9 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, u8 *phy, u8 *role, u8 link_cnt) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; bool is_2g_ch_exist = false, is_multi_role_in_2g_phy = false; - u8 j, k, dbcc_2g_cid, dbcc_2g_cid2, dbcc_2g_phy, pta_req_band; + u8 j, k, dbcc_2g_cid = 0, dbcc_2g_cid2 = 0; /* find out the 2G-PHY by connect-id ->ch */ for (j = 0; j < link_cnt; j++) { @@ -6615,33 +7147,26 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, /* If no any 2G-port exist, it's impossible because 5G-exclude */ if (!is_2g_ch_exist) - return BTC_WLINK_5G; + return BTC_WLINK_SB_MCC; dbcc_2g_cid = j; - dbcc_2g_phy = phy[dbcc_2g_cid]; + rinfo->dbcc_2g_phy = phy[dbcc_2g_cid]; - if (dbcc_2g_phy == RTW89_PHY_1) - pta_req_band = RTW89_PHY_1; + if (rinfo->dbcc_2g_phy == RTW89_PHY_1) + rinfo->pta_req_band = RTW89_PHY_1; else - pta_req_band = RTW89_PHY_0; - - if (rtwdev->btc.ver->fwlrole == 7) { - rinfo_v7->dbcc_2g_phy = dbcc_2g_phy; - } else if (rtwdev->btc.ver->fwlrole == 8) { - rinfo_v8->dbcc_2g_phy = dbcc_2g_phy; - rinfo_v8->pta_req_band = pta_req_band; - } + rinfo->pta_req_band = RTW89_PHY_0; /* connect_cnt <= 2 */ if (link_cnt < BTC_TDMA_WLROLE_MAX) - return (_get_role_link_mode((role[dbcc_2g_cid]))); + return _get_role_link_mode(role[dbcc_2g_cid]); /* find the other-port in the 2G-PHY, ex: PHY-0:6G, PHY1: mcc/scc */ for (k = 0; k < link_cnt; k++) { if (k == dbcc_2g_cid) continue; - if (phy[k] == dbcc_2g_phy) { + if (phy[k] == rinfo->dbcc_2g_phy) { is_multi_role_in_2g_phy = true; dbcc_2g_cid2 = k; break; @@ -6650,319 +7175,86 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, /* Single-role in 2G-PHY */ if (!is_multi_role_in_2g_phy) - return (_get_role_link_mode(role[dbcc_2g_cid])); + return _get_role_link_mode(role[dbcc_2g_cid]); /* 2-role in 2G-PHY */ if (ch[dbcc_2g_cid2].center_ch > 14) - return BTC_WLINK_25G_MCC; + return BTC_WLINK_DB_MCC; else if (_chk_role_ch_group(&ch[dbcc_2g_cid], &ch[dbcc_2g_cid2])) - return BTC_WLINK_2G_SCC; + return BTC_WLINK_SCC; else - return BTC_WLINK_2G_MCC; + return BTC_WLINK_SB_MCC; } -static void _update_role_link_mode(struct rtw89_dev *rtwdev, - bool client_joined, u32 noa) +static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) { - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &rtwdev->btc.cx.wl.role_info_v8; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &rtwdev->btc.cx.wl.role_info_v7; - u8 role_ver = rtwdev->btc.ver->fwlrole; - u32 type = BTC_WLMROLE_NONE, dur = 0; - u8 link_mode, connect_cnt; - u32 wl_role; - - if (role_ver == 7) { - wl_role = rinfo_v7->role_map; - link_mode = rinfo_v7->link_mode; - connect_cnt = rinfo_v7->connect_cnt; - } else if (role_ver == 8) { - wl_role = rinfo_v8->role_map; - link_mode = rinfo_v8->link_mode; - connect_cnt = rinfo_v8->connect_cnt; - } else { - return; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (((wl_role & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (wl_role & BIT(RTW89_WIFI_ROLE_AP))) && !client_joined) { - if (link_mode == BTC_WLINK_2G_SCC) { - if (role_ver == 7) { - rinfo_v7->link_mode = BTC_WLINK_2G_STA; - rinfo_v7->connect_cnt--; - } else if (role_ver == 8) { - rinfo_v8->link_mode = BTC_WLINK_2G_STA; - rinfo_v8->connect_cnt--; - } - } else if (link_mode == BTC_WLINK_2G_GO || - link_mode == BTC_WLINK_2G_AP) { - if (role_ver == 7) { - rinfo_v7->link_mode = BTC_WLINK_NOLINK; - rinfo_v7->connect_cnt--; - } else if (role_ver == 8) { - rinfo_v8->link_mode = BTC_WLINK_NOLINK; - rinfo_v8->connect_cnt--; - } - } - } - - /* Identify 2-Role type */ - if (connect_cnt >= 2 && - (link_mode == BTC_WLINK_2G_SCC || - link_mode == BTC_WLINK_2G_MCC || - link_mode == BTC_WLINK_25G_MCC || - link_mode == BTC_WLINK_5G)) { - if ((wl_role & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (wl_role & BIT(RTW89_WIFI_ROLE_AP))) - type = noa ? BTC_WLMROLE_STA_GO_NOA : BTC_WLMROLE_STA_GO; - else if (wl_role & BIT(RTW89_WIFI_ROLE_P2P_CLIENT)) - type = noa ? BTC_WLMROLE_STA_GC_NOA : BTC_WLMROLE_STA_GC; - else - type = BTC_WLMROLE_STA_STA; - - dur = noa; - } - - if (role_ver == 7) { - rinfo_v7->mrole_type = type; - rinfo_v7->mrole_noa_duration = dur; - } else if (role_ver == 8) { - rinfo_v8->mrole_type = type; - rinfo_v8->mrole_noa_duration = dur; - } -} - -static void _update_wl_info_v7(struct rtw89_dev *rtwdev, u8 rid) -{ - struct rtw89_btc_chdef cid_ch[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo = &wl->role_info_v7; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_active_role_v7 *act_role = NULL; - u8 i, mode, cnt = 0, cnt_2g = 0, cnt_5g = 0, phy_now = RTW89_PHY_NUM, phy_dbcc; - bool b2g = false, b5g = false, client_joined = false, client_inc_2g = false; - u8 client_cnt_last[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 cid_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 cid_phy[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 mac = RTW89_MAC_0, dbcc_2g_phy = RTW89_PHY_0; - u32 noa_duration = 0; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active || wl_linfo[i].phy >= RTW89_PHY_NUM) - continue; - - act_role = &wl_rinfo->active_role[i]; - act_role->role = wl_linfo[i].role; - - /* check if role connect? */ - if (wl_linfo[i].connected == MLME_NO_LINK) { - act_role->connected = 0; - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - continue; - } - - cnt++; - act_role->connected = 1; - act_role->pid = wl_linfo[i].pid; - act_role->phy = wl_linfo[i].phy; - act_role->band = wl_linfo[i].band; - act_role->ch = wl_linfo[i].ch; - act_role->bw = wl_linfo[i].bw; - act_role->noa = wl_linfo[i].noa; - act_role->noa_dur = wl_linfo[i].noa_duration; - cid_ch[cnt - 1] = wl_linfo[i].chdef; - cid_phy[cnt - 1] = wl_linfo[i].phy; - cid_role[cnt - 1] = wl_linfo[i].role; - wl_rinfo->role_map |= BIT(wl_linfo[i].role); - - if (rid == i) - phy_now = act_role->phy; - - if (wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) { - if (wl_linfo[i].client_cnt > 1) - client_joined = true; - if (client_cnt_last[i] < wl_linfo[i].client_cnt && - wl_linfo[i].chdef.band == RTW89_BAND_2G) - client_inc_2g = true; - act_role->client_cnt = wl_linfo[i].client_cnt; - } else { - act_role->client_cnt = 0; - } - - if (act_role->noa && act_role->noa_dur > 0) - noa_duration = act_role->noa_dur; - - if (rtwdev->dbcc_en) { - phy_dbcc = wl_linfo[i].phy; - wl_dinfo->role[phy_dbcc] |= BIT(wl_linfo[i].role); - wl_dinfo->op_band[phy_dbcc] = wl_linfo[i].chdef.band; - } - - if (wl_linfo[i].chdef.band != RTW89_BAND_2G) { - cnt_5g++; - b5g = true; - } else { - if (((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - client_joined) || - wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_CLIENT) - wl_rinfo->p2p_2g = 1; - - if ((wl_linfo[i].mode & BIT(BTC_WL_MODE_11B)) || - (wl_linfo[i].mode & BIT(BTC_WL_MODE_11G))) - wl->bg_mode = 1; - else if (wl_linfo[i].mode & BIT(BTC_WL_MODE_HE)) - wl->he_mode = true; - - cnt_2g++; - b2g = true; - } - - if (act_role->band == RTW89_BAND_5G && act_role->ch >= 100) - wl->is_5g_hi_channel = 1; - else - wl->is_5g_hi_channel = 0; - } - - wl_rinfo->connect_cnt = cnt; - wl->client_cnt_inc_2g = client_inc_2g; - - if (cnt == 0) { - mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map = BIT(RTW89_WIFI_ROLE_NONE); - } else if (!b2g && b5g) { - mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map & BIT(RTW89_WIFI_ROLE_NAN)) { - mode = BTC_WLINK_2G_NAN; - } else if (cnt > BTC_TDMA_WLROLE_MAX) { - mode = BTC_WLINK_OTHER; - } else if (rtwdev->dbcc_en) { - mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt); - - /* correct 2G-located PHY band for gnt ctrl */ - if (dbcc_2g_phy < RTW89_PHY_NUM) - wl_dinfo->op_band[dbcc_2g_phy] = RTW89_BAND_2G; - } else if (b2g && b5g && cnt == 2) { - mode = BTC_WLINK_25G_MCC; - } else if (!b5g && cnt == 2) { /* cnt_connect = 2 */ - if (_chk_role_ch_group(&cid_ch[0], &cid_ch[cnt - 1])) - mode = BTC_WLINK_2G_SCC; - else - mode = BTC_WLINK_2G_MCC; - } else if (!b5g && cnt == 1) { /* cnt_connect = 1 */ - mode = _get_role_link_mode(cid_role[0]); - } else { - mode = BTC_WLINK_NOLINK; - } - - wl_rinfo->link_mode = mode; - _update_role_link_mode(rtwdev, client_joined, noa_duration); - - /* todo DBCC related event */ - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC] wl_info phy_now=%d\n", phy_now); - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC] rlink cnt_2g=%d cnt_5g=%d\n", cnt_2g, cnt_5g); - - if (wl_rinfo->dbcc_en != rtwdev->dbcc_en) { - wl_rinfo->dbcc_chg = 1; - wl_rinfo->dbcc_en = rtwdev->dbcc_en; - btc->cx.cnt_wl[BTC_WCNT_DBCC_CHG]++; - } - - if (rtwdev->dbcc_en) { - wl_rinfo->dbcc_2g_phy = dbcc_2g_phy; - - if (dbcc_2g_phy == RTW89_PHY_1) - mac = RTW89_MAC_1; - - _update_dbcc_band(rtwdev, RTW89_PHY_0); - _update_dbcc_band(rtwdev, RTW89_PHY_1); - } - _wl_req_mac(rtwdev, mac); - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static u8 _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) -{ - struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; - u8 mode = BTC_WLINK_NOLINK; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + u8 p2p_exist = wl->role_info.p2p_exist; + if (hw_band == RTW89_PHY_1) + p2p_exist = wl->role_info.p2p_exist_hb1; + + /* MLD: nLmR --> 2L1R: 2-Link by 1-RF, 2L2R: 2-link by 2-RF */ switch (type) { case RTW89_MR_WTYPE_NONE: /* no-link */ - mode = BTC_WLINK_NOLINK; + r->link_mode = BTC_WLINK_NOLINK; break; case RTW89_MR_WTYPE_NONMLD: /* Non_MLO 1-role 2+0/0+2 */ case RTW89_MR_WTYPE_MLD1L1R: /* MLO only-1 link 2+0/0+2 */ - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - mode = BTC_WLINK_2G_GO; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; - else - mode = BTC_WLINK_2G_STA; + if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { + r->link_mode = BTC_WLINK_GO; + } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT && + p2p_exist) { + r->link_mode = BTC_WLINK_GC; + } else { + r->link_mode = BTC_WLINK_STA; } break; case RTW89_MR_WTYPE_NONMLD_NONMLD: /* Non_MLO 2-role 2+0/0+2 */ case RTW89_MR_WTYPE_MLD1L1R_NONMLD: /* MLO only-1 link + P2P 2+0/0+2 */ - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_5GHZ || - mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_6GHZ) { - mode = BTC_WLINK_25G_MCC; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ) { - mode = BTC_WLINK_2G_MCC; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_2GHZ) { - mode = BTC_WLINK_2G_SCC; + if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_6GHZ) { + r->link_mode = BTC_WLINK_DB_MCC; + } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_6GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_5GHZ_6GHZ) { + r->link_mode = BTC_WLINK_SB_MCC; + } else { + r->link_mode = BTC_WLINK_SCC; } break; case RTW89_MR_WTYPE_MLD2L1R: /* MLO_MLSR 2+0/0+2 */ - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) - mode = BTC_WLINK_5G; - else if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; + if (p2p_exist) /* MLO_MLSR only support STA/GC */ + r->link_mode = BTC_WLINK_GC; else - mode = BTC_WLINK_2G_STA; + r->link_mode = BTC_WLINK_STA; break; case RTW89_MR_WTYPE_MLD2L1R_NONMLD: /* MLO_MLSR + P2P 2+0/0+2 */ - case RTW89_MR_WTYPE_MLD2L2R_NONMLD: /* MLO_MLMR + P2P 1+1/2+2 */ - /* driver may doze 1-link to + case RTW89_MR_WTYPE_MLD2L2R_NONMLD: /* MLO_MLMR + P2P 1+1/2+2*/ + /* driver may doze 1-link (1+1) or 2+0->0+2->1+1 * 2G+5G -> TDMA slot switch by E2G/E5G * 5G only -> TDMA slot switch by E5G */ - mode = BTC_WLINK_25G_MCC; + r->link_mode = BTC_WLINK_DB_MCC; break; case RTW89_MR_WTYPE_MLD2L2R: /* MLO_MLMR 1+1/2+2 */ - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - mode = BTC_WLINK_2G_GO; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; - else - mode = BTC_WLINK_2G_STA; - } + /* MLMR only support STA now (2024) */ + r->link_mode = BTC_WLINK_STA; break; } - return mode; } -static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) +static void _update_wl_mlo_info(struct rtw89_dev *rtwdev, u8 hw_band) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; struct rtw89_mr_chanctx_info qinfo; - u8 track_band = RTW89_PHY_0; + struct rtw89_chanctx *ch; u8 rf_band = RTW89_BAND_2G; u8 i, type; @@ -6971,19 +7263,46 @@ static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) memset(&qinfo, 0, sizeof(qinfo)); rtw89_query_mr_chanctx_info(rtwdev, i, &qinfo); + + switch (qinfo.ctxtype) { + default: + mlo_info->hwb_rf_band[i] = 0; + break; + case RTW89_MR_CTX1_2GHZ: + case RTW89_MR_CTX2_2GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G); + break; + case RTW89_MR_CTX1_5GHZ: + case RTW89_MR_CTX2_5GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_5G); + break; + case RTW89_MR_CTX1_6GHZ: + case RTW89_MR_CTX2_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_6G); + break; + case RTW89_MR_CTX2_2GHZ_5GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G) | + BIT(RTW89_BAND_5G); + break; + case RTW89_MR_CTX2_2GHZ_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G) | + BIT(RTW89_BAND_6G); + break; + case RTW89_MR_CTX2_5GHZ_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_5G) | + BIT(RTW89_BAND_6G); + break; + } mlo_info->wmode[i] = qinfo.wmode; mlo_info->ch_type[i] = qinfo.ctxtype; mlo_info->wtype = qinfo.wtype; - - if (mlo_info->ch_type[i] == RTW89_MR_CTX1_5GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_5GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_5GHZ_6GHZ) - mlo_info->hwb_rf_band[i] = RTW89_BAND_5G; - else if (mlo_info->ch_type[i] == RTW89_MR_CTX1_6GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_6GHZ) - mlo_info->hwb_rf_band[i] = RTW89_BAND_6G; - else /* check if "2G-included" or unknown in each HW-band */ - mlo_info->hwb_rf_band[i] = RTW89_BAND_2G; + wl->rf_band_map[i] = mlo_info->hwb_rf_band[i]; + ch = &rtwdev->hal.chanctx[i]; + wl->rf_ch_info[i].center_ch = ch->chan.channel; + wl->rf_ch_info[i].band = ch->chan.band_type; + wl->rf_ch_info[i].chan = ch->chan.channel; + wl->rf_ch_info[i].offset = ch->chan.pri_ch_idx; + wl->rf_ch_info[i].bw = ch->chan.band_width; } mlo_info->link_status = rtwdev->mlo_dbcc_mode; @@ -7018,71 +7337,64 @@ static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) default: case MLO_2_PLUS_0_1RF: /* 2+0 */ case MLO_2_PLUS_0_2RF: - mlo_info->rf_combination = BTC_MLO_RF_2_PLUS_0; - track_band = RTW89_MAC_0; - rf_band = mlo_info->hwb_rf_band[RTW89_MAC_0]; - mlo_info->path_rf_band[BTC_RF_S0] = rf_band; - mlo_info->path_rf_band[BTC_RF_S1] = rf_band; - + _update_wl_link_mode(rtwdev, RTW89_MAC_0, type); + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; wl_rinfo->pta_req_band = RTW89_MAC_0; wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; wl_rinfo->dbcc_en = 0; + + rf_band = wl->rf_band_map[RTW89_PHY_0]; + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; break; case MLO_0_PLUS_2_1RF: /* 0+2 */ case MLO_0_PLUS_2_2RF: - mlo_info->rf_combination = BTC_MLO_RF_0_PLUS_2; - track_band = RTW89_MAC_1; - rf_band = mlo_info->hwb_rf_band[RTW89_MAC_1]; - mlo_info->path_rf_band[BTC_RF_S0] = rf_band; - mlo_info->path_rf_band[BTC_RF_S1] = rf_band; - + _update_wl_link_mode(rtwdev, RTW89_MAC_1, type); + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; wl_rinfo->pta_req_band = RTW89_MAC_1; wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; wl_rinfo->dbcc_en = 0; + + rf_band = wl->rf_band_map[RTW89_PHY_1]; + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; break; case MLO_1_PLUS_1_1RF: /* 1+1 */ case MLO_1_PLUS_1_2RF: /* 1+1 */ case MLO_2_PLUS_2_2RF: /* 2+2 */ case DBCC_LEGACY: /* DBCC 1+1 */ - if (mlo_info->link_status == MLO_2_PLUS_2_2RF) - mlo_info->rf_combination = BTC_MLO_RF_2_PLUS_2; - else - mlo_info->rf_combination = BTC_MLO_RF_1_PLUS_1; - - if (mlo_info->hwb_rf_band[RTW89_MAC_0] == RTW89_BAND_2G) - track_band = RTW89_MAC_0; - else - track_band = RTW89_MAC_1; - - mlo_info->path_rf_band[BTC_RF_S0] = - mlo_info->hwb_rf_band[RTW89_MAC_0]; - mlo_info->path_rf_band[BTC_RF_S1] = - mlo_info->hwb_rf_band[RTW89_MAC_1]; - - /* Check ch count from ch_type @ 2.4G HW-band, and modify type */ - if (mlo_info->ch_type[track_band] == RTW89_MR_CTX1_2GHZ) - type = RTW89_MR_WTYPE_NONMLD; /* only 1-role at 2G */ + /* Check ch count from ch_type and modify type*/ + if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_2GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_6GHZ) + type = RTW89_MR_WTYPE_NONMLD; /* only 1-role */ else type = RTW89_MR_WTYPE_NONMLD_NONMLD; - if (mlo_info->hwb_rf_band[RTW89_MAC_0] == RTW89_BAND_2G) { - wl_rinfo->pta_req_band = RTW89_MAC_0; - wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + rf_band = wl->rf_band_map[hw_band]; + _update_wl_link_mode(rtwdev, hw_band, type); + + if (hw_band == RTW89_MAC_0) { + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; } else { - wl_rinfo->pta_req_band = RTW89_MAC_1; - wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; } - if (mlo_info->wmode[RTW89_MAC_0] == RTW89_MR_WMODE_NONE && - mlo_info->wmode[RTW89_MAC_1] == RTW89_MR_WMODE_NONE) - wl_rinfo->dbcc_en = 0; - else - wl_rinfo->dbcc_en = 1; + /* set pta_req_band for 1-PTA architecture */ + if (wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G)) { + wl_rinfo->pta_req_band = RTW89_MAC_0; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + } else if (wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G)) { + wl_rinfo->pta_req_band = RTW89_MAC_1; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; + } else { /* Both HW-BAND are not on 2.4G*/ + wl_rinfo->pta_req_band = RTW89_MAC_0; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + } break; } - wl_rinfo->link_mode = _update_wl_link_mode(rtwdev, track_band, type); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(), mode=%s, pta_band=%d", __func__, id_to_linkmode(wl_rinfo->link_mode), wl_rinfo->pta_req_band); @@ -7092,7 +7404,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; struct rtw89_btc_wl_rlink *rlink = NULL; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_chdef cid_ch[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; u8 cid_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; u8 cid_phy[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; @@ -7102,6 +7414,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) u8 i, j, cnt = 0; for (j = RTW89_PHY_0; j < RTW89_PHY_NUM; j++) { + wl->rf_band_map[j] = 0; /* j=link id, is the link use which band */ for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { rlink = &wl_rinfo->rlink[i][j]; @@ -7125,6 +7438,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) cnt_2g++; b2g = true; } + wl->rf_band_map[j] |= BIT(rlink->rf_band); } if (outloop) break; @@ -7133,56 +7447,114 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): cnt_2g=%d, cnt_5g=%d\n", __func__, cnt_2g, cnt_5g); + wl_rinfo->pta_req_band = RTW89_PHY_0; /* no 0+2 for non-MLO */ wl_rinfo->dbcc_en = rtwdev->dbcc_en; + /* Be careful to change the following sequence!! */ if (cnt == 0) { mode = BTC_WLINK_NOLINK; - } else if (!b2g && b5g) { - mode = BTC_WLINK_5G; } else if (wl_rinfo->dbcc_en) { + /* update pta_req_band to 2.4GHz HW-BAND for DBCC = 1*/ mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt); + } else if (!b2g && b5g) { + mode = _get_role_link_mode(cid_role[0]); } else if (b2g && b5g) { - mode = BTC_WLINK_25G_MCC; - } else if (!b5g && cnt >= 2) { + mode = BTC_WLINK_DB_MCC; + } else if (cnt >= 2) { if (_chk_role_ch_group(&cid_ch[0], &cid_ch[1])) - mode = BTC_WLINK_2G_SCC; + mode = BTC_WLINK_SCC; else - mode = BTC_WLINK_2G_MCC; - } else if (!b5g) { /* cnt_connect = 1 */ + mode = BTC_WLINK_SB_MCC; + } else { mode = _get_role_link_mode(cid_role[0]); } wl_rinfo->link_mode = mode; + wl_rinfo->link_mode_hb1 = mode; } -static void _modify_role_link_mode(struct rtw89_dev *rtwdev) +static void _sync_wl_link_mode_v0(struct rtw89_dev *rtwdev) { - struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; + bool has_2g = false; + int i, j; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER && !has_2g; i++) + for (j = 0; j < RTW89_MAC_NUM && !has_2g; j++) + if (r->rlink[i][j].connected && + r->rlink[i][j].rf_band == RTW89_BAND_2G) + has_2g = true; + + if (!has_2g && r->link_mode != BTC_WLINK_NOLINK) { + r->link_mode_v0 = BTC_WLINK_V0_5G; + return; + } + + switch (r->link_mode) { + case BTC_WLINK_NOLINK: + r->link_mode_v0 = BTC_WLINK_V0_NOLINK; + break; + case BTC_WLINK_STA: + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + r->link_mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + r->link_mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + r->link_mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + r->link_mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + case BTC_WLINK_DB_MCC: + r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; + break; + default: + r->link_mode_v0 = BTC_WLINK_V0_OTHER; + break; + } +} + +static void _modify_role_link_mode(struct rtw89_dev *rtwdev, u8 hw_band) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; u8 go_cleint_exist = wl->go_client_exist; - u8 link_mode = wl_rinfo->link_mode; + u8 *link_mode = &wl_rinfo->link_mode; u32 role_map = wl_rinfo->role_map; u8 noa_exist = wl->noa_exist; u32 mrole = BTC_WLMROLE_NONE; + if (hw_band == RTW89_PHY_1) { + *link_mode = wl_rinfo->link_mode_hb1; + role_map = wl_rinfo->role_map_hb1; + go_cleint_exist = wl->go_client_exist_hb1; + } + /* if no client_joined, don't care P2P-GO/AP role */ if (((role_map & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (role_map & BIT(RTW89_WIFI_ROLE_AP))) && !go_cleint_exist) { - if (link_mode == BTC_WLINK_2G_SCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - } else if (link_mode == BTC_WLINK_2G_GO || - link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; + (role_map & BIT(RTW89_WIFI_ROLE_AP))) && !go_cleint_exist) { + if (*link_mode == BTC_WLINK_SCC) { + *link_mode = BTC_WLINK_STA; + } else if (*link_mode == BTC_WLINK_GO || + *link_mode == BTC_WLINK_AP) { + *link_mode = BTC_WLINK_NOLINK; } } - /* Identify 2-Role type */ - if (link_mode == BTC_WLINK_2G_SCC || - link_mode == BTC_WLINK_2G_MCC || - link_mode == BTC_WLINK_25G_MCC || - link_mode == BTC_WLINK_5G) { + /* Identify 2-Role type */ + if (*link_mode == BTC_WLINK_SCC || + *link_mode == BTC_WLINK_SB_MCC || + *link_mode == BTC_WLINK_DB_MCC) { if ((role_map & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (role_map & BIT(RTW89_WIFI_ROLE_AP))) { + (role_map & BIT(RTW89_WIFI_ROLE_AP))) { if (noa_exist) mrole = BTC_WLMROLE_STA_GO_NOA; else @@ -7195,128 +7567,149 @@ static void _modify_role_link_mode(struct rtw89_dev *rtwdev) } else { mrole = BTC_WLMROLE_STA_STA; } - } - wl_rinfo->mrole_type = mrole; + if (hw_band == RTW89_PHY_0) { + wl_rinfo->mrole_type &= GENMASK(31, 16); /* clear Low-Word */ + wl_rinfo->mrole_type |= mrole; + } else { + wl_rinfo->mrole_type &= GENMASK(15, 0); /* clear high-Word */ + wl_rinfo->mrole_type |= (mrole << 16); + } + } else { + wl_rinfo->mrole_type = BTC_WLMROLE_NONE; + } rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): link_mode=%s, mrole_type=%d\n", __func__, id_to_linkmode(wl_rinfo->link_mode), wl_rinfo->mrole_type); } -static void _update_wl_info_v8(struct rtw89_dev *rtwdev, u8 role_id, u8 rlink_id, - enum btc_role_state state) +static void _update_wl_info(struct rtw89_dev *rtwdev, struct rtw89_btc_wl_link_info *wl_linfo) { - struct rtw89_btc_wl_rlink *rlink = NULL; - struct rtw89_btc_wl_link_info *wl_linfo; struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; - bool client_joined = false, noa_exist = false, p2p_exist = false; - bool is_5g_hi_channel = false, bg_mode = false, dbcc_en_ori; - u8 i, j, link_mode_ori; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + struct rtw89_btc_wl_rlink *rlink = NULL; + u8 dbcc_en_ori, is_5g_hi_ch = 0, bg_mode = 0, p2p_exist = 0; + u8 go_client_exist = 0, noa_exist = 0; + u8 rlink_id = wl_linfo->phy; + u8 role_id = wl_linfo->pid; + u8 i, link_mode_ori; u32 role_map = 0; - if (role_id >= RTW89_BE_BTC_WL_MAX_ROLE_NUMBER || rlink_id >= RTW89_MAC_NUM) + if (role_id >= RTW89_BE_BTC_WL_MAX_ROLE_NUMBER || rlink_id >= RTW89_BAND_NUM) return; - /* Extract wl->link_info[role_id][rlink_id] to wl->role_info + /* + * Extract wl->link_info[role_id][rlink_id] to wl->role_info * role_id: role index * rlink_id: rlink index (= HW-band index) * pid: port_index */ - - wl_linfo = &wl->rlink_info[role_id][rlink_id]; rlink = &wl_rinfo->rlink[role_id][rlink_id]; rlink->role = wl_linfo->role; rlink->active = wl_linfo->active; /* Doze or not */ rlink->pid = wl_linfo->pid; + rlink->mac_id = wl_linfo->mac_id; rlink->phy = wl_linfo->phy; - rlink->rf_band = wl_linfo->band; - rlink->ch = wl_linfo->ch; - rlink->bw = wl_linfo->bw; + rlink->rf_band = wl_linfo->chdef.band; + rlink->ch = wl_linfo->chdef.center_ch; + rlink->bw = wl_linfo->chdef.bw; rlink->noa = wl_linfo->noa; rlink->noa_dur = wl_linfo->noa_duration / 1000; rlink->client_cnt = wl_linfo->client_cnt; rlink->mode = wl_linfo->mode; + /* check if rlink connect? */ switch (wl_linfo->connected) { case MLME_NO_LINK: rlink->connected = 0; break; case MLME_LINKED: rlink->connected = 1; + + /* reset special-AP flag if station mode linked */ + dm->leak_ap = 0; break; default: return; } - for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { - for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { - rlink = &wl_rinfo->rlink[i][j]; + /* update by HW-Band */ + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { /* i = role_id */ + rlink = &wl_rinfo->rlink[i][rlink_id]; - if (!rlink->active || !rlink->connected) - continue; + if (!rlink->active || !rlink->connected) + continue; - role_map |= BIT(rlink->role); + role_map |= BIT(rlink->role); - /* only one noa-role exist */ - if (rlink->noa && rlink->noa_dur > 0) - noa_exist = true; - - /* for WL 5G-Rx interfered with BT issue */ - if (rlink->rf_band == RTW89_BAND_5G) { - if (rlink->ch >= 100) - is_5g_hi_channel = true; - - continue; - } - - /* only if client connect for p2p-Go/AP */ - if ((rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_AP) && - rlink->client_cnt > 1) { - p2p_exist = true; - client_joined = true; - } - - /* Identify if P2P-Go (GO/GC/AP) exist at 2G band */ - if (rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT) - p2p_exist = true; - - if ((rlink->mode & BIT(BTC_WL_MODE_11B)) || - (rlink->mode & BIT(BTC_WL_MODE_11G))) - bg_mode = true; + /* Identify if P2P-Go (GO/GC/AP) exist at 2GHz band */ + if (((rlink->role == RTW89_WIFI_ROLE_P2P_GO || + rlink->role == RTW89_WIFI_ROLE_AP) && + rlink->client_cnt > 1)) { + p2p_exist = 1; + go_client_exist = 1; } + + if (rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT) + p2p_exist = 1; + + /* only one noa-role exist */ + if (rlink->noa && rlink->noa_dur > 0) + noa_exist = 1; + + /* for WL 5G-Rx interfered with BT issue */ + if (rlink->rf_band == RTW89_BAND_5G && rlink->ch >= 100) + is_5g_hi_ch = 1; + + if ((rlink->mode & BIT(BTC_WL_MODE_11B)) || + (rlink->mode & BIT(BTC_WL_MODE_11G))) + bg_mode = 1; } - link_mode_ori = wl_rinfo->link_mode; - wl->is_5g_hi_channel = is_5g_hi_channel; - wl->bg_mode = bg_mode; - wl->go_client_exist = client_joined; - wl->noa_exist = noa_exist; - wl_rinfo->p2p_2g = p2p_exist; - wl_rinfo->role_map = role_map; + if (rlink_id == RTW89_PHY_0) { + link_mode_ori = wl_rinfo->link_mode; + wl->is_5g_hi_ch = is_5g_hi_ch; + wl->bg_mode = bg_mode; + wl->go_client_exist = go_client_exist; + wl->noa_exist = noa_exist; + wl_rinfo->p2p_exist = p2p_exist; + wl_rinfo->role_map = role_map; + } else { + link_mode_ori = wl_rinfo->link_mode_hb1; + wl->is_5g_hi_ch_hb1 = is_5g_hi_ch; + wl->bg_mode_hb1 = bg_mode; + wl->go_client_exist_hb1 = go_client_exist; + wl->noa_exist_hb1 = noa_exist; + wl_rinfo->p2p_exist_hb1 = p2p_exist; + wl_rinfo->role_map_hb1 = role_map; + } dbcc_en_ori = wl_rinfo->dbcc_en; + /* for MLO-supported, link-mode from driver directly */ if (rtwdev->chip->para_ver & BTC_FEAT_MLO_SUPPORT) { - /* for MLO-supported, link-mode from driver directly */ - _update_wl_mlo_info(rtwdev); - } else { - /* for non-MLO-supported, link-mode by BTC */ + _update_wl_mlo_info(rtwdev, rlink_id); + } else { /* for non-MLO-supported, link-mode by BTC */ _update_wl_non_mlo_info(rtwdev); } - _modify_role_link_mode(rtwdev); + _modify_role_link_mode(rtwdev, rlink_id); + _sync_wl_link_mode_v0(rtwdev); - if (link_mode_ori != wl_rinfo->link_mode) - wl->link_mode_chg = true; + if ((rlink_id == RTW89_PHY_0 && wl_rinfo->link_mode != link_mode_ori) || + (rlink_id == RTW89_PHY_1 && wl_rinfo->link_mode_hb1 != link_mode_ori)) { + wl_rinfo->link_mode_chg = 1; + wl->link_mode_chg = 1; + } if (wl_rinfo->dbcc_en != dbcc_en_ori) { - wl->dbcc_chg = true; - btc->cx.cnt_wl[BTC_WCNT_DBCC_CHG]++; + wl->dbcc_chg = 1; + wl->role_info.dbcc_chg = 1; + wl->wcnt[BTC_WCNT_DBCC_CHG]++; } } @@ -7333,11 +7726,13 @@ void rtw89_coex_act1_work(struct wiphy *wiphy, struct wiphy_work *work) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): enter\n", __func__); dm->cnt_notify[BTC_NCNT_TIMER]++; - if (wl->status.map._4way) - wl->status.map._4way = false; - if (wl->status.map.connecting) - wl->status.map.connecting = false; - + if (wl->status.map.handshake || + wl->status.map._4way || + wl->status.map.dhcp) { + wl->status.map._4way = 0; + wl->status.map.dhcp = 0; + wl->status.map.handshake = 0; + } _run_coex(rtwdev, BTC_RSN_ACT1_WORK); } @@ -7347,7 +7742,7 @@ void rtw89_coex_bt_devinfo_work(struct wiphy *wiphy, struct wiphy_work *work) coex_bt_devinfo_work.work); struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; lockdep_assert_wiphy(wiphy); @@ -7373,85 +7768,318 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) if (wl->rfk_info.state != BTC_WRFK_STOP) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): RFK timeout\n", __func__); - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]++; + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]++; dm->error.map.wl_rfk_timeout = true; wl->rfk_info.state = BTC_WRFK_STOP; - _write_scbd(rtwdev, BTC_WSCB_WLRFK, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, false); _run_coex(rtwdev, BTC_RSN_RFK_CHK_WORK); } } -static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) +static void _update_bt_ctrl_lps(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_dm *dm = &btc->dm; + bool lps_ctrl = false; + + if (dm->out_of_band || dm->freerun) + lps_ctrl = false; + else if (bt0->whql_test || bt1->whql_test) + lps_ctrl = true; + else if (bt0->link_info.a2dp_desc.exist || + bt0->link_info.hfp_desc.exist || + bt0->link_info.pan_desc.exist) + lps_ctrl = true; + else if (bt1->link_info.a2dp_desc.exist || + bt1->link_info.hfp_desc.exist || + bt1->link_info.pan_desc.exist) + lps_ctrl = true; + + if (dm->lps_ctrl_scbd != lps_ctrl) { + dm->lps_ctrl_scbd = lps_ctrl; + dm->lps_ctrl_change = true; + } else { + dm->lps_ctrl_change = false; + } +} + +static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) +{ + struct rtw89_btc_bt_link_info *bt_2g, *bt_56g; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_bt_info *bt; + u32 val, any_bt_connect, any_bt_6g_connect = 0; + bool bt_link_change = false; + u8 id, id_start, id_stop; + + if (!rtwdev->chip->scbd || bid > BTC_ALL_BT) + return; + + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } + + dm->lps_ctrl_change = false; + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s()\n", __func__); + + for (id = id_start; id <= id_stop; id++) { + bt = (id == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + bt_2g = &bt->link_info; + bt_56g = &bt->link_info_56g; + + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && id == BTC_BT_2ND) + break; + + val = bt->scbd_c2h; + + if (val == 0xffffffff) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s return by invalid scbd value\n", + __func__); + return; + } + + if (!(val & BTC_BSCB_ON)) + bt->enable.now = 0; + else + bt->enable.now = 1; + + if (bt->enable.now != bt->enable.last) + bt_link_change = true; + + /* reset bt info if bt re-enable */ + if (bt->enable.now && !bt->enable.last) { + if (id == BTC_BT_1ST) + _reset_btc_var(rtwdev, BTC_RESET_BTINFO); + else + _reset_btc_var(rtwdev, BTC_RESET_BTINFO2); + + bt->bcnt[BTC_BCNT_REENABLE]++; + bt->enable.now = 1; + bt->rf_band_map = BIT(RTW89_BAND_2G); + bt->link_weight[BTC_BT_B2G] = 5; /* no-profile */ + } + bt->enable.last = bt->enable.now; + + /* if BT will disconnect and adopt protect plan */ + if ((val & BTC_BSCB_BT_PROTECT) && + !(bt->scbd & BTC_BSCB_BT_PROTECT)) + bt->bcnt[BTC_BCNT_PROTECT]++; + + bt->mbx_avl = !!(val & BTC_BSCB_ACT); + if (bt->whql_test != !!(val & BTC_BSCB_WHQL)) + bt_link_change = true; + + bt->whql_test = !!(val & BTC_BSCB_WHQL); + bt->btg_type = (val & BTC_BSCB_BT_S1 ? BTC_BT_BTG : BTC_BT_ALONE); + + bt_2g->a2dp_desc.exist = !!(val & BTC_BSCB_A2DP_ACT); + bt_2g->pan_desc.exist = !!(val & BTC_BSCB_PAN_ACT); + bt_2g->hfp_desc.exist = !!(val & BTC_BSCB_HFP_ACT); + + bt->lna_constrain = 4 + !!(val & BTC_BSCB_BT_LNAB0) + + !!(val & BTC_BSCB_BT_LNAB1) * 2; + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + if (val & BTC_BSCB_BT_15DOT4) + bt->func_type |= (BTC_BTF_THREAD | BTC_BTF_ZB); + else + bt->func_type &= ~(BTC_BTF_THREAD | BTC_BTF_ZB); + } + + bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA); + any_bt_connect = !!(val & BTC_BSCB_BT_CONNECT); + + /* if connect change */ + if (bt_2g->status.map.connect != any_bt_connect || + bt_56g->status.map.connect != any_bt_6g_connect) { + bt_link_change = true; + bt_2g->status.map.connect = any_bt_connect; + bt_56g->status.map.connect = any_bt_6g_connect; + } + + bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); + bt->scbd = val; + } + + _update_bt_ctrl_lps(rtwdev); + + if (bt_link_change) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bt status change!!\n", __func__); + /* TODO: Need to notify driver to update EXT-CTRL-BT-SLOT */ + } +} + +static void _update_bt_link_cnt(struct rtw89_dev *rtwdev, + struct rtw89_btc_bt_info *bt, bool b56g) +{ + struct rtw89_btc_bt_link_info *b = b56g ? &bt->link_info_56g : + &bt->link_info; + struct rtw89_btc_bt_leaudio_desc *leaudio = &b->leaudio_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &b->a2dp_desc; + struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; + struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; + struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc; + u8 bt_rf_band = b56g ? BTC_BT_B5G : BTC_BT_B2G; + u8 val; + + b->link_cnt.last = b->link_cnt.now; + b->link_cnt.now = 0; + b->link_cnt.chg = 0; + + b->link_cnt.now += hfp->exist; + b->link_cnt.now += hid->exist; + b->link_cnt.now += a2dp->exist; + b->link_cnt.now += pan->exist; + b->link_cnt.now += leaudio->bis_exist; + b->link_cnt.now += leaudio->cis_exist; + + if (leaudio->bis_exist || a2dp->sink) { + val = 70; + } else if (a2dp->exist) { + val = 40; + if (a2dp->vendor_id == 0x4c) + val += 10; + if (hid->exist) + val += 10; + } else if (pan->exist || pan->active || a2dp->active || + b->status.map.inq_pag) { + val = 30; + } else if (b->link_cnt.now == 0) { + val = 5; + } else { + val = 9; + } + bt->link_weight[bt_rf_band] = val; + + if (b->link_cnt.last != b->link_cnt.now) + b->link_cnt.chg = 1; +} + +static void _update_bt_leaudio_info(struct rtw89_dev *rtwdev, u8 bid, + void *buf, u32 len) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - u32 val; - bool status_change = false; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + struct rtw89_btc_bt_link_info *b = &bt->link_info; + struct rtw89_btc_bt_leaudio_info *src = buf; + struct rtw89_btc_bt_leaudio_desc *leaudio; + bool bt_56g = false, cis_exist, bis_exist; + u8 bis_cnt, cis_cnt; - if (!chip->scbd) - return; + if ((src->len & BIT(7)) && bt->band_56G_support) { + b = &bt->link_info_56g; + bt_56g = true; + } - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); + leaudio = &b->leaudio_desc; - val = _read_scbd(rtwdev); - if (val == BTC_SCB_INV_VALUE) { + if (!memcmp(b->leaudio_raw_info, src, BTC_BTINFO_MAX)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by invalid scbd value\n", + "[BTC], %s return by leaudio-info duplicate!\n", __func__); + bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++; return; } - if (!(val & BTC_BSCB_ON)) - bt->enable.now = 0; + memcpy(b->leaudio_raw_info, src, BTC_BTINFO_MAX); + + bis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_EX); + cis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_EX); + bis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_CNT); + cis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_CNT); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bis_exist:%d bis_cnt:%d cis_exist:%d cis_cnt:%d rssi:%d\n", + __func__, bis_exist, bis_cnt, cis_exist, cis_cnt, src->rssi); + + if (!bis_exist && !cis_exist) + memset(leaudio, 0, sizeof(*leaudio)); + + leaudio->bis_cnt = bis_cnt; + leaudio->bis_exist = bis_exist; + leaudio->cis_cnt = cis_cnt; + leaudio->cis_exist = cis_exist; + + if (leaudio->bis_exist) + b->status.map.profile_map |= BTC_BT_BIS; else - bt->enable.now = 1; + b->status.map.profile_map &= ~BTC_BT_BIS; - if (bt->enable.now != bt->enable.last) - status_change = true; + if (leaudio->cis_exist) + b->status.map.profile_map |= BTC_BT_CIS; + else + b->status.map.profile_map &= ~BTC_BT_CIS; - /* reset bt info if bt re-enable */ - if (bt->enable.now && !bt->enable.last) { - _reset_btc_var(rtwdev, BTC_RESET_BTINFO); - cx->cnt_bt[BTC_BCNT_REENABLE]++; - bt->enable.now = 1; + _update_bt_link_cnt(rtwdev, bt, bt_56g); + + leaudio->rssi = chip->ops->btc_get_bt_rssi(rtwdev, src->rssi); + + leaudio->bis_cnt_last = leaudio->bis_cnt; + leaudio->bis_exist_last = leaudio->bis_exist; + leaudio->cis_cnt_last = leaudio->cis_cnt; + leaudio->cis_exist_last = leaudio->cis_exist; +} + +static void _update_bt_bistdma_info(struct rtw89_dev *rtwdev, u8 bid, + void *buf, u32 len) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + struct rtw89_btc_bt_link_info *b = &bt->link_info; + struct rtw89_btc_bt_bistdma_info_le *src = buf; + struct rtw89_btc_bt_leaudio_desc *leaudio; + + if ((src->len & BIT(7)) && bt->band_56G_support) + b = &bt->link_info_56g; + + leaudio = &b->leaudio_desc; + + if (!memcmp(b->bistdma_raw_info, src, BTC_BTINFO_MAX)) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s return by bistdma-info duplicate!\n", + __func__); + bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++; + return; } - bt->enable.last = bt->enable.now; - bt->scbd = val; - bt->mbx_avl = !!(val & BTC_BSCB_ACT); + memcpy(b->bistdma_raw_info, src, BTC_BTINFO_MAX); - if (bt->whql_test != !!(val & BTC_BSCB_WHQL)) - status_change = true; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bis_start_end:%d bis_trx:%d\n", + __func__, + u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_START_END), + u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX)); - bt->whql_test = !!(val & BTC_BSCB_WHQL); - bt->btg_type = val & BTC_BSCB_BT_S1 ? BTC_BT_BTG : BTC_BT_ALONE; - bt->link_info.a2dp_desc.exist = !!(val & BTC_BSCB_A2DP_ACT); + leaudio->bis_trx = u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX); + leaudio->bis_start_end = u8_get_bits(src->bis, + RTW89_BTC_BIS_INFO_L2_START_END); - bt->lna_constrain = !!(val & BTC_BSCB_BT_LNAB0) + - !!(val & BTC_BSCB_BT_LNAB1) * 2 + 4; + leaudio->diff_t = (src->diff_t_hb * 256 + src->diff_t_lb) * 625 / 1000; - /* if rfk run 1->0 */ - if (bt->rfk_info.map.run && !(val & BTC_BSCB_RFK_RUN)) - status_change = true; - - bt->rfk_info.map.run = !!(val & BTC_BSCB_RFK_RUN); - bt->rfk_info.map.req = !!(val & BTC_BSCB_RFK_REQ); - bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA); - bt->link_info.status.map.connect = !!(val & BTC_BSCB_BT_CONNECT); - if (bt->run_patch_code != !!(val & BTC_BSCB_PATCH_CODE)) - status_change = true; - bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); - - if (!only_update && status_change) - _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); + if (btc->ant_type == BTC_ANT_SHARED && leaudio->diff_t > 13) + dm->bis_tdma = true; + else + dm->bis_tdma = false; } #define BTC_BTINFO_PWR_LEN 5 static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) { - struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; if (len != BTC_BTINFO_PWR_LEN) @@ -7467,68 +8095,342 @@ static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) memcpy(&b->bt_txpwr_desc, &buf[2], sizeof(b->bt_txpwr_desc)); } -static bool _chk_wl_rfk_request(struct rtw89_dev *rtwdev) +static void _set_bind_info(struct rtw89_btc *btc, u8 type) +{ + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_bt_info *bt = &cx->bt0; + struct rtw89_btc_bt_link_info *b; + struct rtw89_btc_bind_info *bd; + u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + u8 link_weight[BTC_ALL_BT_EZL][BTC_BT_BMAX]; + u8 map[BTC_RF_NUM][BTC_ALL_BT_EZL]; + u8 b2g_score = 0, b5g_score = 0; + u8 i, j, rf_band_map, thres; + u8 weight[BTC_BT_BMAX] = {}; + + if (type == BTC_MECH_TDD) { + bd = &dm->tdd_bind; + memcpy(map, dm->tdd_map, sizeof(map)); + thres = 30; /* BT profile score threshold for TDMA */ + } else { + bd = &dm->fdd_bind; + memcpy(map, dm->fdd_map, sizeof(map)); + thres = 5; + } + + memset(bd, 0, sizeof(*bd)); + /* compare BT 2GHz/5GHz profile by link-weighting */ + for (i = 0; i < BTC_BT_BMAX; i++) { + if (!cx->bt0.enable.now) + cx->bt0.link_weight[i] = 0; + if (!cx->bt1.enable.now) + cx->bt1.link_weight[i] = 0; + link_weight[BTC_BT_1ST][i] = cx->bt0.link_weight[i]; + link_weight[BTC_BT_2ND][i] = cx->bt1.link_weight[i]; + link_weight[BTC_BT_EXT][i] = cx->bt_ext.link_weight[i]; + } + + /* + * get 2GHz/5GHz link weight score by "band-overlap" map + * link_weight from _update_bt_link_cnt(), + * if link_weight >30 --> TDMA-required + */ + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + rf_band_map = map[BTC_RF_S0][j] | map[BTC_RF_S1][j]; + if (rf_band_map & BIT(RTW89_BAND_2G)) + b2g_score += link_weight[j][BTC_BT_B2G]; + if (rf_band_map & BIT(RTW89_BAND_5G)) + b5g_score += link_weight[j][BTC_BT_B5G]; + } + + /* rf-band bound by comparing link weight */ + if (b2g_score == 0 && b5g_score == 0) /* no-rf-band overlap */ + bd->rf_band = 0; + else if (b5g_score > b2g_score) + bd->rf_band = BIT(BTC_BT_B5G); + else + bd->rf_band = BIT(BTC_BT_B2G); + + /* + * if MR_WTYPE_MLD2L1R_NONMLD, FW will change 2+0/0+2/1+1 + * Therefore, BTC_MLO_RF_xxx is not real-time state. + * In this case, RF_S0->HWB0, RF_S1->HWB1 + * If link-mode change by _ntfy_generic(BTC_GNTFY_MRCX_INFO) + * HW-Band is decided by wl->mlo_info.mrcx_act_hwb_map + */ + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { + if (wl->role_info.link_mode != BTC_WLINK_DB_MCC) { /* mode chg */ + if (wl->mlo_info.mrcx_act_hwb_map == BIT(RTW89_MAC_1)) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* S0/1->HWB1 */ + else + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* S0/1->HWB0 */ + } + } else { + /* + * Dual-RF_band(HWB)TDMA if BT-profile is TDMA-type at both + * 2GHz and 5GHz. + * ex: 1+1: HWB0 5GHz + BT0 5GHz (BIS, HDT) + * && HWB1 2.4GHz + BT1 2.4GHz(PAN) + * for TDD: the link score must > 30 (from _bt_link_cnt) + * for FDD: the link score must > 5 (bt enable) + */ + if ((wl->mlo_info.rf_combination == BTC_MLO_RF_1_PLUS_1 || + wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_2) && + (b2g_score >= thres && b5g_score >= thres)) { + bd->rf_band = BIT(BTC_BT_B2G) | BIT(BTC_BT_B5G); + } + + if (wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_0) + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* 2+0 RF-S0/1->HWB0 */ + else if (wl->mlo_info.rf_combination == BTC_MLO_RF_0_PLUS_2) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* 2+0 RF-S0/1->HWB1 */ + } + + /* Get HWB-sel and BT-sel by rf-band-binding */ + for (i = 0; i < BTC_RF_NUM; i++) { + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + if (!(map[i][j] & bd->rf_band)) /* no-overlap */ + continue; + + bd->wl_hwb_sel |= BIT(path_hwb[i]); + bd->bt_sel |= BIT(j); + } + } + + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L2R && + (bd->wl_hwb_sel == (BIT(RTW89_MAC_1) | BIT(RTW89_MAC_0)))) { + if (wl->role_info.link_mode == BTC_WLINK_AP || + wl->role_info.link_mode_hb1 == BTC_WLINK_AP) + bd->wl_link_mode = BTC_WLINK_DB_MCC; + else + bd->wl_link_mode = BTC_WLINK_STA; + bd->wl_bg_mode = wl->bg_mode | wl->bg_mode_hb1; + } else if (bd->wl_hwb_sel == (BIT(RTW89_MAC_1))) { + bd->wl_link_mode = wl->role_info.link_mode_hb1; + bd->wl_bg_mode = wl->bg_mode_hb1; + } else {/* HW-BAND-0 or no-hw-band */ + bd->wl_hwb_sel = BIT(RTW89_MAC_0); + bd->wl_link_mode = wl->role_info.link_mode; + bd->wl_bg_mode = wl->bg_mode; + } + + /* update Bind-BT status map for BT0/BT1 */ + for (i = BTC_BT_1ST; i < BTC_ALL_BT_EZL; i++) { + if (!(bd->bt_sel & BIT(i))) + continue; + + for (j = BTC_BT_B2G; j <= BTC_BT_B5G; j++) { + if (!(bd->rf_band & BIT(j))) + continue; + + if (i == BTC_BT_EXT) { + bd->bt_profile |= cx->bt_ext.profile_map[j]; + weight[j] = cx->bt_ext.link_weight[j]; + if (weight[j] > bd->bt_link_weight) /* max */ + bd->bt_link_weight = weight[j]; + continue; + } else if (i == BTC_BT_1ST) { + bt = &cx->bt0; + } else { + bt = &cx->bt1; + } + + if (j == BTC_BT_B2G) + b = &bt->link_info; + else + b = &bt->link_info_56g; + + bd->bt_profile |= b->status.map.profile_map; + bd->bt_smap.a2dp_active |= b->a2dp_desc.active; + bd->bt_smap.a2dp_sink |= b->a2dp_desc.sink; + bd->bt_smap.pan_active |= b->pan_desc.active; + bd->bt_smap.connect |= b->status.map.connect; + bd->bt_smap.hid_cnt += b->hid_desc.pair_cnt; + bd->bt_smap.hid_type |= b->hid_desc.type; + bd->bt_smap.cis_cnt += b->leaudio_desc.cis_cnt; + bd->bt_smap.link_cnt += b->link_cnt.now; + bd->bt_smap.inq_page |= b->status.map.inq_pag; + bd->bt_smap.page |= b->pag; + + if (bt->link_weight[j] > bd->bt_link_weight) /* max */ + bd->bt_link_weight = bt->link_weight[j]; + + if (b->slave_role) + bd->bt_smap.slave_role = b->slave_role; + + if (b->a2dp_desc.vendor_id != 0) + bd->bt_smap.a2dp_vendor_id = + b->a2dp_desc.vendor_id; + } + } + + if (bd->bt_profile & BTC_BT_HFP) + bd->bt_smap.hfp_exist = 1; + if (bd->bt_profile & BTC_BT_HID) + bd->bt_smap.hid_exist = 1; + if (bd->bt_profile & BTC_BT_A2DP) + bd->bt_smap.a2dp_exist = 1; + if (bd->bt_profile & BTC_BT_PAN) + bd->bt_smap.pan_exist = 1; + if (bd->bt_profile & BTC_BT_BIS) + bd->bt_smap.bis_exist = 1; + if (bd->bt_profile & BTC_BT_CIS) + bd->bt_smap.cis_exist = 1; + if (bd->bt_profile & BTC_BT_THREAD) + bd->bt_smap.thread_exist = 1; + if (bd->bt_profile & BTC_BT_ULL) + bd->bt_smap.ull_exist = 1; +} + +#define _bind_is_btonly 0x7 +static void _set_coex_binding(struct rtw89_btc *btc) +{ + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_extsoc_info *bt2 = &cx->bt_ext; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_dm *dm = &btc->dm; + u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + u8 wl_rf_band[RTW89_BAND_NUM] = {}; + u8 i, j, val = 0; + + /* + * sit_xmap(Space-Interaction) = ant_xmap | xtk_xmap + * 1: WL-BT space-interference, always 1 if BTG/BTA/SPDT = 1 + * if dedicated-ant, it may be 1 if BT-Tx is bigger than WL-Rx(xtk_xmap) + * + * ant_xmap(ANT-Division-Multiplexing) + * ==> dedicated-ant->0, BTG/BTA/SPDT->1 + * + * xtk_xmap(Cross-talk map)-> calculate WL/BT interference by SIR + * ==> 1: interference, 0: no-interference + * + * fdm_map(Frequency-Division-Multiplexing): WL/BT RF_Band overlap-map + * 2bit-map: bit[1]:5/6GHz, bit[0]:2.4GHz + */ + for (i = 0; i < RTW89_PHY_NUM; i++) { + if (wl->rf_band_map[i] & BIT(RTW89_BAND_2G)) + wl_rf_band[i] |= BIT(RTW89_BAND_2G); + + if (wl->rf_band_map[i] & (BIT(RTW89_BAND_5G) | BIT(RTW89_BAND_6G))) + wl_rf_band[i] |= BIT(RTW89_BAND_5G); + + dm->fit_xmap[i][BTC_BT_1ST] = wl_rf_band[i] & bt0->rf_band_map; + dm->fit_xmap[i][BTC_BT_2ND] = wl_rf_band[i] & bt1->rf_band_map; + dm->fit_xmap[i][BTC_BT_EXT] = wl_rf_band[i] & bt2->rf_band_map; + } + + /* + * if MR_WTYPE_MLD2L1R_NONMLD, FW will change 2+0/0+2/1+1 + * Therefore, BTC_MLO_RF_xxx is not real-time state. + * if link mode change by _ntfy_generic(BTC_GNTFY_MRCX_INFO) + * the HW-BAND is decided by wl->mlo_info.mrcx_act_hwb_map + * In this case, BTC_RF_S0->HWB0, BTC_RF_S1->HWB1 + */ + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { + if (wl->role_info.link_mode != BTC_WLINK_DB_MCC) {/* mode chg */ + if (wl->mlo_info.mrcx_act_hwb_map == BIT(RTW89_PHY_1)) + path_hwb[BTC_RF_S0] = RTW89_PHY_1;/* S0/1->HWB1 */ + else + path_hwb[BTC_RF_S1] = RTW89_PHY_0;/* S0/1->HWB0 */ + } + } else { + if (wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_0) + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* 2+0 RF-S0/1->HWB0 */ + else if (wl->mlo_info.rf_combination == BTC_MLO_RF_0_PLUS_2) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* 2+0 RF-S0/1->HWB1 */ + } + + /* + * tdd_map = sit_xmap * fdm_map, 1: WL-RF-Sx vs. BTx take TDD-Action + * fdd_map =(!sit_xmap) * fdm_map, 1: WL-RF-Sx vs.BTx take FDD-Action + * co-rx map = ant_xmap * fdm_map 1:WL/BT co-rx (for halbb-btg-ctrl) + * sit_xmap,ant_xmap = 0 or 1, so tdd/fdd use multiplication (*) + */ + + for (i = 0; i < BTC_RF_NUM; i++) + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + dm->tdd_map[i][j] = dm->sit_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + dm->fdd_map[i][j] = !dm->sit_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + dm->corx_map[i][j] = dm->ant_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + } + + /* TDD-Binding */ + _set_bind_info(btc, BTC_MECH_TDD); + + /* FDD-Binding */ + _set_bind_info(btc, BTC_MECH_FDD); + + dm->out_of_band = !dm->tdd_bind.rf_band && !dm->fdd_bind.rf_band; + dm->fdd_en = !!dm->fdd_bind.rf_band; + dm->tdd_en = !!dm->tdd_bind.rf_band; + + /* set BT on/off state for GNT_WL Combined-MUX control */ + if (bt0->enable.now) + val |= BIT(0); + + if (bt1->enable.now) + val |= BIT(1); + + if (bt2->func_type) + val |= BIT(2); + + dm->ost_info.bt_enable_state = dm->bt_only ? _bind_is_btonly : val; +} + +static void _update_run_ctrl_info(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_ctrl ctrl = btc->ctrl; + struct rtw89_fbtc_wl_ctrl_info *wl_ctrl_info = &ctrl.wl_ctrl_info; + u8 i; - _update_bt_scbd(rtwdev, true); + ctrl.wl_only = dm->wl_only; + ctrl.bt_only = dm->bt_only; + ctrl.ntfy_type = dm->run_reason; - cx->cnt_wl[BTC_WCNT_RFK_REQ]++; + wl_ctrl_info->smap_val = wl->status.val; + wl_ctrl_info->rfk_state = wl->rfk_info.state; + wl_ctrl_info->rfk_type = wl->rfk_info.type; + wl_ctrl_info->client_pstdma_on = dm->client_ps_tdma_on; + wl_ctrl_info->fw_scan = wl->scan_info.fw_scan; - if ((bt->rfk_info.map.run || bt->rfk_info.map.req) && - !bt->rfk_info.map.timeout) { - cx->cnt_wl[BTC_WCNT_RFK_REJECT]++; - } else { - cx->cnt_wl[BTC_WCNT_RFK_GO]++; - return true; + for (i = RTW89_MAC_0; i <= RTW89_MAC_1; i++) { + wl_ctrl_info->rf_band_map[i] = wl->rf_band_map[i]; + wl_ctrl_info->rf_ch[i] = wl->rf_ch_info[i].center_ch; + } + + if (!memcmp(&btc->ctrl, &ctrl, sizeof(struct rtw89_btc_ctrl))) { + memcpy(&btc->ctrl, &ctrl, sizeof(struct rtw89_btc_ctrl)); + if (btc->ver->fcxctrl >= 9) + _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); } - return false; } static void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - u8 mode, igno_bt, always_freerun; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + u8 mode = btc->dm.tdd_bind.wl_link_mode; + u8 mode_v0 = BTC_WLINK_V0_OTHER; + bool has_2ghz; lockdep_assert_wiphy(rtwdev->hw->wiphy); dm->run_reason = reason; _update_dm_step(rtwdev, reason); - _update_btc_state_map(rtwdev); - - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; - - if (ver->fcxctrl == 7) { - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - always_freerun = btc->ctrl.ctrl_v7.always_freerun; - } else { - igno_bt = btc->ctrl.ctrl.igno_bt; - always_freerun = btc->ctrl.ctrl.always_freerun; - } rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): reason=%d, mode=%d\n", __func__, reason, mode); @@ -7543,15 +8445,6 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) return; } - if (igno_bt && - (reason == BTC_RSN_UPDATE_BT_INFO || - reason == BTC_RSN_UPDATE_BT_SCBD)) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return for Stop Coex DM!!\n", - __func__); - return; - } - if (!wl->status.map.init_ok) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return for WL init fail!!\n", @@ -7560,7 +8453,8 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) } if (wl->status.map.rf_off_pre == wl->status.map.rf_off && - wl->status.map.lps_pre == wl->status.map.lps) { + wl->status.map.lps_pre == wl->status.map.lps && + !dm->lps_ctrl_change && !dm->lps_ctrl_scbd) { if (reason == BTC_RSN_NTFY_POWEROFF || reason == BTC_RSN_NTFY_RADIO_STATE) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -7577,29 +8471,33 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) } } + _update_run_ctrl_info(rtwdev); + + if (reason == BTC_RSN_NTFY_INIT || reason == BTC_RSN_NTFY_RADIO_STATE) + _update_bt_scbd(rtwdev, false); + dm->freerun = false; dm->cnt_dm[BTC_DCNT_RUN]++; dm->fddt_train = BTC_FDDT_DISABLE; bt->scan_rx_low_pri = false; - igno_bt = false; + + _set_coex_binding(btc); + _update_btc_state_map(rtwdev); dm->freerun_chk = _check_freerun(rtwdev); /* check if meet freerun */ - if (always_freerun) { + if (btc->ctrl.always_freerun) { _action_freerun(rtwdev); - igno_bt = true; goto exit; } if (dm->wl_only) { _action_wl_only(rtwdev); - igno_bt = true; goto exit; } - if (wl->status.map.rf_off || wl->status.map.lps || dm->bt_only) { + if (wl->status.map.rf_off || dm->bt_only || wl->status.map.lps) { _action_wl_off(rtwdev, mode); - igno_bt = true; goto exit; } @@ -7608,12 +8506,12 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - if (!cx->bt.enable.now && !cx->other.type) { + if (!cx->bt0.enable.now && !cx->bt_ext.func_type) { _action_bt_off(rtwdev); goto exit; } - if (cx->bt.whql_test) { + if (cx->bt0.whql_test) { _action_bt_whql(rtwdev); goto exit; } @@ -7629,50 +8527,110 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - switch (mode) { + if (mode == BTC_WLINK_NOLINK) { + mode_v0 = BTC_WLINK_V0_NOLINK; + } else if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_5G)) { + mode_v0 = BTC_WLINK_V0_5G; + } else if (btc->dm.tdd_bind.rf_band & BIT(RTW89_BAND_5G) && + btc->dm.tdd_bind.rf_band & BIT(RTW89_BAND_2G)) { + mode_v0 = BTC_WLINK_V0_25G_MCC; + } else if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_2G)) { + switch (mode) { + case BTC_WLINK_STA: + mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + default: + mode_v0 = BTC_WLINK_V0_OTHER; + break; + } + } else { + /* No binding (out_of_band): derive mode_v0 from link_mode + rf_band_map */ + has_2ghz = wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G) || + wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G); + + if (!has_2ghz) { + mode_v0 = BTC_WLINK_V0_5G; + } else if (mode == BTC_WLINK_DB_MCC) { + mode_v0 = BTC_WLINK_V0_25G_MCC; + } else { + switch (mode) { + case BTC_WLINK_STA: + mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + default: + mode_v0 = BTC_WLINK_V0_OTHER; + break; + } + } + } + + switch (mode_v0) { case BTC_WLINK_NOLINK: _action_wl_nc(rtwdev); break; - case BTC_WLINK_2G_STA: + case BTC_WLINK_V0_2G_STA: if (wl->status.map.traffic_dir & BIT(RTW89_TFC_DL)) bt->scan_rx_low_pri = true; _action_wl_2g_sta(rtwdev); break; - case BTC_WLINK_2G_AP: + case BTC_WLINK_V0_2G_AP: bt->scan_rx_low_pri = true; _action_wl_2g_ap(rtwdev); break; - case BTC_WLINK_2G_GO: + case BTC_WLINK_V0_2G_GO: bt->scan_rx_low_pri = true; _action_wl_2g_go(rtwdev); break; - case BTC_WLINK_2G_GC: + case BTC_WLINK_V0_2G_GC: bt->scan_rx_low_pri = true; _action_wl_2g_gc(rtwdev); break; - case BTC_WLINK_2G_SCC: + case BTC_WLINK_V0_2G_SCC: bt->scan_rx_low_pri = true; - if (ver->fwlrole == 0) - _action_wl_2g_scc(rtwdev); - else if (ver->fwlrole == 1) - _action_wl_2g_scc_v1(rtwdev); - else if (ver->fwlrole == 2 || ver->fwlrole == 7) - _action_wl_2g_scc_v2(rtwdev); - else if (ver->fwlrole == 8) - _action_wl_2g_scc_v8(rtwdev); + _action_wl_2g_scc(rtwdev); break; - case BTC_WLINK_2G_MCC: + case BTC_WLINK_V0_2G_MCC: bt->scan_rx_low_pri = true; _action_wl_2g_mcc(rtwdev); break; - case BTC_WLINK_25G_MCC: + case BTC_WLINK_V0_25G_MCC: bt->scan_rx_low_pri = true; _action_wl_25g_mcc(rtwdev); break; - case BTC_WLINK_5G: + case BTC_WLINK_V0_5G: _action_wl_5g(rtwdev); break; - case BTC_WLINK_2G_NAN: + case BTC_WLINK_V0_2G_NAN: _action_wl_2g_nan(rtwdev); break; default: @@ -7682,13 +8640,26 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) exit: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): exit\n", __func__); - if (ver->fcxctrl == 7) - btc->ctrl.ctrl_v7.igno_bt = igno_bt; - else - btc->ctrl.ctrl.igno_bt = igno_bt; _action_common(rtwdev); } +void rtw89_btc_init(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): Init %s !!\n", __func__, + chip_id_str(chip->chip_id)); + + _reset_btc_var(rtwdev, BTC_RESET_ALL); + + btc->dm.run_reason = BTC_RSN_NONE; + btc->dm.run_action = BTC_ACT_NONE; + wl->status.map.init_ok = true; +} + void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; @@ -7709,38 +8680,44 @@ void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev) btc->cx.wl.status.map.busy = 0; wl->status.map.lps = BTC_LPS_OFF; - _write_scbd(rtwdev, BTC_WSCB_ALL, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ALL, false); _run_coex(rtwdev, BTC_RSN_NTFY_POWEROFF); - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, 0); - btc->cx.wl.status.map.rf_off_pre = btc->cx.wl.status.map.rf_off; } +#define BTC_PLATFORM_LITTLE_ENDIAN 0 static void _set_init_info(struct rtw89_dev *rtwdev) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - if (ver->fcxinit == 7) { - dm->init_info.init_v7.wl_only = (u8)dm->wl_only; - dm->init_info.init_v7.bt_only = (u8)dm->bt_only; - dm->init_info.init_v7.wl_init_ok = (u8)wl->status.map.init_ok; - dm->init_info.init_v7.cx_other = btc->cx.other.type; - dm->init_info.init_v7.wl_guard_ch = chip->afh_guard_ch; - dm->init_info.init_v7.module = btc->mdinfo.md_v7; - } else { - dm->init_info.init.wl_only = (u8)dm->wl_only; - dm->init_info.init.bt_only = (u8)dm->bt_only; - dm->init_info.init.wl_init_ok = (u8)wl->status.map.init_ok; - dm->init_info.init.dbcc_en = rtwdev->dbcc_en; - dm->init_info.init.cx_other = btc->cx.other.type; - dm->init_info.init.wl_guard_ch = chip->afh_guard_ch; - dm->init_info.init.module = btc->mdinfo.md; - } + dm->init_info.init_mode = wl->coex_mode; + dm->init_info.wl_init_ok = wl->status.map.init_ok; + dm->init_info.endian_type = BTC_PLATFORM_LITTLE_ENDIAN; + dm->init_info.bt0_function = cx->bt0.func_type; + dm->init_info.bt1_function = cx->bt1.func_type; + dm->init_info.bt2_function = cx->bt_ext.func_type; + dm->init_info.pta_mode = RTW89_MAC_AX_COEX_RTK_MODE; + dm->init_info.pta_direction = RTW89_MAC_AX_COEX_INNER; + dm->init_info.wl_only = dm->wl_only; + dm->init_info.bt_only = dm->bt_only; + dm->init_info.wl_init_ok = wl->status.map.init_ok; + dm->init_info.cx_other = btc->cx.bt_ext.func_type; + dm->init_info.wl_guard_ch = chip->afh_guard_ch; + dm->init_info.dbcc_en = rtwdev->dbcc_en; + + dm->init_info.module = btc->mdinfo; + + _fw_set_drv_info(rtwdev, CXDRVINFO_INIT); + _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); + rtw89_btc_fw_set_slots(rtwdev); + btc_fw_set_monreg(rtwdev); + _set_ext_interface(rtwdev); + _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0); } void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) @@ -7749,15 +8726,6 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; const struct rtw89_chip_info *chip = rtwdev->chip; - const struct rtw89_btc_ver *ver = btc->ver; - - _reset_btc_var(rtwdev, BTC_RESET_ALL); - btc->dm.run_reason = BTC_RSN_NONE; - btc->dm.run_action = BTC_ACT_NONE; - if (ver->fcxctrl == 7) - btc->ctrl.ctrl_v7.igno_bt = true; - else - btc->ctrl.ctrl.igno_bt = true; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): mode=%d\n", __func__, mode); @@ -7767,9 +8735,8 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) dm->wl_only = mode == BTC_MODE_WL ? 1 : 0; dm->bt_only = mode == BTC_MODE_BT ? 1 : 0; wl->status.map.rf_off = mode == BTC_MODE_WLOFF ? 1 : 0; - - chip->ops->btc_set_rfe(rtwdev); - chip->ops->btc_init_cfg(rtwdev); + dm->vid = rtwdev->custid; + btc->ctrl.always_freerun = mode == BTC_MODE_COTX; if (!wl->status.map.init_ok) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -7779,9 +8746,24 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) return; } - _write_scbd(rtwdev, + btc->cx.bt0.enable.now = 1; + btc->cx.bt0.run_patch_code = 1; + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + btc->cx.bt1.enable.now = 1; + btc->cx.bt1.run_patch_code = 1; + } + + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) + btc->io_oflld_type = BTC_IO_OFLD_BTC_H2C; + else + btc->io_oflld_type = BTC_IO_OFLD_NO_SUPPORT; + + chip->ops->btc_set_rfe(rtwdev); + chip->ops->btc_init_cfg(rtwdev); + + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true); - _update_bt_scbd(rtwdev, true); + if (rtw89_mac_get_ctrl_path(rtwdev)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): PTA owner warning!!\n", @@ -7790,7 +8772,7 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) } _set_init_info(rtwdev); - _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR); + _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0); btc_fw_set_monreg(rtwdev); rtw89_btc_fw_set_slots(rtwdev); _fw_set_drv_info(rtwdev, CXDRVINFO_INIT); @@ -7823,6 +8805,9 @@ void rtw89_btc_ntfy_scan_start(struct rtw89_dev *rtwdev, u8 phy_idx, u8 band) _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); } + if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD, &rtwdev->fw)) + btc->ctrl.wl_ctrl_info.fw_scan = 1; + _run_coex(rtwdev, BTC_RSN_NTFY_SCAN_START); } @@ -7877,7 +8862,7 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_link_info *b = &cx->bt.link_info; + struct rtw89_btc_bt_link_info *b = &cx->bt0.link_info; struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; u32 cnt; @@ -7886,31 +8871,35 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, switch (pkt_type) { case PACKET_DHCP: - cnt = ++cx->cnt_wl[BTC_WCNT_DHCP]; + cnt = ++wl->wcnt[BTC_WCNT_DHCP]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): DHCP cnt=%d\n", __func__, cnt); - wl->status.map.connecting = true; + wl->status.map.handshake = 0; + wl->status.map._4way = 0; + wl->status.map.dhcp = 1; delay_work = true; break; case PACKET_EAPOL: - cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL]; + cnt = ++wl->wcnt[BTC_WCNT_EAPOL]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL cnt=%d\n", __func__, cnt); - wl->status.map._4way = true; + wl->status.map.handshake = 0; + wl->status.map._4way = 1; + wl->status.map.dhcp = 0; delay_work = true; if (hfp->exist || hid->exist) delay /= 2; break; case PACKET_EAPOL_END: - cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL]; + cnt = ++wl->wcnt[BTC_WCNT_EAPOL]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL_End cnt=%d\n", __func__, cnt); - wl->status.map._4way = false; + wl->status.map._4way = 0; wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); break; case PACKET_ARP: - cnt = ++cx->cnt_wl[BTC_WCNT_ARP]; + cnt = ++wl->wcnt[BTC_WCNT_ARP]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): ARP cnt=%d\n", __func__, cnt); return; @@ -7982,7 +8971,7 @@ static u8 _update_bt_rssi_level(struct rtw89_dev *rtwdev, u8 rssi) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u8 *rssi_st, rssi_th, rssi_level = 0; u8 i; @@ -8009,97 +8998,99 @@ static u8 _update_bt_rssi_level(struct rtw89_dev *rtwdev, u8 rssi) return rssi_level; } -static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) -{ - u8 mode = rtwdev->btc.cx.wl.role_info.link_mode; - u32 zb_tbl0 = 0xda5a5a5a, zb_tbl1 = 0xda5a5a5a; - - if (mode == BTC_WLINK_5G || rtwdev->btc.dm.freerun) { - zb_tbl0 = 0xffffffff; - zb_tbl1 = 0xffffffff; - } else if (mode == BTC_WLINK_25G_MCC) { - zb_tbl0 = 0xffffffff; /* for E5G slot */ - zb_tbl1 = 0xda5a5a5a; /* for E2G slot */ - } - rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_0, zb_tbl0); - rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, zb_tbl1); -} - #define BT_PROFILE_PROTOCOL_MASK GENMASK(7, 4) -static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) +static void _update_bt_info(struct rtw89_dev *rtwdev, u8 bid, u8 *buf, u32 len) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_a2dp_desc *a2dp; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; - struct rtw89_btc_bt_link_info *b = &bt->link_info; - struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; - struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &b->a2dp_desc; - struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc; + struct rtw89_btc_bt_hfp_desc *hfp; + struct rtw89_btc_bt_hid_desc *hid; + struct rtw89_btc_bt_pan_desc *pan; + struct rtw89_btc_bt_link_info *b; + struct rtw89_btc_bt_info *bt; union btc_btinfo btinfo; + u8 is_bt_56g = 0; + u8 *raw_info; - if (buf[BTC_BTINFO_L1] != 6) + /* Bit7 is used for RF-band: 0:BT_2.4 GHz, 1:BT_5/6 GHz */ + if ((buf[BTC_BTINFO_L1] & 0x7f) != 6) return; - if (!memcmp(bt->raw_info, buf, BTC_BTINFO_MAX)) { + bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + + if ((buf[BTC_BTINFO_L1] & BIT(7)) && bt->band_56G_support) { + b = &bt->link_info_56g; + is_bt_56g = 1; + if (b->status.map.connect) + bt->rf_band_map |= BIT(RTW89_BAND_5G); + else + bt->rf_band_map &= ~BIT(RTW89_BAND_5G); + } else { + b = &bt->link_info; + bt->rf_band_map |= BIT(RTW89_BAND_2G); + } + + hfp = &b->hfp_desc; + hid = &b->hid_desc; + a2dp = &b->a2dp_desc; + pan = &b->pan_desc; + raw_info = is_bt_56g ? bt->raw_info_56g : bt->raw_info; + + if (!memcmp(raw_info, buf, BTC_BTINFO_MAX)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return by bt-info duplicate!!\n", __func__); - cx->cnt_bt[BTC_BCNT_INFOSAME]++; + bt->bcnt[BTC_BCNT_INFOSAME]++; return; } - memcpy(bt->raw_info, buf, BTC_BTINFO_MAX); + memcpy(raw_info, buf, BTC_BTINFO_MAX); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): bt_info[2]=0x%02x\n", - __func__, bt->raw_info[2]); + __func__, raw_info[2]); - b->profile_cnt.last = b->profile_cnt.now; - b->profile_cnt.now = 0; hid->type = 0; /* parse raw info low-Byte2 */ - btinfo.val = bt->raw_info[BTC_BTINFO_L2]; + btinfo.val = raw_info[BTC_BTINFO_L2]; b->status.map.connect = btinfo.lb2.connect; b->status.map.sco_busy = btinfo.lb2.sco_busy; b->status.map.acl_busy = btinfo.lb2.acl_busy; b->status.map.inq_pag = btinfo.lb2.inq_pag; bt->inq_pag.now = btinfo.lb2.inq_pag; - cx->cnt_bt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); + bt->bcnt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); hfp->exist = btinfo.lb2.hfp; - b->profile_cnt.now += (u8)hfp->exist; hid->exist = btinfo.lb2.hid; - b->profile_cnt.now += (u8)hid->exist; a2dp->exist = btinfo.lb2.a2dp; - b->profile_cnt.now += (u8)a2dp->exist; pan->exist = btinfo.lb2.pan; - b->profile_cnt.now += (u8)pan->exist; + _update_bt_link_cnt(rtwdev, bt, is_bt_56g); btc->dm.trx_info.bt_profile = u32_get_bits(btinfo.val, BT_PROFILE_PROTOCOL_MASK); /* parse raw info low-Byte3 */ - btinfo.val = bt->raw_info[BTC_BTINFO_L3]; + btinfo.val = raw_info[BTC_BTINFO_L3]; if (btinfo.lb3.retry != 0) - cx->cnt_bt[BTC_BCNT_RETRY]++; + bt->bcnt[BTC_BCNT_RETRY]++; b->cqddr = btinfo.lb3.cqddr; - cx->cnt_bt[BTC_BCNT_INQ] += !!(btinfo.lb3.inq && !bt->inq); + bt->bcnt[BTC_BCNT_INQ] += !!(btinfo.lb3.inq && !bt->inq); bt->inq = btinfo.lb3.inq; - cx->cnt_bt[BTC_BCNT_PAGE] += !!(btinfo.lb3.pag && !bt->pag); + bt->bcnt[BTC_BCNT_PAGE] += !!(btinfo.lb3.pag && !bt->pag); bt->pag = btinfo.lb3.pag; b->status.map.mesh_busy = btinfo.lb3.mesh_busy; /* parse raw info high-Byte0 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H0]; + btinfo.val = raw_info[BTC_BTINFO_H0]; /* raw val is dBm unit, translate from -100~ 0dBm to 0~100%*/ b->rssi = chip->ops->btc_get_bt_rssi(rtwdev, btinfo.hb0.rssi); bt->rssi_level = _update_bt_rssi_level(rtwdev, b->rssi); btc->dm.trx_info.bt_rssi = bt->rssi_level; /* parse raw info high-Byte1 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H1]; + btinfo.val = raw_info[BTC_BTINFO_H1]; b->status.map.ble_connect = btinfo.hb1.ble_connect; if (btinfo.hb1.ble_connect) { if (hid->exist) @@ -8110,11 +9101,11 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) hid->type |= BTC_HID_RCU; } - cx->cnt_bt[BTC_BCNT_REINIT] += !!(btinfo.hb1.reinit && !bt->reinit); + bt->bcnt[BTC_BCNT_REINIT] += !!(btinfo.hb1.reinit && !bt->reinit); bt->reinit = btinfo.hb1.reinit; - cx->cnt_bt[BTC_BCNT_RELINK] += !!(btinfo.hb1.relink && !b->relink.now); + bt->bcnt[BTC_BCNT_RELINK] += !!(btinfo.hb1.relink && !b->relink.now); b->relink.now = btinfo.hb1.relink; - cx->cnt_bt[BTC_BCNT_IGNOWL] += !!(btinfo.hb1.igno_wl && !bt->igno_wl); + bt->bcnt[BTC_BCNT_IGNOWL] += !!(btinfo.hb1.igno_wl && !bt->igno_wl); bt->igno_wl = btinfo.hb1.igno_wl; if (bt->igno_wl && !cx->wl.status.map.rf_off) @@ -8122,16 +9113,16 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) bt->ble_scan_en = btinfo.hb1.ble_scan; - cx->cnt_bt[BTC_BCNT_ROLESW] += !!(btinfo.hb1.role_sw && !b->role_sw); + bt->bcnt[BTC_BCNT_ROLESW] += !!(btinfo.hb1.role_sw && !b->role_sw); b->role_sw = btinfo.hb1.role_sw; b->multi_link.now = btinfo.hb1.multi_link; /* parse raw info high-Byte2 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H2]; + btinfo.val = raw_info[BTC_BTINFO_H2]; pan->active = !!btinfo.hb2.pan_active; - cx->cnt_bt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update); + bt->bcnt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update); b->afh_update = btinfo.hb2.afh_update; a2dp->active = btinfo.hb2.a2dp_active; b->slave_role = btinfo.hb2.slave; @@ -8141,11 +9132,11 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) hid->type |= (hid->slot_info == BTC_HID_218 ? BTC_HID_218 : BTC_HID_418); /* parse raw info high-Byte3 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H3]; + btinfo.val = raw_info[BTC_BTINFO_H3]; a2dp->bitpool = btinfo.hb3.a2dp_bitpool; if (b->tx_3m != (u32)btinfo.hb3.tx_3m) - cx->cnt_bt[BTC_BCNT_RATECHG]++; + bt->bcnt[BTC_BCNT_RATECHG]++; b->tx_3m = (u32)btinfo.hb3.tx_3m; a2dp->sink = btinfo.hb3.a2dp_sink; @@ -8159,9 +9150,82 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) RTW89_COEX_BT_DEVINFO_WORK_PERIOD); } + _update_bt_ctrl_lps(rtwdev); _run_coex(rtwdev, BTC_RSN_UPDATE_BT_INFO); } +static void _ntfy_role_info(struct rtw89_dev *rtwdev, u8 rid, + struct rtw89_btc_wl_link_info *info, + enum btc_role_state reason) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_link_info *wlinfo = NULL; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + const struct rtw89_btc_ver *ver = btc->ver; + u8 rlink_id = info->phy; /* 1 role_id has 2 rlink_id(by HW_Band0/1) */ + bool refresh_role = false; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(), role_id=%d, reason=%d\n", + __func__, rid, reason); + + if (rid >= ver->max_role_num || rlink_id >= RTW89_PHY_NUM) + return; + + btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; + + wlinfo = &wl->rlink_info[rid][rlink_id]; + memcpy(wlinfo, info, sizeof(struct rtw89_btc_wl_link_info)); + + switch (reason) { + case BTC_ROLE_START: + wlinfo->active = true; + return; + case BTC_ROLE_STOP: + wlinfo->active = false; + return; + case BTC_ROLE_MSTS_STA_CONN_START: + wl->status.map.transacting = 1; + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work, + RTW89_COEX_ACT1_WORK_PERIOD); + break; + case BTC_ROLE_MSTS_STA_CONN_END: + wl->status.map.transacting = 0; + refresh_role = true; + break; + case BTC_ROLE_MSTS_STA_DIS_CONN: + wl->status.map.transacting = 1; + refresh_role = false; + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work, + RTW89_COEX_ACT1_WORK_PERIOD); + break; + case BTC_ROLE_MSTS_AP_START: + case BTC_ROLE_MSTS_AP_STOP: + refresh_role = true; + break; + case BTC_ROLE_STATE_UNKNOWN: + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(), role_id=%d, Unknown reason return!\n", + __func__, rid); + return; + default: + return; + } + + if (refresh_role) { + _update_wl_info(rtwdev, wlinfo); + _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); + } + + _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); +} + void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_sta_link *rtwsta_link, @@ -8170,24 +9234,24 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link); + struct ieee80211_p2p_noa_attr *noa_attr; + struct ieee80211_p2p_noa_desc *noa_desc; struct ieee80211_bss_conf *bss_conf; struct ieee80211_link_sta *link_sta; - struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info r = {0}; - struct rtw89_btc_wl_link_info *wlinfo = NULL; - u8 mode = 0, rlink_id, link_mode_ori, pta_req_mac_ori, wa_type; + u8 i, role_id, mode = 0; rcu_read_lock(); bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, false); + role_id = rtwvif_link->port; + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], state=%d\n", state); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], role is STA=%d\n", vif->type == NL80211_IFTYPE_STATION); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", rtwvif_link->port); + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", role_id); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], band=%d ch=%d bw=%d\n", chan->band_type, chan->channel, chan->band_width); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], associated=%d\n", @@ -8225,11 +9289,29 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], wifi_role=%d\n", rtwvif_link->wifi_role); + if (vif->type == NL80211_IFTYPE_P2P_GO || + vif->type == NL80211_IFTYPE_P2P_CLIENT) { + noa_attr = &bss_conf->p2p_noa_attr; + + for (i = 0; i < RTW89_P2P_MAX_NOA_NUM; i++) { + noa_desc = &noa_attr->desc[i]; + if (noa_desc->count != 0) { + r.noa = 1; + r.noa_duration = le32_to_cpu(noa_desc->duration); + break; + } + } + } + + r.mode = mode; r.role = rtwvif_link->wifi_role; r.phy = rtwvif_link->phy_idx; - r.pid = rtwvif_link->port; + r.pid = role_id; r.active = true; - r.connected = MLME_LINKED; + if (vif->active_links) { + if (rtwvif_link->link_id < 16) + r.active = !!(vif->active_links & BIT(rtwvif_link->link_id)); + } r.bcn_period = bss_conf->beacon_int; r.dtim_period = bss_conf->dtim_period; r.band = chan->band_type; @@ -8239,66 +9321,25 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, r.chdef.center_ch = chan->channel; r.chdef.bw = chan->band_width; r.chdef.chan = chan->primary_channel; - ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr); + + if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) { + r.mac_id = rtwsta_link->mac_id; + ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr); + } + + switch (state) { + case BTC_ROLE_MSTS_STA_CONN_END: + case BTC_ROLE_MSTS_AP_START: + r.connected = MLME_LINKED; + break; + default: + r.connected = MLME_NO_LINK; + break; + } rcu_read_unlock(); - if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) - r.mac_id = rtwsta_link->mac_id; - - btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; - - wlinfo = &wl->link_info[r.pid]; - - if (ver->fwlrole == 0) { - *wlinfo = r; - _update_wl_info(rtwdev); - } else if (ver->fwlrole == 1) { - *wlinfo = r; - _update_wl_info_v1(rtwdev); - } else if (ver->fwlrole == 2) { - *wlinfo = r; - _update_wl_info_v2(rtwdev); - } else if (ver->fwlrole == 7) { - *wlinfo = r; - _update_wl_info_v7(rtwdev, r.pid); - } else if (ver->fwlrole == 8) { - rlink_id = rtwvif_link->mac_idx; - wlinfo = &wl->rlink_info[r.pid][rlink_id]; - *wlinfo = r; - link_mode_ori = wl->role_info_v8.link_mode; - pta_req_mac_ori = wl->pta_req_mac; - _update_wl_info_v8(rtwdev, r.pid, rlink_id, state); - - if (wl->role_info_v8.link_mode != link_mode_ori) { - wl->role_info_v8.link_mode_chg = 1; - if (ver->fcxinit == 7) - wa_type = btc->mdinfo.md_v7.wa_type; - else - wa_type = btc->mdinfo.md.wa_type; - - if (wa_type & BTC_WA_HFP_ZB) - _update_zb_coex_tbl(rtwdev); - } - - if (wl->pta_req_mac != pta_req_mac_ori) - wl->pta_reg_mac_chg = 1; - } - - if (wlinfo->role == RTW89_WIFI_ROLE_STATION && - wlinfo->connected == MLME_NO_LINK) - btc->dm.leak_ap = 0; - - if (state == BTC_ROLE_MSTS_STA_CONN_START) - wl->status.map.connecting = 1; - else - wl->status.map.connecting = 0; - - if (state == BTC_ROLE_MSTS_STA_DIS_CONN || - state == BTC_ROLE_MSTS_STA_CONN_END) - wl->status.map._4way = false; - - _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); + _ntfy_role_info(rtwdev, role_id, &r, state); } void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_state) @@ -8336,23 +9377,16 @@ void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_sta } if (rf_state == BTC_RFCTRL_WL_ON) { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, true); val = BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG; - _write_scbd(rtwdev, val, true); - _update_bt_scbd(rtwdev, true); + _write_scbd(rtwdev, BTC_ALL_BT, val, true); chip->ops->btc_init_cfg(rtwdev); } else { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, false); if (rf_state == BTC_RFCTRL_FW_CTRL) - _write_scbd(rtwdev, BTC_WSCB_ACTIVE, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE, false); else if (rf_state == BTC_RFCTRL_WL_OFF) - _write_scbd(rtwdev, BTC_WSCB_ALL, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ALL, false); else - _write_scbd(rtwdev, BTC_WSCB_ACTIVE, false); - - if (rf_state == BTC_RFCTRL_LPS_WL_ON && - wl->status.map.lps_pre != BTC_LPS_OFF) - _update_bt_scbd(rtwdev, true); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE, false); } btc->dm.cnt_dm[BTC_DCNT_BTCNT_HANG] = 0; @@ -8384,33 +9418,31 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, switch (state) { case BTC_WRFK_START: - result = _chk_wl_rfk_request(rtwdev); - wl->rfk_info.state = result ? BTC_WRFK_START : BTC_WRFK_STOP; - - _write_scbd(rtwdev, BTC_WSCB_WLRFK, result); + result = BTC_WRFK_ALLOW; + wl->rfk_info.state = BTC_WRFK_START; + btc->cx.wl.wcnt[BTC_WCNT_RFK_REQ]++; btc->dm.cnt_notify[BTC_NCNT_WL_RFK]++; + + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, true); break; case BTC_WRFK_ONESHOT_START: case BTC_WRFK_ONESHOT_STOP: - if (wl->rfk_info.state == BTC_WRFK_STOP) { - result = BTC_WRFK_REJECT; - } else { - result = BTC_WRFK_ALLOW; - wl->rfk_info.state = state; - } + btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++; + if (type != BTC_WRFKT_RXDCK) + return BTC_WRFK_ALLOW; break; case BTC_WRFK_STOP: result = BTC_WRFK_ALLOW; wl->rfk_info.state = BTC_WRFK_STOP; - _write_scbd(rtwdev, BTC_WSCB_WLRFK, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, false); wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_rfk_chk_work); break; default: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s() warning state=%d\n", __func__, state); - break; + return result; } if (result == BTC_WRFK_ALLOW) { @@ -8428,7 +9460,7 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, "[BTC], %s()_finish: rfk_cnt=%d, result=%d\n", __func__, btc->dm.cnt_notify[BTC_NCNT_WL_RFK], result); - return result == BTC_WRFK_ALLOW; + return result; } void rtw89_btc_ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_map, @@ -8487,15 +9519,14 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, struct rtw89_dev *rtwdev = iter_data->rtwdev; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info *link_info = NULL; struct rtw89_traffic_stats *link_info_t = NULL; struct rtw89_traffic_stats *stats = &rtwvif->stats; const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc_wl_role_info *r; - struct rtw89_btc_wl_role_info_v1 *r1; u32 last_tx_rate, last_rx_rate; + u8 link = rtwvif_link->phy_idx; u16 last_tx_lvl, last_rx_lvl; u8 port = rtwvif_link->port; u8 rssi; @@ -8508,10 +9539,7 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, rssi = ewma_rssi_read(&rtwsta_link->avg_rssi) >> RSSI_FACTOR; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], rssi=%d\n", rssi); - if (btc->ver->fwlrole != 8) - link_info = &wl->link_info[port]; - else - link_info = &wl->rlink_info[port][rtwvif_link->mac_idx]; + link_info = &wl->rlink_info[port][link]; link_info->stat.traffic = *stats; link_info_t = &link_info->stat.traffic; @@ -8551,7 +9579,6 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, else dir = RTW89_TFC_DL; - link_info = &wl->link_info[port]; if (link_info->busy != busy || link_info->dir != dir) { is_sta_change = true; link_info->busy = busy; @@ -8581,19 +9608,11 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, dm->trx_info.rx_rate = link_info_t->rx_rate; } - if (ver->fwlrole == 0) { - r = &wl->role_info; - r->active_role[port].tx_lvl = stats->tx_tfc_lv; - r->active_role[port].rx_lvl = stats->rx_tfc_lv; - r->active_role[port].tx_rate = rtwsta_link->ra_report.hw_rate; - r->active_role[port].rx_rate = rtwsta_link->rx_hw_rate; - } else if (ver->fwlrole == 1) { - r1 = &wl->role_info_v1; - r1->active_role_v1[port].tx_lvl = stats->tx_tfc_lv; - r1->active_role_v1[port].rx_lvl = stats->rx_tfc_lv; - r1->active_role_v1[port].tx_rate = rtwsta_link->ra_report.hw_rate; - r1->active_role_v1[port].rx_rate = rtwsta_link->rx_hw_rate; - } + r = &wl->role_info; + r->rlink[port][link].tx_lvl = stats->tx_tfc_lv; + r->rlink[port][link].rx_lvl = stats->rx_tfc_lv; + r->rlink[port][link].tx_rate = rtwsta_link->ra_report.hw_rate; + r->rlink[port][link].rx_rate = rtwsta_link->rx_hw_rate; dm->trx_info.tx_lvl = stats->tx_tfc_lv; dm->trx_info.rx_lvl = stats->rx_tfc_lv; @@ -8661,7 +9680,7 @@ void rtw89_btc_ntfy_wl_sta(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): busy=%d\n", __func__, !!wl->status.map.busy); - _write_scbd(rtwdev, BTC_WSCB_WLBUSY, (!!wl->status.map.busy)); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLBUSY, (!!wl->status.map.busy)); if (data.is_traffic_change) _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); @@ -8705,7 +9724,7 @@ static u8 rtw89_btc_c2h_get_index_by_ver(struct rtw89_dev *rtwdev, u8 func) return BTF_EVNT_BUF_OVERFLOW; else if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_BUF_OVERFLOW; else return BTF_EVNT_MAX; @@ -8716,19 +9735,26 @@ static u8 rtw89_btc_c2h_get_index_by_ver(struct rtw89_dev *rtwdev, u8 func) return BTF_EVNT_C2H_LOOPBACK; else if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_C2H_LOOPBACK; else return BTF_EVNT_MAX; case BTF_EVNT_C2H_LOOPBACK: if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_BT_LEAUDIO_INFO; else return BTF_EVNT_MAX; case BTF_EVNT_BT_QUERY_TXPWR: - if (ver->fwc2hfunc == 3) + if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) + return func; + else + return BTF_EVNT_MAX; + case BTF_EVNT_ZB_INFO: + case BTF_EVNT_ZB_CH: + case BTF_EVNT_ZB_QUERY_TXPWR: + if (ver->fwc2hfunc == 4) return func; else return BTF_EVNT_MAX; @@ -8742,8 +9768,10 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, u32 len, u8 class, u8 func) { struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; u8 *buf = &skb->data[RTW89_C2H_HEADER_LEN]; + u8 bid = BTC_BT_1ST; len -= RTW89_C2H_HEADER_LEN; @@ -8754,6 +9782,12 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, if (class != BTFC_FW_EVENT) return; + if (func & BT_C2H_FUNC_BT2ND) { + bid = BTC_BT_2ND; + func &= ~BT_C2H_FUNC_BT2ND; + bt = &btc->cx.bt1; + } + func = rtw89_btc_c2h_get_index_by_ver(rtwdev, func); pfwinfo->cnt_c2h++; @@ -8769,14 +9803,19 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, case BTF_EVNT_BT_INFO: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], handle C2H BT INFO with data %8ph\n", buf); - btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE]++; - _update_bt_info(rtwdev, buf, len); + bt->bcnt[BTC_BCNT_INFOUPDATE]++; + _update_bt_info(rtwdev, bid, buf, len); break; case BTF_EVNT_BT_SCBD: + bt->bcnt[BTC_BCNT_SCBDUPDATE]++; + bt->scbd_c2h = ((buf[3] << 24) | (buf[2] << 16) | + (buf[1] << 8) | (buf[0])); + bt->scbd_rb = bt->scbd_c2h; rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], handle C2H BT SCBD with data %8ph\n", buf); - btc->cx.cnt_bt[BTC_BCNT_SCBDUPDATE]++; - _update_bt_scbd(rtwdev, false); + "[BTC], handle C2H BT%d SCBD with data 0x%08x\n", + bid, bt->scbd_c2h); + _update_bt_scbd(rtwdev, bid); + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); break; case BTF_EVNT_BT_PSD: break; @@ -8792,8 +9831,16 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, case BTF_EVNT_CX_RUNINFO: btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++; break; + case BTF_EVNT_BT_LEAUDIO_INFO: + bt->bcnt[BTC_BCNT_LEAUDIO_INFOUPDATE]++; + if (buf[BTC_BTINFO_L0] == BTC_BTINFO_BISTDMA) + _update_bt_bistdma_info(rtwdev, bid, buf, len); + else + _update_bt_leaudio_info(rtwdev, bid, buf, len); + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_LEAUDINFO); + break; case BTF_EVNT_BT_QUERY_TXPWR: - btc->cx.cnt_bt[BTC_BCNT_BTTXPWR_UPDATE]++; + bt->bcnt[BTC_BCNT_TXPWR_UPDATE]++; _update_bt_txpwr_info(rtwdev, buf, len); } } @@ -8802,16 +9849,15 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_btc_ver *ver = rtwdev->btc.ver; struct rtw89_hal *hal = &rtwdev->hal; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u32 ver_main = 0, ver_sub = 0, ver_hotfix = 0, id_branch = 0; - u8 cv, rfe, iso, ant_num, ant_single_pos; char *p = buf, *end = buf + bufsz; if (!(dm->coex_info_map & BTC_COEX_INFO_CX)) @@ -8848,44 +9894,27 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) (bt->ver_info.fw_coex >= ver->bt_desired ? "Match" : "Mismatch"), ver->bt_desired); - if (bt->enable.now && bt->ver_info.fw == 0) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, false); - ver_main = FIELD_GET(GENMASK(31, 24), wl->ver_info.fw); ver_sub = FIELD_GET(GENMASK(23, 16), wl->ver_info.fw); ver_hotfix = FIELD_GET(GENMASK(15, 8), wl->ver_info.fw); id_branch = FIELD_GET(GENMASK(7, 0), wl->ver_info.fw); p += scnprintf(p, end - p, - " %-15s : WL_FW:%d.%d.%d.%d, BT_FW:0x%x(%s)\n", + " %-15s : WL_FW:%d.%d.%d.%d(built:%.12s-%.12s), BT_FW:0x%x(%s)\n", "[sub_module]", ver_main, ver_sub, ver_hotfix, id_branch, + wl->ver_info.build_time, wl->ver_info.build_date, bt->ver_info.fw, bt->run_patch_code ? "patch" : "ROM"); - if (ver->fcxinit == 7) { - cv = md->md_v7.kt_ver; - rfe = md->md_v7.rfe_type; - iso = md->md_v7.ant.isolation; - ant_num = md->md_v7.ant.num; - ant_single_pos = md->md_v7.ant.single_pos; - } else { - cv = md->md.cv; - rfe = md->md.rfe_type; - iso = md->md.ant.isolation; - ant_num = md->md.ant.num; - ant_single_pos = md->md.ant.single_pos; - } - p += scnprintf(p, end - p, " %-15s : cv:%x, rfe_type:0x%x, ant_iso:%d, ant_pg:%d, %s", - "[hw_info]", cv, rfe, iso, ant_num, - ant_num > 1 ? "" : - ant_single_pos ? "1Ant_Pos:S1, " : "1Ant_Pos:S0, "); + "[hw_info]", + md->kt_ver, md->rfe_type, md->ant.isolation, + md->ant.num, md->ant.num > 1 ? "" : + md->ant.single_pos ? "1Ant_Pos:S1, " : "1Ant_Pos:S0, "); p += scnprintf(p, end - p, "3rd_coex:%d, dbcc:%d, tx_num:%d, rx_num:%d\n", - btc->cx.other.type, rtwdev->dbcc_en, hal->tx_nss, + btc->cx.bt_ext.func_type, rtwdev->dbcc_en, hal->tx_nss, hal->rx_nss); return p - buf; @@ -8901,10 +9930,7 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) for (i = 0; i < btc->ver->max_role_num; i++) { for (j = 0; j < RTW89_MAC_NUM; j++) { - if (btc->ver->fwlrole == 8) - plink = &btc->cx.wl.rlink_info[i][j]; - else - plink = &btc->cx.wl.link_info[i]; + plink = &btc->cx.wl.rlink_info[i][j]; if (!plink->active) continue; @@ -8916,8 +9942,10 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) plink->client_cnt - 1, plink->mode, plink->ch, plink->bw); - if (plink->connected == MLME_NO_LINK) + if (plink->connected == MLME_NO_LINK) { + p += scnprintf(p, end - p, "\n"); continue; + } p += scnprintf(p, end - p, ", mac_id=%d, max_tx_time=%dus, max_tx_retry=%d\n", @@ -8947,14 +9975,9 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; char *p = buf, *end = buf + bufsz; u8 mode; @@ -8963,18 +9986,7 @@ static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [WL Status] ==========\n"); - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - goto out; + mode = wl_rinfo->link_mode; p += scnprintf(p, end - p, " %-15s : link_mode:%s, ", "[status]", id_to_linkmode(mode)); @@ -8986,15 +9998,14 @@ static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) wl->scan_info.band[RTW89_PHY_0], wl->scan_info.phy_map); p += scnprintf(p, end - p, - "connecting:%s, roam:%s, 4way:%s, init_ok:%s\n", - wl->status.map.connecting ? "Y" : "N", + "handshake:%s, roam:%s, 4way:%s, init_ok:%s\n", + wl->status.map.handshake ? "Y" : "N", wl->status.map.roaming ? "Y" : "N", wl->status.map._4way ? "Y" : "N", wl->status.map.init_ok ? "Y" : "N"); p += _show_wl_role_info(rtwdev, p, end - p); -out: return p - buf; } @@ -9004,10 +10015,13 @@ enum btc_bt_a2dp_type { BTC_A2DP_TWS_RELAY = 2, }; -static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, + size_t bufsz, u8 bid) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? + &btc->cx.bt0 : &btc->cx.bt1; + struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hfp_desc hfp = bt_linfo->hfp_desc; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; @@ -9024,7 +10038,7 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf if (hid.exist) { p += scnprintf(p, end - p, - "\n\r %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n", + "\n %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n", "[HID]", hid.type & BTC_HID_218 ? "2/18," : "", hid.type & BTC_HID_418 ? "4/18," : "", @@ -9060,32 +10074,36 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf return p - buf; } -static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz, u8 bid) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; struct rtw89_btc_wl_info *wl = &cx->wl; - u32 ver_main = FIELD_GET(GENMASK(31, 24), wl->ver_info.fw_coex); struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - union rtw89_btc_module_info *md = &btc->mdinfo; + struct rtw89_btc_module *md = &btc->mdinfo; + u8 bt_pos = (bid == BTC_BT_1ST) ? md->bt0_pos : md->bt1_pos; s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm; s8 le_dbm = bt->link_info.bt_txpwr_desc.le_dbm; + u8 hw_band = wl->role_info.pta_req_band; + struct rtw89_btc_wl_afh_info *wl_afh; char *p = buf, *end = buf + bufsz; u8 *afh = bt_linfo->afh_map; u8 *afh_le = bt_linfo->afh_map_le; - u8 bt_pos; if (!(btc->dm.coex_info_map & BTC_COEX_INFO_BT)) return 0; - if (ver->fcxinit == 7) - bt_pos = md->md_v7.bt_pos; - else - bt_pos = md->md.bt_pos; + if (bid == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + return 0; + if (!bt->enable.now) + return 0; + } - p += scnprintf(p, end - p, "========== [BT Status] ==========\n"); + p += scnprintf(p, end - p, "========== [BT_%s Status] ==========\n", + bid == BTC_BT_1ST ? "1ST" : "2ND"); p += scnprintf(p, end - p, " %-15s : enable:%s, btg:%s%s, connect:%s, ", @@ -9102,7 +10120,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : profile:%s%s%s%s%s ", "[profile]", - (bt_linfo->profile_cnt.now == 0) ? "None," : "", + (bt_linfo->link_cnt.now == 0) ? "None," : "", bt_linfo->hfp_desc.exist ? "HFP," : "", bt_linfo->hid_desc.exist ? "HID," : "", bt_linfo->a2dp_desc.exist ? @@ -9139,24 +10157,25 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) afh_le[0], afh_le[1], afh_le[2], afh_le[3], afh_le[4]); - p += scnprintf(p, end - p, "wl_ch_map[en:%d/ch:%d/bw:%d]\n", - wl->afh_info.en, wl->afh_info.ch, wl->afh_info.bw); + wl_afh = &wl->afh_info[hw_band][RTW89_BAND_2G]; + p += scnprintf(p, end - p, "wl_ch_map[hwb:%d/en:%d/ch:%d/bw:%d/rf_band:%d]\n", + hw_band, wl_afh->en, wl_afh->ch, wl_afh->bw, wl_afh->band); p += scnprintf(p, end - p, " %-15s : retry:%d, relink:%d, rate_chg:%d, reinit:%d, reenable:%d, ", - "[stat_cnt]", cx->cnt_bt[BTC_BCNT_RETRY], - cx->cnt_bt[BTC_BCNT_RELINK], - cx->cnt_bt[BTC_BCNT_RATECHG], - cx->cnt_bt[BTC_BCNT_REINIT], - cx->cnt_bt[BTC_BCNT_REENABLE]); + "[stat_cnt]", bt->bcnt[BTC_BCNT_RETRY], + bt->bcnt[BTC_BCNT_RELINK], + bt->bcnt[BTC_BCNT_RATECHG], + bt->bcnt[BTC_BCNT_REINIT], + bt->bcnt[BTC_BCNT_REENABLE]); p += scnprintf(p, end - p, "role-switch:%d, afh:%d, inq_page:%d(inq:%d/page:%d), igno_wl:%d\n", - cx->cnt_bt[BTC_BCNT_ROLESW], cx->cnt_bt[BTC_BCNT_AFH], - cx->cnt_bt[BTC_BCNT_INQPAG], cx->cnt_bt[BTC_BCNT_INQ], - cx->cnt_bt[BTC_BCNT_PAGE], cx->cnt_bt[BTC_BCNT_IGNOWL]); + bt->bcnt[BTC_BCNT_ROLESW], bt->bcnt[BTC_BCNT_AFH], + bt->bcnt[BTC_BCNT_INQPAG], bt->bcnt[BTC_BCNT_INQ], + bt->bcnt[BTC_BCNT_PAGE], bt->bcnt[BTC_BCNT_IGNOWL]); - p += _show_bt_profile_info(rtwdev, p, end - p); + p += _show_bt_profile_info(rtwdev, p, end - p, bid); p += scnprintf(p, end - p, " %-15s : raw_data[%02x %02x %02x %02x %02x %02x] (type:%s/cnt:%d/same:%d)\n", @@ -9164,52 +10183,41 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) bt->raw_info[4], bt->raw_info[5], bt->raw_info[6], bt->raw_info[7], bt->raw_info[0] == BTC_BTINFO_AUTO ? "auto" : "reply", - cx->cnt_bt[BTC_BCNT_INFOUPDATE], - cx->cnt_bt[BTC_BCNT_INFOSAME]); + bt->bcnt[BTC_BCNT_INFOUPDATE], + bt->bcnt[BTC_BCNT_INFOSAME]); p += scnprintf(p, end - p, " %-15s : Hi-rx = %d, Hi-tx = %d, Lo-rx = %d, Lo-tx = %d (bt_polut_wl_tx = %d)", - "[trx_req_cnt]", cx->cnt_bt[BTC_BCNT_HIPRI_RX], - cx->cnt_bt[BTC_BCNT_HIPRI_TX], - cx->cnt_bt[BTC_BCNT_LOPRI_RX], - cx->cnt_bt[BTC_BCNT_LOPRI_TX], - cx->cnt_bt[BTC_BCNT_POLUT]); + "[trx_req_cnt]", bt->bcnt[BTC_BCNT_HIPRI_RX], + bt->bcnt[BTC_BCNT_HIPRI_TX], + bt->bcnt[BTC_BCNT_LOPRI_RX], + bt->bcnt[BTC_BCNT_LOPRI_TX], + bt->bcnt[BTC_BCNT_POLLUTED]); - if (!bt->scan_info_update) { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, true); - p += scnprintf(p, end - p, "\n"); - } else { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, false); - if (ver->fcxbtscan == 1) { - p += scnprintf(p, end - p, - "(INQ:%d-%d/PAGE:%d-%d/LE:%d-%d/INIT:%d-%d)", - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].intvl)); - } else if (ver->fcxbtscan == 2) { - p += scnprintf(p, end - p, - "(BG:%d-%d/INIT:%d-%d/LE:%d-%d)", - le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].intvl), - le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].intvl), - le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].intvl)); - } - p += scnprintf(p, end - p, "\n"); + if (ver->fcxbtscan == 1) { + p += scnprintf(p, end - p, + "(INQ:%d-%d/PAGE:%d-%d/LE:%d-%d/INIT:%d-%d)", + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].intvl)); + } else if (ver->fcxbtscan == 2) { + p += scnprintf(p, end - p, + "(BG:%d-%d/INIT:%d-%d/LE:%d-%d)", + le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].intvl), + le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].intvl), + le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].intvl)); } + p += scnprintf(p, end - p, "\n"); - if (ver_main >= 9 && bt_linfo->profile_cnt.now) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, false); - - if (cx->cnt_bt[BTC_BCNT_BTTXPWR_UPDATE]) { + if (bt->bcnt[BTC_BCNT_TXPWR_UPDATE]) { p += scnprintf(p, end - p, " %-15s : br_index:0x%x, le_index:0x%x", "[bt_txpwr_lvl]", @@ -9226,24 +10234,6 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } p += scnprintf(p, end - p, "\n"); - if (bt_linfo->profile_cnt.now || bt_linfo->status.map.ble_connect) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, false); - - if (ver->fcxbtafh == 2 && bt_linfo->status.map.ble_connect) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP_LE, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP_LE, false); - - if (bt_linfo->a2dp_desc.exist && - (bt_linfo->a2dp_desc.flush_time == 0 || - bt_linfo->a2dp_desc.vendor_id == 0 || - bt_linfo->a2dp_desc.play_latency == 1)) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_DEVICE_INFO, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_DEVICE_INFO, false); - return p - buf; } @@ -9258,6 +10248,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) #define CASE_BTC_POLUT_STR(e) case BTC_PLT_## e: return #e #define CASE_BTC_REGTYPE_STR(e) case REG_## e: return #e #define CASE_BTC_GDBG_STR(e) case BTC_DBG_## e: return #e +#define CASE_BTC_H2CCMD(e) case SET_##e: return #e static const char *id_to_polut(u32 id) { @@ -9294,6 +10285,8 @@ static const char *id_to_gdbg(u32 id) switch (id) { CASE_BTC_GDBG_STR(GNT_BT); CASE_BTC_GDBG_STR(GNT_WL); + CASE_BTC_GDBG_STR(GNT_BT1); + CASE_BTC_GDBG_STR(GNT_WL1); CASE_BTC_GDBG_STR(BCN_EARLY); CASE_BTC_GDBG_STR(WL_NULL0); CASE_BTC_GDBG_STR(WL_NULL1); @@ -9322,8 +10315,6 @@ static const char *id_to_gdbg(u32 id) CASE_BTC_GDBG_STR(SLOT_B1FDD); CASE_BTC_GDBG_STR(BT_CHANGE); CASE_BTC_GDBG_STR(WL_CCA); - CASE_BTC_GDBG_STR(BT_LEAUDIO); - CASE_BTC_GDBG_STR(USER_DEF); default: return "unknown"; } @@ -9349,6 +10340,7 @@ static const char *steps_to_str(u16 step) CASE_BTC_RSN_STR(ACT1_WORK); CASE_BTC_RSN_STR(BT_DEVINFO_WORK); CASE_BTC_RSN_STR(RFK_CHK_WORK); + CASE_BTC_RSN_STR(UPDATE_BT_LEAUDINFO); CASE_BTC_ACT_STR(NONE); CASE_BTC_ACT_STR(WL_ONLY); @@ -9373,6 +10365,8 @@ static const char *steps_to_str(u16 step) CASE_BTC_ACT_STR(BT_A2DP_PAN); CASE_BTC_ACT_STR(BT_PAN_HID); CASE_BTC_ACT_STR(BT_A2DP_PAN_HID); + CASE_BTC_ACT_STR(BT_BIS); + CASE_BTC_ACT_STR(BT_CIS); CASE_BTC_ACT_STR(WL_25G_MCC); CASE_BTC_ACT_STR(WL_2G_MCC); CASE_BTC_ACT_STR(WL_2G_SCC); @@ -9526,17 +10520,50 @@ static const char *id_to_ant(u32 id) CASE_BTC_ANTPATH_STR(WONLY); CASE_BTC_ANTPATH_STR(WOFF); CASE_BTC_ANTPATH_STR(W2G); - CASE_BTC_ANTPATH_STR(W5G); + CASE_BTC_ANTPATH_STR(FDD); CASE_BTC_ANTPATH_STR(W25G); CASE_BTC_ANTPATH_STR(FREERUN); CASE_BTC_ANTPATH_STR(WRFK); - CASE_BTC_ANTPATH_STR(BRFK); + CASE_BTC_ANTPATH_STR(WRFK2); + CASE_BTC_ANTPATH_STR(PTA); CASE_BTC_ANTPATH_STR(MAX); default: return "unknown"; } } +static const char *id_to_h2c(u32 id) +{ + switch (id) { + CASE_BTC_H2CCMD(REPORT_EN); + CASE_BTC_H2CCMD(SLOT_TABLE); + CASE_BTC_H2CCMD(MREG_TABLE); + CASE_BTC_H2CCMD(CX_POLICY); + CASE_BTC_H2CCMD(GPIO_DBG); + CASE_BTC_H2CCMD(DRV_INFO); + CASE_BTC_H2CCMD(DRV_EVENT); + CASE_BTC_H2CCMD(BT_WREG_ADDR); + CASE_BTC_H2CCMD(BT_WREG_VAL); + CASE_BTC_H2CCMD(BT_RREG_ADDR); + CASE_BTC_H2CCMD(BT_WL_CH_INFO); + CASE_BTC_H2CCMD(BT_INFO_REPORT); + CASE_BTC_H2CCMD(BT_IGNORE_WLAN_ACT); + CASE_BTC_H2CCMD(BT_TX_PWR); + CASE_BTC_H2CCMD(BT_LNA_CONSTRAIN); + CASE_BTC_H2CCMD(BT_QUERY_DEV_LIST); + CASE_BTC_H2CCMD(BT_QUERY_DEV_INFO); + CASE_BTC_H2CCMD(BT_PSD_REPORT); + CASE_BTC_H2CCMD(H2C_TEST); + CASE_BTC_H2CCMD(IOFLD_RF); + CASE_BTC_H2CCMD(IOFLD_BB); + CASE_BTC_H2CCMD(IOFLD_MAC); + CASE_BTC_H2CCMD(IOFLD_SCBD); + CASE_BTC_H2CCMD(H2C_MACRO); + default: + return "unknown"; + } +} + static int scnprintf_segment(char *buf, size_t bufsz, const char *prefix, const u16 *data, u8 len, u8 seg_len, u8 start_idx, u8 ring_len) @@ -9585,12 +10612,10 @@ static int _show_dm_step(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; char *p = buf, *end = buf + bufsz; - u8 igno_bt; if (!(dm->coex_info_map & BTC_COEX_INFO_DM)) return 0; @@ -9611,15 +10636,10 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_dm_step(rtwdev, p, end - p); - if (ver->fcxctrl == 7) - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - else - igno_bt = btc->ctrl.ctrl.igno_bt; - p += scnprintf(p, end - p, " %-15s : wl_only:%d, bt_only:%d, igno_bt:%d, free_run:%d, wl_ps_ctrl:%d, wl_mimo_ps:%d, ", - "[dm_flag]", dm->wl_only, dm->bt_only, igno_bt, - dm->freerun, btc->lps, dm->wl_mimo_ps); + "[dm_flag]", dm->wl_only, dm->bt_only, btc->ctrl.igno_bt, + dm->freerun, btc->btc_ctrl_lps, dm->wl_mimo_ps); p += scnprintf(p, end - p, "leak_ap:%d, fw_offload:%s%s\n", dm->leak_ap, @@ -9627,31 +10647,25 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) (dm->wl_fw_cx_offload == BTC_CX_FW_OFFLOAD ? "" : "(Mismatch!!)")); - if (dm->rf_trx_para.wl_tx_power == 0xff) - p += scnprintf(p, end - p, - " %-15s : wl_rssi_lvl:%d, para_lvl:%d, wl_tx_pwr:orig, ", - "[trx_ctrl]", wl->rssi_level, - dm->trx_para_level); - - else - p += scnprintf(p, end - p, - " %-15s : wl_rssi_lvl:%d, para_lvl:%d, wl_tx_pwr:%d, ", - "[trx_ctrl]", wl->rssi_level, - dm->trx_para_level, - dm->rf_trx_para.wl_tx_power); + p += scnprintf(p, end - p, + " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n", + "[dm_rf_ctrl]", + wl->rssi_level, dm->trx_para_level, + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1], + dm->wl_lna2, dm->wl_stb_chg); p += scnprintf(p, end - p, - "wl_rx_lvl:%d, bt_tx_pwr_dec:%d, bt_rx_lna:%d(%s-tbl), wl_btg_rx:%d\n", - dm->rf_trx_para.wl_rx_gain, - dm->rf_trx_para.bt_tx_power, - dm->rf_trx_para.bt_rx_gain, - (bt->hi_lna_rx ? "Hi" : "Ori"), dm->wl_btg_rx); + " %-15s : pre_agc:%d, btg_rx:%d\n", + "[dm_bb_ctrl]", dm->wl_pre_agc, dm->wl_btg_rx); p += scnprintf(p, end - p, " %-15s : wl_tx_limit[en:%d/max_t:%dus/max_retry:%d], bt_slot_reg:%d-TU, bt_scan_rx_low_pri:%d\n", "[dm_ctrl]", dm->wl_tx_limit.enable, dm->wl_tx_limit.tx_time, - dm->wl_tx_limit.tx_retry, btc->bt_req_len, + dm->wl_tx_limit.tx_retry, btc->bt_req_len[RTW89_PHY_0], bt->scan_rx_low_pri); return p - buf; @@ -9747,26 +10761,25 @@ static int _show_fbtc_tdma(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : ", "[tdma_policy]"); p += scnprintf(p, end - p, - "type:%d, rx_flow_ctrl:%d, tx_pause:%d, ", + "type:%d, rx_flow_ctrl:%d, txflctrl:%d, ", (u32)t->type, - t->rxflctrl, t->txpause); + t->rxflctrl, t->txflctrl); p += scnprintf(p, end - p, - "wl_toggle_n:%d, leak_n:%d, ext_ctrl:%d, ", - t->wtgle_n, t->leak_n, t->ext_ctrl); + "bind:%d, leak_n:%d, ext_ctrl:%d, ", + t->bind, t->leak_n, t->ext_ctrl); p += scnprintf(p, end - p, - "policy_type:%d", + "policy_type:%d\n", (u32)btc->policy_type); - p += scnprintf(p, end - p, "\n"); - return p - buf; } static int _show_fbtc_slots(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; + const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; u16 dur, cxtype; @@ -9774,11 +10787,11 @@ static int _show_fbtc_slots(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) u8 i = 0; for (i = 0; i < CXST_MAX; i++) { - if (btc->ver->fcxslots == 1) { + if (ver->fcxslots == 1) { dur = le16_to_cpu(dm->slot_now.v1[i].dur); tbl = le32_to_cpu(dm->slot_now.v1[i].cxtbl); cxtype = le16_to_cpu(dm->slot_now.v1[i].cxtype); - } else if (btc->ver->fcxslots == 7) { + } else if (ver->fcxslots == 2 || ver->fcxslots == 7) { dur = le16_to_cpu(dm->slot_now.v7[i].dur); tbl = le32_to_cpu(dm->slot_now.v7[i].cxtbl); cxtype = le16_to_cpu(dm->slot_now.v7[i].cxtype); @@ -9810,7 +10823,7 @@ static int _show_fbtc_cysta_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_cysta_v2 *pcysta_le32 = NULL; union rtw89_btc_fbtc_rxflct r; @@ -9941,7 +10954,7 @@ static int _show_fbtc_cysta_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat *a2dp_trx; @@ -10066,11 +11079,9 @@ static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -10080,7 +11091,7 @@ static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; @@ -10205,11 +11216,9 @@ static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -10219,7 +11228,7 @@ static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; @@ -10343,11 +11352,145 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); + } - p += scnprintf(p, end - p, "\n"); +out: + return p - buf; +} + +static int _show_fbtc_cysta_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; + struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; + struct rtw89_btc_fbtc_cysta_v105 *pcysta; + struct rtw89_btc_rpt_cmn_info *pcinfo; + u8 i, cnt = 0, slot_pair, divide_cnt; + u16 cycle, c_begin, c_end, store_index; + char *p = buf, *end = buf + bufsz; + + pcinfo = &pfwinfo->rpt_fbtc_cysta.cinfo; + if (!pcinfo->valid) + return 0; + + pcysta = &pfwinfo->rpt_fbtc_cysta.finfo.v105; + p += scnprintf(p, end - p, + " %-15s : cycle:%d, bcn[all:%d/all_ok:%d/bt:%d/bt_ok:%d]", + "[cycle_cnt]", + le16_to_cpu(pcysta->cycles), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_ALL]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_ALL_OK]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_BT_SLOT]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_BT_OK])); + + for (i = 0; i < CXST_MAX; i++) { + if (!le16_to_cpu(pcysta->slot_cnt[i])) + continue; + + p += scnprintf(p, end - p, ", %s:%d", id_to_slot(i), + le16_to_cpu(pcysta->slot_cnt[i])); + } + + if (dm->tdma_now.rxflctrl) + p += scnprintf(p, end - p, ", leak_rx:%d", + le32_to_cpu(pcysta->leak_slot.cnt_rximr)); + + if (pcysta->collision_cnt) + p += scnprintf(p, end - p, ", collision:%d", + pcysta->collision_cnt); + + if (le16_to_cpu(pcysta->skip_cnt)) + p += scnprintf(p, end - p, ", skip:%d", + le16_to_cpu(pcysta->skip_cnt)); + + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", + "[cycle_time]", + le16_to_cpu(pcysta->cycle_time.tavg[CXT_WL]), + le16_to_cpu(pcysta->cycle_time.tavg[CXT_BT]), + le16_to_cpu(pcysta->leak_slot.tavg) / 1000, + le16_to_cpu(pcysta->leak_slot.tavg) % 1000); + p += scnprintf(p, end - p, + ", max_t[wl:%d/bt:%d/lk:%d.%03d]\n", + le16_to_cpu(pcysta->cycle_time.tmax[CXT_WL]), + le16_to_cpu(pcysta->cycle_time.tmax[CXT_BT]), + le16_to_cpu(pcysta->leak_slot.tmax) / 1000, + le16_to_cpu(pcysta->leak_slot.tmax) % 1000); + + cycle = le16_to_cpu(pcysta->cycles); + if (cycle <= 1) + goto out; + + /* 1 cycle record 1 wl-slot and 1 bt-slot */ + slot_pair = BTC_CYCLE_SLOT_MAX / 2; + + if (cycle <= slot_pair) + c_begin = 1; + else + c_begin = cycle - slot_pair + 1; + + c_end = cycle; + + if (a2dp->exist) + divide_cnt = 3; + else + divide_cnt = BTC_CYCLE_SLOT_MAX / 4; + + if (c_begin > c_end) + goto out; + + for (cycle = c_begin; cycle <= c_end; cycle++) { + cnt++; + store_index = ((cycle - 1) % slot_pair) * 2; + + if (cnt % divide_cnt == 1) + p += scnprintf(p, end - p, " %-15s : ", + "[cycle_step]"); + + p += scnprintf(p, end - p, "->b%02d", + le16_to_cpu(pcysta->slot_step_time[store_index])); + if (a2dp->exist) { + a2dp_trx = &pcysta->a2dp_trx[store_index]; + p += scnprintf(p, end - p, "(%d/%d/%dM/%d/%d/%d)", + a2dp_trx->empty_cnt, + a2dp_trx->retry_cnt, + a2dp_trx->tx_rate ? 3 : 2, + a2dp_trx->tx_cnt, + a2dp_trx->ack_cnt, + a2dp_trx->nack_cnt); + } + p += scnprintf(p, end - p, "->w%02d", + le16_to_cpu(pcysta->slot_step_time[store_index + 1])); + if (a2dp->exist) { + a2dp_trx = &pcysta->a2dp_trx[store_index + 1]; + p += scnprintf(p, end - p, "(%d/%d/%dM/%d/%d/%d)", + a2dp_trx->empty_cnt, + a2dp_trx->retry_cnt, + a2dp_trx->tx_rate ? 3 : 2, + a2dp_trx->tx_cnt, + a2dp_trx->ack_cnt, + a2dp_trx->nack_cnt); + } + if (cnt % divide_cnt == 0 || cnt == c_end) + p += scnprintf(p, end - p, "\n"); + } + + if (a2dp->exist) { + p += scnprintf(p, end - p, + " %-15s : a2dp_ept:%d, a2dp_late:%d", + "[a2dp_t_sta]", + le16_to_cpu(pcysta->a2dp_ept.cnt), + le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); + + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", + le16_to_cpu(pcysta->a2dp_ept.tavg), + le16_to_cpu(pcysta->a2dp_ept.tmax)); } out: @@ -10356,7 +11499,7 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { - struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_bt_a2dp_desc *a2dp = &bt->link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; struct rtw89_btc_fbtc_cysta_v7 *pcysta = NULL; @@ -10396,7 +11539,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->skip_cnt)); p += scnprintf(p, end - p, - "\n\r %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", + "\n %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", "[cycle_stat]", le16_to_cpu(pcysta->cycle_time.tavg[CXT_WL]), le16_to_cpu(pcysta->cycle_time.tavg[CXT_BT]), @@ -10417,7 +11560,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz if (a2dp->exist) { p += scnprintf(p, end - p, - "\n\r %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)", + "\n %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)", "[a2dp_stat]", le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout), @@ -10456,10 +11599,10 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz if (cnt % divide_cnt == 1) { if (a2dp->exist) - p += scnprintf(p, end - p, "\n\r %-15s : ", + p += scnprintf(p, end - p, "\n %-15s : ", "[slotT_wermtan]"); else - p += scnprintf(p, end - p, "\n\r %-15s : ", + p += scnprintf(p, end - p, "\n %-15s : ", "[slotT_rxerr]"); } @@ -10520,7 +11663,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) ns = &pfwinfo->rpt_fbtc_nullsta.finfo; if (ver->fcxnullsta == 1) { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[ok:%d/", le32_to_cpu(ns->v1.result[i][1])); @@ -10539,7 +11682,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } } else if (ver->fcxnullsta == 7) { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[Tx:%d/", le32_to_cpu(ns->v7.result[i][4])); @@ -10560,7 +11703,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } } else { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[Tx:%d/", le32_to_cpu(ns->v2.result[i][4])); @@ -10615,7 +11758,7 @@ static int _show_fbtc_step_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (ver->fcxctrl == 7 || ver->fcxctrl == 1) trace_step = 50; else - trace_step = btc->ctrl.ctrl.trace_step; + trace_step = btc->ctrl.trace_step; n_start = pos_old; if (pos_new >= pos_old) @@ -10745,6 +11888,8 @@ static int _show_fw_dm_msg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_fbtc_cysta_v4(rtwdev, p, end - p); else if (ver->fcxcysta == 5) p += _show_fbtc_cysta_v5(rtwdev, p, end - p); + else if (ver->fcxcysta == 105) + p += _show_fbtc_cysta_v105(rtwdev, p, end - p); else if (ver->fcxcysta == 7) p += _show_fbtc_cysta_v7(rtwdev, p, end - p); @@ -10762,6 +11907,7 @@ static int _show_fw_dm_msg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static void _get_gnt(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_coex_gnt *gnt_cfg) { const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_mac_ax_wl_act *bt; struct rtw89_mac_ax_gnt *gnt; u32 val, status; @@ -10796,7 +11942,39 @@ static void _get_gnt(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_coex_gnt *gnt gnt->gnt_bt = !!(status & B_AX_GNT_BT_RFC_S1); gnt->gnt_wl_sw_en = !!(val & B_AX_GNT_WL_RFC_S1_SWCTRL); gnt->gnt_wl = !!(status & B_AX_GNT_WL_RFC_S1); - } else { + + bt = &gnt_cfg->bt[0]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT_VAL); + + bt = &gnt_cfg->bt[1]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT2_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT2_VAL); + } else if (chip->chip_id == RTL8922A) { + val = rtw89_read32(rtwdev, R_BE_GNT_SW_CTRL); + status = rtw89_read32(rtwdev, R_BE_PTA_GNT_VAL); + + gnt = &gnt_cfg->band[0]; + gnt->gnt_bt_sw_en = !!(val & B_BE_GNT_BT_BB0_SWCTRL); + gnt->gnt_bt = !!(status & B_BE_GNT_BT_BB0_VAL); + gnt->gnt_wl_sw_en = !!(val & B_BE_GNT_WL_BB0_SWCTRL); + gnt->gnt_wl = !!(status & B_BE_GNT_WL_BB0_VAL); + + gnt = &gnt_cfg->band[1]; + gnt->gnt_bt_sw_en = !!(val & B_BE_GNT_BT_BB1_SWCTRL); + gnt->gnt_bt = !!(status & B_BE_GNT_BT_BB1_VAL); + gnt->gnt_wl_sw_en = !!(val & B_BE_GNT_WL_BB1_SWCTRL); + gnt->gnt_wl = !!(status & B_BE_GNT_WL_BB1_VAL); + + bt = &gnt_cfg->bt[0]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT_VAL); + + bt = &gnt_cfg->bt[1]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT2_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT2_VAL); + } else if (chip->chip_id == RTL8922D) { + /* Get from firmware */ return; } } @@ -10808,8 +11986,8 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; union rtw89_btc_fbtc_gpio_dbg *gdbg = NULL; char *p = buf, *end = buf + bufsz; - u8 *gpio_map, i; - u32 en_map; + u32 en_map_raw, en_map; + u8 *gpio_map, i, sig; pcinfo = &pfwinfo->rpt_fbtc_gpio_dbg.cinfo; gdbg = &rtwdev->btc.fwinfo.rpt_fbtc_gpio_dbg.finfo; @@ -10820,27 +11998,32 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) goto out; } - if (ver->fcxgpiodbg == 7) { - en_map = le32_to_cpu(gdbg->v7.en_map); + if (ver->fcxgpiodbg == 7 || ver->fcxgpiodbg == 8) { + en_map_raw = le32_to_cpu(gdbg->v7.en_map); gpio_map = gdbg->v7.gpio_map; } else { - en_map = le32_to_cpu(gdbg->v1.en_map); + en_map_raw = le32_to_cpu(gdbg->v1.en_map); gpio_map = gdbg->v1.gpio_map; } + en_map = _convert_gpio_enmap_from_ver(rtwdev, en_map_raw); if (!en_map) goto out; - p += scnprintf(p, end - p, " %-15s : enable_map:0x%08x", + p += scnprintf(p, end - p, "\n %-15s : enable_map:0x%08x", "[gpio_dbg]", en_map); - for (i = 0; i < BTC_DBG_MAX1; i++) { + for (i = 0; i < BTC_DBG_NUM; i++) { if (!(en_map & BIT(i))) continue; + + sig = ver->fcxgpiodbg >= 8 ? i : _get_gpiosig_for_ver(rtwdev, i); + if (sig >= BTC_DBG_NUM) + continue; + p += scnprintf(p, end - p, ", %s->GPIO%d", id_to_gdbg(i), - gpio_map[i]); + gpio_map[sig]); } - p += scnprintf(p, end - p, "\n"); out: return p - buf; @@ -10851,11 +12034,11 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; - struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v1 *pmreg = NULL; - struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -10868,11 +12051,20 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [HW Status] ==========\n"); p += scnprintf(p, end - p, - " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", - "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], + wl->wcnt[BTC_WCNT_SCBDUPDATE], + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -10936,11 +12128,11 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; - struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v2 *pmreg = NULL; - struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -10953,11 +12145,20 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [HW Status] ==========\n"); p += scnprintf(p, end - p, - " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", - "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], + wl->wcnt[BTC_WCNT_SCBDUPDATE], + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -11024,9 +12225,9 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_fbtc_mreg_val_v7 *pmreg = NULL; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; - struct rtw89_mac_ax_gnt *gnt = NULL; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; u8 i, type, cnt = 0; @@ -11035,38 +12236,71 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (!(dm->coex_info_map & BTC_COEX_INFO_MREG)) return 0; - p += scnprintf(p, end - p, "\n\r========== [HW Status] =========="); + p += scnprintf(p, end - p, "\n========== [HW Status] ==========\n"); p += scnprintf(p, end - p, - "\n\r %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)", - "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], + wl->wcnt[BTC_WCNT_SCBDUPDATE], + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } /* To avoid I/O if WL LPS or power-off */ dm->pta_owner = rtw89_mac_get_ctrl_path(rtwdev); p += scnprintf(p, end - p, - "\n\r %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s", + " %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s\n", "[gnt_status]", rtwdev->chip->para_ver & BTC_FEAT_PTA_ONOFF_CTRL ? "HW" : dm->pta_owner == BTC_CTRL_BY_WL ? "WL" : "BT", wl->pta_req_mac, id_to_polut(wl->bt_polut_type[wl->pta_req_mac])); - gnt = &dm->gnt.band[RTW89_PHY_0]; - - p += scnprintf(p, end - p, ", phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]", - gnt->gnt_wl_sw_en ? "SW" : "HW", gnt->gnt_wl, - gnt->gnt_bt_sw_en ? "SW" : "HW", gnt->gnt_bt); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + p += scnprintf(p, end - p, + " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]", + "[gnt_status]", + dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_wl, + dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt0, + dm->gnt_set[RTW89_PHY_0].gnt_bt1_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt1); + else + p += scnprintf(p, end - p, + " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]", + "[gnt_status]", + dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_wl, + dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt0); if (rtwdev->dbcc_en) { - gnt = &dm->gnt.band[RTW89_PHY_1]; - p += scnprintf(p, end - p, - ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]", - gnt->gnt_wl_sw_en ? "SW" : "HW", gnt->gnt_wl, - gnt->gnt_bt_sw_en ? "SW" : "HW", gnt->gnt_bt); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + p += scnprintf(p, end - p, + ", phy-1[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]", + dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_wl, + dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt0, + dm->gnt_set[RTW89_PHY_1].gnt_bt1_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt1); + else + p += scnprintf(p, end - p, + ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]", + dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_wl, + dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt0); } pcinfo = &pfwinfo->rpt_fbtc_mregval.cinfo; @@ -11082,7 +12316,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (cnt % 6 == 0) p += scnprintf(p, end - p, - "\n\r %-15s : %s_0x%x=0x%x", "[reg]", + "\n %-15s : %s_0x%x=0x%x", "[reg]", id_to_regtype(type), offset, val); else p += scnprintf(p, end - p, ", %s_0x%x=0x%x", @@ -11103,7 +12337,7 @@ static int _show_summary_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; u32 cnt_sum = 0, *cnt = btc->dm.cnt_notify; char *p = buf, *end = buf + bufsz; u8 i; @@ -11146,10 +12380,10 @@ static int _show_summary_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/timeout:%d]", - "[RFK]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d/go:%d/reject:%d/timeout:%d/fail:%d]\n", @@ -11212,7 +12446,7 @@ static int _show_summary_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; u32 cnt_sum = 0, *cnt = btc->dm.cnt_notify; char *p = buf, *end = buf + bufsz; u8 i; @@ -11261,10 +12495,10 @@ static int _show_summary_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/timeout:%d]", - "[RFK]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d/go:%d/reject:%d/timeout:%d/fail:%d]\n", @@ -11375,10 +12609,10 @@ static int _show_summary_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", @@ -11418,12 +12652,11 @@ static int _show_summary_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); - p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ", "[notify_cnt]", @@ -11496,10 +12729,10 @@ static int _show_summary_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", @@ -11539,12 +12772,11 @@ static int _show_summary_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); - p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ", "[notify_cnt]", @@ -11577,7 +12809,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -11585,7 +12817,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v7; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d)," + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d)," "c2h_cnt=%d(fw_send:%d, len:%d, max:%d), ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, @@ -11603,16 +12835,17 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -11620,22 +12853,22 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT], + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], wl->rfk_info.proc_time); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -11646,19 +12879,19 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -11690,7 +12923,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -11698,7 +12931,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v8; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, le16_to_cpu(prptctrl->rpt_info.cnt_h2c), @@ -11716,16 +12949,17 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -11733,22 +12967,22 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT], + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], wl->rfk_info.proc_time); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -11759,19 +12993,242 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + "[notify_cnt]", + cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], + cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], + cnt[BTC_NCNT_SPECIAL_PACKET]); + + p += scnprintf(p, end - p, + "timer=%d, customerize=%d, hub_msg=%d, chg_fw=%d, send_cc=%d", + cnt[BTC_NCNT_TIMER], cnt[BTC_NCNT_CUSTOMERIZE], + rtwdev->btc.hubmsg_cnt, cnt[BTC_NCNT_RESUME_DL_FW], + cnt[BTC_NCNT_COUNTRYCODE]); + + return p - buf; +} + +static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_fbtc_rpt_ctrl_v9 *prptctrl = NULL; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 *cnt = rtwdev->btc.dm.cnt_notify; + char *p = buf, *end = buf + bufsz; + u32 cnt_sum = 0; + u8 i; + + if (!(dm->coex_info_map & BTC_COEX_INFO_SUMMARY)) + return 0; + + p += scnprintf(p, end - p, "%s", + "\n========== [Statistics] ==========\n"); + + pcinfo = &pfwinfo->rpt_ctrl.cinfo; + if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && + !wl->status.map.rf_off) { + prptctrl = &pfwinfo->rpt_ctrl.finfo.v9; + + p += scnprintf(p, end - p, + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ", + "[summary]", pfwinfo->cnt_h2c, + pfwinfo->cnt_h2c_fail, + le16_to_cpu(prptctrl->rpt_info.cnt_h2c), + pfwinfo->cnt_c2h, + le16_to_cpu(prptctrl->rpt_info.cnt_c2h), + le16_to_cpu(prptctrl->rpt_info.len_c2h)); + + p += scnprintf(p, end - p, + "rpt_cnt=%d(fw_send:%d), rpt_map=0x%x", + pfwinfo->event[BTF_EVNT_RPT], + le16_to_cpu(prptctrl->rpt_info.cnt), + le32_to_cpu(prptctrl->rpt_info.en)); + + if (dm->error.map.wl_fw_hang) + p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + "[mailbox]", + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); + + p += scnprintf(p, end - p, + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_ack), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); + + p += scnprintf(p, end - p, + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], + wl->rfk_info.proc_time); + + p += scnprintf(p, end - p, ", bt_rfk[req:%d]", + le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); + + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); + } else { + p += scnprintf(p, end - p, + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", + "[summary]", + pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, + pfwinfo->cnt_c2h, + wl->status.map.lps, wl->status.map.rf_off); + } + + for (i = 0; i < BTC_NCNT_NUM; i++) + cnt_sum += dm->cnt_notify[i]; + + p += scnprintf(p, end - p, + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + "[notify_cnt]", + cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], + cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); + + p += scnprintf(p, end - p, + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", + cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], + cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], + cnt[BTC_NCNT_WL_STA]); + + p += scnprintf(p, end - p, + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + "[notify_cnt]", + cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], + cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], + cnt[BTC_NCNT_SPECIAL_PACKET]); + + p += scnprintf(p, end - p, + "timer=%d, customerize=%d, hub_msg=%d, chg_fw=%d, send_cc=%d", + cnt[BTC_NCNT_TIMER], cnt[BTC_NCNT_CUSTOMERIZE], + rtwdev->btc.hubmsg_cnt, cnt[BTC_NCNT_RESUME_DL_FW], + cnt[BTC_NCNT_COUNTRYCODE]); + + return p - buf; +} + +static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_fbtc_rpt_ctrl_v11 *prptctrl = NULL; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 *cnt = rtwdev->btc.dm.cnt_notify; + char *p = buf, *end = buf + bufsz; + u32 cnt_sum = 0; + u8 i; + + if (!(dm->coex_info_map & BTC_COEX_INFO_SUMMARY)) + return 0; + + p += scnprintf(p, end - p, "%s", + "\n========== [Statistics] ==========\n"); + + pcinfo = &pfwinfo->rpt_ctrl.cinfo; + if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && + !wl->status.map.rf_off) { + prptctrl = &pfwinfo->rpt_ctrl.finfo.v11; + + p += scnprintf(p, end - p, + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", + "[summary]", pfwinfo->cnt_h2c, + pfwinfo->cnt_h2c_fail, + le16_to_cpu(prptctrl->rpt_info.cnt_h2c), + pfwinfo->cnt_c2h, + le16_to_cpu(prptctrl->rpt_info.cnt_c2h), + le16_to_cpu(prptctrl->rpt_info.len_c2h), + (prptctrl->rpt_len_max_h << 8) + prptctrl->rpt_len_max_l, + rtwdev->btc.ver->info_buf); + + p += scnprintf(p, end - p, + "rpt_cnt=%d(fw_send:%d), rpt_map=0x%x", + pfwinfo->event[BTF_EVNT_RPT], + le16_to_cpu(prptctrl->rpt_info.cnt), + le32_to_cpu(prptctrl->rpt_info.en)); + + if (dm->error.map.wl_fw_hang) + p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + "[mailbox]", + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); + + p += scnprintf(p, end - p, + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_ack), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); + + p += scnprintf(p, end - p, + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], + wl->rfk_info.proc_time); + + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); + } else { + p += scnprintf(p, end - p, + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", + "[summary]", + pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, + pfwinfo->cnt_c2h, + wl->status.map.lps, wl->status.map.rf_off); + } + + for (i = 0; i < BTC_NCNT_NUM; i++) + cnt_sum += dm->cnt_notify[i]; + + p += scnprintf(p, end - p, + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + "[notify_cnt]", + cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], + cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); + + p += scnprintf(p, end - p, + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", + cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], + cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], + cnt[BTC_NCNT_WL_STA]); + + p += scnprintf(p, end - p, + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -11824,7 +13281,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_cx_info(rtwdev, p, end - p); p += _show_wl_info(rtwdev, p, end - p); - p += _show_bt_info(rtwdev, p, end - p); + p += _show_bt_info(rtwdev, p, end - p, BTC_BT_1ST); + p += _show_bt_info(rtwdev, p, end - p, BTC_BT_2ND); p += _show_dm_info(rtwdev, p, end - p); p += _show_fw_dm_msg(rtwdev, p, end - p); @@ -11849,6 +13307,12 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_summary_v7(rtwdev, p, end - p); else if (ver->fcxbtcrpt == 8) p += _show_summary_v8(rtwdev, p, end - p); + else if (ver->fcxbtcrpt == 9) + p += _show_summary_v9(rtwdev, p, end - p); + else if (ver->fcxbtcrpt == 11) + p += _show_summary_v11(rtwdev, p, end - p); + + p += scnprintf(p, end - p, "\n"); return p - buf; } @@ -11886,7 +13350,7 @@ void rtw89_coex_recognize_ver(struct rtw89_dev *rtwdev) void rtw89_btc_ntfy_preserve_bt_time(struct rtw89_dev *rtwdev, u32 ms) { - struct rtw89_btc_bt_link_info *bt_linfo = &rtwdev->btc.cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &rtwdev->btc.cx.bt0.link_info; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; if (test_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags)) diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index ea2c1e5d70f5..3e9e1510c44f 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -11,11 +11,47 @@ #define BTC_TLV_SLOT_ID_LEN_V7 1 #define BTC_SLOT_REQ_TH 2 +#define BTC_FREQ_W2G 2412 +#define BTC_FREQ_W5G 5005 +#define BTC_FREQ_W6G 5955 + +#define BTC_FREQ_B2G 2402 +#define BTC_FREQ_B6G 5125 + +#define BTC_FREQ_W2G_CH14 2484 + +#define BTC_LO_2G_DNM 125 /* LO Coefficient Denominator */ +#define BTC_LO_5G_DNM 125 +#define BTC_LO_6G_DNM 150 + +#define BTC_LO_2G_NMR 400 /* LO Coefficient Numerator */ +#define BTC_LO_5G_NMR 200 +#define BTC_LO_6G_NMR 200 + +#define BTC_CH_W2G_MIN 1 /* start from 2412MHz */ +#define BTC_CH_W2G_MAX 14 +#define BTC_CH_W5G_MIN 1 /* start from 5005MHz */ +#define BTC_CH_W5G_MAX 165 +#define BTC_CH_W6G_MIN 1 /* start from 5955MHz */ +#define BTC_CH_W6G_MAX 233 + +#define BTC_CH_B2G_MIN 0 /* start from 2402MHz */ +#define BTC_CH_B2G_MAX 78 + +#define BTC_CH_B5G_MIN 600 /* start from 5725MHz */ +#define BTC_CH_B5G_MAX 1300 + +#define BTC_VCO_GUARD_W2W 10 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ +#define BTC_VCO_GUARD_W2B 25 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ +#define BTC_VCO_GUARD_B2B 10 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ + enum btc_mode { BTC_MODE_NORMAL, BTC_MODE_WL, BTC_MODE_BT, BTC_MODE_WLOFF, + BTC_MODE_COTX, + BTC_MODE_MECHANISM_INIT, BTC_MODE_MAX }; @@ -33,7 +69,7 @@ enum btc_wl_rfk_type { #define NM_EXEC false #define FC_EXEC true -#define RTW89_COEX_ACT1_WORK_PERIOD round_jiffies_relative(HZ * 4) +#define RTW89_COEX_ACT1_WORK_PERIOD round_jiffies_relative(HZ) #define RTW89_COEX_BT_DEVINFO_WORK_PERIOD round_jiffies_relative(HZ * 16) #define RTW89_COEX_RFK_CHK_WORK_PERIOD msecs_to_jiffies(300) #define BTC_RFK_PATH_MAP GENMASK(3, 0) @@ -91,6 +127,11 @@ enum btc_fddt_en { ((__rssi == BTC_RSSI_ST_LOW || \ __rssi == BTC_RSSI_ST_HIGH) ? 1 : 0); }) +/* Antenna TX/RX path position masks and helpers */ +#define BTC_ANT_TX_MASK 0xf0 +#define BTC_ANT_RX_MASK 0x0f +#define BTC_ANT_SHIFT 4 + enum btc_ant { BTC_ANT_SHARED = 0, BTC_ANT_DEDICATED, @@ -107,6 +148,11 @@ enum btc_switch { BTC_SWITCH_EXTERNAL }; +enum btc_ant_switch_type { + BTC_SWITCH_V1_NONE = 0, /* independent antenna */ + BTC_SWITCH_V1_INTERNAL, /* internal-switch: BTGA structure */ +}; + enum btc_pkt_type { PACKET_DHCP, PACKET_ARP, @@ -205,6 +251,14 @@ enum btc_3cx_type { BTC_3CX_MAX, }; +enum btc_fddt_type { + BTC_FDDT_TYPE_STOP, + BTC_FDDT_TYPE_AUTO, + BTC_FDDT_TYPE_FIX_TDD, + BTC_FDDT_TYPE_FIX_FULL_FDD, + BTC_FDDT_MAX, +}; + enum btc_chip_feature { BTC_FEAT_PTA_ONOFF_CTRL = BIT(0), /* on/off ctrl by HW (not 0x73[2]) */ BTC_FEAT_NONBTG_GWL_THRU = BIT(1), /* non-BTG GNT_WL!=0 if GNT_BT = 1 */ @@ -212,6 +266,36 @@ enum btc_chip_feature { BTC_FEAT_NEW_BBAPI_FLOW = BIT(3), /* new btg_ctrl/pre_agc_ctrl */ BTC_FEAT_MLO_SUPPORT = BIT(4), BTC_FEAT_H2C_MACRO = BIT(5), + BTC_FEAT_DUAL_BT = BIT(6), + BTC_FEAT_BT_6G = BIT(7), + BTC_FEAT_MULTI_PTA = BIT(8), + BTC_FEAT_DUAL_BTGA = BIT(9) /* the future A-Die */ +}; + +enum btc_efuse_ant_function_map { + BTC_EFMAP_NONE = 0, + BTC_EFMAP_BT0 = BIT(0), + BTC_EFMAP_BT1 = BIT(1), + BTC_EFMAP_ZB = BIT(2), /* ZB or thread */ + BTC_EFMAP_24GP = BIT(3), + BTC_EFMAP_ULL = BIT(4), +}; + +enum btc_extsoc_interface { /* cx->other.hw_coex */ + BTC_EXTSOC_INTF_NONE = 0, + BTC_EXTSOC_INTF_PTA = BIT(0), + BTC_EXTSOC_INTF_MBX = BIT(1), + BTC_EXTSOC_INTF_SWIO = BIT(2), + BTC_EXTSOC_INTF_MAX, +}; + +enum btc_esoc_type { + BTC_ESOC_NONE, + BTC_ESOC_8761, + BTC_ESOC_8771, + BTC_ESOC_SILAB_MG21, + BTC_ESOC_NORDI_NRF52840, + BTC_ESOC_MAX, }; enum btc_wl_mode { @@ -231,41 +315,7 @@ enum btc_mlo_rf_combin { BTC_MLO_RF_2_PLUS_2 = 3, }; -enum btc_wl_gpio_debug { - BTC_DBG_GNT_BT = 0, - BTC_DBG_GNT_WL = 1, - BTC_DBG_BCN_EARLY = 2, - BTC_DBG_WL_NULL0 = 3, - BTC_DBG_WL_NULL1 = 4, - BTC_DBG_WL_RXISR = 5, - BTC_DBG_TDMA_ENTRY = 6, - BTC_DBG_A2DP_EMPTY = 7, - BTC_DBG_BT_RETRY = 8, - BTC_DBG_BT_RELINK = 9, - BTC_DBG_SLOT_WL = 10, - BTC_DBG_SLOT_BT = 11, - BTC_DBG_WL_ERR = 12, - BTC_DBG_WL_OK = 13, - BTC_DBG_SLOT_B2W = 14, - BTC_DBG_SLOT_W1 = 15, - BTC_DBG_SLOT_W2 = 16, - BTC_DBG_SLOT_W2B = 17, - BTC_DBG_SLOT_B1 = 18, - BTC_DBG_SLOT_B2 = 19, - BTC_DBG_SLOT_B3 = 20, - BTC_DBG_SLOT_B4 = 21, - BTC_DBG_SLOT_LK = 22, - BTC_DBG_SLOT_E2G = 23, - BTC_DBG_SLOT_E5G = 24, - BTC_DBG_SLOT_EBT = 25, - BTC_DBG_SLOT_WLK = 26, - BTC_DBG_SLOT_B1FDD = 27, - BTC_DBG_BT_CHANGE = 28, - BTC_DBG_WL_CCA = 29, - BTC_DBG_BT_LEAUDIO = 30, - BTC_DBG_USER_DEF = 31, -}; - +void rtw89_btc_init(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode); @@ -329,7 +379,7 @@ static inline u16 rtw89_coex_query_bt_req_len(struct rtw89_dev *rtwdev, { struct rtw89_btc *btc = &rtwdev->btc; - return btc->bt_req_len; + return btc->bt_req_len[phy_idx]; } static inline u32 rtw89_get_antpath_type(u8 phy_map, u8 type) @@ -344,7 +394,7 @@ void _slot_set_le(struct rtw89_btc *btc, u8 sid, __le16 dura, __le32 tbl, __le16 btc->dm.slot.v1[sid].dur = dura; btc->dm.slot.v1[sid].cxtbl = tbl; btc->dm.slot.v1[sid].cxtype = type; - } else if (btc->ver->fcxslots == 7) { + } else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) { btc->dm.slot.v7[sid].dur = dura; btc->dm.slot.v7[sid].cxtype = type; btc->dm.slot.v7[sid].cxtbl = tbl; @@ -362,7 +412,7 @@ void _slot_set_dur(struct rtw89_btc *btc, u8 sid, u16 dura) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].dur = cpu_to_le16(dura); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].dur = cpu_to_le16(dura); } @@ -371,7 +421,7 @@ void _slot_set_type(struct rtw89_btc *btc, u8 sid, u16 type) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].cxtype = cpu_to_le16(type); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].cxtype = cpu_to_le16(type); } @@ -380,8 +430,31 @@ void _slot_set_tbl(struct rtw89_btc *btc, u8 sid, u32 tbl) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].cxtbl = cpu_to_le32(tbl); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].cxtbl = cpu_to_le32(tbl); } +static inline +void btc_dw2b(u8 *buf, size_t idx, u32 val) +{ + buf[idx] = u32_get_bits(val, MASKBYTE0); + buf[idx + 1] = u32_get_bits(val, MASKBYTE1); + buf[idx + 2] = u32_get_bits(val, MASKBYTE2); + buf[idx + 3] = u32_get_bits(val, MASKBYTE3); +} + +static inline +u8 _btc_get_rf_path_from_ant_num(struct rtw89_btc *btc, u8 antnum) +{ + switch (antnum) { + default: + case 1: + return btc->mdinfo.ant.single_pos; + case 2: + return RF_PATH_AB; + case 3: + return RF_PATH_ABC; + } +} + #endif diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 68dad6090f87..397ebbfcac09 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -11,6 +11,7 @@ #include "core.h" #include "efuse.h" #include "fw.h" +#include "led.h" #include "mac.h" #include "phy.h" #include "ps.h" @@ -486,6 +487,37 @@ void rtw89_core_set_chip_txpwr(struct rtw89_dev *rtwdev) __rtw89_core_set_chip_txpwr(rtwdev, conf.chans[1], RTW89_PHY_1); } +static void rtw89_core_rfk_record(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + bool start) +{ + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + struct rtw89_rfk_record *record = &info->records[info->record_idx]; + const struct rtw89_chip_info *chip = rtwdev->chip; + u8 path_num = min(chip->rf_path_num, RTW89_RFK_RECORD_PATH_NR); + int path; + + if (!start) + goto end; + + info->record_ptr = record; + record->phy_idx = rtwvif_link->phy_idx; + + for (path = 0; path < path_num; path++) { + record->ch[path] = rtw89_read_rf(rtwdev, path, RR_CFGCH, + RR_CFGCH_BAND0 | RR_CFGCH_CH); + record->cv[path] = rtw89_read_rf(rtwdev, path, RR_VCO, + RR_VCO_SEL | RR_VCO_STS); + record->c5[path] = rtw89_read_rf(rtwdev, path, RR_SYNFB, RFREG_MASK); + } + + return; + +end: + info->record_idx = (info->record_idx + 1) % RTW89_RFK_RECORD_HISTORY_NR; + info->record_ptr = NULL; +} + void rtw89_chip_rfk_channel(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { @@ -503,9 +535,14 @@ void rtw89_chip_rfk_channel(struct rtw89_dev *rtwdev, rtw89_set_channel(rtwdev); } - if (chip->ops->rfk_channel) + if (chip->ops->rfk_channel) { + rtw89_core_rfk_record(rtwdev, rtwvif_link, true); + chip->ops->rfk_channel(rtwdev, rtwvif_link); + rtw89_core_rfk_record(rtwdev, rtwvif_link, false); + } + if (prehdl_link) { rtw89_entity_force_hw(rtwdev, RTW89_PHY_NUM); rtw89_set_channel(rtwdev); @@ -5236,9 +5273,18 @@ static bool rtw89_traffic_stats_track(struct rtw89_dev *rtwdev) static void rtw89_enter_lps_track(struct rtw89_dev *rtwdev, enum rtw89_tfc_interval interval) { + struct rtw89_hal *hal = &rtwdev->hal; struct ieee80211_vif *vif; struct rtw89_vif *rtwvif; + /* + * If vcore level is set, temperature is high and voltage is low. As + * entering power save must reset voltage to default, avoid power save + * until vcore decreases to zero resulting from temperature becomes low. + */ + if (hal->thermal_prot_vlv) + return; + rtw89_for_each_rtwvif(rtwdev, rtwvif) { if (rtwvif->tdls_peer) continue; @@ -5265,6 +5311,37 @@ static void rtw89_enter_lps_track(struct rtw89_dev *rtwdev, } } +static void rtw89_core_rfk_tssi_record(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + u8 path_num = min(chip->rf_path_num, RTW89_RFK_RECORD_PATH_NR); + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + int i = info->record_tssi_idx; + u32 base; + u32 mask; + int path; + + if (chip->chip_gen == RTW89_CHIP_AX) + return; + + info->record_tssi_idx = (info->record_tssi_idx + 1) % + RTW89_RFK_RECORD_HISTORY_NR; + + if (chip->chip_id == RTL8922A) { + base = 0x2ee10; + mask = 0x7fc00; + } else { + base = 0x2f918; + mask = 0x000001ff; + } + + for (path = 0; path < path_num; path++) { + u32 addr = base + 0x100 * path; + + info->tssi_code[i][path] = rtw89_read32_mask(rtwdev, addr, mask); + } +} + static void rtw89_core_rfk_track(struct rtw89_dev *rtwdev) { enum rtw89_entity_mode mode; @@ -5274,6 +5351,8 @@ static void rtw89_core_rfk_track(struct rtw89_dev *rtwdev) return; rtw89_chip_rfk_track(rtwdev); + + rtw89_core_rfk_tssi_record(rtwdev); } void rtw89_core_update_p2p_ps(struct rtw89_dev *rtwdev, @@ -5417,7 +5496,7 @@ static void rtw89_track_ps_work(struct wiphy *wiphy, struct wiphy_work *work) if (rtwdev->scanning) return; - if (rtwdev->lps_enabled && !rtwdev->btc.lps) + if (rtwdev->lps_enabled && !rtwdev->btc.btc_ctrl_lps) rtw89_enter_lps_track(rtwdev, RTW89_TFC_INTERVAL_100MS); } @@ -5465,7 +5544,7 @@ static void rtw89_track_work(struct wiphy *wiphy, struct wiphy_work *work) rtw89_core_rfkill_poll(rtwdev, false); rtw89_core_mlo_track(rtwdev); - if (rtwdev->lps_enabled && !rtwdev->btc.lps) + if (rtwdev->lps_enabled && !rtwdev->btc.btc_ctrl_lps) rtw89_enter_lps_track(rtwdev, RTW89_TFC_INTERVAL_2SEC); } @@ -5806,6 +5885,7 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, rtwsta_link); const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); + struct rtw89_bb_ctx *bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx); struct ieee80211_link_sta *link_sta; int ret; @@ -5858,6 +5938,7 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) { struct ieee80211_bss_conf *bss_conf; + u8 link_mode = 0; rcu_read_lock(); @@ -5865,6 +5946,12 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, link_sta = rtw89_sta_rcu_dereference_link(rtwsta_link, true); rtwsta_link->er_cap = rtw89_sta_link_can_er(rtwdev, bss_conf, link_sta); + if (link_sta->he_cap.has_he || link_sta->eht_cap.has_eht) + link_mode = 2; + else if (link_sta->vht_cap.vht_supported) + link_mode = 1; + bb->path_diff.link_mode = link_mode; + rcu_read_unlock(); rtw89_btc_ntfy_role_info(rtwdev, rtwvif_link, rtwsta_link, @@ -6331,6 +6418,9 @@ static int rtw89_core_set_supported_band(struct rtw89_dev *rtwdev) u8 support_bands = rtwdev->chip->support_bands; int ret; + if (test_bit(RTW89_QUIRK_DISABLE_2GHZ, rtwdev->quirks)) + support_bands &= ~BIT(NL80211_BAND_2GHZ); + if (support_bands & BIT(NL80211_BAND_2GHZ)) { sband = rtw89_core_sband_dup(rtwdev, &rtw89_sband_2ghz); if (!sband) @@ -6522,15 +6612,18 @@ void rtw89_complete_cond(struct rtw89_wait_info *wait, unsigned int cond, rtw89_complete_cond_resp(resp, data); } -void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, enum rtw89_btc_hmsg event) +void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, + enum rtw89_btc_hmsg event, + enum rtw89_phy_idx phy_idx) { - u16 bt_req_len; + u16 bt_slot_req[RTW89_PHY_NUM]; switch (event) { case RTW89_BTC_HMSG_SET_BT_REQ_SLOT: - bt_req_len = rtw89_coex_query_bt_req_len(rtwdev, RTW89_PHY_0); + bt_slot_req[phy_idx] = rtw89_coex_query_bt_req_len(rtwdev, phy_idx); rtw89_debug(rtwdev, RTW89_DBG_BTC, - "coex updates BT req len to %d TU\n", bt_req_len); + "coex updates PHY-%d BT req len to %d TU\n", + phy_idx, bt_slot_req[phy_idx]); rtw89_queue_chanctx_change(rtwdev, RTW89_CHANCTX_BT_SLOT_CHANGE); break; default: @@ -6867,6 +6960,7 @@ void rtw89_sta_unset_link(struct rtw89_sta *rtwsta, unsigned int link_id) int rtw89_core_init(struct rtw89_dev *rtwdev) { + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; struct rtw89_btc *btc = &rtwdev->btc; u8 band; @@ -6923,7 +7017,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) rtw89_core_ppdu_sts_init(rtwdev); rtw89_traffic_stats_init(rtwdev, &rtwdev->stats); - rtwdev->hal.rx_fltr = DEFAULT_AX_RX_FLTR; + rtwdev->hal.rx_fltr = mac->default_rx_fltr; rtwdev->dbcc_en = false; rtwdev->mlo_dbcc_mode = MLO_DBCC_NOT_SUPPORT; rtwdev->mac.qta_mode = RTW89_QTA_SCC; @@ -7180,9 +7274,19 @@ int rtw89_core_mlsr_switch(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif, static int rtw89_chip_efuse_info_setup(struct rtw89_dev *rtwdev) { const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_chip_info *chip = rtwdev->chip; + bool bb_preinit = false; int ret; - ret = rtw89_mac_partial_init(rtwdev, false); + /* + * Normally in probe stage, we don't need to do BB pre-initialization. + * However, RTL8922D firmware can access BB registers at firmware + * initial state, so initialize it to avoid BB IO stuck in firmware. + */ + if (chip->chip_id == RTL8922D) + bb_preinit = true; + + ret = rtw89_mac_partial_init(rtwdev, false, bb_preinit); if (ret) return ret; @@ -7266,7 +7370,7 @@ void rtw89_core_rfkill_poll(struct rtw89_dev *rtwdev, bool force) return; rtw89_info(rtwdev, "rfkill hardware state changed to %s\n", - blocked ? "disable" : "enable"); + str_enable_disable(!blocked)); if (blocked) set_bit(RTW89_FLAG_HW_RFKILL_STATE, rtwdev->flags); @@ -7432,9 +7536,6 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev) if (!chip->support_rnr) hw->wiphy->flags |= WIPHY_FLAG_SPLIT_SCAN_6GHZ; - if (chip->chip_gen == RTW89_CHIP_BE) - hw->wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT; - if (rtwdev->support_mlo) { hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_MLO; hw->wiphy->iftype_ext_capab = rtw89_iftypes_ext_capa; @@ -7488,6 +7589,8 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev) } rtw89_rfkill_polling_init(rtwdev); + rtw89_btc_init(rtwdev); + rtw89_led_init(rtwdev); return 0; @@ -7501,6 +7604,7 @@ static void rtw89_core_unregister_hw(struct rtw89_dev *rtwdev) { struct ieee80211_hw *hw = rtwdev->hw; + rtw89_led_deinit(rtwdev); rtw89_rfkill_polling_deinit(rtwdev); ieee80211_unregister_hw(hw); } @@ -7598,6 +7702,7 @@ struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device, rtwdev->ops = ops; rtwdev->chip = chip; rtwdev->variant = variant; + rtwdev->board = info->board; rtwdev->fw.req.firmware = firmware; rtwdev->fw.fw_format = fw_format; rtwdev->support_mlo = support_mlo; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 5547888d7e67..2b21d969ece7 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -10,10 +10,13 @@ #include #include #include +#include +#include #include #include struct rtw89_dev; +struct rtw89_bb_ctx; struct rtw89_pci_info; struct rtw89_usb_info; struct rtw89_mac_gen_def; @@ -1321,6 +1324,17 @@ struct rtw89_mac_ax_gnt { u8 gnt_wl; } __packed; +struct rtw89_btc_gnt_ctrl { + u8 gnt_zb_sw_en; + u8 gnt_zb; + u8 gnt_bt1_sw_en; + u8 gnt_bt1; + u8 gnt_bt0_sw_en; + u8 gnt_bt0; + u8 gnt_wl_sw_en; + u8 gnt_wl; +} __packed; + struct rtw89_mac_ax_wl_act { u8 wlan_act_en; u8 wlan_act; @@ -1369,6 +1383,8 @@ enum rtw89_btc_btinfo { BTC_BTINFO_MAX }; +#define BTC_BTINFO_BISTDMA 0x48 /* cmd value that identifies BISTDMA data */ + enum rtw89_btc_dcnt { BTC_DCNT_RUN = 0x0, BTC_DCNT_CX_RUNINFO, @@ -1394,6 +1410,7 @@ enum rtw89_btc_dcnt { BTC_DCNT_WL_FW_VER_MATCH, BTC_DCNT_NULL_TX_FAIL, BTC_DCNT_WL_STA_NTFY, + BTC_DCNT_W2B_SCBD_NOSYNC, BTC_DCNT_NUM, }; @@ -1415,6 +1432,7 @@ enum rtw89_btc_wl_state_cnt { BTC_WCNT_RX_ERR_LAST, BTC_WCNT_RX_ERR_LAST2S, BTC_WCNT_RX_LAST, + BTC_WCNT_SCBDUPDATE2, BTC_WCNT_NUM }; @@ -1431,30 +1449,61 @@ enum rtw89_btc_bt_state_cnt { BTC_BCNT_ROLESW, BTC_BCNT_AFH, BTC_BCNT_INFOUPDATE, + BTC_BCNT_LEAUDIO_INFOUPDATE, BTC_BCNT_INFOSAME, + BTC_BCNT_LEAUDIO_INFOSAME, BTC_BCNT_SCBDUPDATE, BTC_BCNT_HIPRI_TX, BTC_BCNT_HIPRI_RX, BTC_BCNT_LOPRI_TX, BTC_BCNT_LOPRI_RX, - BTC_BCNT_POLUT, BTC_BCNT_POLUT_NOW, BTC_BCNT_POLUT_DIFF, BTC_BCNT_RATECHG, - BTC_BCNT_BTTXPWR_UPDATE, + BTC_BCNT_AFH_CONFLICT, + BTC_BCNT_AFH_LE_CONFLICT, + BTC_BCNT_AFH_UPDATE, + BTC_BCNT_AFH_LE_UPDATE, + BTC_BCNT_AFH_CHN, + BTC_BCNT_AFH_LE_CHN, + BTC_BCNT_TXPWR_UPDATE, + BTC_BCNT_PROTECT, BTC_BCNT_NUM, }; +enum rtw89_btc_bt_mech_type { + BTC_MECH_TDD = 0, + BTC_MECH_FDD = 1, +}; + +enum rtw89_btc_bt_rf_band { + BTC_BT_B2G = 0x0, /* 2.4GHz */ + BTC_BT_B5G = 0x1, /* 5GHz or 6GHz */ + BTC_BT_BMAX = 0x2 +}; + +enum rtw89_btc_io_offload_type { + BTC_IO_OFLD_NO_SUPPORT = 0, + BTC_IO_OFLD_MAC_API = 1, + BTC_IO_OFLD_BTC_H2C = 2 +}; + enum rtw89_btc_bt_profile { BTC_BT_NOPROFILE = 0, BTC_BT_HFP = BIT(0), BTC_BT_HID = BIT(1), BTC_BT_A2DP = BIT(2), BTC_BT_PAN = BIT(3), + BTC_BT_BIS = BIT(4), + BTC_BT_CIS = BIT(5), + BTC_BT_THREAD = BIT(6), + BTC_BT_ULL = BIT(7), + BTC_BT_LEGACY = 0xf, + BTC_BT_FULL = 0x3f, BTC_PROFILE_MAX = 4, }; -struct rtw89_btc_ant_info { +struct rtw89_btc_ant_info_v0 { u8 type; /* shared, dedicated */ u8 num; u8 isolation; @@ -1477,6 +1526,50 @@ struct rtw89_btc_ant_info_v7 { u8 rsvd; } __packed; +struct rtw89_btc_ant_info_v10 { + u8 type; /* shared, dedicated(non-shared) */ + u8 num; /* antenna count */ + u8 isolation; /* Ant-Iso between WL/BT */ + u8 single_pos; /* wifi 1ss-1ant at 0:S0 or 1:S1 */ + + u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */ + u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */ + u8 ant_xmap[2][4]; +} __packed; + +struct rtw89_btc_ant_info_v11 { + u8 type; + u8 num; + u8 isolation; + u8 single_pos; + + u8 stream_cnt; + u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */ + u8 btg_pos; + u8 btg1_pos; + u8 func[5]; + u8 ant_xmap[2][4]; + u8 rsvd0[3]; +} __packed; + +struct rtw89_btc_ant_info { + u8 type; /* shared, dedicated(non-shared) */ + u8 num; /* antenna count */ + u8 isolation; /* Ant-Iso between WL/BT */ + u8 single_pos; /* wifi 1ss-1ant at 0:S0 or 1:S1 */ + + u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */ + u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */ + u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */ + u8 ant_xmap[2][4]; + + u8 diversity; /* only for wifi use 1-antenna */ +}; + enum rtw89_tfc_dir { RTW89_TFC_UL, RTW89_TFC_DL, @@ -1485,19 +1578,20 @@ enum rtw89_tfc_dir { struct rtw89_btc_wl_smap { u32 busy: 1; u32 scan: 1; - u32 connecting: 1; + u32 dhcp: 1; u32 roaming: 1; - u32 dbccing: 1; + u32 transacting: 1; u32 _4way: 1; + u32 handshake: 1; u32 rf_off: 1; - u32 lps: 2; - u32 ips: 1; - u32 init_ok: 1; - u32 traffic_dir : 2; u32 rf_off_pre: 1; + u32 ips: 1; + u32 lps: 2; u32 lps_pre: 2; u32 lps_exiting: 1; u32 emlsr: 1; + u32 init_ok: 1; + u32 traffic_dir : 2; }; enum rtw89_tfc_interval { @@ -1663,23 +1757,25 @@ struct rtw89_btc_bt_ver_info { }; struct rtw89_btc_bool_sta_chg { - u32 now: 1; - u32 last: 1; - u32 remain: 1; - u32 srvd: 29; + u8 now: 1; + u8 last: 1; + u8 remain: 1; + u8 srvd: 5; }; struct rtw89_btc_u8_sta_chg { u8 now; u8 last; - u8 remain; + u8 chg; u8 rsvd; }; struct rtw89_btc_wl_scan_info { u8 band[RTW89_PHY_NUM]; u8 phy_map; - u8 rsvd; + u8 hw_band_map; + u8 type; + u8 fw_scan; }; struct rtw89_btc_wl_dbcc_info { @@ -1689,6 +1785,25 @@ struct rtw89_btc_wl_dbcc_info { u8 role[RTW89_PHY_NUM]; /* role in each phy */ }; +struct rtw89_btc_wl_mlo_info_v2 { + u8 wmode[RTW89_PHY_NUM]; + u8 ch_type[RTW89_PHY_NUM]; + u8 hwb_rf_band[RTW89_PHY_NUM]; + u8 path_rf_band[RTW89_PHY_NUM]; + + u8 wtype; + u8 mrcx_mode; + u8 mrcx_act_hwb_map; + u8 mrcx_bt_slot_rsp; + + u8 rf_combination; + u8 mlo_en; + u8 mlo_adie; + u8 dual_hw_band_en; + + __le32 link_status; +} __packed; + struct rtw89_btc_wl_mlo_info { u8 wmode[RTW89_PHY_NUM]; /* enum phl_mr_wmode */ u8 ch_type[RTW89_PHY_NUM]; /* enum phl_mr_ch_type */ @@ -1796,10 +1911,11 @@ struct rtw89_btc_wl_role_info_bpos { u16 nan: 1; }; -struct rtw89_btc_wl_scc_ctrl { - u8 null_role1; - u8 null_role2; - u8 ebt_null; /* if tx null at EBT slot */ +struct rtw89_btc_eslot_ctrl { + u8 en; /* 1: toggle tx-flow-ctrl (null 0/1), tx-pause by Ext-slot */ + u8 nulltx_role1; + u8 nulltx_role2; + u8 nulltx_pre_time; /* null-tx time prior to EBT-start (from E2G-end) */ }; union rtw89_btc_wl_role_info_map { @@ -1807,7 +1923,7 @@ union rtw89_btc_wl_role_info_map { struct rtw89_btc_wl_role_info_bpos role; }; -struct rtw89_btc_wl_role_info { /* struct size must be n*4 bytes */ +struct rtw89_btc_wl_role_info_v0 { /* struct size must be n*4 bytes */ u8 connect_cnt; u8 link_mode; union rtw89_btc_wl_role_info_map role_map; @@ -1844,7 +1960,7 @@ struct rtw89_btc_wl_role_info_v2 { /* struct size must be n*4 bytes */ u32 rsvd: 27; }; -struct rtw89_btc_wl_rlink { /* H2C info, struct size must be n*4 bytes */ +struct rtw89_btc_wl_rlink_v0 { /* H2C info, struct size must be n*4 bytes */ u8 connected; u8 pid; u8 phy; @@ -1861,6 +1977,28 @@ struct rtw89_btc_wl_rlink { /* H2C info, struct size must be n*4 bytes */ u8 mode; /* wifi protocol */ } __packed; +struct rtw89_btc_wl_rlink_v10 { /* H2C info, struct size must be n*4 bytes */ + u8 connected; + u8 pid; + u8 phy; + u8 noa; + + u8 rf_band; /* enum band_type RF band: 2.4G/5G/6G */ + u8 active; /* 0:rlink is under doze */ + u8 bw; /* enum channel_width */ + u8 role; /*enum role_type */ + + u8 ch; + u8 noa_dur; /* ms */ + u8 client_cnt; /* for Role = P2P-Go/AP */ + u8 mode; /* wifi protocol */ + + u8 mac_id; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; +} __packed; + #define RTW89_BE_BTC_WL_MAX_ROLE_NUMBER 6 struct rtw89_btc_wl_role_info_v7 { /* struct size must be n*4 bytes */ u8 connect_cnt; @@ -1870,12 +2008,12 @@ struct rtw89_btc_wl_role_info_v7 { /* struct size must be n*4 bytes */ struct rtw89_btc_wl_active_role_v7 active_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; - u32 role_map; - u32 mrole_type; /* btc_wl_mrole_type */ - u32 mrole_noa_duration; /* ms */ - u32 dbcc_en; - u32 dbcc_chg; - u32 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + __le32 role_map; + __le32 mrole_type; /* btc_wl_mrole_type */ + __le32 mrole_noa_duration; /* ms */ + __le32 dbcc_en; + __le32 dbcc_chg; + __le32 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ } __packed; struct rtw89_btc_wl_role_info_v8 { /* H2C info, struct size must be n*4 bytes */ @@ -1889,14 +2027,94 @@ struct rtw89_btc_wl_role_info_v8 { /* H2C info, struct size must be n*4 bytes */ u8 dbcc_chg; u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ - struct rtw89_btc_wl_rlink rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + struct rtw89_btc_wl_rlink_v0 rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; - u32 role_map; - u32 mrole_type; /* btc_wl_mrole_type */ - u32 mrole_noa_duration; /* ms */ + __le32 role_map; + __le32 mrole_type; /* btc_wl_mrole_type */ + __le32 mrole_noa_duration; /* ms */ } __packed; +struct rtw89_btc_wl_role_info_v10 { /* H2C info, struct size must be n*4 bytes */ + struct rtw89_btc_wl_rlink_v10 rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + u8 link_mode; + u8 link_mode_hb1; + u8 p2p_exist; + u8 p2p_exist_hb1; + + u8 pta_req_band; + u8 dbcc_en; /* 1+1 and 2.4G-included */ + u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + u8 rsvd; + + __le32 role_map; + __le32 role_map_hb1; + __le32 mrole_type; /* btc_wl_mrole_type: [31:16]:band1, [15:0]:band0 */ +} __packed; + +struct rtw89_btc_wl_rlink { /* Logic dynamic using */ + u8 connected; + u8 pid; + u8 phy; + u8 noa; + + u8 rf_band; /* enum band_type RF band: 2.4G/5G/6G */ + u8 active; /* 0:rlink is under doze */ + u8 bw; /* enum channel_width */ + u8 role; /*enum role_type */ + + u8 ch; + u8 noa_dur; /* ms */ + u8 client_cnt; /* for Role = P2P-Go/AP */ + u8 mode; /* wifi protocol */ + + /*v0 v1*/ + u16 tx_lvl; + u16 rx_lvl; + u16 tx_rate; + u16 rx_rate; + + /* v7 */ + u8 client_ps; /*v7 v2 v1 v0*/ + + /* v10 */ + u8 mac_id; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; +}; + +struct rtw89_btc_wl_role_info { /* Logic dynamic using */ + struct rtw89_btc_wl_rlink rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + u8 link_mode; + u8 link_mode_hb1; + u8 p2p_exist; + u8 p2p_exist_hb1; + + u8 pta_req_band; + u8 dbcc_en; /* 1+1 and 2.4G-included */ + u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + u8 rsvd; + + u32 role_map; + u32 role_map_hb1; + u32 mrole_type; /* btc_wl_mrole_type: [31:16]:band1, [15:0]:band0 */ + + /* Before v10 use this linkmode */ + u8 link_mode_v0; + + /* v7 */ + u8 connect_cnt; + u32 dbcc_chg; /* v7 v2*/ + + /* v8 */ + u8 link_mode_chg; /* v8, v7 v2*/ + u8 p2p_2g; /* v8, v7 */ + u32 mrole_noa_duration; /* v8, v7 v2*/ +}; + struct rtw89_btc_wl_ver_info { + char build_time[12]; + char build_date[12]; u32 fw_coex; /* match with which coex_ver */ u32 fw; u32 mac; @@ -1908,7 +2126,7 @@ struct rtw89_btc_wl_afh_info { u8 en; u8 ch; u8 bw; - u8 rsvd; + u8 band; } __packed; struct rtw89_btc_wl_rfk_info { @@ -1931,6 +2149,8 @@ struct rtw89_btc_bt_smap { u32 sco_busy: 1; u32 mesh_busy: 1; u32 inq_pag: 1; + u32 profile_map: 8; + u32 rsvd: 18; }; union rtw89_btc_bt_state_map { @@ -1949,14 +2169,57 @@ struct rtw89_btc_bt_txpwr_desc { u8 le_gain_index; }; +struct rtw89_btc_bt_leaudio_info { + u8 cmd; + u8 len; + u8 bis_cis; +#define RTW89_BTC_LEAU_INFO_L2_BIS_EX BIT(0) +#define RTW89_BTC_LEAU_INFO_L2_BIS_CNT GENMASK(3, 1) +#define RTW89_BTC_LEAU_INFO_L2_CIS_EX BIT(4) +#define RTW89_BTC_LEAU_INFO_L2_CIS_CNT GENMASK(7, 5) + u8 rssi; + __le32 hbrsvd; +} __packed; + +struct rtw89_btc_bt_bistdma_info_le { + u8 cmd; + u8 len; + u8 bis; /* BIT(2) ~ BIT(7) is rsvd */ +#define RTW89_BTC_BIS_INFO_L2_START_END BIT(0) /* 0: BIS start, 1: BIS end */ +#define RTW89_BTC_BIS_INFO_L2_TRX BIT(1) /* 0: BIS Tx, 1: BIS Rx */ + u8 diff_t_lb; + u8 diff_t_hb; /* diff_t = (diff_t_hb * 256 + diff_t_lb) * 0.625 ms */ + u8 hb1rsvd; + u8 hb2rsvd; + u8 hb3rsvd; +} __packed; + +struct rtw89_btc_bt_leaudio_desc { + u32 bis_exist: 1; + u32 bis_exist_last: 1; + u32 cis_exist: 1; + u32 cis_exist_last: 1; + u32 bis_cnt: 3; + u32 cis_cnt: 3; + u32 rssi: 8; + u32 bis_cnt_last: 3; + u32 cis_cnt_last: 3; + u32 bis_trx: 1; + u32 bis_start_end: 1; + u32 rsvd: 6; + + u16 diff_t; +}; + struct rtw89_btc_bt_link_info { - struct rtw89_btc_u8_sta_chg profile_cnt; + struct rtw89_btc_u8_sta_chg link_cnt; struct rtw89_btc_bool_sta_chg multi_link; struct rtw89_btc_bool_sta_chg relink; struct rtw89_btc_bt_hfp_desc hfp_desc; struct rtw89_btc_bt_hid_desc hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp_desc; struct rtw89_btc_bt_pan_desc pan_desc; + struct rtw89_btc_bt_leaudio_desc leaudio_desc; union rtw89_btc_bt_state_map status; struct rtw89_btc_bt_txpwr_desc bt_txpwr_desc; @@ -1965,20 +2228,83 @@ struct rtw89_btc_bt_link_info { u8 rssi_state[BTC_BT_RSSI_THMAX]; u8 afh_map[BTC_BT_AFH_GROUP]; u8 afh_map_le[BTC_BT_AFH_LE_GROUP]; + u8 rssi; - u32 role_sw: 1; - u32 slave_role: 1; - u32 afh_update: 1; - u32 cqddr: 1; - u32 rssi: 8; - u32 tx_3m: 1; - u32 rsvd: 19; + u8 role_sw: 1; + u8 slave_role: 1; + u8 afh_update: 1; + u8 cqddr: 1; + u8 tx_3m: 1; + u8 inq: 1; + u8 pag: 1; + u8 igno_wl: 1; + + u8 ble_scan_en: 1; + u8 reinit: 1; + u8 rsvd: 6; + + u8 leaudio_raw_info[BTC_BTINFO_MAX]; /* raw LE audio info from BT mailbox */ + u8 bistdma_raw_info[BTC_BTINFO_MAX]; /* raw BIS-TDMA info from BT mailbox */ }; -struct rtw89_btc_3rdcx_info { - u8 type; /* 0: none, 1:zigbee, 2:LTE */ - u8 hw_coex; - u16 rsvd; +struct rtw89_btc_bind_bt_status { + u8 a2dp_active: 1; + u8 a2dp_sink: 1; + u8 pan_active: 1; + u8 connect: 1; + u8 inq_page: 1; + u8 multi_link: 1; + u8 slave_role: 1; + u8 page: 1; + + u8 hfp_exist: 1; + u8 hid_exist: 1; + u8 a2dp_exist: 1; + u8 pan_exist: 1; + u8 bis_exist: 1; + u8 cis_exist: 1; + u8 thread_exist: 1; + u8 ull_exist: 1; + + u8 hid_cnt; + u8 hid_type; + u8 cis_cnt; + u8 link_cnt; + + u16 a2dp_vendor_id; +}; + +struct rtw89_btc_bind_info { + u8 wl_hwb_sel; /* map */ + u8 wl_link_mode; + u8 wl_bg_mode; + u8 rf_band; /* map, 0: no any rf-band bind */ + u8 bt_sel; /* map */ + u8 bt_link_weight; /* select the highest weight between bt/rf-band */ + + u32 bt_profile; /* map */ + struct rtw89_btc_bind_bt_status bt_smap; +}; + +struct rtw89_btc_extsoc_info { + u8 chip_id; + u8 max_tx_pwr; + u8 rf_band_map; + u8 ant_iso_to_wl; + + u8 link_weight[BTC_BT_BMAX]; + + u8 func_type; /* 0: none, 1:zigbee, 2:LTE */ + u8 hw_coex; /* Hard-Wire coex interface support */ + u8 pta_type; /* 0: RTK 4-wire mode, 1: 3-wire mode */ + u8 pta_req_exist; + + u32 hpta_cfg; + u32 hmbx_cfg; + u32 swout_cfg; + u32 swin_cfg; + u32 profile_map[BTC_BT_BMAX]; + u32 bcnt[BTC_BCNT_NUM]; }; struct rtw89_btc_dm_emap { @@ -2006,6 +2332,7 @@ struct rtw89_btc_dm_emap { u32 h2c_buffer_over: 1; u32 bt_tx_hang: 1; /* for SNR too low bug, BT has no Tx req*/ u32 wl_no_sta_ntfy: 1; + u32 w2b_scbd_no_sync: 1; u32 h2c_bmap_mismatch: 1; u32 c2h_bmap_mismatch: 1; @@ -2042,16 +2369,13 @@ struct rtw89_btc_wl_nhm { }; struct rtw89_btc_wl_info { - struct rtw89_btc_wl_link_info link_info[RTW89_PORT_NUM]; struct rtw89_btc_wl_link_info rlink_info[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + struct rtw89_btc_chdef rf_ch_info[RTW89_PHY_NUM]; struct rtw89_btc_wl_rfk_info rfk_info; - struct rtw89_btc_wl_ver_info ver_info; - struct rtw89_btc_wl_afh_info afh_info; + struct rtw89_btc_wl_ver_info ver_info; + struct rtw89_btc_wl_afh_info afh_info[RTW89_MAC_NUM][RTW89_BAND_NUM]; + struct rtw89_btc_wl_afh_info afh_info_last[RTW89_MAC_NUM][RTW89_BAND_NUM]; struct rtw89_btc_wl_role_info role_info; - struct rtw89_btc_wl_role_info_v1 role_info_v1; - struct rtw89_btc_wl_role_info_v2 role_info_v2; - struct rtw89_btc_wl_role_info_v7 role_info_v7; - struct rtw89_btc_wl_role_info_v8 role_info_v8; struct rtw89_btc_wl_scan_info scan_info; struct rtw89_btc_wl_dbcc_info dbcc_info; struct rtw89_btc_wl_mlo_info mlo_info; @@ -2065,23 +2389,32 @@ struct rtw89_btc_wl_info { u8 coex_mode; u8 pta_req_mac; u8 bt_polut_type[RTW89_PHY_NUM]; /* BT polluted WL-Tx type for phy0/1 */ + u8 rf_band_map[RTW89_PHY_NUM]; /* rf_band bit-map */ + u8 ch_map[12]; + u8 ch_map_le[5]; - bool is_5g_hi_channel; + bool is_5g_hi_ch; + bool is_5g_hi_ch_hb1; bool go_client_exist; + bool go_client_exist_hb1; bool noa_exist; + bool noa_exist_hb1; bool pta_reg_mac_chg; bool bg_mode; + bool bg_mode_hb1; bool he_mode; - bool scbd_change; + bool scbd_chg[BTC_ALL_BT]; bool fw_ver_mismatch; bool client_cnt_inc_2g; bool link_mode_chg; bool dbcc_chg; - u32 scbd; + u32 scbd[BTC_ALL_BT]; + u32 scbd_rb[BTC_ALL_BT]; + u32 wcnt[BTC_WCNT_NUM]; }; -struct rtw89_btc_module { - struct rtw89_btc_ant_info ant; +struct rtw89_btc_module_v0 { + struct rtw89_btc_ant_info_v0 ant; u8 rfe_type; u8 cv; @@ -2107,9 +2440,55 @@ struct rtw89_btc_module_v7 { struct rtw89_btc_ant_info_v7 ant; } __packed; +struct rtw89_btc_module_v10 { + u8 rfe_type; + u8 wa_type; /* Refer to enum btc_wa_type */ + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; /* wl-end view: get from efuse, must compare bt.btg_type*/ + u8 bt0_sw_type; /* BT Ant-switch: None(non-share), Int(BTG), Ext(SPDT)*/ + u8 bt1_pos; /* BTC_BT_ALONE or BTC_BT_BTG */ + u8 bt1_sw_type; + + struct rtw89_btc_ant_info_v10 ant; +} __packed; + +struct rtw89_btc_module_v11 { + u8 rfe_type; + u8 wa_type; + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; + u8 bt0_sw_type; + u8 bt1_pos; + u8 bt1_sw_type; + + struct rtw89_btc_ant_info_v11 ant; +} __packed; + union rtw89_btc_module_info { - struct rtw89_btc_module md; + struct rtw89_btc_module_v0 md_v0; struct rtw89_btc_module_v7 md_v7; + struct rtw89_btc_module_v10 md_v10; + struct rtw89_btc_module_v11 md_v11; +}; + +struct rtw89_btc_module { + u8 rfe_type; + u8 wa_type; /* Refer to enum btc_wa_type */ + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; /* wl-end view: get from efuse, must compare bt.btg_type*/ + u8 bt0_sw_type; /* BT Ant-switch: None(non-share), Int(BTG), Ext(SPDT)*/ + u8 bt1_pos; /* BTC_BT_ALONE or BTC_BT_BTG */ + u8 bt1_sw_type; + + u8 bt_solo; + + struct rtw89_btc_ant_info ant; }; #define RTW89_BTC_DM_MAXSTEP 30 @@ -2121,8 +2500,8 @@ struct rtw89_btc_dm_step { bool step_ov; }; -struct rtw89_btc_init_info { - struct rtw89_btc_module module; +struct rtw89_btc_init_info_v0 { + struct rtw89_btc_module_v0 module; u8 wl_guard_ch; u8 wl_only: 1; @@ -2132,7 +2511,7 @@ struct rtw89_btc_init_info { u8 bt_only: 1; u16 rsvd; -}; +} __packed; struct rtw89_btc_init_info_v7 { u8 wl_guard_ch; @@ -2148,9 +2527,75 @@ struct rtw89_btc_init_info_v7 { struct rtw89_btc_module_v7 module; } __packed; +struct rtw89_btc_init_info_v107 { + u8 wl_guard_ch; + u8 wl_only; + u8 wl_init_ok; + u8 dbcc_en; + + u8 cx_other; + u8 bt_only; + u8 rsvd; + u8 rsvd1; + + struct rtw89_btc_module_v7 module; +} __packed; + +struct rtw89_btc_init_info_v10 { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + struct rtw89_btc_module_v10 module; +} __packed; + +struct rtw89_btc_init_info_v11 { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + struct rtw89_btc_module_v11 module; +}; + union rtw89_btc_init_info_u { - struct rtw89_btc_init_info init; + struct rtw89_btc_init_info_v0 init_v0; struct rtw89_btc_init_info_v7 init_v7; + struct rtw89_btc_init_info_v10 init_v10; + struct rtw89_btc_init_info_v107 init_v107; + struct rtw89_btc_init_info_v11 init_v11; +}; + +struct rtw89_btc_init_info { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + u8 dbcc_en; + u8 cx_other; + u8 bt_only; + u8 wl_only; + + u8 wl_guard_ch; + + struct rtw89_btc_module module; }; struct rtw89_btc_wl_tx_limit_para { @@ -2159,6 +2604,15 @@ struct rtw89_btc_wl_tx_limit_para { u16 tx_retry; }; +struct rtw89_btc_wl_trx_nss_para { + u8 tx_limit; + u8 rx_limit; + u8 tx_ss; + u8 rx_ss; + u8 tx_path; + u8 rx_path; +}; + enum rtw89_btc_bt_scan_type { BTC_SCAN_INQ = 0, BTC_SCAN_PAGE, @@ -2176,8 +2630,21 @@ enum rtw89_btc_ble_scan_type { CXSCAN_MAX }; +enum rtw89_btc_bt_func_type { + BTC_BTF_NONE = 0, + BTC_BTF_BT = BIT(0), + BTC_BTF_ZB = BIT(1), + BTC_BTF_THREAD = BIT(2), + BTC_BTF_24GPRO = BIT(3), /* 2.4GHz Proprietary */ + BTC_BTF_ULL = BIT(4), +}; + #define RTW89_BTC_BTC_SCAN_V1_FLAG_ENABLE BIT(0) #define RTW89_BTC_BTC_SCAN_V1_FLAG_INTERLACE BIT(1) +#define RTW89_BTC_BT_DEF_BR_TX_PWR 4 +#define RTW89_BTC_BT_DEF_LE_TX_PWR 4 +#define RTW89_BTC_DEFAULT_ANISO 10 +#define RTW89_BTC_BT_DEF_LE_TX_PWR_1 10 struct rtw89_btc_bt_scan_info_v1 { __le16 win; @@ -2212,14 +2679,24 @@ struct rtw89_btc_fbtc_btscan_v7 { struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX]; } __packed; +struct rtw89_btc_fbtc_btscan_v8 { + u8 fver; /* btc_ver::fcxbtscan */ + u8 type; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX]; +} __packed; + union rtw89_btc_fbtc_btscan { struct rtw89_btc_fbtc_btscan_v1 v1; struct rtw89_btc_fbtc_btscan_v2 v2; struct rtw89_btc_fbtc_btscan_v7 v7; + struct rtw89_btc_fbtc_btscan_v8 v8; }; struct rtw89_btc_bt_info { struct rtw89_btc_bt_link_info link_info; + struct rtw89_btc_bt_link_info link_info_56g; struct rtw89_btc_bt_scan_info_v1 scan_info_v1[BTC_SCAN_MAX1]; struct rtw89_btc_bt_scan_info_v2 scan_info_v2[CXSCAN_MAX]; struct rtw89_btc_bt_ver_info ver_info; @@ -2228,11 +2705,28 @@ struct rtw89_btc_bt_info { struct rtw89_btc_rf_para rf_para; union rtw89_btc_bt_rfk_info_map rfk_info; - u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox */ + u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox (2.4G) */ + u8 raw_info_56g[BTC_BTINFO_MAX]; /* raw bt info from mailbox (5/6G) */ u8 txpwr_info[BTC_BTINFO_MAX]; + u8 link_weight[BTC_BT_BMAX]; /* Link Weight for RF-band/HWB selection */ u8 rssi_level; + u8 rf_band_map; + u8 func_type; + u8 tx_power_now; + u8 tx_power_now_6g; + u8 ant_iso_to_wl; /* ant isolation between BTx and WL */ + + u8 fw_ver_mismatch: 1; + u8 band_56G_support: 1; + u8 hi_lna_rx: 1; + u8 lna_constrain: 3; + u8 hi_lna_rx_6g: 1; + u8 lna_constrain_6g: 3; + u8 rsvd: 6; u32 scbd; + u32 scbd_rb; + u32 scbd_c2h; u32 feature; u32 mbx_avl: 1; @@ -2244,11 +2738,19 @@ struct rtw89_btc_bt_info { u32 inq: 1; u32 pag: 1; u32 run_patch_code: 1; - u32 hi_lna_rx: 1; u32 scan_rx_low_pri: 1; u32 scan_info_update: 1; - u32 lna_constrain: 3; - u32 rsvd: 17; + u32 rsvd1: 22; + + u32 bcnt[BTC_BCNT_NUM]; +}; + +#define RTW89_BTC_WL_DEF_TX_PWR GENMASK(7, 0) +struct rtw89_btc_rf_trx_para_v0 { + u32 wl_tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ + u32 wl_rx_gain; /* rx gain table index (TBD.) */ + u8 bt_tx_power; /* decrease Tx power (dB) */ + u8 bt_rx_gain; /* LNA constrain level */ }; struct rtw89_btc_rf_trx_para_v9 { @@ -2262,18 +2764,18 @@ struct rtw89_btc_rf_trx_para_v9 { struct rtw89_btc_cx { struct rtw89_btc_wl_info wl; - struct rtw89_btc_bt_info bt; - struct rtw89_btc_3rdcx_info other; + struct rtw89_btc_bt_info bt0; + struct rtw89_btc_bt_info bt1; + struct rtw89_btc_extsoc_info bt_ext; + struct rtw89_btc_rf_trx_para_v9 rf_para; u32 state_map; - u32 cnt_bt[BTC_BCNT_NUM]; - u32 cnt_wl[BTC_WCNT_NUM]; }; struct rtw89_btc_fbtc_tdma { u8 type; /* btc_ver::fcxtdma */ u8 rxflctrl; - u8 txpause; - u8 wtgle_n; + u8 txflctrl; + u8 bind; u8 leak_n; u8 ext_ctrl; u8 rxflctrl_role; @@ -2463,6 +2965,43 @@ struct rtw89_btc_fbtc_rpt_ctrl_v8 { struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; } __packed; +#define RTW89_BTC_TIME_DATE_FMT 12 +struct rtw89_btc_fbtc_rpt_ctrl_v9 { + u8 fver; + u8 ext_req_exist; + u8 pta_owner; + u8 rsvd; + + u8 build_time[RTW89_BTC_TIME_DATE_FMT]; + u8 build_date[RTW89_BTC_TIME_DATE_FMT]; + + u8 gnt_val[RTW89_PHY_NUM][4]; /* gwl/gbt012 refer to struct btc_gnt_ctrl */ + __le16 bt_cnt[BTC_BCNT_STA_MAX_V105]; + + struct rtw89_btc_fbtc_rpt_ctrl_info_v8 rpt_info; + struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; +} __packed; + +struct rtw89_btc_fbtc_rpt_ctrl_v11 { + u8 fver; + u8 rsvd0; + u8 rpt_len_max_l; /* BTC_RPT_MAX bit0~7 */ + u8 rpt_len_max_h; /* BTC_RPT_MAX bit8~15 */ + + u8 build_time[12]; + u8 build_date[12]; + + u8 gnt_val[RTW89_PHY_NUM][8]; /* gwl/gbt012 refer to struct btc_gnt_ctrl */ + __le16 bt_cnt[BTC_ALL_BT_EZL][BTC_BCNT_STA_MAX_V105]; + + struct rtw89_btc_fbtc_rpt_ctrl_info_v8 rpt_info; + struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; + + __le32 error_code; + __le32 scbd_w2b[2]; + __le32 scbd_b2w[2]; +} __packed; + union rtw89_btc_fbtc_rpt_ctrl_ver_info { struct rtw89_btc_fbtc_rpt_ctrl_v1 v1; struct rtw89_btc_fbtc_rpt_ctrl_v4 v4; @@ -2470,6 +3009,8 @@ union rtw89_btc_fbtc_rpt_ctrl_ver_info { struct rtw89_btc_fbtc_rpt_ctrl_v105 v105; struct rtw89_btc_fbtc_rpt_ctrl_v7 v7; struct rtw89_btc_fbtc_rpt_ctrl_v8 v8; + struct rtw89_btc_fbtc_rpt_ctrl_v9 v9; + struct rtw89_btc_fbtc_rpt_ctrl_v11 v11; }; enum rtw89_fbtc_ext_ctrl_type { @@ -2575,14 +3116,55 @@ enum rtw89_btc_afh_map_type { /*AFH MAP TYPE */ RPT_BT_AFH_SEQ_LE = 0x20 }; -#define BTC_DBG_MAX1 32 +enum btc_wl_gpio_debug { + BTC_DBG_GNT_BT = 0, + BTC_DBG_GNT_WL = 1, + BTC_DBG_GNT_BT1 = 2, + BTC_DBG_GNT_WL1 = 3, + /* The following signals should 0-1 tiggle by each function-call */ + BTC_DBG_BCN_EARLY = 4, + BTC_DBG_WL_NULL0 = 5, + BTC_DBG_WL_NULL1 = 6, + BTC_DBG_WL_RXISR = 7, + BTC_DBG_TDMA_ENTRY = 8, + BTC_DBG_A2DP_EMPTY = 9, + BTC_DBG_BT_RETRY = 10, + /* The following signals should 0-1 tiggle by state L/H */ + BTC_DBG_BT_RELINK = 11, + BTC_DBG_SLOT_WL = 12, + BTC_DBG_SLOT_BT = 13, + /* The following signals should 0-1 tiggle by external*/ + BTC_DBG_WL_ERR = 14, + BTC_DBG_WL_OK = 15, + /* The following signals appear only 1-active at same time*/ + BTC_DBG_SLOT_B2W = 16, + BTC_DBG_SLOT_W1 = 17, + BTC_DBG_SLOT_W2 = 18, + BTC_DBG_SLOT_W2B = 19, + BTC_DBG_SLOT_B1 = 20, + BTC_DBG_SLOT_B2 = 21, + BTC_DBG_SLOT_B3 = 22, + BTC_DBG_SLOT_B4 = 23, + BTC_DBG_SLOT_LK = 24, + BTC_DBG_SLOT_E2G = 25, + BTC_DBG_SLOT_E5G = 26, + BTC_DBG_SLOT_EBT = 27, + BTC_DBG_SLOT_WLK = 28, + BTC_DBG_SLOT_B1FDD = 29, + BTC_DBG_BT_CHANGE = 30, + /* The following signals should 0-1 tiggle by external*/ + BTC_DBG_WL_CCA = 31, + + BTC_DBG_NUM, +}; + struct rtw89_btc_fbtc_gpio_dbg_v1 { u8 fver; /* btc_ver::fcxgpiodbg */ u8 rsvd; __le16 rsvd2; __le32 en_map; /* which debug signal (see btc_wl_gpio_debug) is enable */ __le32 pre_state; /* the debug signal is 1 or 0 */ - u8 gpio_map[BTC_DBG_MAX1]; /*the debug signals to GPIO-Position */ + u8 gpio_map[BTC_DBG_NUM]; /*the debug signals to GPIO-Position */ } __packed; struct rtw89_btc_fbtc_gpio_dbg_v7 { @@ -2591,7 +3173,7 @@ struct rtw89_btc_fbtc_gpio_dbg_v7 { u8 rsvd1; u8 rsvd2; - u8 gpio_map[BTC_DBG_MAX1]; + u8 gpio_map[BTC_DBG_NUM]; __le32 en_map; __le32 pre_state; @@ -2602,6 +3184,120 @@ union rtw89_btc_fbtc_gpio_dbg { struct rtw89_btc_fbtc_gpio_dbg_v7 v7; }; +/* + * SET_GPIO_CTRL payload (max len = 7 bytes) + * + * type = CXDGPIO_EN_MAP + * data.val[31:0] = debug signal enable map + * + * type = CXDGPIO_MUX_MAP + * data.mux.sig = debug signal id + * data.mux.gpio = GPIO id + * + * type = CXDGPIO_EXT_HPTA / CXDGPIO_EXT_HMBX / CXDGPIO_EXT_SWOUT + * data.map.map_low = GPIO 7~0 map + * data.map.map_high = GPIO 15~8 map + * + * type = CXDGPIO_EXT_SWIN + * data.swin.in_map_low = GPIO 7~0 input-en-map + * data.swin.in_map_high = GPIO 15~8 input-en-map + * data.swin.int_map_low = GPIO 7~0 interrupt source map + * data.swin.int_map_high = GPIO 15~8 interrupt source map + */ +#define CXDGPIO_SET_L4 4 +#define CXDGPIO_SET_L2 2 +struct rtw89_fbtc_h2c_set_gpio_en_map { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u32 en_map; +}; + +struct rtw89_fbtc_h2c_set_gpio_mux { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 sig; + u8 gpio; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_pta { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_mb { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_swout { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_swin { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 in_map_low; + u8 in_map_high; + u8 int_map_low; + u8 int_map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_2b { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 data[CXDGPIO_SET_L2]; +} __packed; + +struct rtw89_fbtc_h2c_set_gpio_4b { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 data[CXDGPIO_SET_L4]; +} __packed; + +union rtw89_fbtc_h2c_set_gpio_en_map_u { + struct rtw89_fbtc_h2c_set_gpio_4b fmt; + struct rtw89_fbtc_h2c_set_gpio_en_map data; +}; + +union rtw89_fbtc_h2c_set_gpio_mux_u { + struct rtw89_fbtc_h2c_set_gpio_2b fmt; + struct rtw89_fbtc_h2c_set_gpio_mux data; +}; + +union rtw89_fbtc_h2c_set_gpio_ext_pta_u { + struct rtw89_fbtc_h2c_set_gpio_2b fmt; + struct rtw89_fbtc_h2c_set_gpio_ext_pta data; +}; + +union rtw89_fbtc_h2c_set_gpio_ext_swin_u { + struct rtw89_fbtc_h2c_set_gpio_4b fmt; + struct rtw89_fbtc_h2c_set_gpio_ext_swin data; +}; + +struct rtw89_fbtc_h2c_set_gpio { + union rtw89_fbtc_h2c_set_gpio_en_map_u en_map; + union rtw89_fbtc_h2c_set_gpio_mux_u mux; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_pta; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_mb; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_swout; + union rtw89_fbtc_h2c_set_gpio_ext_swin_u ext_swin; +}; + struct rtw89_btc_fbtc_mreg_val_v1 { u8 fver; /* btc_ver::fcxmreg */ u8 reg_num; @@ -2660,6 +3356,14 @@ struct rtw89_btc_fbtc_slot_v7 { __le32 cxtbl; } __packed; +struct rtw89_btc_fbtc_slots_v2 { + u8 fver; /* btc_ver::fcxslots */ + u8 tbl_num; + __le16 rsvd; + __le32 update_map; + struct rtw89_btc_fbtc_slot_v7 slot[CXST_MAX]; +} __packed; + struct rtw89_btc_fbtc_slot_u16 { __le16 dur; /* slot duration */ __le16 cxtype; @@ -2685,6 +3389,7 @@ struct rtw89_btc_fbtc_slots_v7 { union rtw89_btc_fbtc_slots_info { struct rtw89_btc_fbtc_slots v1; + struct rtw89_btc_fbtc_slots_v2 v2; struct rtw89_btc_fbtc_slots_v7 v7; } __packed; @@ -2912,6 +3617,26 @@ struct rtw89_btc_fbtc_cysta_v5 { /* statistics for cycles */ __le32 except_map; } __packed; +struct rtw89_btc_fbtc_cysta_v105 { + u8 fver; + u8 rsvd; + u8 collision_cnt; + u8 except_cnt; + u8 wl_rx_err_ratio[BTC_CYCLE_SLOT_MAX]; + + __le16 skip_cnt; + __le16 cycles; + + __le16 slot_step_time[BTC_CYCLE_SLOT_MAX]; + __le16 slot_cnt[CXST_MAX]; + __le16 bcn_cnt[CXBCN_MAX]; + struct rtw89_btc_fbtc_cycle_time_info_v5 cycle_time; + struct rtw89_btc_fbtc_cycle_leak_info leak_slot; + struct rtw89_btc_fbtc_cycle_a2dp_empty_info a2dp_ept; + struct rtw89_btc_fbtc_a2dp_trx_stat_v4 a2dp_trx[BTC_CYCLE_SLOT_MAX]; + __le32 except_map; +} __packed; + struct rtw89_btc_fbtc_cysta_v7 { /* statistics for cycles */ u8 fver; u8 rsvd; @@ -2941,6 +3666,7 @@ union rtw89_btc_fbtc_cysta_info { struct rtw89_btc_fbtc_cysta_v3 v3; struct rtw89_btc_fbtc_cysta_v4 v4; struct rtw89_btc_fbtc_cysta_v5 v5; + struct rtw89_btc_fbtc_cysta_v105 v105; struct rtw89_btc_fbtc_cysta_v7 v7; }; @@ -2999,9 +3725,21 @@ struct rtw89_btc_fbtc_btver_v7 { __le32 feature; } __packed; +struct rtw89_btc_fbtc_btver_v8 { + u8 fver; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + u8 rsvd2; + + __le32 coex_ver; /*bit[15:8]->shared, bit[7:0]->non-shared */ + __le32 fw_ver; + __le32 feature; +} __packed; + union rtw89_btc_fbtc_btver { struct rtw89_btc_fbtc_btver_v1 v1; struct rtw89_btc_fbtc_btver_v7 v7; + struct rtw89_btc_fbtc_btver_v8 v8; } __packed; struct rtw89_btc_fbtc_btafh { @@ -3037,6 +3775,18 @@ struct rtw89_btc_fbtc_btafh_v7 { u8 afh_le_b[4]; } __packed; +struct rtw89_btc_fbtc_btafh_v8 { + u8 fver; + u8 map_type; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + u8 afh_l[4]; /*bit0:2402, bit1:2403.... bit31:2433 */ + u8 afh_m[4]; /*bit0:2434, bit1:2435.... bit31:2465 */ + u8 afh_h[4]; /*bit0:2466, bit1:2467.....bit14:2480 */ + u8 afh_le_a[4]; + u8 afh_le_b[4]; +} __packed; + struct rtw89_btc_fbtc_btdevinfo { u8 fver; /* btc_ver::fcxbtdevinfo */ u8 rsvd; @@ -3045,24 +3795,18 @@ struct rtw89_btc_fbtc_btdevinfo { __le32 flush_time; } __packed; -#define RTW89_BTC_WL_DEF_TX_PWR GENMASK(7, 0) -struct rtw89_btc_rf_trx_para { - u32 wl_tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ - u32 wl_rx_gain; /* rx gain table index (TBD.) */ - u8 bt_tx_power; /* decrease Tx power (dB) */ - u8 bt_rx_gain; /* LNA constrain level */ -}; - struct rtw89_btc_trx_info { u8 tx_lvl; u8 rx_lvl; u8 wl_rssi; u8 bt_rssi; - s8 tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ - s8 rx_gain; /* rx gain table index (TBD.) */ - s8 bt_tx_power; /* decrease Tx power (dB) */ - s8 bt_rx_gain; /* LNA constrain level */ + s8 wl_tx_power[RTW89_PHY_NUM]; /* absolute Tx power (dBm), 0xff-> no BTC control */ + s8 wl_rx_gain[RTW89_PHY_NUM]; /* rx gain table index (TBD.) */ + s8 bt_tx_power[BTC_ALL_BT]; /* decrease Tx power (dB) */ + s8 bt_rx_gain[BTC_ALL_BT]; /* LNA constrain level */ + s8 zb_tx_power[BTC_ALL_BT]; + s8 zb_rx_gain[BTC_ALL_BT]; u8 cn; /* condition_num */ s8 nhm; @@ -3083,23 +3827,212 @@ enum btc_rf_path { BTC_RF_NUM, }; -struct rtw89_btc_fbtc_outsrc_set_info { - u8 rf_band[BTC_RF_NUM]; /* 0:2G, 1:non-2G */ +struct rtw89_btc_fbtc_outsrc_set_info_v1 { + u8 rf_band[BTC_RF_NUM]; u8 btg_rx[BTC_RF_NUM]; u8 nbtg_tx[BTC_RF_NUM]; - struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; /* refer to btc_gnt_ctrl */ - struct rtw89_mac_ax_wl_act wlact_set[BTC_RF_NUM]; /* BT0/BT1 */ + struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT]; u8 pta_req_hw_band; u8 rf_gbt_source; + u8 bt_enable_state; + u8 wl_btg_standby_chg; + + u8 fbd_group_en[RTW89_MAC_NUM][2]; + __le16 rf_center_freq[RTW89_MAC_NUM]; + __le16 fbd_group_bound[RTW89_MAC_NUM][2]; + __le16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT]; } __packed; +struct rtw89_btc_fbtc_outsrc_set_info_v6 { + u8 rf_band[BTC_RF_NUM]; + u8 btg_rx[BTC_RF_NUM]; + u8 nbtg_tx[BTC_RF_NUM]; + + struct rtw89_btc_gnt_ctrl gnt_set[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; + + u8 pta_req_hw_band; + u8 rf_gbt_source; + u8 bt_enable_state; + u8 bt_plut_type; + u8 wl_tx_limit_en; + u8 fc_exec; + u8 wl_btg_standby_chg; + u8 rsvd; + u8 bb_path_sel_bt[BTC_RF_NUM]; + u8 bb_phy_sel_bt[RTW89_PHY_NUM]; + u8 fbd_group_en[RTW89_MAC_NUM][2]; + __le16 rf_center_freq[RTW89_MAC_NUM]; + __le16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT_EZL]; + __le16 fbd_group_bound[RTW89_MAC_NUM][2]; + __le32 wl_tx_limit_time; +} __packed; + +struct rtw89_btc_fbtc_outsrc_set_info { + u8 rf_band[BTC_RF_NUM]; /* 0:2GHz/1:5GHz for MPI_bb_hwsi_ignore_gnt_wl() */ + u8 btg_rx[BTC_RF_NUM]; /* for MPI_bb_btg_bt_rx() */ + u8 nbtg_tx[BTC_RF_NUM]; /* for MPI_bb_nbtg_bt_tx pre=AGC control */ + + struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; /* refer to btc_gnt_ctrl */ + struct rtw89_btc_gnt_ctrl gnt_set_be[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; + + u8 pta_req_hw_band; /* Bind PTA to HWB0 or HWB1, only for 8922a 1-PTA */ + u8 rf_gbt_source; /* gbt from S0 or S1 for RF 0x2[9], only for 8922a */ + + /* The followngs are for 8922c/d new Multi-PTA design */ + /* 0:BT0/1:BT1/2:ZB on/off for MAC(0xe580[0]/0xe680[0])/ RF 0x4[3:2] */ + u8 bt_enable_state; + u8 bt_plut_type; /* BT polluted type, refer to enum btc_plt_map */ + + u8 wl_tx_limit_en; + u8 fc_exec; + u8 wl_btg_standby_chg; /* keep RX-IQGen on in standby mode */ + u8 rsvd; + + u8 bb_path_sel_bt[BTC_RF_NUM]; /* bb s0(1) select GNT_BT0 or BT1 */ + u8 bb_phy_sel_bt[RTW89_PHY_NUM]; /* bb phy0(1) select GNT_BT0 or BT1 */ + + /* forbidden group-> bit[1]:fbd rf-band, bit[0]: fbd enable */ + u8 fbd_group_en[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + + /* bit[15]-> 0:2G/1:5G,6G, bit[14:0]-> WL HWBx ch freq in MHz */ + u16 rf_center_freq[RTW89_MAC_NUM]; /* HWB0/1 */ + + /* 11-bit in MHz, freq diff threshold */ + u16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT_EZL]; /* HWB0/1 vs.BT0/1/2 */ + + /* forbidden group boundary: [15:8]->UP, [7:0]->LO */ + u16 fbd_group_bound[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + + u32 wl_tx_limit_time; +}; + union rtw89_btc_fbtc_slot_u { struct rtw89_btc_fbtc_slot v1[CXST_MAX]; struct rtw89_btc_fbtc_slot_v7 v7[CXST_MAX]; }; +struct rtw89_btc_fddr_cell { + u8 en; + u8 wl_rx_max; + u8 wl_rx_min; +}; + +struct rtw89_btc_fddr_result { + u8 wl_rx_limit; + u8 wl_rx_limit_step[6]; /* record search process */ + u8 search_cnt; /* the rx-limit serach count */ + u32 wl_tp; + u32 wl_tp_step[6]; /* record search process */ +}; + +struct rtw89_btc_fddr_train_info { + u8 rx_limit_pre; + u8 rx_limit_now; + + u32 tp_rec_cnt; + u32 tp_avg_cnt; + + u32 wl_tp_pre; + u32 wl_tp_now; +}; + +struct rtw89_btc_fddr_info { + u8 state; /* refer to enum btc_fddr_state */ + u8 cell_now; /* wl rssi_level after filter LNA != 6 */ + u8 cell_change; + u8 wl_low_rate; + + u8 tp_setup_time; /* calculate TP after this value (in second) */ + u8 tp_hold_time; /* TP calculation period (in second) */ + u8 wl_rssi_thres[BTC_WL_RSSI_THMAX]; /* index 0 -> Max RSSI */ + + u8 search_mode; /* 0: search, 1:look-up */ + u8 search_dir; /* 0: Max->Min, 1:Min->Max */ + + struct rtw89_btc_fddr_train_info tctrl; /* train flag */ + struct rtw89_btc_fddr_result cell_result[BTC_WL_RSSI_THMAX + 1]; + struct rtw89_btc_fddr_cell cell[BTC_WL_RSSI_THMAX + 1]; /* parameters */ + + u16 wl_rx_rate_thres; /* switch to TDD if rx_rate < this threshold */ + + u32 nrsn_map; /* the reason map for no-run fdd-traing */ + u32 wl_rx_rate_now; +}; + +struct rtw89_btc_rpt_ctrl_a2dp_empty { + u32 cnt_empty; /* a2dp empty count */ + u32 cnt_flowctrl; /* a2dp empty flow control counter */ + u32 cnt_tx; + u32 cnt_ack; + u32 cnt_nack; +}; + +struct rtw89_btc_fddt_bt_stat { + struct rtw89_btc_rpt_ctrl_a2dp_empty a2dp_last; + u32 retry_last; +}; + +struct rtw89_btc_fddt_cell { + s8 wl_pwr_min; + s8 wl_pwr_max; + s8 bt_pwr_dec_max; + s8 bt_rx_gain; +}; + +struct rtw89_btc_fddt_fail_check { /* for cell stay in training */ + u8 check_map; /* check pass condition if bit-map = 1 */ + u8 bt_no_empty_cnt; /* 0-fail if no bt-empty >= th in train_cycle */ + u8 wl_tp_ratio; /* 1-fail if wl tp rise ratio < th */ + u8 wl_kpibtr_ratio; /* 2-fail if phase_now_tp < phase_last_tp * kpibtr_ratio */ +}; + +struct rtw89_btc_fddt_break_check { /* for cell stay in training or train-ok */ + u8 check_map; /* check break condition if bit-map = 1 */ + u8 bt_no_empty_cnt; /* 0-break if no empty count >= th */ + u8 wl_tp_ratio; /* 1-break if wl tp ratio < th (%) */ + u8 wl_tp_low_bound; /* 2-break if wl tp (in Mbps) < th */ + + u8 cn; /* 3-break if (cn >= cn_limit) >= th cycle */ + u8 cell_chg; /* 4-break if non-matched-RSSI >= th cycle */ + s8 nhm_limit; /* 5-break if nhm >= th --> ill-condition */ + u8 cn_limit; /* if condition number >= th --> ill-condition */ +}; + +struct rtw89_btc_fddt_time_ctrl { + /* 1 TDD cycle = w1 + b1, FDD 1cycle = w1fdd-slot + b1fdd-slot */ + u8 m_cycle; /* KPI Moving-Average-Cycle: 1~32 cycles */ + u8 w_cycle; /* Start to calcul WKPI after this if train-phase change */ + u8 k_cycle; /* Total kpi-estimate cycles for each training-step */ + u8 rsvd; +}; + +struct rtw89_btc_fddt_train_info { + struct rtw89_btc_fddt_time_ctrl t_ctrl; + struct rtw89_btc_fddt_break_check b_chk; + struct rtw89_btc_fddt_fail_check f_chk; + struct rtw89_btc_fddt_cell cell_ul[5][5]; + struct rtw89_btc_fddt_cell cell_dl[5][5]; +}; + +struct rtw89_btc_fddt_info { + u8 type; /* refer to enum btc_fddt_type */ + u8 result; /* fw send fdd-training status by c2h */ + u8 state; /* refer to enum btc_fddt_state */ + + u8 wl_iot[6]; /* wl bssid */ + u16 bt_iot; /* bt vendor-id */ + + u32 nrsn_map; /* the reason map for no-run fdd-traing */ + struct rtw89_btc_fddt_bt_stat bt_stat; /* bt statistics */ + struct rtw89_btc_fddt_train_info train; + struct rtw89_btc_fddt_train_info train_now; +}; + struct rtw89_btc_dm { struct rtw89_btc_fbtc_outsrc_set_info ost_info_last; /* outsrc API setup info */ struct rtw89_btc_fbtc_outsrc_set_info ost_info; /* outsrc API setup info */ @@ -3107,16 +4040,37 @@ struct rtw89_btc_dm { union rtw89_btc_fbtc_slot_u slot_now; struct rtw89_btc_fbtc_tdma tdma; struct rtw89_btc_fbtc_tdma tdma_now; - struct rtw89_mac_ax_coex_gnt gnt; - union rtw89_btc_init_info_u init_info; /* pass to wl_fw if offload */ - struct rtw89_btc_rf_trx_para rf_trx_para; + struct rtw89_btc_gnt_ctrl gnt_set[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_btc_gnt_ctrl gnt_val[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; + struct rtw89_btc_init_info init_info; /* pass to wl_fw if offload */ + struct rtw89_btc_rf_trx_para_v9 rf_trx_para; struct rtw89_btc_wl_tx_limit_para wl_tx_limit; struct rtw89_btc_dm_step dm_step; - struct rtw89_btc_wl_scc_ctrl wl_scc; + struct rtw89_btc_eslot_ctrl eslot_ctrl; struct rtw89_btc_trx_info trx_info; union rtw89_btc_dm_error_map error; + struct rtw89_btc_bind_info tdd_bind; + struct rtw89_btc_bind_info fdd_bind; + struct rtw89_btc_fddt_info fddt_info; + struct rtw89_btc_fddr_info fddr_info; + struct rtw89_btc_wl_trx_nss_para wl_trx_nss; u32 cnt_dm[BTC_DCNT_NUM]; u32 cnt_notify[BTC_NCNT_NUM]; + u8 ant_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT ANT interact-map */ + u8 xtk_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* 1: If RSSI<(BT-Pin -SIR) */ + u8 sit_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT space interact-map */ + u8 fit_xmap[RTW89_PHY_NUM][BTC_ALL_BT_EZL]; /* HWB-BT freq interact-map */ + u8 tdd_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT tdd-map */ + u8 fdd_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT fdd-map */ + u8 corx_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT Co-Rx */ + + u8 sit_xmap_last[BTC_RF_NUM][BTC_ALL_BT_EZL]; + u8 fit_xmap_last[RTW89_PHY_NUM][BTC_ALL_BT_EZL]; + + u8 tdd_rssi_thres; /* The FDD/TDD switch RSSI (in %) */ + u8 sir_thres; /* WL(Signal) to BT(interference Pin) ratio */ + u8 sir_state[BTC_ALL_BT_EZL]; /* 1: WL RSSI > BTx-interference */ u32 update_slot_map; u32 set_ant_path; @@ -3146,22 +4100,62 @@ struct rtw89_btc_dm { u8 run_reason; u8 run_action; + u8 wl_tx_pwr_phy_map; + u8 vid; + u8 client_ps_tdma_on; + u8 wl_trx_nss_en; u8 wl_pre_agc: 2; u8 wl_lna2: 1; u8 freerun_chk: 1; u8 wl_pre_agc_rb: 2; u8 bt_select: 2; /* 0:s0, 1:s1, 2:s0 & s1, refer to enum btc_bt_index */ + u8 slot_req_more: 1; + u8 out_of_band: 1; + u8 fdd_en: 1; + u8 tdd_en: 1; + u8 lps_ctrl_scbd: 1; + u8 lps_ctrl_change: 1; + u8 bis_tdma: 1; /* BIS TDMA mode active */ + u8 scbd_write_instant; + bool scbd_b2w_update; + bool scbd_w2b_update; + bool pre_agc_chg; +}; + +struct rtw89_fbtc_wl_ctrl_info { + u8 rf_band_map[RTW89_MAC_NUM]; + u8 rf_ch[RTW89_MAC_NUM]; + + u8 client_pstdma_on; + u8 fw_scan; + u8 rfk_state; + u8 rfk_type; + + u32 smap_val; }; struct rtw89_btc_ctrl { + u32 manual; + u32 igno_bt: 1; + u32 always_freerun: 1; + u32 trace_step: 16; + + u8 wl_only; + u8 bt_only; + + u8 ntfy_type; + struct rtw89_fbtc_wl_ctrl_info wl_ctrl_info; +}; + +struct rtw89_btc_ctrl_v0 { u32 manual: 1; u32 igno_bt: 1; u32 always_freerun: 1; u32 trace_step: 16; u32 rsvd: 12; -}; +} __packed; struct rtw89_btc_ctrl_v7 { u8 manual; @@ -3170,10 +4164,31 @@ struct rtw89_btc_ctrl_v7 { u8 rsvd; } __packed; -union rtw89_btc_ctrl_list { - struct rtw89_btc_ctrl ctrl; - struct rtw89_btc_ctrl_v7 ctrl_v7; -}; +struct rtw89_fbtc_wl_ctrl_info_v9 { + u8 rf_band_map[RTW89_MAC_NUM]; + u8 rf_ch[RTW89_MAC_NUM]; + + u8 client_pstdma_on; + u8 fw_scan; + u8 rfk_state; + u8 rfk_type; + + __le32 smap_val; +} __packed; + +struct rtw89_btc_ctrl_v9 { + u8 manual; + u8 always_freerun; + u8 wl_only; + u8 bt_only; + + u8 ntfy_type; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; + + struct rtw89_fbtc_wl_ctrl_info_v9 wl_ctrl_info; +} __packed; struct rtw89_btc_dbg { /* cmd "rb" */ @@ -3193,6 +4208,9 @@ enum rtw89_btc_btf_fw_event { BTF_EVNT_BUF_OVERFLOW, BTF_EVNT_C2H_LOOPBACK, BTF_EVNT_BT_QUERY_TXPWR, /* fwc2hfunc > 3 */ + BTF_EVNT_ZB_INFO = 11, + BTF_EVNT_ZB_CH = 12, + BTF_EVNT_ZB_QUERY_TXPWR = 13, BTF_EVNT_MAX, }; @@ -3242,6 +4260,7 @@ union rtw89_btc_fbtc_btafh_info { struct rtw89_btc_fbtc_btafh v1; struct rtw89_btc_fbtc_btafh_v2 v2; struct rtw89_btc_fbtc_btafh_v7 v7; + struct rtw89_btc_fbtc_btafh_v8 v8; }; struct rtw89_btc_report_ctrl_state { @@ -3334,12 +4353,13 @@ struct rtw89_btc_ver { u8 fwevntrptl; u8 fwc2hfunc; - u8 drvinfo_type; + u8 drvinfo_ver; u16 info_buf; u8 max_role_num; u8 fcxosi; u8 fcxmlo; u8 bt_desired; + u8 fcxtrx; }; struct rtw89_btc_btf_fwinfo { @@ -3369,34 +4389,41 @@ struct rtw89_btc_btf_fwinfo { }; #define RTW89_BTC_POLICY_MAXLEN 512 +#define BTC_H2C_MAXLENC 2020 struct rtw89_btc { const struct rtw89_btc_ver *ver; struct rtw89_btc_cx cx; struct rtw89_btc_dm dm; - union rtw89_btc_ctrl_list ctrl; - union rtw89_btc_module_info mdinfo; + struct rtw89_btc_ctrl ctrl; + struct rtw89_btc_module mdinfo; struct rtw89_btc_btf_fwinfo fwinfo; struct rtw89_btc_dbg dbg; + struct rtw89_fbtc_h2c_set_gpio gpio; struct wiphy_work eapol_notify_work; struct wiphy_work arp_notify_work; struct wiphy_work dhcp_notify_work; struct wiphy_work icmp_notify_work; - u32 bt_req_len; + u32 bt_req_len[RTW89_PHY_NUM]; u8 policy[RTW89_BTC_POLICY_MAXLEN]; + u8 hbuf[BTC_H2C_MAXLENC]; /* H2C Macro buffer */ + u8 hbuf_cnt; /* H2C cmd count in buffer */ u8 ant_type; u8 btg_pos; + u8 io_oflld_type; u16 policy_len; u16 policy_type; + u16 hbuf_len; /* H2C used length, it sshould be <= BTC_H2C_MAXLEN */ u32 hubmsg_cnt; bool bt_req_en; bool update_policy_force; - bool lps; + bool btc_ctrl_lps; bool manual_ctrl; + bool cli_h2c_cmd; }; enum rtw89_btc_hmsg { @@ -3624,6 +4651,8 @@ struct rtw89_sta_link { struct rtw89_efuse { bool valid; bool power_k_valid; + bool vcore_valid; + bool dswr_valid; u8 xtal_cap; u8 addr[ETH_ALEN]; u8 rfe_type; @@ -3633,6 +4662,8 @@ struct rtw89_efuse { u8 bt_setting_3; u8 sn[RTW89_EFUSE_SN_LEN]; u8 uuid[RTW89_EFUSE_UUID_LEN]; + u8 vcore_vmax_reduce; + u8 dswr_vmin; }; struct rtw89_phy_rate_pattern { @@ -3912,6 +4943,8 @@ struct rtw89_chip_ops { enum rtw89_rf_path path, enum rtw89_phy_idx phy_idx, struct rtw89_phy_calc_efuse_gain *calc); + void (*path_diff_update)(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb); int (*pwr_on_func)(struct rtw89_dev *rtwdev); int (*pwr_off_func)(struct rtw89_dev *rtwdev); void (*query_rxdesc)(struct rtw89_dev *rtwdev, @@ -4294,8 +5327,12 @@ struct rtw89_txpwr_rule_6ghz { }; struct rtw89_tx_shape { - const u8 (*lmt)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; - const u8 (*lmt_ru)[RTW89_BAND_NUM][RTW89_REGD_NUM]; + const u8 (*lmt)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM] + [NUM_OF_RTW89_REG_6GHZ_POWER]; + const u8 (*lmt_ru)[RTW89_BAND_NUM][RTW89_REGD_NUM][NUM_OF_RTW89_REG_6GHZ_POWER]; + + const u8 (*lmt_v0)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; + const u8 (*lmt_ru_v0)[RTW89_BAND_NUM][RTW89_REGD_NUM]; }; struct rtw89_rfe_parms { @@ -4389,12 +5426,13 @@ struct rtw89_txpwr_lmt_ru_6ghz_data { struct rtw89_tx_shape_lmt_data { struct rtw89_txpwr_conf conf; - u8 v[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; + u8 v[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM] + [NUM_OF_RTW89_REG_6GHZ_POWER]; }; struct rtw89_tx_shape_lmt_ru_data { struct rtw89_txpwr_conf conf; - u8 v[RTW89_BAND_NUM][RTW89_REGD_NUM]; + u8 v[RTW89_BAND_NUM][RTW89_REGD_NUM][NUM_OF_RTW89_REG_6GHZ_POWER]; }; struct rtw89_rfe_data { @@ -4646,6 +5684,32 @@ struct rtw89_chanctx_listener { #define RTW89_NHM_TH_NUM 11 #define RTW89_NHM_RPT_NUM 12 +#define RTW89_LED_MAX_NUM 4 + +struct rtw89_led_gpio_entry { + u8 pin; + unsigned int color; + u8 intensity; + struct rtw89_reg3_def pinmux; + struct rtw89_reg2_def mode; + struct rtw89_reg2_def out; +}; + +struct rtw89_led_desc { + const struct rtw89_led_gpio_entry *gpios; + u8 n_gpio; +}; + +struct rtw89_led { + bool registered; + const struct rtw89_led_desc *desc; + struct led_classdev led; + struct led_classdev_mc led_mc; + struct mc_subled subled[RTW89_LED_MAX_NUM]; + enum led_brightness brightness_cache[RTW89_LED_MAX_NUM]; + char name[32]; +}; + struct rtw89_chip_info { enum rtw89_core_chip_id chip_id; enum rtw89_chip_gen chip_gen; @@ -4739,16 +5803,17 @@ struct rtw89_chip_info { u8 mailbox; u8 afh_guard_ch; + u16 fdd_iso_freq; const u8 *wl_rssi_thres; const u8 *bt_rssi_thres; u8 rssi_tol; u8 mon_reg_num; const struct rtw89_btc_fbtc_mreg *mon_reg; - u8 rf_para_ulink_num; - const struct rtw89_btc_rf_trx_para *rf_para_ulink; - u8 rf_para_dlink_num; - const struct rtw89_btc_rf_trx_para *rf_para_dlink; + const struct rtw89_btc_rf_trx_para_v0 *rf_para_ulink_v0; + const struct rtw89_btc_rf_trx_para_v0 *rf_para_dlink_v0; + u8 rf_para_ulink_num_v0; + u8 rf_para_dlink_num_v0; const struct rtw89_btc_rf_trx_para_v9 *rf_para_ulink_v9; const struct rtw89_btc_rf_trx_para_v9 *rf_para_dlink_v9; u8 rf_para_ulink_num_v9; @@ -4790,6 +5855,7 @@ struct rtw89_chip_info { const struct wiphy_wowlan_support *wowlan_stub; const struct rtw89_xtal_info *xtal_info; unsigned long default_quirks; /* bitmap of rtw89_quirks */ + u16 txtime_limit_2ghz; }; struct rtw89_chip_variant { @@ -4799,6 +5865,10 @@ struct rtw89_chip_variant { const struct rtw89_qta_def *qta_def_override; }; +struct rtw89_board_variant { + const struct rtw89_led_desc *led_desc; +}; + union rtw89_bus_info { const struct rtw89_pci_info *pci; const struct rtw89_usb_info *usb; @@ -4807,6 +5877,7 @@ union rtw89_bus_info { struct rtw89_driver_info { const struct rtw89_chip_info *chip; const struct rtw89_chip_variant *variant; + const struct rtw89_board_variant *board; const struct dmi_system_id *quirks; unsigned long dev_id_quirks; /* bitmap of rtw89_quirks */ union rtw89_bus_info bus; @@ -4917,6 +5988,8 @@ enum rtw89_fw_feature { RTW89_FW_FEATURE_BEACON_FILTER, RTW89_FW_FEATURE_MACID_PAUSE_SLEEP, RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V0, + RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V1, + RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V2, RTW89_FW_FEATURE_WOW_REASON_V1, RTW89_FW_FEATURE_GROUP(WITH_RFK_PRE_NOTIFY, RTW89_FW_FEATURE_RFK_PRE_NOTIFY_V0, @@ -4931,6 +6004,7 @@ enum rtw89_fw_feature { ), RTW89_FW_FEATURE_RFK_RXDCK_V0, RTW89_FW_FEATURE_RFK_IQK_V0, + RTW89_FW_FEATURE_RFK_TXIQK_V0, RTW89_FW_FEATURE_NO_WOW_CPU_IO_RX, RTW89_FW_FEATURE_NOTIFY_AP_INFO, RTW89_FW_FEATURE_CH_INFO_BE_V0, @@ -5300,10 +6374,13 @@ enum rtw89_dm_type { RTW89_DM_HW_SCAN, RTW89_DM_INACTIVE_PS, RTW89_DM_DIG_PD, + RTW89_DM_VCORE, }; #define RTW89_THERMAL_PROT_LV_MAX 5 #define RTW89_THERMAL_PROT_STEP 5 /* -5% for each level */ +#define RTW89_THERMAL_PROT_VLV_MAX 6 +#define RTW89_THERMAL_PROT_VLV_TH_OFFSET 20 struct rtw89_hal { u32 rx_fltr; @@ -5342,6 +6419,10 @@ struct rtw89_hal { u8 thermal_prot_th; u8 thermal_prot_lv; /* 0 ~ RTW89_THERMAL_PROT_LV_MAX */ + u8 thermal_prot_vmax; + u8 thermal_prot_vmin; + u8 thermal_prot_vlv; /* 0 ~ RTW89_THERMAL_PROT_VLV_MAX (6) */ + u8 fixed_dig_pd_th; /* v = (X(dBm) + 102)/2 */ s8 fixed_dig_cck_pd_th; /* dBm */ }; @@ -5373,6 +6454,7 @@ enum rtw89_flags { RTW89_FLAG_CHANGING_INTERFACE, RTW89_FLAG_HW_RFKILL_STATE, RTW89_FLAG_UNPLUGGED, + RTW89_FLAG_SHUTDOWN, NUM_OF_RTW89_FLAGS, }; @@ -5382,6 +6464,7 @@ enum rtw89_quirks { RTW89_QUIRK_THERMAL_PROT_120C, RTW89_QUIRK_THERMAL_PROT_110C, RTW89_QUIRK_HW_INFO_SYSFS, + RTW89_QUIRK_DISABLE_2GHZ, NUM_OF_RTW89_QUIRKS, }; @@ -5467,6 +6550,14 @@ struct rtw89_beacon_stat { }; DECLARE_EWMA(thermal, 4, 4); +DECLARE_EWMA(path_diff, 4, 2); + +struct rtw89_phy_path_diff { + struct ewma_path_diff avg; + u8 raw; + bool bf_smo_en; + u8 link_mode; +}; #define RTW89_TX_RATE_NR 40 struct rtw89_phy_stat { @@ -5484,11 +6575,43 @@ enum rtw89_rfk_report_state { RTW89_RFK_STATE_H2C_CMD_ERR = 0x4, }; +enum rtw89_rfk_report_types { + RTW89_RFK_REPORT_PRE_NTFY, + RTW89_RFK_REPORT_TSSI, + RTW89_RFK_REPORT_IQK, + RTW89_RFK_REPORT_DPK, + RTW89_RFK_REPORT_TXGAPK, + RTW89_RFK_REPORT_DACK, + RTW89_RFK_REPORT_RX_DCK, + RTW89_RFK_REPORT_TX_IQK, + RTW89_RFK_REPORT_CIM3k, + + NUM_OF_RTW89_RFK_REPORT_TYPES, +}; + +#define RTW89_RFK_RECORD_PATH_NR 2 +#define RTW89_RFK_RECORD_HISTORY_NR 10 + +struct rtw89_rfk_record { + u32 ch[RTW89_RFK_RECORD_PATH_NR]; + u32 cv[RTW89_RFK_RECORD_PATH_NR]; + u32 c5[RTW89_RFK_RECORD_PATH_NR]; + enum rtw89_phy_idx phy_idx; + enum rtw89_rfk_report_state states[NUM_OF_RTW89_RFK_REPORT_TYPES]; +}; + struct rtw89_rfk_wait_info { struct completion completion; ktime_t start_time; enum rtw89_rfk_report_state state; u8 version; + + int record_idx; + struct rtw89_rfk_record *record_ptr; + struct rtw89_rfk_record records[RTW89_RFK_RECORD_HISTORY_NR]; + + int record_tssi_idx; + u32 tssi_code[RTW89_RFK_RECORD_HISTORY_NR][RTW89_RFK_RECORD_PATH_NR]; }; #define RTW89_DACK_PATH_NR 2 @@ -5567,6 +6690,7 @@ struct rtw89_iqk_info { u8 iqk_table_idx[RTW89_IQK_PATH_NR]; u32 lok_idac[RTW89_IQK_CHS_NR][RTW89_IQK_PATH_NR]; u32 lok_vbuf[RTW89_IQK_CHS_NR][RTW89_IQK_PATH_NR]; + u32 iqc_bak[2]; }; #define RTW89_DPK_RF_PATH 2 @@ -5836,6 +6960,8 @@ struct rtw89_power_trim_info { u8 pa_bias_trim[RF_PATH_MAX]; u8 pad_bias_trim[RF_PATH_MAX]; u8 vco_trim[RF_PATH_MAX]; + + s16 thermal_k; }; enum rtw89_regd_func { @@ -6059,6 +7185,9 @@ struct rtw89_hw_scan_info { struct list_head chan_list; struct rtw89_chan op_chan; struct rtw89_hw_scan_extra_op extra_op; + u8 wildcard_pkt_id[NUM_NL80211_BANDS]; + u16 ssid_total_len; + int n_ssids; bool connected; bool abort; u16 delay; /* in unit of ms */ @@ -6431,6 +7560,7 @@ struct rtw89_dev { struct rtw89_hw_scan_info scan_info; const struct rtw89_chip_info *chip; const struct rtw89_chip_variant *variant; + const struct rtw89_board_variant *board; const struct rtw89_pci_info *pci_info; const struct rtw89_rfe_parms *rfe_parms; struct rtw89_hal hal; @@ -6525,6 +7655,7 @@ struct rtw89_dev { struct rtw89_pmac_stat_info pmac_stat; struct rtw89_tx_stat_info tx_stat; struct rtw89_diag_bb diag; + struct rtw89_phy_path_diff path_diff; } bbs[RTW89_PHY_NUM]; struct wiphy_delayed_work track_work; @@ -6560,6 +7691,7 @@ struct rtw89_dev { struct rtw89_debugfs *debugfs; struct rtw89_vif *pure_monitor_mode_vif; + struct rtw89_led led; /* HCI related data, keep last */ u8 priv[] __aligned(sizeof(void *)); @@ -7569,8 +8701,12 @@ static inline void rtw89_chip_bb_sethw(struct rtw89_dev *rtwdev) { const struct rtw89_chip_info *chip = rtwdev->chip; + rtw89_io_pack(rtwdev); + if (chip->ops->bb_sethw) chip->ops->bb_sethw(rtwdev); + + rtw89_io_unpack(rtwdev); } static inline void rtw89_chip_rfk_init(struct rtw89_dev *rtwdev) @@ -7636,6 +8772,15 @@ static inline void rtw89_chip_power_trim(struct rtw89_dev *rtwdev) chip->ops->power_trim(rtwdev); } +static inline void rtw89_chip_path_diff_update(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + + if (chip->ops->path_diff_update) + chip->ops->path_diff_update(rtwdev, bb); +} + static inline void __rtw89_chip_init_txpwr_unit(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) { @@ -7787,6 +8932,30 @@ static inline u8 rtw89_regd_get(struct rtw89_dev *rtwdev, u8 band) return txpwr_regd; } +static inline u8 rtw89_get_tx_shape_idx(struct rtw89_dev *rtwdev, u8 band, + enum rtw89_rate_section rs) +{ + struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory; + const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; + const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 reg6_pwr = regulatory->reg_6ghz_power; + u8 regd = rtw89_regd_get(rtwdev, band); + + if (unlikely(rs >= RTW89_RS_TX_SHAPE_NUM)) + rs = RTW89_RS_OFDM; + + if (!tx_shape->lmt) + goto v0; + + if (band != RTW89_BAND_6G) + reg6_pwr = RTW89_REG_6GHZ_POWER_DFLT; + + return (*tx_shape->lmt)[band][rs][regd][reg6_pwr]; + +v0: + return (*tx_shape->lmt_v0)[band][rs][regd]; +} + static inline void rtw89_ctrl_btg_bt_rx(struct rtw89_dev *rtwdev, bool en, enum rtw89_phy_idx phy_idx) { @@ -8210,7 +9379,9 @@ int rtw89_reg_6ghz_recalc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvi void rtw89_core_update_p2p_ps(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct ieee80211_bss_conf *bss_conf); -void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, enum rtw89_btc_hmsg event); +void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, + enum rtw89_btc_hmsg event, + enum rtw89_phy_idx phy_idx); int rtw89_core_mlsr_switch(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif, unsigned int link_id); void rtw89_core_dm_disable_cfg(struct rtw89_dev *rtwdev, u32 new); diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index 8f5af873e09f..7e640c7166e4 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -93,6 +93,7 @@ struct rtw89_debugfs { struct rtw89_debugfs_priv beacon_info; struct rtw89_debugfs_priv diag_mac; struct rtw89_debugfs_priv diag_bb; + struct rtw89_debugfs_priv diag_rf; struct rtw89_debugfs_priv monitor_opts; }; @@ -3900,17 +3901,13 @@ static ssize_t rtw89_debug_priv_btc_manual_set(struct rtw89_dev *rtwdev, const char *buf, size_t count) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; int ret; ret = kstrtobool(buf, &btc->manual_ctrl); if (ret) return ret; - if (ver->fcxctrl == 7) - btc->ctrl.ctrl_v7.manual = btc->manual_ctrl; - else - btc->ctrl.ctrl.manual = btc->manual_ctrl; + btc->ctrl.manual = btc->manual_ctrl; return count; } @@ -4348,7 +4345,7 @@ static const char *rtw89_ppdu_str(struct rtw89_dev *rtwdev, u8 type, u8 subtype) const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_ppdu_info *ppdu_info; - if (type > ARRAY_SIZE(rtw89_ppdu_infos)) + if (type >= ARRAY_SIZE(rtw89_ppdu_infos)) return "RSVD"; ppdu_info = &rtw89_ppdu_infos[type]; @@ -4778,6 +4775,7 @@ static const struct rtw89_disabled_dm_info { DM_INFO(HW_SCAN), DM_INFO(INACTIVE_PS), DM_INFO(DIG_PD), + DM_INFO(VCORE), }; static ssize_t @@ -5396,6 +5394,118 @@ rtw89_debug_priv_diag_bb_get(struct rtw89_dev *rtwdev, return p - buf; } +static ssize_t +rtw89_debug_priv_diag_rf_get(struct rtw89_dev *rtwdev, + struct rtw89_debugfs_priv *debugfs_priv, + char *buf, size_t bufsz) +{ + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + char *p = buf, *end = buf + bufsz; + int i, j; + + p += scnprintf(p, end - p, "RFK (next index: %d)\n", info->record_idx); + + p += scnprintf(p, end - p, " PHY-X ="); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %4d ", record->phy_idx); + } + p += scnprintf(p, end - p, "\n"); + + for (j = 0; j < RTW89_RFK_RECORD_PATH_NR; j++) { + p += scnprintf(p, end - p, " S%d-CH =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->ch[j]); + } + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " S%d-CV =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->cv[j]); + } + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " S%d-C5 =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->c5[j]); + } + p += scnprintf(p, end - p, "\n"); + } + + for (j = 0; j < NUM_OF_RTW89_RFK_REPORT_TYPES; j++) { + p += scnprintf(p, end - p, " %-8s =", rtw89_rfk_report_names[j]); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %4d ", record->states[j]); + } + p += scnprintf(p, end - p, "\n"); + } + + p += scnprintf(p, end - p, "TSSI-track (next index: %d)\n", info->record_tssi_idx); + + for (j = 0; j < RTW89_RFK_RECORD_PATH_NR; j++) { + p += scnprintf(p, end - p, " S%d =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) + p += scnprintf(p, end - p, " %03x", info->tssi_code[i][j]); + p += scnprintf(p, end - p, "\n"); + } + + return p - buf; +} + +static void rtw89_dbg_diag_rf_set_rfk(struct rtw89_dev *rtwdev) +{ + struct rtw89_entity_mgnt *mgnt = &rtwdev->hal.entity_mgnt; + struct rtw89_vif_link *rtwvif_link; + struct rtw89_vif *rtwvif; + unsigned int link_id; + + list_for_each_entry(rtwvif, &mgnt->active_list, mgnt_entry) + rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) + rtw89_chip_rfk_channel(rtwdev, rtwvif_link); +} + +enum rtw89_dbg_diag_rf_type { + RTW89_DBG_DIAG_RF_RSVD = 0, + RTW89_DBG_DIAG_RF_RFK = 1, +}; + +static ssize_t +rtw89_debug_priv_diag_rf_set(struct rtw89_dev *rtwdev, + struct rtw89_debugfs_priv *debugfs_priv, + const char *buf, size_t count) +{ + u8 type; + int ret; + + ret = kstrtou8(buf, 0, &type); + if (ret) + return -EINVAL; + + if (rtwdev->scanning) + rtw89_hw_scan_abort(rtwdev, rtwdev->scan_info.scanning_vif); + + rtw89_leave_ps_mode(rtwdev); + + switch (type) { + case RTW89_DBG_DIAG_RF_RFK: + rtw89_dbg_diag_rf_set_rfk(rtwdev); + break; + default: + return -EOPNOTSUPP; + } + + return count; +} + static ssize_t rtw89_debug_priv_monitor_opts_get(struct rtw89_dev *rtwdev, struct rtw89_debugfs_priv *debugfs_priv, @@ -5508,6 +5618,7 @@ static const struct rtw89_debugfs rtw89_debugfs_templ = { .beacon_info = rtw89_debug_priv_get(beacon_info), .diag_mac = rtw89_debug_priv_get(diag_mac, RSIZE_16K, RLOCK), .diag_bb = rtw89_debug_priv_get(diag_bb, RSIZE_8K, RLOCK), + .diag_rf = rtw89_debug_priv_set_and_get(diag_rf, RWLOCK), .monitor_opts = rtw89_debug_priv_set_and_get(monitor_opts, RWLOCK), }; @@ -5565,6 +5676,7 @@ void rtw89_debugfs_add_sec2(struct rtw89_dev *rtwdev, struct dentry *debugfs_top rtw89_debugfs_add_r(beacon_info); rtw89_debugfs_add_r(diag_mac); rtw89_debugfs_add_r(diag_bb); + rtw89_debugfs_add_rw(diag_rf); rtw89_debugfs_add_rw(monitor_opts); } diff --git a/drivers/net/wireless/realtek/rtw89/debug.h b/drivers/net/wireless/realtek/rtw89/debug.h index 7cdceb24f52d..f53d8acbd0d2 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.h +++ b/drivers/net/wireless/realtek/rtw89/debug.h @@ -32,6 +32,7 @@ enum rtw89_debug_mask { RTW89_DBG_ACPI = BIT(21), RTW89_DBG_EDCCA = BIT(22), RTW89_DBG_PS = BIT(23), + RTW89_DBG_LED = BIT(24), RTW89_DBG_UNEXP = BIT(31), }; diff --git a/drivers/net/wireless/realtek/rtw89/efuse.h b/drivers/net/wireless/realtek/rtw89/efuse.h index a14a9dfed8e8..4eb629273ab9 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse.h +++ b/drivers/net/wireless/realtek/rtw89/efuse.h @@ -16,6 +16,11 @@ #define EF_CV_MASK GENMASK(7, 4) #define EF_CV_INV 15 +#define EFUSE_THERMAL_K_OFFSET_BE 0x17CC +#define EFUSE_THERMAL_K_VALID_BE BIT(9) +#define EFUSE_THERMAL_K_SIGN_BE BIT(8) +#define EFUSE_THERMAL_K_VAL_BE GENMASK(7, 0) + struct rtw89_efuse_block_cfg { u32 offset; u32 size; @@ -32,5 +37,7 @@ int rtw89_efuse_recognize_mss_info_v1(struct rtw89_dev *rtwdev, u8 b1, u8 b2); int rtw89_efuse_read_fw_secure_ax(struct rtw89_dev *rtwdev); int rtw89_efuse_read_fw_secure_be(struct rtw89_dev *rtwdev); int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev); +int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev); +int rtw89_efuse_read_pwr_data_be(struct rtw89_dev *rtwdev); #endif diff --git a/drivers/net/wireless/realtek/rtw89/efuse_be.c b/drivers/net/wireless/realtek/rtw89/efuse_be.c index 70c1b8be662e..825274bd7c41 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse_be.c +++ b/drivers/net/wireless/realtek/rtw89/efuse_be.c @@ -16,6 +16,15 @@ #define EFUSE_SEC_BE_START 0x1580 #define EFUSE_SEC_BE_SIZE 4 +#define EFUSE_VCORE_PWR_BE 0x17D6 +#define EFUSE_VCORE_PWR_BE_VALID BIT(12) +#define EFUSE_VCORE_PWR_BE_VAL GENMASK(11, 0) +#define SWR_DIG_MIN 860 +#define EFUSE_DIG_K_STEP 0xC +#define EFUSE_DSWR_V0_86_BE 0x17DA +#define EFUSE_DSWR_V0_86_BE_VALID BIT(5) +#define EFUSE_DSWR_V0_86_BE_VAL GENMASK(4, 0) + static const u32 sb_sel_mgn[SB_SEL_MGN_MAX_SIZE] = { 0x8000100, 0xC000180 }; @@ -392,6 +401,17 @@ int rtw89_parse_efuse_map_be(struct rtw89_dev *rtwdev) goto out_free; } + if (rtwdev->chip->chip_id != RTL8922D) + goto out_free; + + ret = rtw89_parse_logical_efuse_block_be(rtwdev, phy_map, phy_size, + RTW89_EFUSE_BLOCK_SYS); + if (ret) { + rtw89_warn(rtwdev, "failed to parse efuse logic block %d\n", + RTW89_EFUSE_BLOCK_SYS); + goto out_free; + } + out_free: kfree(dav_phy_map); kfree(phy_map); @@ -537,4 +557,99 @@ int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev) return 0; } -EXPORT_SYMBOL(rtw89_efuse_read_ecv_be); + +int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_power_trim_info *info = &rtwdev->pwr_trim; + u32 dump_addr = EFUSE_THERMAL_K_OFFSET_BE; + u8 buff[4]; /* efuse access must be multiple of 4 bytes in size */ + bool no_k; + u16 val16; + int ret; + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val16 = buff[0] | buff[1] << 8; + + no_k = !!u16_get_bits(val16, EFUSE_THERMAL_K_VALID_BE); + if (no_k) { + info->thermal_k = 0; + return -ENOENT; + } + + info->thermal_k = u16_get_bits(val16, EFUSE_THERMAL_K_VAL_BE); + + if (u16_get_bits(val16, EFUSE_THERMAL_K_SIGN_BE)) + info->thermal_k *= -1; + + return 0; +} + +static int rtw89_efuse_read_pwr_data_vcore_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + u32 dump_addr; + u8 buff[4]; /* efuse access must 4 bytes align */ + u16 val16; + int ret; + + dump_addr = ALIGN_DOWN(EFUSE_VCORE_PWR_BE, 4); + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val16 = buff[EFUSE_VCORE_PWR_BE - dump_addr] | + buff[EFUSE_VCORE_PWR_BE - dump_addr + 1] << 8; + + if (val16 & EFUSE_VCORE_PWR_BE_VALID) + return 0; + + efuse->vcore_valid = true; + efuse->vcore_vmax_reduce = + (u16_get_bits(val16, EFUSE_VCORE_PWR_BE_VAL) - SWR_DIG_MIN) / + EFUSE_DIG_K_STEP; + + return 0; +} + +static int rtw89_efuse_read_pwr_data_dswr_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + u32 dump_addr; + u8 buff[4]; /* efuse access must 4 bytes align */ + u8 val8; + int ret; + + dump_addr = ALIGN_DOWN(EFUSE_DSWR_V0_86_BE, 4); + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val8 = buff[EFUSE_DSWR_V0_86_BE - dump_addr]; + + if (val8 & EFUSE_DSWR_V0_86_BE_VALID) + return 0; + + efuse->dswr_valid = true; + efuse->dswr_vmin = u8_get_bits(val8, EFUSE_DSWR_V0_86_BE_VAL); + + return 0; +} + +int rtw89_efuse_read_pwr_data_be(struct rtw89_dev *rtwdev) +{ + int ret; + + if (rtwdev->chip->chip_id != RTL8922D) + return 0; + + ret = rtw89_efuse_read_pwr_data_vcore_be(rtwdev); + if (ret) + return ret; + + return rtw89_efuse_read_pwr_data_dswr_be(rtwdev); +} diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index d6a594b75ab2..0099c5c03e7a 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -933,6 +933,24 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 92, 0, TX_HISTORY_V1), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), + __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), + __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 113, 2, SCAN_OFFLOAD_BE_V2), + __CFG_FW_FEAT(RTL8922D, ge, 0, 0, 0, 0, MACID_PAUSE_SLEEP), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, SCAN_OFFLOAD), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, BEACON_FILTER), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 76, 0, LPS_DACK_BY_C2H_REG), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 84, 0, CRASH_TRIGGER_TYPE_1), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 84, 0, ADDR_CAM_V0), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 87, 0, BEACON_LOSS_COUNT_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 91, 0, RFK_PRE_NOTIFY_MCC_V2), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 91, 4, LPS_ML_INFO_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 93, 0, NOTIFY_AP_INFO), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 104, 0, TX_HISTORY_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 113, 0, RFK_TXIQK_V0), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 113, 2, SCAN_OFFLOAD_BE_V2), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, @@ -1547,6 +1565,7 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) u32 unrecognized_elements = chip->needed_fw_elms; const struct rtw89_fw_element_handler *handler; const struct rtw89_fw_element_hdr *hdr; + bool transition; u32 elm_size; u32 elem_id; u32 offset; @@ -1554,6 +1573,9 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) BUILD_BUG_ON(sizeof(chip->needed_fw_elms) * 8 < RTW89_FW_ELEMENT_ID_NUM); + transition = !!((chip->needed_fw_elms & BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION))); + unrecognized_elements &= ~BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION); + offset = rtw89_mfw_get_size(rtwdev); offset = ALIGN(offset, RTW89_FW_ELEMENT_ALIGN); if (offset == 0) @@ -1593,6 +1615,12 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) } if (unrecognized_elements) { + if (transition) { + rtw89_info(rtwdev, "NOTE: This firmware is going to be obsolete!\n" + "Please download the latest firmware from https://gitlab.com/kernel-firmware/linux-firmware.git\n"); + return 0; + } + rtw89_err(rtwdev, "Firmware elements 0x%08x are unrecognized\n", unrecognized_elements); return -ENOENT; @@ -1914,13 +1942,14 @@ static void rtw89_fw_prog_cnt_dump(struct rtw89_dev *rtwdev) static void rtw89_fw_dl_fail_dump(struct rtw89_dev *rtwdev) { + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; u32 val32; val32 = rtw89_read32(rtwdev, R_AX_WCPU_FW_CTRL); rtw89_err(rtwdev, "[ERR]fwdl 0x1E0 = 0x%x\n", val32); - val32 = rtw89_read32(rtwdev, R_AX_BOOT_DBG); - rtw89_err(rtwdev, "[ERR]fwdl 0x83F0 = 0x%x\n", val32); + val32 = rtw89_read32(rtwdev, mac->boot_dbg); + rtw89_err(rtwdev, "[ERR]fwdl 0x%x = 0x%x\n", mac->boot_dbg, val32); rtw89_fw_prog_cnt_dump(rtwdev); } @@ -2928,6 +2957,7 @@ static struct sk_buff *rtw89_sa_query_get(struct rtw89_dev *rtwdev, static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { + struct ieee80211_vif *vif = rtwvif_to_vif(rtwvif_link->rtwvif); struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; u8 sec_hdr_len = rtw89_wow_get_sec_hdr_len(rtwdev); struct rtw89_wow_param *rtw_wow = &rtwdev->wow; @@ -2935,26 +2965,42 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, struct rtw89_arp_rsp *arp_skb; struct arphdr *arp_hdr; struct sk_buff *skb; - __le16 fc; + bool with_qos; + u16 fc; - skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb)); + with_qos = ieee80211_vif_is_mld(vif); + + rtw89_debug(rtwdev, RTW89_DBG_WOW, "[arp_reply] with qos field: %s\n", + str_yes_no(with_qos)); + + skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb) + + (with_qos ? 2 : 0)); if (!skb) return NULL; hdr = skb_put_zero(skb, sizeof(*hdr)); - if (rtw_wow->ptk_alg) - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS | - IEEE80211_FCTL_PROTECTED); - else - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS); + fc = IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS; + + if (rtw_wow->ptk_alg) + fc |= IEEE80211_FCTL_PROTECTED; + + if (with_qos) + fc |= IEEE80211_STYPE_QOS_DATA; + else + fc |= IEEE80211_STYPE_DATA; + + hdr->frame_control = cpu_to_le16(fc); - hdr->frame_control = fc; ether_addr_copy(hdr->addr1, rtwvif_link->bssid); ether_addr_copy(hdr->addr2, rtwvif_link->mac_addr); ether_addr_copy(hdr->addr3, rtwvif_link->bssid); - skb_put_zero(skb, sec_hdr_len); + if (with_qos) + skb_put_zero(skb, sizeof(__le16)); + + if (sec_hdr_len) + skb_put_zero(skb, sec_hdr_len); arp_skb = skb_put_zero(skb, sizeof(*arp_skb)); memcpy(arp_skb->llc_hdr, rfc1042_header, sizeof(rfc1042_header)); @@ -2967,9 +3013,9 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, arp_hdr->ar_pln = 4; arp_hdr->ar_op = htons(ARPOP_REPLY); - ether_addr_copy(arp_skb->sender_hw, rtwvif_link->mac_addr); + ether_addr_copy(arp_skb->sender_hw, rtwvif->mac_addr); arp_skb->sender_ip = rtwvif->ip_addr; - ether_addr_copy(arp_skb->target_hw, rtwvif_link->mac_addr); + ether_addr_copy(arp_skb->target_hw, rtwvif->mac_addr); arp_skb->target_ip = rtwvif->ip_addr; return skb; @@ -3439,7 +3485,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev, h2c->rfe_type = efuse->rfe_type; h2c->rssi_main = U8_MAX; - memset(h2c->link_id, 0xfe, RTW89_BB_PS_LINK_BUF_MAX); + memset(h2c->link_id, RTW89_BB_PS_LINK_ID_SKIP, RTW89_BB_PS_LINK_BUF_MAX); rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) { u8 phy_idx = rtwvif_link->phy_idx; @@ -3447,7 +3493,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev, bb = rtw89_get_bb_ctx(rtwdev, phy_idx); chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); - h2c->link_id[phy_idx] = phy_idx; + h2c->link_id[phy_idx] = link_id; h2c->central_ch[phy_idx] = chan->channel; h2c->pri_ch[phy_idx] = chan->primary_channel; h2c->band[phy_idx] = chan->band_type; @@ -3729,7 +3775,7 @@ int rtw89_fw_h2c_default_cmac_tbl_be(struct rtw89_dev *rtwdev, le32_encode_bits(4, BE_CCTL_INFO_W1_RTS_RTY_LOWEST_RATE); h2c->m1 = cpu_to_le32(BE_CCTL_INFO_W1_ALL); - h2c->w1 = le32_encode_bits(preld, BE_CCTL_INFO_W2_PRELOAD_ENABLE); + h2c->w2 = le32_encode_bits(preld, BE_CCTL_INFO_W2_PRELOAD_ENABLE); h2c->m2 = cpu_to_le32(BE_CCTL_INFO_W2_ALL); h2c->m3 = cpu_to_le32(BE_CCTL_INFO_W3_ALL); @@ -5727,9 +5773,9 @@ int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_init_info *init_info = &dm->init_info.init; - struct rtw89_btc_module *module = &init_info->module; - struct rtw89_btc_ant_info *ant = &module->ant; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; struct rtw89_h2c_cxinit *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -5755,22 +5801,22 @@ int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type) u8_encode_bits(ant->btg_pos, RTW89_H2C_CXINIT_ANT_INFO_BTG_POS) | u8_encode_bits(ant->stream_cnt, RTW89_H2C_CXINIT_ANT_INFO_STREAM_CNT); - h2c->mod_rfe = module->rfe_type; - h2c->mod_cv = module->cv; + h2c->mod_rfe = md->rfe_type; + h2c->mod_cv = md->kt_ver; h2c->mod_info = - u8_encode_bits(module->bt_solo, RTW89_H2C_CXINIT_MOD_INFO_BT_SOLO) | - u8_encode_bits(module->bt_pos, RTW89_H2C_CXINIT_MOD_INFO_BT_POS) | - u8_encode_bits(module->switch_type, RTW89_H2C_CXINIT_MOD_INFO_SW_TYPE) | - u8_encode_bits(module->wa_type, RTW89_H2C_CXINIT_MOD_INFO_WA_TYPE); - h2c->mod_adie_kt = module->kt_ver_adie; - h2c->wl_gch = init_info->wl_guard_ch; + u8_encode_bits(md->bt_solo, RTW89_H2C_CXINIT_MOD_INFO_BT_SOLO) | + u8_encode_bits(md->bt0_pos, RTW89_H2C_CXINIT_MOD_INFO_BT_POS) | + u8_encode_bits(md->bt0_sw_type, RTW89_H2C_CXINIT_MOD_INFO_SW_TYPE) | + u8_encode_bits(md->wa_type, RTW89_H2C_CXINIT_MOD_INFO_WA_TYPE); + h2c->mod_adie_kt = md->kt_ver_adie; + h2c->wl_gch = init->wl_guard_ch; h2c->info = - u8_encode_bits(init_info->wl_only, RTW89_H2C_CXINIT_INFO_WL_ONLY) | - u8_encode_bits(init_info->wl_init_ok, RTW89_H2C_CXINIT_INFO_WL_INITOK) | - u8_encode_bits(init_info->dbcc_en, RTW89_H2C_CXINIT_INFO_DBCC_EN) | - u8_encode_bits(init_info->cx_other, RTW89_H2C_CXINIT_INFO_CX_OTHER) | - u8_encode_bits(init_info->bt_only, RTW89_H2C_CXINIT_INFO_BT_ONLY); + u8_encode_bits(init->wl_only, RTW89_H2C_CXINIT_INFO_WL_ONLY) | + u8_encode_bits(init->wl_init_ok, RTW89_H2C_CXINIT_INFO_WL_INITOK) | + u8_encode_bits(init->dbcc_en, RTW89_H2C_CXINIT_INFO_DBCC_EN) | + u8_encode_bits(init->cx_other, RTW89_H2C_CXINIT_INFO_CX_OTHER) | + u8_encode_bits(init->bt_only, RTW89_H2C_CXINIT_INFO_BT_ONLY); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -5794,7 +5840,9 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_init_info_v7 *init_info = &dm->init_info.init_v7; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; struct rtw89_h2c_cxinit_v7 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -5809,9 +5857,180 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxinit_v7 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxinit; + h2c->hdr.ver = 7; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - h2c->init = *init_info; + + h2c->init.wl_guard_ch = init->wl_guard_ch; + h2c->init.wl_only = init->wl_only; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.cx_other = init->cx_other; + h2c->init.bt_only = init->bt_only; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + h2c->init.rsvd3 = init->dbcc_en; /* available at v107 */ + + h2c->init.module.rfe_type = md->rfe_type; + h2c->init.module.kt_ver = md->kt_ver; + h2c->init.module.bt_solo = md->bt_solo; + h2c->init.module.bt_pos = md->bt0_pos; + h2c->init.module.switch_type = md->bt0_sw_type; + h2c->init.module.wa_type = md->wa_type; + h2c->init.module.kt_ver_adie = md->kt_ver_adie; + + h2c->init.module.ant.type = ant->type; + h2c->init.module.ant.num = ant->num; + h2c->init.module.ant.isolation = ant->isolation; + h2c->init.module.ant.single_pos = ant->single_pos; + h2c->init.module.ant.diversity = ant->diversity; + h2c->init.module.ant.btg_pos = ant->btg_pos; + h2c->init.module.ant.stream_cnt = ant->stream_cnt; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; + struct rtw89_h2c_cxinit_v10 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v10\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxinit_v10 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 10; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + h2c->init.endian_type = init->endian_type; + h2c->init.init_mode = init->init_mode; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.bt0_function = init->bt0_function; + h2c->init.bt1_function = init->bt1_function; + h2c->init.bt2_function = init->bt2_function; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + + h2c->init.module.rfe_type = md->rfe_type; + h2c->init.module.wa_type = md->wa_type; + h2c->init.module.kt_ver = md->kt_ver; + h2c->init.module.kt_ver_adie = md->kt_ver_adie; + h2c->init.module.bt0_pos = md->bt0_pos; + h2c->init.module.bt0_sw_type = md->bt0_sw_type; + h2c->init.module.bt1_pos = md->bt1_pos; + h2c->init.module.bt1_sw_type = md->bt1_sw_type; + + h2c->init.module.ant.type = ant->type; + h2c->init.module.ant.num = ant->num; + h2c->init.module.ant.isolation = ant->isolation; + h2c->init.module.ant.single_pos = ant->single_pos; + h2c->init.module.ant.stream_cnt = ant->stream_cnt; + h2c->init.module.ant.btg_pos = ant->btg_pos; + h2c->init.module.ant.btg1_pos = ant->btg1_pos; + + memcpy(h2c->init.module.ant.func, ant->func, sizeof(ant->func)); + memcpy(h2c->init.module.ant.ant_xmap, ant->ant_xmap, + sizeof(ant->ant_xmap)); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_h2c_cxinit_v11 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v11\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxinit_v11 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 11; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + h2c->init.endian_type = init->endian_type; + h2c->init.init_mode = init->init_mode; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.bt0_function = init->bt0_function; + + h2c->init.bt1_function = init->bt1_function; + h2c->init.bt2_function = init->bt2_function; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + + h2c->init.module.rfe_type = init->module.rfe_type; + h2c->init.module.wa_type = init->module.wa_type; + h2c->init.module.kt_ver = init->module.kt_ver; + h2c->init.module.kt_ver_adie = init->module.kt_ver_adie; + + h2c->init.module.bt0_pos = init->module.bt0_pos; + h2c->init.module.bt0_sw_type = init->module.bt0_sw_type; + h2c->init.module.bt1_pos = init->module.bt1_pos; + h2c->init.module.bt1_sw_type = init->module.bt1_sw_type; + + h2c->init.module.ant.type = init->module.ant.type; + h2c->init.module.ant.num = init->module.ant.num; + h2c->init.module.ant.isolation = init->module.ant.isolation; + h2c->init.module.ant.single_pos = init->module.ant.single_pos; + + h2c->init.module.ant.stream_cnt = init->module.ant.stream_cnt; + h2c->init.module.ant.path_pos = init->module.ant.path_pos; + h2c->init.module.ant.btg_pos = init->module.ant.btg_pos; + h2c->init.module.ant.btg1_pos = init->module.ant.btg1_pos; + memcpy(h2c->init.module.ant.func, init->module.ant.func, + sizeof(init->module.ant.func)); + + memcpy(h2c->init.module.ant.ant_xmap, init->module.ant.ant_xmap, + sizeof(init->module.ant.ant_xmap)); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -5839,14 +6058,14 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info *role_info = &wl->role_info; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role *active = role_info->active_role; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; - u32 len; + u32 rmap = r->role_map; u8 offset = 0; + u32 len; u8 *cmd; int ret; int i; @@ -5864,36 +6083,37 @@ int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, active->tx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, active->rx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, active->tx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, active->rx_rate, i, offset); + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, rl->tx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, rl->rx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, rl->tx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, rl->rx_rate, i, offset); } rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, @@ -5922,12 +6142,12 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v1 *role_info = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role_v1 *active = role_info->active_role_v1; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; - u32 len; + u32 rmap = r->role_map; u8 *cmd, offset; + u32 len; int ret; int i; @@ -5944,47 +6164,131 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); offset = PORT_DATA_OFFSET; - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, active->tx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, active->rx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, active->tx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, active->rx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR(cmd, active->noa_duration, i, offset); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, rl->tx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, rl->rx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, rl->tx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, rl->rx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR(cmd, rl->noa_dur, i, offset); } offset = len - H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN; - RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, role_info->mrole_type, offset); - RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, role_info->mrole_noa_duration, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, role_info->dbcc_en, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, role_info->dbcc_chg, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, role_info->dbcc_2g_phy, offset); - RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, role_info->link_mode_chg, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, r->mrole_type, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, r->mrole_noa_duration, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, r->dbcc_en, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, r->dbcc_chg, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, r->dbcc_2g_phy, offset); + RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, r->link_mode_chg, offset); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_role_v101(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *role_info = &wl->role_info; + struct rtw89_btc_wl_rlink *active; + struct rtw89_h2c_cxrole_v101 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_role_v101\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxrole_v101 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR; + + h2c->connect_cnt = role_info->connect_cnt; + h2c->link_mode = role_info->link_mode; + h2c->role_map = cpu_to_le16(role_info->role_map); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + active = &role_info->rlink[i][0]; + h2c->act_role[i].map_role_status = + u8_encode_bits(active->connected, + RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED) | + u8_encode_bits(active->pid, + RTW89_H2C_CXROLE_V101_ROLE_STAT_PID) | + u8_encode_bits(active->phy, + RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY) | + u8_encode_bits(active->noa, + RTW89_H2C_CXROLE_V101_ROLE_STAT_NOA) | + u8_encode_bits(active->rf_band, + RTW89_H2C_CXROLE_V101_ROLE_STAT_BADN); + h2c->act_role[i].map_clips_bw = + u8_encode_bits(active->client_ps, + RTW89_H2C_CXROLE_V101_CLIPS_BW_CLIENTPS) | + u8_encode_bits(active->bw, + RTW89_H2C_CXROLE_V101_CLIPS_BW_BW); + h2c->act_role[i].role = active->role; + h2c->act_role[i].ch = active->ch; + h2c->act_role[i].noa_duration = cpu_to_le32(active->noa_dur); + } + + h2c->mrole_type = cpu_to_le32(role_info->mrole_type); + h2c->mrole_noa_duration = cpu_to_le32(role_info->mrole_noa_duration); + h2c->map_dbcc_linkmode_chg = + le32_encode_bits(role_info->dbcc_en, + RTW89_H2C_CXROLE_V101_DBCC_EN) | + le32_encode_bits(role_info->dbcc_chg, + RTW89_H2C_CXROLE_V101_DBCC_CHG) | + le32_encode_bits(role_info->dbcc_2g_phy, + RTW89_H2C_CXROLE_V101_DBCC_2G_PHY) | + le32_encode_bits(role_info->link_mode_chg, + RTW89_H2C_CXROLE_V101_DBCC_LINKMODE_CHG) | + le32_encode_bits(role_info->rsvd, + RTW89_H2C_CXROLE_V101_DBCC_RSVD); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6012,10 +6316,10 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v2 *role_info = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role_v2 *active = role_info->active_role_v2; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; + u32 rmap = r->role_map; u32 len; u8 *cmd, offset; int ret; @@ -6034,43 +6338,45 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); offset = PORT_DATA_OFFSET; - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED_V2(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID_V2(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY_V2(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_V2(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND_V2(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS_V2(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW_V2(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE_V2(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH_V2(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR_V2(cmd, active->noa_duration, i, offset); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED_V2(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID_V2(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY_V2(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_V2(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND_V2(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS_V2(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW_V2(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE_V2(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH_V2(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR_V2(cmd, rl->noa_dur, i, offset); } offset = len - H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN; - RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, role_info->mrole_type, offset); - RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, role_info->mrole_noa_duration, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, role_info->dbcc_en, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, role_info->dbcc_chg, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, role_info->dbcc_2g_phy, offset); - RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, role_info->link_mode_chg, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, r->mrole_type, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, r->mrole_noa_duration, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, r->dbcc_en, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, r->dbcc_chg, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, r->dbcc_2g_phy, offset); + RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, r->link_mode_chg, offset); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6092,12 +6398,14 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_role_info_v7 *role = &btc->cx.wl.role_info_v7; + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; struct rtw89_h2c_cxrole_v7 *h2c; + struct rtw89_btc_wl_rlink *rl; u32 len = sizeof(*h2c); struct sk_buff *skb; + __le32 rm; int ret; + u8 i; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6108,15 +6416,38 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxrole_v7 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fwlrole; + h2c->hdr.ver = 7; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - memcpy(&h2c->_u8, role, sizeof(h2c->_u8)); - h2c->_u32.role_map = cpu_to_le32(role->role_map); - h2c->_u32.mrole_type = cpu_to_le32(role->mrole_type); - h2c->_u32.mrole_noa_duration = cpu_to_le32(role->mrole_noa_duration); - h2c->_u32.dbcc_en = cpu_to_le32(role->dbcc_en); - h2c->_u32.dbcc_chg = cpu_to_le32(role->dbcc_chg); - h2c->_u32.dbcc_2g_phy = cpu_to_le32(role->dbcc_2g_phy); + + h2c->r.connect_cnt = r->connect_cnt; + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_chg = r->link_mode_chg; + h2c->r.p2p_2g = r->p2p_2g; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + h2c->r.active_role[i].connected = rl->connected; + h2c->r.active_role[i].pid = rl->pid; + h2c->r.active_role[i].phy = rl->phy; + h2c->r.active_role[i].noa = rl->noa; + + h2c->r.active_role[i].band = rl->rf_band; + h2c->r.active_role[i].client_ps = rl->client_ps; + h2c->r.active_role[i].bw = rl->bw; + h2c->r.active_role[i].role = rl->role; + + h2c->r.active_role[i].ch = rl->ch; + h2c->r.active_role[i].noa_dur = rl->noa_dur; + h2c->r.active_role[i].client_cnt = rl->client_cnt; + } + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); + h2c->r.mrole_noa_duration = cpu_to_le32(r->mrole_noa_duration); + h2c->r.dbcc_en = cpu_to_le32((u32)r->dbcc_en); + h2c->r.dbcc_chg = cpu_to_le32(r->dbcc_chg); + h2c->r.dbcc_2g_phy = cpu_to_le32(r->dbcc_2g_phy); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6138,12 +6469,14 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_role_info_v8 *role = &btc->cx.wl.role_info_v8; + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; struct rtw89_h2c_cxrole_v8 *h2c; + struct rtw89_btc_wl_rlink *rl; u32 len = sizeof(*h2c); struct sk_buff *skb; + __le32 rm; int ret; + u8 i, j; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6154,12 +6487,176 @@ int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxrole_v8 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fwlrole; + h2c->hdr.ver = 8; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - memcpy(&h2c->_u8, role, sizeof(h2c->_u8)); - h2c->_u32.role_map = cpu_to_le32(role->role_map); - h2c->_u32.mrole_type = cpu_to_le32(role->mrole_type); - h2c->_u32.mrole_noa_duration = cpu_to_le32(role->mrole_noa_duration); + h2c->r.connect_cnt = r->connect_cnt; + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_chg = r->link_mode_chg; + h2c->r.p2p_2g = r->p2p_2g; + h2c->r.pta_req_band = r->pta_req_band; + h2c->r.dbcc_en = r->dbcc_en; + h2c->r.dbcc_chg = r->dbcc_chg; + h2c->r.dbcc_2g_phy = r->dbcc_2g_phy; + + for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][j]; + h2c->r.rlink[i][j].connected = rl->connected; + h2c->r.rlink[i][j].pid = rl->pid; + h2c->r.rlink[i][j].phy = rl->phy; + h2c->r.rlink[i][j].noa = rl->noa; + + h2c->r.rlink[i][j].rf_band = rl->rf_band; + h2c->r.rlink[i][j].active = rl->active; + h2c->r.rlink[i][j].bw = rl->bw; + h2c->r.rlink[i][j].role = rl->role; + + h2c->r.rlink[i][j].ch = rl->ch; + h2c->r.rlink[i][j].noa_dur = rl->noa_dur; + h2c->r.rlink[i][j].client_cnt = rl->client_cnt; + h2c->r.rlink[i][j].mode = rl->mode; + } + } + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); + h2c->r.mrole_noa_duration = cpu_to_le32(r->mrole_noa_duration); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; + struct rtw89_h2c_cxrole_v10 *h2c; + struct rtw89_btc_wl_rlink *rl; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + __le32 rm; + int ret; + u8 i, j; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_role\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxrole_v10 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 10; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][j]; + h2c->r.rlink[i][j].connected = rl->connected; + h2c->r.rlink[i][j].pid = rl->pid; + h2c->r.rlink[i][j].phy = rl->phy; + h2c->r.rlink[i][j].noa = rl->noa; + + h2c->r.rlink[i][j].rf_band = rl->rf_band; + h2c->r.rlink[i][j].active = rl->active; + h2c->r.rlink[i][j].bw = rl->bw; + h2c->r.rlink[i][j].role = rl->role; + + h2c->r.rlink[i][j].ch = rl->ch; + h2c->r.rlink[i][j].noa_dur = rl->noa_dur; + h2c->r.rlink[i][j].client_cnt = rl->client_cnt; + h2c->r.rlink[i][j].mode = rl->mode; + h2c->r.rlink[i][j].mac_id = rl->mac_id; + } + } + + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_hb1 = r->link_mode_hb1; + h2c->r.p2p_exist = r->p2p_exist; + h2c->r.p2p_exist_hb1 = r->p2p_exist_hb1; + + h2c->r.pta_req_band = r->pta_req_band; + h2c->r.dbcc_en = r->dbcc_en; + h2c->r.dbcc_2g_phy = r->dbcc_2g_phy; + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + rm = cpu_to_le32(r->role_map_hb1); + memcpy(&h2c->r.role_map_hb1, &rm, sizeof(h2c->r.role_map_hb1)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc_wl_mlo_info *mlo = &rtwdev->btc.cx.wl.mlo_info; + struct rtw89_h2c_cxmlo_v2 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_mlo_v2\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxmlo_v2 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 2; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + memcpy(h2c->mlo.wmode, mlo->wmode, + sizeof(h2c->mlo.wmode)); + memcpy(h2c->mlo.ch_type, mlo->ch_type, + sizeof(h2c->mlo.ch_type)); + memcpy(h2c->mlo.hwb_rf_band, mlo->hwb_rf_band, + sizeof(h2c->mlo.hwb_rf_band)); + memcpy(h2c->mlo.path_rf_band, mlo->path_rf_band, + sizeof(h2c->mlo.path_rf_band)); + + h2c->mlo.wtype = mlo->wtype; + h2c->mlo.mrcx_mode = mlo->mrcx_mode; + h2c->mlo.mrcx_act_hwb_map = mlo->mrcx_act_hwb_map; + h2c->mlo.mrcx_bt_slot_rsp = mlo->mrcx_bt_slot_rsp; + + h2c->mlo.rf_combination = mlo->rf_combination; + h2c->mlo.mlo_en = mlo->mlo_en; + h2c->mlo.mlo_adie = mlo->mlo_adie; + h2c->mlo.dual_hw_band_en = mlo->dual_hw_band_en; + h2c->mlo.link_status = cpu_to_le32(mlo->link_status); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6187,6 +6684,7 @@ int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type) u32 len = sizeof(*h2c); struct sk_buff *skb; int ret; + u8 i, j; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6199,7 +6697,105 @@ int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type) h2c->hdr.type = type; h2c->hdr.ver = btc->ver->fcxosi; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - h2c->osi = *osi; + + memcpy(h2c->osi.rf_band, osi->rf_band, sizeof(osi->rf_band)); + memcpy(h2c->osi.btg_rx, osi->btg_rx, sizeof(osi->btg_rx)); + memcpy(h2c->osi.nbtg_tx, osi->nbtg_tx, sizeof(osi->nbtg_tx)); + + for (i = 0; i < BTC_RF_NUM; i++) { + h2c->osi.gnt_set[i] = osi->gnt_set[i]; + h2c->osi.wlact_set[i] = osi->wlact_set[i]; + } + + h2c->osi.pta_req_hw_band = osi->pta_req_hw_band; + h2c->osi.rf_gbt_source = osi->rf_gbt_source; + h2c->osi.bt_enable_state = osi->bt_enable_state; + h2c->osi.wl_btg_standby_chg = osi->wl_btg_standby_chg; + + memcpy(h2c->osi.fbd_group_en, osi->fbd_group_en, sizeof(osi->fbd_group_en)); + + for (i = 0; i < RTW89_MAC_NUM; i++) { + h2c->osi.rf_center_freq[i] = cpu_to_le16(osi->rf_center_freq[i]); + for (j = 0; j < BTC_RF_NUM; j++) + h2c->osi.fbd_group_bound[i][j] = cpu_to_le16(osi->fbd_group_bound[i][j]); + } + + for (i = 0; i < RTW89_MAC_NUM; i++) { + for (j = 0; j < BTC_ALL_BT; j++) + h2c->osi.freq_diff_thres[i][j] = cpu_to_le16(osi->freq_diff_thres[i][j]); + } + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_fbtc_outsrc_set_info *osi = &btc->dm.ost_info; + struct rtw89_h2c_cxosi_v6 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i, j; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_osi_v6\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxosi_v6 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxosi; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + memcpy(h2c->osi.rf_band, osi->rf_band, sizeof(osi->rf_band)); + memcpy(h2c->osi.btg_rx, osi->btg_rx, sizeof(osi->btg_rx)); + memcpy(h2c->osi.nbtg_tx, osi->nbtg_tx, sizeof(osi->nbtg_tx)); + memcpy(h2c->osi.gnt_set, osi->gnt_set, sizeof(osi->gnt_set)); + memcpy(h2c->osi.wlact_set, osi->wlact_set, sizeof(osi->wlact_set)); + + h2c->osi.pta_req_hw_band = osi->pta_req_hw_band; + h2c->osi.rf_gbt_source = osi->rf_gbt_source; + h2c->osi.bt_enable_state = osi->bt_enable_state; + h2c->osi.bt_plut_type = osi->bt_plut_type; + h2c->osi.wl_tx_limit_en = osi->wl_tx_limit_en; + h2c->osi.fc_exec = osi->fc_exec; + h2c->osi.wl_btg_standby_chg = osi->wl_btg_standby_chg; + h2c->osi.rsvd = osi->rsvd; + + memcpy(h2c->osi.bb_path_sel_bt, osi->bb_path_sel_bt, sizeof(osi->bb_path_sel_bt)); + memcpy(h2c->osi.bb_phy_sel_bt, osi->bb_phy_sel_bt, sizeof(osi->bb_phy_sel_bt)); + memcpy(h2c->osi.fbd_group_en, osi->fbd_group_en, sizeof(osi->fbd_group_en)); + + for (i = 0; i < RTW89_MAC_NUM; i++) { + h2c->osi.rf_center_freq[i] = cpu_to_le16(osi->rf_center_freq[i]); + for (j = 0; j < BTC_RF_NUM; j++) + h2c->osi.fbd_group_bound[i][j] = cpu_to_le16(osi->fbd_group_bound[i][j]); + } + + for (i = 0; i < RTW89_MAC_NUM; i++) { + for (j = 0; j < BTC_ALL_BT_EZL; j++) + h2c->osi.freq_diff_thres[i][j] = cpu_to_le16(osi->freq_diff_thres[i][j]); + } + + h2c->osi.wl_tx_limit_time = cpu_to_le32(osi->wl_tx_limit_time); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6224,7 +6820,7 @@ int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_ctrl *ctrl = &btc->ctrl.ctrl; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; struct sk_buff *skb; u8 *cmd; int ret; @@ -6267,7 +6863,7 @@ int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_ctrl_v7 *ctrl = &btc->ctrl.ctrl_v7; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; struct rtw89_h2c_cxctrl_v7 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -6284,7 +6880,10 @@ int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) h2c->hdr.type = type; h2c->hdr.ver = btc->ver->fcxctrl; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; - h2c->ctrl = *ctrl; + + h2c->ctrl.manual = ctrl->manual; + h2c->ctrl.igno_bt = ctrl->igno_bt; + h2c->ctrl.always_freerun = ctrl->always_freerun; rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6303,48 +6902,266 @@ int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) return ret; } -#define H2C_LEN_CXDRVINFO_TRX (28 + H2C_LEN_CXDRVHDR) -int rtw89_fw_h2c_cxdrv_trx(struct rtw89_dev *rtwdev, u8 type) +int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; + struct rtw89_fbtc_wl_ctrl_info_v9 *cinfo; + struct rtw89_h2c_cxctrl_v9 *h2c; + u32 len = sizeof(*h2c); struct sk_buff *skb; - u8 *cmd; int ret; - skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_LEN_CXDRVINFO_TRX); + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { - rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_trx\n"); + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl_v7\n"); return -ENOMEM; } - skb_put(skb, H2C_LEN_CXDRVINFO_TRX); - cmd = skb->data; + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxctrl_v9 *)skb->data; + cinfo = &h2c->ctrl.wl_ctrl_info; - RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); - RTW89_SET_FWCMD_CXHDR_LEN(cmd, H2C_LEN_CXDRVINFO_TRX - H2C_LEN_CXDRVHDR); + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxctrl; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; - RTW89_SET_FWCMD_CXTRX_TXLV(cmd, trx->tx_lvl); - RTW89_SET_FWCMD_CXTRX_RXLV(cmd, trx->rx_lvl); - RTW89_SET_FWCMD_CXTRX_WLRSSI(cmd, trx->wl_rssi); - RTW89_SET_FWCMD_CXTRX_BTRSSI(cmd, trx->bt_rssi); - RTW89_SET_FWCMD_CXTRX_TXPWR(cmd, trx->tx_power); - RTW89_SET_FWCMD_CXTRX_RXGAIN(cmd, trx->rx_gain); - RTW89_SET_FWCMD_CXTRX_BTTXPWR(cmd, trx->bt_tx_power); - RTW89_SET_FWCMD_CXTRX_BTRXGAIN(cmd, trx->bt_rx_gain); - RTW89_SET_FWCMD_CXTRX_CN(cmd, trx->cn); - RTW89_SET_FWCMD_CXTRX_NHM(cmd, trx->nhm); - RTW89_SET_FWCMD_CXTRX_BTPROFILE(cmd, trx->bt_profile); - RTW89_SET_FWCMD_CXTRX_RSVD2(cmd, trx->rsvd2); - RTW89_SET_FWCMD_CXTRX_TXRATE(cmd, trx->tx_rate); - RTW89_SET_FWCMD_CXTRX_RXRATE(cmd, trx->rx_rate); - RTW89_SET_FWCMD_CXTRX_TXTP(cmd, trx->tx_tp); - RTW89_SET_FWCMD_CXTRX_RXTP(cmd, trx->rx_tp); - RTW89_SET_FWCMD_CXTRX_RXERRRA(cmd, trx->rx_err_ratio); + h2c->ctrl.manual = ctrl->manual; + h2c->ctrl.always_freerun = ctrl->always_freerun; + h2c->ctrl.wl_only = ctrl->wl_only; + h2c->ctrl.bt_only = ctrl->bt_only; + h2c->ctrl.ntfy_type = ctrl->ntfy_type; + memcpy(cinfo->rf_band_map, ctrl->wl_ctrl_info.rf_band_map, + sizeof(cinfo->rf_band_map)); + memcpy(cinfo->rf_ch, ctrl->wl_ctrl_info.rf_ch, sizeof(cinfo->rf_ch)); + cinfo->client_pstdma_on = ctrl->wl_ctrl_info.client_pstdma_on; + cinfo->fw_scan = ctrl->wl_ctrl_info.fw_scan; + cinfo->rfk_state = ctrl->wl_ctrl_info.rfk_state; + cinfo->rfk_type = ctrl->wl_ctrl_info.rfk_type; + cinfo->smap_val = cpu_to_le32(ctrl->wl_ctrl_info.smap_val); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, - SET_DRV_INFO, 0, 0, - H2C_LEN_CXDRVINFO_TRX); + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +static void rtw89_btc_load_trx_para(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_rf_trx_para_v9 rf_para = btc->dm.rf_trx_para; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + u8 i; + + for (i = 0; i < RTW89_PHY_NUM; i++) { + trx->wl_tx_power[i] = u32_get_bits(rf_para.wl_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->wl_rx_gain[i] = u32_get_bits(rf_para.wl_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + if (btc->ver->fcxtrx == 107) + break; + } + + for (i = 0; i < BTC_ALL_BT; i++) { + trx->bt_tx_power[i] = u32_get_bits(rf_para.bt_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->bt_rx_gain[i] = u32_get_bits(rf_para.bt_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + if (btc->ver->fcxtrx == 107) + break; + + trx->zb_tx_power[i] = u32_get_bits(rf_para.zb_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->zb_rx_gain[i] = u32_get_bits(rf_para.zb_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + } + + trx->cn = wl->cn_report; + trx->nhm = wl->nhm.pwr; +} + +int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_h2c_cxtrx_v7 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + rtw89_btc_load_trx_para(rtwdev); + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v9\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v7 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 7; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->v7_u8.tx_lvl = trx->tx_lvl; + h2c->v7_u8.rx_lvl = trx->rx_lvl; + h2c->v7_u8.wl_rssi = trx->wl_rssi; + h2c->v7_u8.bt_rssi = trx->bt_rssi; + h2c->v7_u8.wl_tx_power = trx->wl_tx_power[RTW89_PHY_0]; + h2c->v7_u8.wl_rx_gain = trx->wl_rx_gain[RTW89_PHY_0]; + h2c->v7_u8.bt_tx_power = trx->bt_tx_power[BTC_BT_1ST]; + h2c->v7_u8.bt_rx_gain = trx->bt_rx_gain[BTC_BT_1ST]; + h2c->v7_u8.zb_tx_power = trx->zb_tx_power[BTC_BT_1ST]; + h2c->v7_u8.zb_rx_gain = trx->zb_rx_gain[BTC_BT_1ST]; + h2c->v7_u8.cn = trx->cn; + h2c->v7_u8.nhm = trx->nhm; + h2c->v7_u8.bt_profile = trx->bt_profile; + h2c->v7_u8.rsvd2 = trx->rsvd2; + h2c->v7_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v7_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v7_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v7_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v7_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_h2c_cxtrx_v9 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i; + + rtw89_btc_load_trx_para(rtwdev); + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v9\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v9 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 9; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->v9_u8.tx_lvl = trx->tx_lvl; + h2c->v9_u8.rx_lvl = trx->rx_lvl; + h2c->v9_u8.wl_rssi = trx->wl_rssi; + h2c->v9_u8.bt_rssi = trx->bt_rssi; + + for (i = 0; i < RTW89_PHY_NUM; i++) { + h2c->v9_u8.wl_tx_power[i] = trx->wl_tx_power[i]; + h2c->v9_u8.wl_rx_gain[i] = trx->wl_rx_gain[i]; + } + + for (i = 0; i < BTC_ALL_BT; i++) { + h2c->v9_u8.bt_tx_power[i] = trx->bt_tx_power[i]; + h2c->v9_u8.bt_rx_gain[i] = trx->bt_rx_gain[i]; + h2c->v9_u8.zb_tx_power[i] = trx->zb_tx_power[i]; + h2c->v9_u8.zb_rx_gain[i] = trx->zb_rx_gain[i]; + } + h2c->v9_u8.cn = trx->cn; + h2c->v9_u8.nhm = trx->nhm; + h2c->v9_u8.bt_profile = trx->bt_profile; + h2c->v9_u8.rsvd2 = trx->rsvd2; + h2c->v9_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v9_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v9_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v9_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v9_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_trx_v107(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_h2c_cxtrx_v107 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + rtw89_btc_load_trx_para(rtwdev); + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v107\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v107 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 7; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->v107_u8.tx_lvl = trx->tx_lvl; + h2c->v107_u8.rx_lvl = trx->rx_lvl; + h2c->v107_u8.wl_rssi = trx->wl_rssi; + h2c->v107_u8.bt_rssi = trx->bt_rssi; + h2c->v107_u8.wl_tx_power = trx->bt_tx_power[RTW89_PHY_0]; + h2c->v107_u8.wl_rx_gain = trx->wl_rx_gain[RTW89_PHY_0]; + h2c->v107_u8.bt_tx_power = trx->bt_tx_power[BTC_BT_1ST]; + h2c->v107_u8.bt_rx_gain = trx->bt_rx_gain[BTC_BT_1ST]; + h2c->v107_u8.cn = trx->cn; + h2c->v107_u8.nhm = trx->nhm; + h2c->v107_u8.bt_profile = trx->bt_profile; + h2c->v107_u8.rsvd2 = trx->rsvd2; + h2c->v7_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v7_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v7_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v7_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v7_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); ret = rtw89_h2c_tx(rtwdev, skb, false); if (ret) { @@ -6404,6 +7221,90 @@ int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_rf_trx_para_v9 rp = dm->rf_trx_para; + struct rtw89_h2c_cxtxpwr_v7 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtxpwr_v7 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 7; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + h2c->pwr = rp.wl_tx_power[RTW89_PHY_0] & 0xff; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_rf_trx_para_v9 rp = dm->rf_trx_para; + struct rtw89_h2c_cxtxpwr_v9 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtxpwr_v9 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 9; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + if (dm->wl_tx_pwr_phy_map == BIT(RTW89_PHY_1)) + h2c->pwr = rp.wl_tx_power[RTW89_PHY_1] & 0xff; + else + h2c->pwr = rp.wl_tx_power[RTW89_PHY_0] & 0xff; + h2c->band = dm->wl_tx_pwr_phy_map; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define H2C_LEN_PKT_OFLD 4 int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id) { @@ -6443,8 +7344,8 @@ int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id) return 0; } -int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, - struct sk_buff *skb_ofld) +static int __rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, + struct sk_buff *skb_ofld) { struct rtw89_wait_info *wait = &rtwdev->mac.fw_ofld_wait; struct sk_buff *skb; @@ -6486,13 +7387,33 @@ int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, rtw89_debug(rtwdev, RTW89_DBG_FW, "failed to add pkt ofld: id %d, ret %d\n", alloc_id, ret); + /* + * Firmware may consider that it has added this entry + * successfully even though the H2C return timeout. + * Send a delete H2C command to drop it, and thus the + * next add on the same id won't be rejected as duplicate. + */ + rtw89_fw_h2c_del_pkt_offload(rtwdev, alloc_id); rtw89_core_release_bit_map(rtwdev->pkt_offload, alloc_id); - return ret; + + return -EAGAIN; } return 0; } +int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, + struct sk_buff *skb_ofld) +{ + int ret; + + ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld); + if (ret == -EAGAIN) + ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld); + + return ret; +} + static int rtw89_fw_h2c_scan_list_offload_ax(struct rtw89_dev *rtwdev, int ch_num, struct list_head *chan_list) @@ -6579,6 +7500,7 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, struct rtw89_h2c_chinfo_elem_be *elem; struct rtw89_mac_chinfo_be *ch_info; struct rtw89_h2c_chinfo_be *h2c; + bool wildcard_by_drv; struct sk_buff *skb; unsigned int cond; u8 ver = U8_MAX; @@ -6597,6 +7519,10 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, if (RTW89_CHK_FW_FEATURE(CH_INFO_BE_V0, &rtwdev->fw)) ver = 0; + wildcard_by_drv = !(RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw) || + RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw) || + RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V2, &rtwdev->fw)); + skb_put(skb, sizeof(*h2c)); h2c = (struct rtw89_h2c_chinfo_be *)skb->data; @@ -6606,6 +7532,8 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, RTW89_H2C_CHINFO_ARG_MAC_IDX_MASK); list_for_each_entry(ch_info, chan_list, list) { + bool with_probe_id = ch_info->probe_id != RTW89_SCANOFLD_PKT_NONE; + elem = (struct rtw89_h2c_chinfo_elem_be *)skb_put(skb, sizeof(*elem)); elem->w0 = le32_encode_bits(ch_info->dwell_time, RTW89_H2C_CHINFO_BE_W0_DWELL) | @@ -6623,7 +7551,7 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, RTW89_H2C_CHINFO_BE_W1_RANDOM) | le32_encode_bits(ch_info->notify_action, RTW89_H2C_CHINFO_BE_W1_NOTIFY) | - le32_encode_bits(ch_info->probe_id != 0xff ? 1 : 0, + le32_encode_bits(wildcard_by_drv || with_probe_id, RTW89_H2C_CHINFO_BE_W1_PROBE) | le32_encode_bits(ch_info->leave_crit, RTW89_H2C_CHINFO_BE_W1_EARLY_LEAVE_CRIT) | @@ -6788,6 +7716,34 @@ static void rtw89_scan_get_6g_disabled_chan(struct rtw89_dev *rtwdev, } } +void rtw89_hw_scan_calc_req_ssid(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, bool wowlan) +{ + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; + struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; + struct cfg80211_scan_request *req = rtwvif->scan_req; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + struct cfg80211_ssid *s; + u16 sum = 0, n_ssids; + int i, num = 0; + + nd_config = rtw_wow->nd_config; + n_ssids = wowlan ? nd_config->n_match_sets : req->n_ssids; + + for (i = 0; i < n_ssids; i++) { + s = wowlan ? &nd_config->match_sets[i].ssid : &req->ssids[i]; + if (s->ssid_len == 0) + continue; + + num++; + sum += s->ssid_len + 1; + } + + scan_info->n_ssids = num; + scan_info->ssid_total_len = sum; +} + int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, struct rtw89_scan_option *option, struct rtw89_vif_link *rtwvif_link, @@ -6802,18 +7758,21 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, struct rtw89_hw_scan_extra_op scan_op[2] = {}; struct rtw89_chan *op = &scan_info->op_chan; struct rtw89_h2c_scanofld_be_opch *opch; - struct rtw89_pktofld_info *pkt_info; struct rtw89_h2c_scanofld_be *h2c; struct ieee80211_vif *vif; struct sk_buff *skb; u8 macc_role_size = sizeof(*macc_role) * option->num_macc_role; u8 opch_size = sizeof(*opch) * option->num_opch; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + u8 *probe_id = scan_info->wildcard_pkt_id; enum rtw89_scan_be_opmode opmode; - u8 probe_id[NUM_NL80211_BANDS]; u8 scan_offload_ver = U8_MAX; u8 cfg_len = sizeof(*h2c); + struct cfg80211_ssid *s; unsigned int cond; u8 ver = U8_MAX; + u8 n_ssids = 0; u8 policy_val; void *ptr; u8 txnull; @@ -6832,9 +7791,17 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) { cfg_len = offsetofend(typeof(*h2c), w8); scan_offload_ver = 0; + } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) { + cfg_len = offsetofend(typeof(*h2c), w9); + scan_offload_ver = 1; + } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V2, &rtwdev->fw)) { + scan_offload_ver = 2; } len = cfg_len + macc_role_size + opch_size; + if (scan_offload_ver > 2) + len += scan_info->ssid_total_len; + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { rtw89_err(rtwdev, "failed to alloc skb for h2c scan offload\n"); @@ -6845,21 +7812,9 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, h2c = (struct rtw89_h2c_scanofld_be *)skb->data; ptr = skb->data; - memset(probe_id, RTW89_SCANOFLD_PKT_NONE, sizeof(probe_id)); - if (RTW89_CHK_FW_FEATURE(CH_INFO_BE_V0, &rtwdev->fw)) ver = 0; - if (!wowlan) { - list_for_each_entry(pkt_info, &scan_info->pkt_list[NL80211_BAND_6GHZ], list) { - if (pkt_info->wildcard_6ghz) { - /* Provide wildcard as template */ - probe_id[NL80211_BAND_6GHZ] = pkt_info->id; - break; - } - } - } - h2c->w0 = le32_encode_bits(option->operation, RTW89_H2C_SCANOFLD_BE_W0_OP) | le32_encode_bits(option->scan_mode, RTW89_H2C_SCANOFLD_BE_W0_SCAN_MODE) | @@ -6878,7 +7833,10 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, le32_encode_bits(option->norm_cy, RTW89_H2C_SCANOFLD_BE_W2_NORM_CY) | le32_encode_bits(option->opch_end, RTW89_H2C_SCANOFLD_BE_W2_OPCH_END); - h2c->w3 = le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_SSID) | + if (scan_offload_ver > 2) + n_ssids = scan_info->n_ssids; + + h2c->w3 = le32_encode_bits(n_ssids, RTW89_H2C_SCANOFLD_BE_W3_NUM_SSID) | le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_SHORT_SSID) | le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_BSSID) | le32_encode_bits(probe_id[NL80211_BAND_2GHZ], RTW89_H2C_SCANOFLD_BE_W3_PROBEID); @@ -6908,7 +7866,7 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, if (scan_offload_ver == 0) goto flex_member; - h2c->w9 = le32_encode_bits(sizeof(*h2c) / sizeof(h2c->w0), + h2c->w9 = le32_encode_bits(cfg_len / sizeof(h2c->w0), RTW89_H2C_SCANOFLD_BE_W9_SIZE_CFG) | le32_encode_bits(sizeof(*macc_role) / sizeof(macc_role->w0), RTW89_H2C_SCANOFLD_BE_W9_SIZE_MACC) | @@ -7005,6 +7963,24 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, ptr += sizeof(*opch); } + if (scan_offload_ver <= 2) + goto set_hdr; + + nd_config = rtw_wow->nd_config; + n_ssids = wowlan ? nd_config->n_match_sets : req->n_ssids; + + for (i = 0; i < n_ssids; i++) { + s = wowlan ? &nd_config->match_sets[i].ssid : &req->ssids[i]; + if (s->ssid_len == 0) + continue; + + memcpy(ptr, &s->ssid_len, 1); + ptr++; + memcpy(ptr, s->ssid, s->ssid_len); + ptr += s->ssid_len; + } + +set_hdr: rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_MAC, H2C_CL_MAC_FW_OFLD, H2C_FUNC_SCANOFLD_BE, 1, 1, @@ -7873,29 +8849,43 @@ int rtw89_fw_h2c_rf_tas_trigger(struct rtw89_dev *rtwdev, bool enable) int rtw89_fw_h2c_rf_txiqk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, const struct rtw89_chan *chan) { + struct rtw89_h2c_rf_txiqk_v0 *h2c_v0; struct rtw89_h2c_rf_txiqk *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; + u8 ver = U8_MAX; int ret; + if (RTW89_CHK_FW_FEATURE(RFK_TXIQK_V0, &rtwdev->fw)) { + len = sizeof(*h2c_v0); + ver = 0; + } + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { rtw89_err(rtwdev, "failed to alloc skb for h2c RF TXIQK\n"); return -ENOMEM; } skb_put(skb, len); + h2c_v0 = (struct rtw89_h2c_rf_txiqk_v0 *)skb->data; + + h2c_v0->len = len; + h2c_v0->phy = phy_idx; + h2c_v0->txiqk_enable = true; + h2c_v0->is_wb_txiqk = true; + h2c_v0->kpath = RF_AB; + h2c_v0->cur_band = chan->band_type; + h2c_v0->cur_bw = chan->band_width; + h2c_v0->cur_ch = chan->channel; + h2c_v0->txiqk_dbg_en = rtw89_debug_is_enabled(rtwdev, RTW89_DBG_RFK); + + if (ver == 0) + goto hdr; + h2c = (struct rtw89_h2c_rf_txiqk *)skb->data; + h2c->is_ther_rek = false; - h2c->len = len; - h2c->phy = phy_idx; - h2c->txiqk_enable = true; - h2c->is_wb_txiqk = true; - h2c->kpath = RF_AB; - h2c->cur_band = chan->band_type; - h2c->cur_bw = chan->band_width; - h2c->cur_ch = chan->channel; - h2c->txiqk_dbg_en = rtw89_debug_is_enabled(rtwdev, RTW89_DBG_RFK); - +hdr: rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, H2C_CL_OUTSRC_RF_FW_RFK, H2C_FUNC_RFK_TXIQK_OFFOAD, 0, 0, len); @@ -8320,14 +9310,19 @@ void rtw89_fw_st_dbg_dump(struct rtw89_dev *rtwdev) static void rtw89_hw_scan_release_pkt_list(struct rtw89_dev *rtwdev) { + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; struct list_head *pkt_list = rtwdev->scan_info.pkt_list; struct rtw89_pktofld_info *info, *tmp; - u8 idx; + u8 idx, wildcard_pkt_id; for (idx = NL80211_BAND_2GHZ; idx < NUM_NL80211_BANDS; idx++) { if (!(rtwdev->chip->support_bands & BIT(idx))) continue; + wildcard_pkt_id = scan_info->wildcard_pkt_id[idx]; + if (wildcard_pkt_id != RTW89_SCANOFLD_PKT_NONE) + rtw89_fw_h2c_del_pkt_offload(rtwdev, wildcard_pkt_id); + list_for_each_entry_safe(info, tmp, &pkt_list[idx], list) { if (test_bit(info->id, rtwdev->pkt_offload)) rtw89_fw_h2c_del_pkt_offload(rtwdev, info->id); @@ -8422,16 +9417,74 @@ static int rtw89_append_probe_req_ie(struct rtw89_dev *rtwdev, return ret; } +int rtw89_hw_scan_append_wildcard_probe_req(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + const u8 *mac_addr, bool wowlan) +{ + static const u8 basic_rate_ie[] = {WLAN_EID_SUPP_RATES, 0x08, 0x0c, 0x12, + 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c}; + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; + struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; + struct cfg80211_scan_request *req = rtwvif->scan_req; + struct ieee80211_scan_ies *ies = rtwvif->scan_ies; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + enum nl80211_band band; + struct sk_buff *skb; + int ret; + u8 id; + + for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { + if (!(rtwdev->chip->support_bands & BIT(band))) + continue; + + if (wowlan) { + nd_config = rtw_wow->nd_config; + + skb = ieee80211_probereq_get(rtwdev->hw, rtwvif_link->mac_addr, NULL, 0, + nd_config->ie_len + + sizeof(basic_rate_ie)); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, basic_rate_ie, sizeof(basic_rate_ie)); + skb_put_data(skb, nd_config->ie, nd_config->ie_len); + } else { + skb = ieee80211_probereq_get(rtwdev->hw, mac_addr, + NULL, 0, req->ie_len); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, ies->ies[band], ies->len[band]); + skb_put_data(skb, ies->common_ies, ies->common_ie_len); + } + + ret = rtw89_fw_h2c_add_pkt_offload(rtwdev, &id, skb); + kfree_skb(skb); + + if (ret) + return ret; + + scan_info->wildcard_pkt_id[band] = id; + } + + return 0; +} + static int rtw89_hw_scan_update_probe_req(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, const u8 *mac_addr) { struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; struct cfg80211_scan_request *req = rtwvif->scan_req; - struct sk_buff *skb; u8 num = req->n_ssids, i; + struct sk_buff *skb; int ret; + ret = rtw89_hw_scan_append_wildcard_probe_req(rtwdev, rtwvif_link, mac_addr, false); + if (ret) + return ret; + for (i = 0; i < num; i++) { skb = ieee80211_probereq_get(rtwdev->hw, mac_addr, req->ssids[i].ssid, @@ -8739,6 +9792,9 @@ static void rtw89_hw_scan_add_chan_be(struct rtw89_dev *rtwdev, int chan_type, if (probe_count >= RTW89_SCANOFLD_MAX_SSID) break; } + + if (scan_info->n_ssids) + ch_info->fw_probe0_ssids = GENMASK(scan_info->n_ssids - 1, 0); } if (ch_info->ch_band == RTW89_BAND_6G) { @@ -9341,6 +10397,10 @@ int rtw89_hw_scan_start(struct rtw89_dev *rtwdev, rtwdev->scan_info.delay = 0; rtwvif->scan_ies = &scan_req->ies; rtwvif->scan_req = req; + rtw89_hw_scan_calc_req_ssid(rtwdev, rtwvif_link, false); + + memset(rtwdev->scan_info.wildcard_pkt_id, RTW89_SCANOFLD_PKT_NONE, + sizeof(rtwdev->scan_info.wildcard_pkt_id)); if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) get_random_mask_addr(mac_addr, req->mac_addr, @@ -11325,6 +12385,12 @@ fw_tx_shape_lmt_entry_valid(const struct rtw89_fw_tx_shape_lmt_entry *e, if (e->regd >= RTW89_REGD_NUM) return false; + /* ensure compatibility work with 2/5 GHz */ + static_assert(RTW89_REG_6GHZ_POWER_DFLT == 0); + + if (e->reg6_pwr >= NUM_OF_RTW89_REG_6GHZ_POWER) + return false; + return true; } @@ -11339,7 +12405,7 @@ void rtw89_fw_load_tx_shape_lmt(struct rtw89_tx_shape_lmt_data *data) if (!fw_tx_shape_lmt_entry_valid(&entry, cursor, conf)) continue; - data->v[entry.band][entry.tx_shape_rs][entry.regd] = entry.v; + data->v[entry.band][entry.tx_shape_rs][entry.regd][entry.reg6_pwr] = entry.v; } } @@ -11356,6 +12422,12 @@ fw_tx_shape_lmt_ru_entry_valid(const struct rtw89_fw_tx_shape_lmt_ru_entry *e, if (e->regd >= RTW89_REGD_NUM) return false; + /* ensure compatibility work with 2/5 GHz */ + static_assert(RTW89_REG_6GHZ_POWER_DFLT == 0); + + if (e->reg6_pwr >= NUM_OF_RTW89_REG_6GHZ_POWER) + return false; + return true; } @@ -11370,7 +12442,7 @@ void rtw89_fw_load_tx_shape_lmt_ru(struct rtw89_tx_shape_lmt_ru_data *data) if (!fw_tx_shape_lmt_ru_entry_valid(&entry, cursor, conf)) continue; - data->v[entry.band][entry.regd] = entry.v; + data->v[entry.band][entry.regd][entry.reg6_pwr] = entry.v; } } @@ -11780,7 +12852,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) { struct rtw89_fw_cmd_ofld_arg cmd = { - .src = RTW89_FW_CMD_OFLD_SRC_OTHER, + .src = RTW89_FW_CMD_OFLD_SRC_MAC, .type = RTW89_FW_CMD_OFLD_DELAY, .value = us, }; @@ -11794,7 +12866,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) { struct rtw89_fw_cmd_ofld_arg cmd = { - .src = RTW89_FW_CMD_OFLD_SRC_OTHER, + .src = RTW89_FW_CMD_OFLD_SRC_MAC, .type = RTW89_FW_CMD_OFLD_DELAY, .value = ms * 1000, }; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 20721d5209aa..375ca0a243a6 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2053,6 +2053,8 @@ enum rtw89_bb_link_rx_gain_table_type { RTW89_BB_PS_LINK_RX_GAIN_TAB_MAX, }; +#define RTW89_BB_PS_LINK_ID_SKIP 0xfe + enum rtw89_bb_ps_link_buf_id { RTW89_BB_PS_LINK_BUF_0 = 0x00, RTW89_BB_PS_LINK_BUF_1 = 0x01, @@ -2335,6 +2337,9 @@ struct rtw89_h2c_arp_offload { #define RTW89_H2C_ARP_OFFLOAD_W0_PKT_ID GENMASK(31, 24) #define RTW89_H2C_ARP_OFFLOAD_W1_CONTENT GENMASK(31, 0) +#define BT_H2C_FUNC_BT2ND 0x80 +#define BT_C2H_FUNC_BT2ND 0x80 + enum rtw89_btc_btf_h2c_class { BTFC_SET = 0x10, BTFC_GET = 0x11, @@ -2386,6 +2391,16 @@ enum rtw89_btc_cxdrvinfo { CXDRVINFO_MAX, }; +enum rtw89_fbtc_gpio_type { + CXDGPIO_EN_MAP = 0x0, + CXDGPIO_MUX_MAP = 0x1, + CXDGPIO_EXT_HPTA = 0x2, + CXDGPIO_EXT_HMBX = 0x3, + CXDGPIO_EXT_SWOUT = 0x4, + CXDGPIO_EXT_SWIN = 0x5, + CXDGPIO_MAX, +}; + enum rtw89_scan_mode { RTW89_SCAN_IMMEDIATE, RTW89_SCAN_DELAY, @@ -2425,62 +2440,42 @@ struct rtw89_h2c_cxctrl_v7 { struct rtw89_btc_ctrl_v7 ctrl; } __packed; +struct rtw89_h2c_cxctrl_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_ctrl_v9 ctrl; +} __packed; + #define H2C_LEN_CXDRVHDR sizeof(struct rtw89_h2c_cxhdr) #define H2C_LEN_CXDRVHDR_V7 sizeof(struct rtw89_h2c_cxhdr_v7) -struct rtw89_btc_wl_role_info_v7_u8 { - u8 connect_cnt; - u8 link_mode; - u8 link_mode_chg; - u8 p2p_2g; - - struct rtw89_btc_wl_active_role_v7 active_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; -} __packed; - -struct rtw89_btc_wl_role_info_v7_u32 { - __le32 role_map; - __le32 mrole_type; - __le32 mrole_noa_duration; - __le32 dbcc_en; - __le32 dbcc_chg; - __le32 dbcc_2g_phy; -} __packed; - struct rtw89_h2c_cxrole_v7 { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_wl_role_info_v7_u8 _u8; - struct rtw89_btc_wl_role_info_v7_u32 _u32; -} __packed; - -struct rtw89_btc_wl_role_info_v8_u8 { - u8 connect_cnt; - u8 link_mode; - u8 link_mode_chg; - u8 p2p_2g; - - u8 pta_req_band; - u8 dbcc_en; - u8 dbcc_chg; - u8 dbcc_2g_phy; - - struct rtw89_btc_wl_rlink rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; -} __packed; - -struct rtw89_btc_wl_role_info_v8_u32 { - __le32 role_map; - __le32 mrole_type; - __le32 mrole_noa_duration; + struct rtw89_btc_wl_role_info_v7 r; } __packed; struct rtw89_h2c_cxrole_v8 { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_wl_role_info_v8_u8 _u8; - struct rtw89_btc_wl_role_info_v8_u32 _u32; + struct rtw89_btc_wl_role_info_v8 r; +} __packed; + +struct rtw89_h2c_cxrole_v10 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_wl_role_info_v10 r; +} __packed; + +struct rtw89_h2c_cxmlo_v2 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_wl_mlo_info_v2 mlo; } __packed; struct rtw89_h2c_cxosi { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_fbtc_outsrc_set_info osi; + struct rtw89_btc_fbtc_outsrc_set_info_v1 osi; +} __packed; + +struct rtw89_h2c_cxosi_v6 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_fbtc_outsrc_set_info_v6 osi; } __packed; struct rtw89_h2c_cxinit { @@ -2499,6 +2494,99 @@ struct rtw89_h2c_cxinit { u8 rsvd1; } __packed; +struct rtw89_btc_trx_info_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power[RTW89_PHY_NUM]; /* absolute Tx power (dBm), 0xff-> no BTC control */ + s8 wl_rx_gain[RTW89_PHY_NUM]; /* rx gain table index (TBD.) */ + s8 bt_tx_power[BTC_ALL_BT]; /* decrease Tx power (dB) */ + s8 bt_rx_gain[BTC_ALL_BT]; /* LNA constrain level */ + s8 zb_tx_power[BTC_ALL_BT]; + s8 zb_rx_gain[BTC_ALL_BT]; + + u8 cn; /* condition_num */ + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + +struct rtw89_btc_trx_info_v7_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power; + s8 wl_rx_gain; + s8 bt_tx_power; + s8 bt_rx_gain; + s8 zb_tx_power; + s8 zb_rx_gain; + + u8 cn; + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + +struct rtw89_btc_trx_info_v107_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power; + s8 wl_rx_gain; + s8 bt_tx_power; + s8 bt_rx_gain; + + u8 cn; + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + +struct rtw89_btc_trx_info_le { + __le16 tx_rate; + __le16 rx_rate; + + __le32 tx_tp; + __le32 rx_tp; + __le32 rx_err_ratio; +} __packed; + +struct rtw89_h2c_cxtrx_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_u8 v9_u8; + struct rtw89_btc_trx_info_le v9_le; +} __packed; + +struct rtw89_h2c_cxtrx_v7 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_v7_u8 v7_u8; + struct rtw89_btc_trx_info_le v7_le; +} __packed; + +struct rtw89_h2c_cxtrx_v107 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_v107_u8 v107_u8; + struct rtw89_btc_trx_info_le v7_le; +} __packed; + +struct rtw89_h2c_cxtxpwr_v7 { + struct rtw89_h2c_cxhdr_v7 hdr; + u8 pwr; +} __packed; + +struct rtw89_h2c_cxtxpwr_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + u8 pwr; + u8 band; +} __packed; + #define RTW89_H2C_CXINIT_ANT_INFO_POS BIT(0) #define RTW89_H2C_CXINIT_ANT_INFO_DIVERSITY BIT(1) #define RTW89_H2C_CXINIT_ANT_INFO_BTG_POS GENMASK(3, 2) @@ -2520,6 +2608,55 @@ struct rtw89_h2c_cxinit_v7 { struct rtw89_btc_init_info_v7 init; } __packed; +struct rtw89_h2c_cxinit_v107 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v107 init; +} __packed; + +struct rtw89_h2c_cxinit_v10 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v10 init; +} __packed; + +struct rtw89_h2c_cxinit_v11 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v11 init; +} __packed; + +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED BIT(0) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PID GENMASK(3, 1) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY BIT(4) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_NOA BIT(5) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_BADN GENMASK(7, 6) + +#define RTW89_H2C_CXROLE_V101_CLIPS_BW_CLIENTPS BIT(0) +#define RTW89_H2C_CXROLE_V101_CLIPS_BW_BW GENMASK(7, 1) + +#define RTW89_H2C_CXROLE_V101_DBCC_EN BIT(0) +#define RTW89_H2C_CXROLE_V101_DBCC_CHG BIT(1) +#define RTW89_H2C_CXROLE_V101_DBCC_2G_PHY GENMASK(3, 2) +#define RTW89_H2C_CXROLE_V101_DBCC_LINKMODE_CHG BIT(4) +#define RTW89_H2C_CXROLE_V101_DBCC_RSVD GENMASK(31, 5) + +struct rtw89_btc_wl_active_role_v101 { + u8 map_role_status; + u8 map_clips_bw; + u8 role; + u8 ch; + __le32 noa_duration; +} __packed; + +struct rtw89_h2c_cxrole_v101 { + struct rtw89_h2c_cxhdr hdr; + u8 connect_cnt; + u8 link_mode; + __le16 role_map; + struct rtw89_btc_wl_active_role_v101 act_role[RTW89_PORT_NUM]; + __le32 mrole_type; + __le32 mrole_noa_duration; + __le32 map_dbcc_linkmode_chg; +} __packed; + static inline void RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(void *cmd, u8 val) { u8p_replace_bits((u8 *)(cmd) + 2, val, GENMASK(7, 0)); @@ -2760,91 +2897,6 @@ static inline void RTW89_SET_FWCMD_CXCTRL_TRACE_STEP(void *cmd, u32 val) le32p_replace_bits((__le32 *)((u8 *)(cmd) + 2), val, GENMASK(18, 3)); } -static inline void RTW89_SET_FWCMD_CXTRX_TXLV(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 2, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXLV(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 3, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_WLRSSI(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 4, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTRSSI(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 5, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXPWR(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 6, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXGAIN(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 7, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTTXPWR(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 8, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTRXGAIN(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 9, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_CN(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 10, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_NHM(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 11, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTPROFILE(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 12, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RSVD2(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 13, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXRATE(void *cmd, u16 val) -{ - le16p_replace_bits((__le16 *)((u8 *)cmd + 14), val, GENMASK(15, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXRATE(void *cmd, u16 val) -{ - le16p_replace_bits((__le16 *)((u8 *)cmd + 16), val, GENMASK(15, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXTP(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 18), val, GENMASK(31, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXTP(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 22), val, GENMASK(31, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXERRRA(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 26), val, GENMASK(31, 0)); -} - static inline void RTW89_SET_FWCMD_CXRFK_STATE(void *cmd, u32 val) { le32p_replace_bits((__le32 *)((u8 *)(cmd) + 2), val, GENMASK(1, 0)); @@ -3063,8 +3115,11 @@ struct rtw89_h2c_scanofld_be { __le32 w7; __le32 w8; __le32 w9; /* Added after SCAN_OFFLOAD_BE_V1 */ + __le32 w10; /* Added after SCAN_OFFLOAD_BE_V2 */ + __le32 w11; /* Added after SCAN_OFFLOAD_BE_V2 */ /* struct rtw89_h2c_scanofld_be_macc_role (flexible number) */ /* struct rtw89_h2c_scanofld_be_opch (flexible number) */ + /* probe SSID list (flexible number); Added after SCAN_OFFLOAD_BE_V3 */ } __packed; #define RTW89_H2C_SCANOFLD_BE_W0_OP GENMASK(1, 0) @@ -3142,7 +3197,6 @@ enum rtw89_fw_cmd_ofld_arg_src { RTW89_FW_CMD_OFLD_SRC_RF, RTW89_FW_CMD_OFLD_SRC_MAC, RTW89_FW_CMD_OFLD_SRC_RF_DDIE, - RTW89_FW_CMD_OFLD_SRC_OTHER, }; enum rtw89_fw_cmd_ofld_arg_type { @@ -4357,31 +4411,50 @@ enum rtw89_fw_element_id { RTW89_FW_ELEMENT_ID_DIAG_MAC = 28, RTW89_FW_ELEMENT_ID_TX_COMP = 29, + __RTW89_FW_ELEMENT_ID_INTL_TRANSITION, RTW89_FW_ELEMENT_ID_NUM, }; +#define BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE \ + (BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT) | \ + BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU)) + #define BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ \ (BIT(RTW89_FW_ELEMENT_ID_TXPWR_BYRATE) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_2GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_5GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_2GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_5GHZ) | \ - BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT) | \ - BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU)) + BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE) #define BITS_OF_RTW89_TXPWR_FW_ELEMENTS \ (BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_6GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_6GHZ)) -#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ \ +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE \ (BIT(RTW89_FW_ELEMENT_ID_BB_REG) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_A) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_B) | \ BIT(RTW89_FW_ELEMENT_ID_RF_NCTL) | \ - BIT(RTW89_FW_ELEMENT_ID_TXPWR_TRK) | \ + BIT(RTW89_FW_ELEMENT_ID_TXPWR_TRK)) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE | \ BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ) +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE | \ + BITS_OF_RTW89_TXPWR_FW_ELEMENTS) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8852A \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ & \ + ~BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8851B \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ & \ + ~BIT(RTW89_FW_ELEMENT_ID_RADIO_B)) + #define RTW89_BE_GEN_DEF_NEEDED_FW_ELEMENTS_BASE \ (BIT(RTW89_FW_ELEMENT_ID_BB_REG) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_A) | \ @@ -5052,7 +5125,7 @@ struct rtw89_h2c_rf_rxdck { u8 is_chl_k; } __packed; -struct rtw89_h2c_rf_txiqk { +struct rtw89_h2c_rf_txiqk_v0 { u8 len; u8 phy; u8 txiqk_enable; @@ -5064,6 +5137,11 @@ struct rtw89_h2c_rf_txiqk { u8 txiqk_dbg_en; } __packed; +struct rtw89_h2c_rf_txiqk { + struct rtw89_h2c_rf_txiqk_v0 v0; + u8 is_ther_rek; +} __packed; + struct rtw89_h2c_rf_cim3k { u8 len; u8 phy; @@ -5391,16 +5469,27 @@ int rtw89_fw_h2c_tx_history(struct rtw89_dev *rtwdev, u16 mac_id); int rtw89_fw_h2c_drv_ctrl_fw(struct rtw89_dev *rtwdev); int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_role_v101(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); -int rtw89_fw_h2c_cxdrv_trx(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v107(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id); int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, struct sk_buff *skb_ofld); @@ -5511,6 +5600,11 @@ int rtw89_hw_scan_add_chan_list_be(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); int rtw89_pno_scan_add_chan_list_be(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); +int rtw89_hw_scan_append_wildcard_probe_req(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + const u8 *mac_addr, bool wowlan); +void rtw89_hw_scan_calc_req_ssid(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, bool wowlan); int rtw89_fw_h2c_trigger_cpu_exception(struct rtw89_dev *rtwdev); int rtw89_fw_h2c_pkt_drop(struct rtw89_dev *rtwdev, const struct rtw89_pkt_drop_params *params); @@ -5814,6 +5908,7 @@ struct rtw89_fw_tx_shape_lmt_entry { u8 tx_shape_rs; u8 regd; u8 v; + u8 reg6_pwr; } __packed; /* must consider compatibility; don't insert new in the mid */ @@ -5821,6 +5916,7 @@ struct rtw89_fw_tx_shape_lmt_ru_entry { u8 band; u8 regd; u8 v; + u8 reg6_pwr; } __packed; const struct rtw89_rfe_parms * diff --git a/drivers/net/wireless/realtek/rtw89/led.c b/drivers/net/wireless/realtek/rtw89/led.c new file mode 100644 index 000000000000..ae74b7075d7c --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright(c) 2026 Realtek Corporation + */ + +#include "core.h" +#include "debug.h" +#include "led.h" +#include "reg.h" + +static const struct rtw89_led_gpio_entry rtw89_common_led_gpios[] = {{ + .pin = 8, + .pinmux = {.addr = R_AX_GPIO8_15_FUNC_SEL, .mask = GENMASK(3, 0), .data = 0xf}, + .mode = {.addr = R_AX_GPIO_EXT_CTRL + 2, .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_EXT_CTRL + 1, .data = BIT(0)}, +}}; + +static const struct rtw89_led_desc rtw89_common_led_desc = { + .gpios = rtw89_common_led_gpios, + .n_gpio = ARRAY_SIZE(rtw89_common_led_gpios), +}; + +void rtw89_led_gpio_config(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e) +{ + rtw89_write32_mask(rtwdev, e->pinmux.addr, e->pinmux.mask, e->pinmux.data); + rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data); +} + +void rtw89_led_gpio_set(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e, + enum led_brightness brightness) +{ + if (brightness == LED_OFF) { + rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data); + } else { + rtw89_write16_set(rtwdev, e->mode.addr, e->mode.data); + rtw89_write8_clr(rtwdev, e->out.addr, e->out.data); + } +} + +static int rtw89_led_set(struct led_classdev *led, enum led_brightness brightness) +{ + struct rtw89_led *rtw_led = container_of(led, struct rtw89_led, led); + struct rtw89_dev *rtwdev = container_of(rtw_led, struct rtw89_dev, led); + const struct rtw89_led_desc *desc = rtw_led->desc; + const struct rtw89_led_gpio_entry *e = &desc->gpios[0]; + + if (rtw_led->brightness_cache[0] == brightness) { + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u skip (no change)\n", + e->pin); + return 0; + } + + wiphy_lock(rtwdev->hw->wiphy); + + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u brightness=%u\n", + e->pin, brightness); + rtw89_led_gpio_set(rtwdev, e, brightness); + rtw_led->brightness_cache[0] = brightness; + + wiphy_unlock(rtwdev->hw->wiphy); + + return 0; +} + +static int rtw89_led_sc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + int ret; + + snprintf(rtw_led->name, sizeof(rtw_led->name), "rtw89-%s", + wiphy_name(rtwdev->hw->wiphy)); + rtw_led->led.name = rtw_led->name; + rtw_led->led.brightness_set_blocking = rtw89_led_set; + rtw_led->led.max_brightness = LED_ON; + rtw_led->led.default_trigger = ieee80211_get_assoc_led_name(rtwdev->hw); + + ret = led_classdev_register(rtwdev->dev, &rtw_led->led); + if (ret) { + rtw89_warn(rtwdev, "failed to register LED, ret=%d\n", ret); + return ret; + } + + rtw89_led_gpio_config(rtwdev, &desc->gpios[0]); + + return 0; +} + +static void rtw89_led_sc_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + + led_classdev_unregister(&rtw_led->led); +} + +void rtw89_led_init(struct rtw89_dev *rtwdev) +{ + const struct rtw89_led_desc *desc = &rtw89_common_led_desc; + struct rtw89_led *rtw_led = &rtwdev->led; + const struct rtw89_board_variant *board = rtwdev->board; + int ret; + int i; + + /* single-GPIO monochrome LED is the only supported layout */ + BUILD_BUG_ON(ARRAY_SIZE(rtw89_common_led_gpios) != 1); + + if (board) + desc = board->led_desc; + if (!desc->n_gpio || desc->n_gpio > RTW89_LED_MAX_NUM) + return; + + rtw_led->desc = desc; + for (i = 0; i < ARRAY_SIZE(rtw_led->brightness_cache); i++) + rtw_led->brightness_cache[i] = LED_OFF; + + if (desc->n_gpio == 1) + ret = rtw89_led_sc_init(rtwdev, desc); + else + ret = rtw89_led_mc_init(rtwdev, desc); + + if (ret) + return; + + rtw_led->registered = true; +} + +void rtw89_led_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + const struct rtw89_led_desc *desc = rtw_led->desc; + + if (!rtw_led->registered) + return; + + if (desc->n_gpio == 1) + rtw89_led_sc_deinit(rtwdev); + else + rtw89_led_mc_deinit(rtwdev); +} diff --git a/drivers/net/wireless/realtek/rtw89/led.h b/drivers/net/wireless/realtek/rtw89/led.h new file mode 100644 index 000000000000..f5ccfe1d1752 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* Copyright(c) 2026 Realtek Corporation + */ + +#ifndef __RTW89_LED_H__ +#define __RTW89_LED_H__ + +#include "core.h" + +void rtw89_led_gpio_config(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e); +void rtw89_led_gpio_set(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e, + enum led_brightness brightness); + +#ifdef CONFIG_RTW89_LEDS +void rtw89_led_init(struct rtw89_dev *rtwdev); +void rtw89_led_deinit(struct rtw89_dev *rtwdev); +#else +static inline void rtw89_led_init(struct rtw89_dev *rtwdev) {} +static inline void rtw89_led_deinit(struct rtw89_dev *rtwdev) {} +#endif + +#ifdef CONFIG_RTW89_LEDS_MC +int rtw89_led_mc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc); +void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev); +#else +static inline int rtw89_led_mc_init(struct rtw89_dev *rtwdev, + const struct rtw89_led_desc *desc) +{ + return -EOPNOTSUPP; +} + +static inline void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev) {} +#endif + +#endif diff --git a/drivers/net/wireless/realtek/rtw89/led_mc.c b/drivers/net/wireless/realtek/rtw89/led_mc.c new file mode 100644 index 000000000000..7e9243baef68 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led_mc.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright(c) 2026 Realtek Corporation + */ + +#include "core.h" +#include "debug.h" +#include "led.h" + +static int rtw89_led_mc_brightness_set(struct led_classdev *led, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev = lcdev_to_mccdev(led); + struct rtw89_led *rtw_led = container_of(mcdev, struct rtw89_led, led_mc); + struct rtw89_dev *rtwdev = container_of(rtw_led, struct rtw89_dev, led); + const struct rtw89_led_desc *desc = rtw_led->desc; + const struct rtw89_led_gpio_entry *e; + unsigned int i; + + led_mc_calc_color_components(mcdev, brightness); + + wiphy_lock(rtwdev->hw->wiphy); + + for (i = 0; i < mcdev->num_colors; i++) { + e = &desc->gpios[i]; + if (rtw_led->brightness_cache[i] == mcdev->subled_info[i].brightness) { + rtw89_debug(rtwdev, RTW89_DBG_LED, + "led_mc_set: pin=%u skip (no change)\n", e->pin); + continue; + } + + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_mc_set: pin=%u brightness=%u\n", + e->pin, mcdev->subled_info[i].brightness); + rtw89_led_gpio_set(rtwdev, e, mcdev->subled_info[i].brightness); + rtw_led->brightness_cache[i] = mcdev->subled_info[i].brightness; + } + + wiphy_unlock(rtwdev->hw->wiphy); + + return 0; +} + +int rtw89_led_mc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + struct led_classdev_mc *led_mc = &rtw_led->led_mc; + int i, ret; + + led_mc->num_colors = desc->n_gpio; + led_mc->subled_info = rtw_led->subled; + + for (i = 0; i < desc->n_gpio; i++) { + const struct rtw89_led_gpio_entry *e = &desc->gpios[i]; + + rtw_led->subled[i].color_index = e->color; + rtw_led->subled[i].intensity = e->intensity; + rtw_led->subled[i].brightness = 0; + rtw_led->subled[i].channel = i; + + rtw89_led_gpio_config(rtwdev, e); + } + + snprintf(rtw_led->name, sizeof(rtw_led->name), "rtw89-%s-multicolor", + wiphy_name(rtwdev->hw->wiphy)); + led_mc->led_cdev.name = rtw_led->name; + led_mc->led_cdev.brightness_set_blocking = rtw89_led_mc_brightness_set; + led_mc->led_cdev.max_brightness = LED_ON; + led_mc->led_cdev.default_trigger = ieee80211_get_assoc_led_name(rtwdev->hw); + + ret = led_classdev_multicolor_register(rtwdev->dev, led_mc); + if (ret) + rtw89_warn(rtwdev, "failed to register multicolor LED, ret=%d\n", ret); + + return ret; +} + +void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + + led_classdev_multicolor_unregister(&rtw_led->led_mc); +} diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 8c395517bd2f..df396fbfca26 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -1558,6 +1558,8 @@ static int rtw89_mac_power_switch(struct rtw89_dev *rtwdev, bool on) if (!test_bit(RTW89_FLAG_PROBE_DONE, rtwdev->flags)) { rtw89_mac_efuse_read_ecv(rtwdev); mac->efuse_read_fw_secure(rtwdev); + rtw89_mac_efuse_read_thermal_k(rtwdev); + rtw89_mac_efuse_read_pwr_data(rtwdev); } set_bit(RTW89_FLAG_POWERON, rtwdev->flags); @@ -1600,6 +1602,8 @@ int rtw89_mac_pwr_on(struct rtw89_dev *rtwdev) void rtw89_mac_pwr_off(struct rtw89_dev *rtwdev) { + rtw89_mac_set_vcore_reset(rtwdev); + rtw89_mac_power_switch(rtwdev, false); } @@ -1706,11 +1710,7 @@ static int sys_init_ax(struct rtw89_dev *rtwdev) if (ret) return ret; - ret = chip_func_en_ax(rtwdev); - if (ret) - return ret; - - return ret; + return chip_func_en_ax(rtwdev); } const struct rtw89_mac_size_set rtw89_mac_size = { @@ -4304,13 +4304,14 @@ int rtw89_mac_disable_bb_rf(struct rtw89_dev *rtwdev) } EXPORT_SYMBOL(rtw89_mac_disable_bb_rf); -int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb) +int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb, + bool bb_preinit) { int ret; rtw89_mac_ctrl_hci_dma_trx(rtwdev, true); - if (include_bb) { + if (include_bb || bb_preinit) { /* Only call BB preinit including configuration of BB MCU for * the chips which need to download BB MCU firmware. Otherwise, * calling preinit later to prevent touching registers affecting @@ -4363,7 +4364,7 @@ int rtw89_mac_init(struct rtw89_dev *rtwdev) bool include_bb = !!chip->bbmcu_nr; int ret; - ret = rtw89_mac_partial_init(rtwdev, include_bb); + ret = rtw89_mac_partial_init(rtwdev, include_bb, include_bb); if (ret) goto fail; @@ -5167,7 +5168,7 @@ static void rtw89_mac_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, ies->len); - if (!elem || elem->datalen < 10 || + if (!elem || elem->datalen < 11 || !(elem->data[10] & WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) *tolerated = false; rcu_read_unlock(); @@ -6534,8 +6535,6 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, if (gnt_cfg->band[0].gnt_bt) val |= B_AX_GNT_BT_RFC_S0_VAL | B_AX_GNT_BT_RX_VAL | B_AX_GNT_BT_TX_VAL; - else - val |= B_AX_WL_ACT_VAL; if (gnt_cfg->band[0].gnt_bt_sw_en) val |= B_AX_GNT_BT_RFC_S0_SWCTRL | B_AX_GNT_BT_RX_SWCTRL | @@ -6552,8 +6551,6 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, if (gnt_cfg->band[1].gnt_bt) val |= B_AX_GNT_BT_RFC_S1_VAL | B_AX_GNT_BT_RX_VAL | B_AX_GNT_BT_TX_VAL; - else - val |= B_AX_WL_ACT_VAL; if (gnt_cfg->band[1].gnt_bt_sw_en) val |= B_AX_GNT_BT_RFC_S1_SWCTRL | B_AX_GNT_BT_RX_SWCTRL | @@ -6567,6 +6564,15 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, val |= B_AX_GNT_WL_RFC_S1_SWCTRL | B_AX_GNT_WL_RX_SWCTRL | B_AX_GNT_WL_TX_SWCTRL | B_AX_GNT_WL_BB_SWCTRL; + if (gnt_cfg->bt[0].wlan_act_en) + val |= B_AX_WL_ACT_SWCTRL; + if (gnt_cfg->bt[0].wlan_act) + val |= B_AX_WL_ACT_VAL; + if (gnt_cfg->bt[1].wlan_act_en) + val |= B_AX_WL_ACT2_SWCTRL; + if (gnt_cfg->bt[1].wlan_act) + val |= B_AX_WL_ACT2_VAL; + rtw89_write32(rtwdev, R_AX_GNT_SW_CTRL, val); return 0; @@ -6641,22 +6647,20 @@ EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path); int rtw89_mac_cfg_ctrl_path_v1(struct rtw89_dev *rtwdev, bool wl) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; + struct rtw89_mac_ax_coex_gnt gnt = {}; int i; if (wl) return 0; for (i = 0; i < RTW89_PHY_NUM; i++) { - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + gnt.band[i].gnt_bt_sw_en = 1; + gnt.band[i].gnt_bt = 1; + gnt.band[i].gnt_wl_sw_en = 1; + gnt.band[i].gnt_wl = 0; } - return rtw89_mac_cfg_gnt_v1(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v1(rtwdev, &gnt); } EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path_v1); @@ -7043,11 +7047,23 @@ __rtw89_mac_set_tx_time(struct rtw89_dev *rtwdev, struct rtw89_sta_link *rtwsta_ { #define MAC_AX_DFLT_TX_TIME 5280 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_chip_info *chip = rtwdev->chip; u8 mac_idx = rtwsta_link->rtwvif_link->mac_idx; u32 max_tx_time = tx_time == 0 ? MAC_AX_DFLT_TX_TIME : tx_time; + struct rtw89_entity_conf conf; + const struct rtw89_chan *chan; u32 reg; int ret = 0; + if (chip->txtime_limit_2ghz) { + rtw89_entity_get_conf(rtwdev, &conf); + chan = conf.chans[mac_idx]; + + if (chan->band_type == RTW89_BAND_2G) + max_tx_time = min_t(u32, max_tx_time, + chip->txtime_limit_2ghz); + } + if (rtwsta_link->cctl_tx_time) { rtwsta_link->ampdu_max_time = (max_tx_time - 512) >> 9; ret = rtw89_chip_h2c_txtime_cmac_tbl(rtwdev, rtwsta_link); @@ -7058,9 +7074,13 @@ __rtw89_mac_set_tx_time(struct rtw89_dev *rtwdev, struct rtw89_sta_link *rtwsta_ return ret; } + if (chip->chip_gen == RTW89_CHIP_AX) + max_tx_time >>= 5; + else + max_tx_time = max_tx_time * 1000 >> 15; + reg = rtw89_mac_reg_by_idx(rtwdev, mac->agg_limit.addr, mac_idx); - rtw89_write32_mask(rtwdev, reg, mac->agg_limit.mask, - max_tx_time >> 5); + rtw89_write32_mask(rtwdev, reg, mac->agg_limit.mask, max_tx_time); } return ret; @@ -7434,10 +7454,12 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .mem_base_addrs = rtw89_mac_mem_base_addrs_ax, .mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_AX, .rx_fltr = R_AX_RX_FLTR_OPT, + .default_rx_fltr = DEFAULT_AX_RX_FLTR, .port_base = &rtw89_port_base_ax, .agg_len_ht = R_AX_AGG_LEN_HT_0, .ps_status = R_AX_PPWRBIT_SETTING, .mu_gid = &rtw89_mac_mu_gid_addr_ax, + .boot_dbg = R_AX_BOOT_DBG, .muedca_ctrl = { .addr = R_AX_MUEDCA_EN, @@ -7475,6 +7497,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .cfg_ppdu_status = rtw89_mac_cfg_ppdu_status_ax, .cfg_phy_rpt = NULL, .set_edcca_mode = NULL, + .set_vcore_cfg = NULL, .dle_mix_cfg = dle_mix_cfg_ax, .chk_dle_rdy = chk_dle_rdy_ax, @@ -7500,6 +7523,8 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .cnv_efuse_state = rtw89_cnv_efuse_state_ax, .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_ax, .efuse_read_ecv = NULL, + .efuse_read_thermal_k = NULL, + .efuse_read_pwr_data = NULL, .cfg_plt = rtw89_mac_cfg_plt_ax, .get_plt_cnt = rtw89_mac_get_plt_cnt_ax, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 539061fc15e8..8419bcd3956a 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1063,10 +1063,12 @@ struct rtw89_mac_gen_def { const u32 *mem_base_addrs; u32 mem_page_size; u32 rx_fltr; + u32 default_rx_fltr; const struct rtw89_port_reg *port_base; u32 agg_len_ht; u32 ps_status; const struct rtw89_mac_mu_gid_addr *mu_gid; + u32 boot_dbg; struct rtw89_reg_def muedca_ctrl; struct rtw89_reg_def bfee_ctrl; @@ -1100,6 +1102,7 @@ struct rtw89_mac_gen_def { int (*cfg_ppdu_status)(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable); void (*cfg_phy_rpt)(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable); void (*set_edcca_mode)(struct rtw89_dev *rtwdev, u8 mac_idx, bool normal); + void (*set_vcore_cfg)(struct rtw89_dev *rtwdev, u8 vlv); int (*dle_mix_cfg)(struct rtw89_dev *rtwdev, const struct rtw89_dle_mem *cfg); int (*chk_dle_rdy)(struct rtw89_dev *rtwdev, bool wde_or_ple); @@ -1132,6 +1135,8 @@ struct rtw89_mac_gen_def { int (*cnv_efuse_state)(struct rtw89_dev *rtwdev, bool idle); int (*efuse_read_fw_secure)(struct rtw89_dev *rtwdev); int (*efuse_read_ecv)(struct rtw89_dev *rtwdev); + int (*efuse_read_thermal_k)(struct rtw89_dev *rtwdev); + int (*efuse_read_pwr_data)(struct rtw89_dev *rtwdev); int (*cfg_plt)(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_plt *plt); u16 (*get_plt_cnt)(struct rtw89_dev *rtwdev, u8 band); @@ -1298,7 +1303,8 @@ rtw89_write32_port_set(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_l int rtw89_mac_pwr_on(struct rtw89_dev *rtwdev); void rtw89_mac_pwr_off(struct rtw89_dev *rtwdev); -int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb); +int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb, + bool bb_preinit); int rtw89_mac_preinit(struct rtw89_dev *rtwdev); int rtw89_mac_init(struct rtw89_dev *rtwdev); int rtw89_mac_dle_init(struct rtw89_dev *rtwdev, enum rtw89_qta_mode mode, @@ -1457,6 +1463,29 @@ void rtw89_mac_set_edcca_mode(struct rtw89_dev *rtwdev, u8 mac_idx, bool normal) mac->set_edcca_mode(rtwdev, mac_idx, normal); } +static inline +void rtw89_mac_set_vcore_cfg(struct rtw89_dev *rtwdev, u8 vlv) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->set_vcore_cfg) + return; + + mac->set_vcore_cfg(rtwdev, vlv); +} + +static inline +void rtw89_mac_set_vcore_reset(struct rtw89_dev *rtwdev) +{ + struct rtw89_hal *hal = &rtwdev->hal; + + if (hal->thermal_prot_vlv == 0) + return; + + hal->thermal_prot_vlv = 0; + rtw89_mac_set_vcore_cfg(rtwdev, 0); +} + static inline void rtw89_mac_set_edcca_mode_bands(struct rtw89_dev *rtwdev, bool normal) { @@ -1654,6 +1683,7 @@ enum rtw89_mac_xtal_si_offset { XTAL_SI_PWR_CUT = 0x10, #define XTAL_SI_SMALL_PWR_CUT BIT(0) #define XTAL_SI_BIG_PWR_CUT BIT(1) + XTAL_SI_AONLDO_CTRL = 0x10, XTAL_SI_XTAL_DRV = 0x15, #define XTAL_SI_DRV_LATCH BIT(4) XTAL_SI_XTAL_PLL = 0x16, @@ -1735,6 +1765,26 @@ static inline int rtw89_mac_efuse_read_ecv(struct rtw89_dev *rtwdev) return mac->efuse_read_ecv(rtwdev); } +static inline int rtw89_mac_efuse_read_thermal_k(struct rtw89_dev *rtwdev) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->efuse_read_thermal_k) + return -ENOENT; + + return mac->efuse_read_thermal_k(rtwdev); +} + +static inline int rtw89_mac_efuse_read_pwr_data(struct rtw89_dev *rtwdev) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->efuse_read_pwr_data) + return -ENOENT; + + return mac->efuse_read_pwr_data(rtwdev); +} + static inline void rtw89_mac_fwdl_preconfig(struct rtw89_dev *rtwdev) { diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c index aade5c5b79e8..c1be69a3c192 100644 --- a/drivers/net/wireless/realtek/rtw89/mac80211.c +++ b/drivers/net/wireless/realtek/rtw89/mac80211.c @@ -826,11 +826,36 @@ static int rtw89_ops_start_ap(struct ieee80211_hw *hw, ether_addr_copy(rtwvif_link->bssid, link_conf->bssid); rtw89_cam_bssid_changed(rtwdev, rtwvif_link); - rtw89_mac_port_update(rtwdev, rtwvif_link); - rtw89_chip_h2c_assoc_cmac_tbl(rtwdev, rtwvif_link, NULL); - rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, NULL, RTW89_ROLE_TYPE_CHANGE); - rtw89_fw_h2c_join_info(rtwdev, rtwvif_link, NULL, true); - rtw89_fw_h2c_cam(rtwdev, rtwvif_link, NULL, NULL, RTW89_ROLE_TYPE_CHANGE); + ret = rtw89_mac_port_update(rtwdev, rtwvif_link); + if (ret) { + rtw89_warn(rtwdev, "failed to update mac port\n"); + return ret; + } + + ret = rtw89_chip_h2c_assoc_cmac_tbl(rtwdev, rtwvif_link, NULL); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c cmac table\n"); + return ret; + } + + ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, NULL, RTW89_ROLE_TYPE_CHANGE); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c role info\n"); + return ret; + } + + ret = rtw89_fw_h2c_join_info(rtwdev, rtwvif_link, NULL, true); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c join info\n"); + return ret; + } + + ret = rtw89_fw_h2c_cam(rtwdev, rtwvif_link, NULL, NULL, RTW89_ROLE_TYPE_CHANGE); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c cam\n"); + return ret; + } + rtw89_chip_rfk_channel(rtwdev, rtwvif_link); if (RTW89_CHK_FW_FEATURE(NOTIFY_AP_INFO, &rtwdev->fw)) { @@ -1979,7 +2004,8 @@ static void rtw89_ops_rfkill_poll(struct ieee80211_hw *hw) lockdep_assert_wiphy(hw->wiphy); /* wl_disable GPIO get floating when entering LPS */ - if (test_bit(RTW89_FLAG_RUNNING, rtwdev->flags)) + if (test_bit(RTW89_FLAG_RUNNING, rtwdev->flags) || + test_bit(RTW89_FLAG_SHUTDOWN, rtwdev->flags)) return; rtw89_core_rfkill_poll(rtwdev, false); diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index f24c119b99f1..c8f343c8d545 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -641,6 +641,7 @@ static void set_cpu_en(struct rtw89_dev *rtwdev, bool include_bb) static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) { const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_hal *hal = &rtwdev->hal; u32 val32; int ret; @@ -651,8 +652,10 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) } val32 = rtw89_read32(rtwdev, R_BE_UDM1); if (val32) { - rtw89_warn(rtwdev, "[SER] AON L2 Debug register not empty before Boot.\n"); - rtw89_warn(rtwdev, "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32); + rtw89_debug(rtwdev, RTW89_DBG_UNEXP, + "[SER] AON L2 Debug register not empty before Boot.\n"); + rtw89_debug(rtwdev, RTW89_DBG_UNEXP, + "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32); } val32 = rtw89_read32(rtwdev, R_BE_UDM2); if (val32) { @@ -683,6 +686,8 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) if (chip->chip_id != RTL8922A) rtw89_write32_set(rtwdev, R_BE_WCPU_FW_CTRL, B_BE_HOST_EXIST); + rtw89_write32_mask(rtwdev, R_BE_WCPU_FW_CTRL, + B_BE_WCPU_ROM_CUT_VAL_MASK, hal->cv + 1); rtw89_write16_mask(rtwdev, R_BE_BOOT_REASON, B_BE_BOOT_REASON_MASK, boot_reason); rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_WCPU_EN); rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_HOLD_AFTER_RESET); @@ -924,11 +929,7 @@ static int sys_init_be(struct rtw89_dev *rtwdev) if (ret) return ret; - ret = chip_func_en_be(rtwdev); - if (ret) - return ret; - - return ret; + return chip_func_en_be(rtwdev); } static int mac_func_en_be(struct rtw89_dev *rtwdev) @@ -1196,6 +1197,9 @@ static int scheduler_init_be(struct rtw89_dev *rtwdev, u8 mac_idx) rtw89_io_pack(rtwdev); + reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MISC_1, mac_idx); + rtw89_write32_set(rtwdev, reg, B_BE_EN_TX_FINISH_PRD_RESP); + if (chip->chip_id == RTL8922D) { reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_SCH_EXT_CTRL, mac_idx); rtw89_write32_set(rtwdev, reg, B_BE_CWCNT_PLUS_MODE); @@ -1324,11 +1328,7 @@ static int rx_fltr_init_be(struct rtw89_dev *rtwdev, u8 mac_idx) rtw89_mac_typ_fltr_opt_be(rtwdev, RTW89_DATA, RTW89_FWD_TO_HOST, mac_idx); reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_RX_FLTR_OPT, mac_idx); - val = B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | B_BE_A_MC | - B_BE_A_BC | B_BE_A_A1_MATCH | B_BE_SNIFFER_MODE | - u32_encode_bits(15, B_BE_UID_FILTER_MASK); - rtw89_write32(rtwdev, reg, val); - u32p_replace_bits(&rtwdev->hal.rx_fltr, 15, B_BE_UID_FILTER_MASK); + rtw89_write32(rtwdev, reg, rtwdev->hal.rx_fltr); reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_PLCP_HDR_FLTR, mac_idx); val = B_BE_HE_SIGB_CRC_CHK | B_BE_VHT_MU_SIGB_CRC_CHK | @@ -2458,29 +2458,26 @@ EXPORT_SYMBOL(rtw89_mac_cfg_gnt_v3); int rtw89_mac_cfg_ctrl_path_v2(struct rtw89_dev *rtwdev, bool wl) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; - struct rtw89_mac_ax_wl_act *gbt = dm->gnt.bt; const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_mac_ax_coex_gnt gnt = {}; int i; if (wl) return 0; for (i = 0; i < RTW89_PHY_NUM; i++) { - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; - gbt[i].wlan_act = 1; - gbt[i].wlan_act_en = 0; + gnt.band[i].gnt_bt_sw_en = 1; + gnt.band[i].gnt_bt = 1; + gnt.band[i].gnt_wl_sw_en = 1; + gnt.band[i].gnt_wl = 0; + gnt.bt[i].wlan_act = 1; + gnt.bt[i].wlan_act_en = 0; } if (chip->chip_id == RTL8922A) - return rtw89_mac_cfg_gnt_v2(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v2(rtwdev, &gnt); else - return rtw89_mac_cfg_gnt_v3(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v3(rtwdev, &gnt); } EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path_v2); @@ -2668,6 +2665,43 @@ void rtw89_mac_set_edcca_mode_be(struct rtw89_dev *rtwdev, u8 mac_idx, bool norm } } +static +void rtw89_mac_set_vcore_cfg_be(struct rtw89_dev *rtwdev, u8 vlv) +{ + struct rtw89_hal *hal = &rtwdev->hal; + u32 val32; + u8 target; + u8 vpwm; + int i; + + if (rtwdev->chip->chip_id != RTL8922D) + return; + + target = clamp(hal->thermal_prot_vmax - vlv, + hal->thermal_prot_vmin, hal->thermal_prot_vmax); + + val32 = rtw89_read32(rtwdev, R_BE_SPS_DIG_ON_CTRL0); + vpwm = u32_get_bits(val32, B_BE_PWMTUNE_MASK); + val32 &= ~B_BE_PWMTUNE_MASK; + + if (vpwm == target) + return; + + for (i = 0; i < RTW89_THERMAL_PROT_VLV_MAX; i++) { + if (vpwm > target) + vpwm--; + else + vpwm++; + + rtw89_write32(rtwdev, R_BE_SPS_DIG_ON_CTRL0, val32 | vpwm); + + if (vpwm == target) + break; + + mdelay(50); + } +} + static int rtw89_mac_cfg_ppdu_status_be(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable) { @@ -3238,10 +3272,12 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .mem_base_addrs = rtw89_mac_mem_base_addrs_be, .mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_BE, .rx_fltr = R_BE_RX_FLTR_OPT, + .default_rx_fltr = DEFAULT_BE_RX_FLTR, .port_base = &rtw89_port_base_be, .agg_len_ht = R_BE_AGG_LEN_HT_0, .ps_status = R_BE_WMTX_POWER_BE_BIT_CTL, .mu_gid = &rtw89_mac_mu_gid_addr_be, + .boot_dbg = R_BE_BOOT_DBG, .muedca_ctrl = { .addr = R_BE_MUEDCA_EN, @@ -3279,6 +3315,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .cfg_ppdu_status = rtw89_mac_cfg_ppdu_status_be, .cfg_phy_rpt = rtw89_mac_cfg_phy_rpt_be, .set_edcca_mode = rtw89_mac_set_edcca_mode_be, + .set_vcore_cfg = rtw89_mac_set_vcore_cfg_be, .dle_mix_cfg = dle_mix_cfg_be, .chk_dle_rdy = chk_dle_rdy_be, @@ -3304,6 +3341,8 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .cnv_efuse_state = rtw89_cnv_efuse_state_be, .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_be, .efuse_read_ecv = rtw89_efuse_read_ecv_be, + .efuse_read_thermal_k = rtw89_efuse_read_thermal_k_be, + .efuse_read_pwr_data = rtw89_efuse_read_pwr_data_be, .cfg_plt = rtw89_mac_cfg_plt_be, .get_plt_cnt = rtw89_mac_get_plt_cnt_be, diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 102bae488180..8c3f4eb52cc5 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -3326,6 +3326,10 @@ static bool rtw89_pci_is_dac_compatible_bridge(struct rtw89_dev *rtwdev) if (bridge->device == 0x2806) return true; break; + case PCI_VENDOR_ID_SPACEMIT: + if (bridge->device == PCI_DEVICE_ID_SPACEMIT_K3) + return true; + break; } return false; @@ -4874,6 +4878,19 @@ void rtw89_pci_remove(struct pci_dev *pdev) } EXPORT_SYMBOL(rtw89_pci_remove); +void rtw89_pci_shutdown(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct rtw89_dev *rtwdev; + + if (!hw) + return; + + rtwdev = hw->priv; + set_bit(RTW89_FLAG_SHUTDOWN, rtwdev->flags); +} +EXPORT_SYMBOL(rtw89_pci_shutdown); + MODULE_AUTHOR("Realtek Corporation"); MODULE_DESCRIPTION("Realtek PCI 802.11ax wireless driver"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index c3f2d0df5846..cba52584ea68 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -58,7 +58,7 @@ #define B_AX_DIV GENMASK(15, 14) #define RAC_SET_PPR_V1 0x31 #define RAC_ANA40 0x40 -#define PHY_ERR_IMR_DIS (BIT(9) | BIT(8) | BIT(0)) +#define PHY_ERR_IMR_DIS (BIT(9) | BIT(2) | BIT(1) | BIT(0)) #define RAC_ANA41 0x41 #define PHY_ERR_FLAG_EN BIT(6) @@ -1022,7 +1022,7 @@ #define B_BE_PL1_IGNORE_HOT_RST BIT(30) #define B_BE_PL1_TIMER_UNIT_MASK GENMASK(19, 17) #define PCIE_SER_TIMER_UNIT 0x2 -#define PCIE_SER_WOW_TIMER_UNIT 0x4 +#define PCIE_SER_WOW_TIMER_UNIT 0x7 #define B_BE_PL1_TIMER_CLEAR BIT(0) #define R_BE_REG_PL1_MASK 0x34B0 @@ -1138,6 +1138,7 @@ #define RTW89_PCIE_GEN1_SPEED 0x01 #define RTW89_PCIE_GEN2_SPEED 0x02 #define RTW89_PCIE_PHY_RATE 0x82 +#define RTW89_PCIE_EXTENDED_SYNCH BIT(7) #define RTW89_PCIE_PHY_RATE_MASK GENMASK(1, 0) #define RTW89_PCIE_LINK_CHANGE_SPEED 0xA0 #define RTW89_PCIE_L1SS_STS_V1 0x0168 @@ -1751,6 +1752,7 @@ struct pci_device_id; int rtw89_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id); void rtw89_pci_remove(struct pci_dev *pdev); +void rtw89_pci_shutdown(struct pci_dev *pdev); void rtw89_pci_basic_cfg(struct rtw89_dev *rtwdev, bool resume); void rtw89_pci_ops_reset(struct rtw89_dev *rtwdev); int rtw89_pci_ltr_set(struct rtw89_dev *rtwdev, bool en); diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index 6390980b8ee0..d338bf633272 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -85,13 +85,22 @@ static void _patch_pcie_power_wake_be(struct rtw89_dev *rtwdev, bool power_up) static void _patch_pre_init_be(struct rtw89_dev *rtwdev) { + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; struct rtw89_hal *hal = &rtwdev->hal; + struct pci_dev *pdev = rtwpci->pdev; - if (!(rtwdev->chip->chip_id == RTL8922D && hal->cid == RTL8922D_CID7090)) + if (rtwdev->chip->chip_id != RTL8922D) return; + if (hal->cid != RTL8922D_CID7090) + goto set_ts1; + rtw89_write16_clr(rtwdev, R_RAC_DIRECT_OFFSET_BE_LANE0_G2 + RAC_ANA14 * RAC_MULT, EIEOS_L1SS_WAIT_CLKRDY); + +set_ts1: + pci_clear_and_set_config_dword(pdev, RTW89_PCIE_L1_STS_V1, + 0, RTW89_PCIE_EXTENDED_SYNCH); } static void rtw89_pci_set_io_rcy_be(struct rtw89_dev *rtwdev) diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 759be4dab42b..c78107e92206 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -3968,6 +3968,23 @@ void (* const rtw89_phy_c2h_rfk_log_handler[])(struct rtw89_dev *rtwdev, [RTW89_PHY_C2H_RFK_LOG_FUNC_CIM3K] = rtw89_phy_c2h_rfk_log_cim3k, }; +#define NAME_RTW89_RFK_REPORT(type) \ + [RTW89_RFK_REPORT_ ## type] = #type + +const char * const rtw89_rfk_report_names[] = { + NAME_RTW89_RFK_REPORT(PRE_NTFY), + NAME_RTW89_RFK_REPORT(TSSI), + NAME_RTW89_RFK_REPORT(IQK), + NAME_RTW89_RFK_REPORT(DPK), + NAME_RTW89_RFK_REPORT(TXGAPK), + NAME_RTW89_RFK_REPORT(DACK), + NAME_RTW89_RFK_REPORT(RX_DCK), + NAME_RTW89_RFK_REPORT(TX_IQK), + NAME_RTW89_RFK_REPORT(CIM3k), +}; + +static_assert(ARRAY_SIZE(rtw89_rfk_report_names) == NUM_OF_RTW89_RFK_REPORT_TYPES); + static void rtw89_phy_rfk_report_prep(struct rtw89_dev *rtwdev) { @@ -3979,8 +3996,8 @@ void rtw89_phy_rfk_report_prep(struct rtw89_dev *rtwdev) } static -int rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, - unsigned int ms) +int ____rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, + unsigned int ms) { struct rtw89_rfk_wait_info *wait = &rtwdev->rfk_wait; unsigned long time_left; @@ -4017,6 +4034,29 @@ int rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, return 0; } +static +int __rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, + enum rtw89_rfk_report_types rfk_type, + enum rtw89_phy_idx phy_idx, + const struct rtw89_chan *chan, + unsigned int ms) +{ + const char *rfk_name = rtw89_rfk_report_names[rfk_type]; + struct rtw89_rfk_wait_info *wait = &rtwdev->rfk_wait; + struct rtw89_rfk_record *record = wait->record_ptr; + int ret; + + ret = ____rtw89_phy_rfk_report_wait(rtwdev, rfk_name, ms); + + if (record) + record->states[rfk_type] = wait->state; + + return ret; +} + +#define rtw89_phy_rfk_report_wait(rtwdev, type, phy_idx, chan, ms) \ + __rtw89_phy_rfk_report_wait(rtwdev, RTW89_RFK_REPORT_ ## type, phy_idx, chan, ms) + static void rtw89_phy_c2h_rfk_report_state(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) { @@ -4122,7 +4162,7 @@ int rtw89_phy_rfk_pre_ntfy_and_wait(struct rtw89_dev *rtwdev, if (RTW89_CHK_FW_FEATURE_GROUP(WITH_RFK_PRE_NOTIFY, &rtwdev->fw)) { rtw89_phy_rfk_report_prep(rtwdev); rtw89_fw_h2c_rf_pre_ntfy(rtwdev, phy_idx); - ret = rtw89_phy_rfk_report_wait(rtwdev, "PRE_NTFY", ms); + ret = rtw89_phy_rfk_report_wait(rtwdev, PRE_NTFY, phy_idx, NULL, ms); if (ret) return ret; } @@ -4152,7 +4192,7 @@ int rtw89_phy_rfk_tssi_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TSSI", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TSSI, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_tssi_and_wait); @@ -4169,7 +4209,7 @@ int rtw89_phy_rfk_iqk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "IQK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, IQK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_iqk_and_wait); @@ -4186,7 +4226,7 @@ int rtw89_phy_rfk_dpk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "DPK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, DPK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_dpk_and_wait); @@ -4203,7 +4243,7 @@ int rtw89_phy_rfk_txgapk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TXGAPK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TXGAPK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_txgapk_and_wait); @@ -4220,7 +4260,7 @@ int rtw89_phy_rfk_dack_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "DACK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, DACK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_dack_and_wait); @@ -4237,7 +4277,7 @@ int rtw89_phy_rfk_rxdck_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "RX_DCK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, RX_DCK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_rxdck_and_wait); @@ -4254,7 +4294,7 @@ int rtw89_phy_rfk_txiqk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TX_IQK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TX_IQK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_txiqk_and_wait); @@ -4271,7 +4311,7 @@ int rtw89_phy_rfk_cim3k_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "CIM3k", ms); + return rtw89_phy_rfk_report_wait(rtwdev, CIM3k, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_cim3k_and_wait); @@ -5718,6 +5758,32 @@ static void rtw89_phy_antdiv_init(struct rtw89_dev *rtwdev) rtw89_phy_antdiv_reg_init(rtwdev); } +static void rtw89_phy_thermal_protect_vcore(struct rtw89_dev *rtwdev) +{ + struct rtw89_phy_stat *phystat = &rtwdev->phystat; + struct rtw89_hal *hal = &rtwdev->hal; + bool vcore_enabled = !!hal->thermal_prot_vmax; + u8 th_max = phystat->last_thermal_max; + u8 vlv = hal->thermal_prot_vlv; + u8 prot_th; + + if (!vcore_enabled || (hal->disabled_dm_bitmap & BIT(RTW89_DM_VCORE))) + return; + + prot_th = hal->thermal_prot_th - RTW89_THERMAL_PROT_VLV_TH_OFFSET; + if (th_max > prot_th && vlv < RTW89_THERMAL_PROT_VLV_MAX) + vlv++; + else if (th_max < prot_th - 2 && vlv > 0) + vlv--; + else + return; + + rtw89_debug(rtwdev, RTW89_DBG_RFK_TRACK, "thermal protection vlv=%d\n", vlv); + + hal->thermal_prot_vlv = vlv; + rtw89_mac_set_vcore_cfg(rtwdev, vlv); +} + static void rtw89_phy_thermal_protect(struct rtw89_dev *rtwdev) { struct rtw89_phy_stat *phystat = &rtwdev->phystat; @@ -5842,6 +5908,9 @@ static void rtw89_phy_stat_init(struct rtw89_dev *rtwdev) memset(&bb->last_pkt_stat, 0, sizeof(bb->last_pkt_stat)); ewma_rssi_init(&bb->bcn_rssi); + bb->path_diff.raw = 0; + ewma_path_diff_init(&bb->path_diff.avg); + bb->path_diff.bf_smo_en = false; } rtwdev->hal.thermal_prot_lv = 0; @@ -6183,6 +6252,7 @@ void rtw89_phy_stat_track(struct rtw89_dev *rtwdev) struct rtw89_bb_ctx *bb; rtw89_phy_stat_thermal_update(rtwdev); + rtw89_phy_thermal_protect_vcore(rtwdev); rtw89_phy_thermal_protect(rtwdev); rtw89_phy_stat_rssi_update(rtwdev); rtw89_phy_stat_update(rtwdev); @@ -6190,6 +6260,8 @@ void rtw89_phy_stat_track(struct rtw89_dev *rtwdev) rtw89_for_each_active_bb(rtwdev, bb) { bb->last_pkt_stat = bb->cur_pkt_stat; memset(&bb->cur_pkt_stat, 0, sizeof(bb->cur_pkt_stat)); + + rtw89_chip_path_diff_update(rtwdev, bb); } } @@ -8978,6 +9050,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_ax = { .physts = &rtw89_physts_regs_ax, .cfo = &rtw89_cfo_regs_ax, .bb_wrap = NULL, + .nctl = NULL, .phy0_phy1_offset = rtw89_phy0_phy1_offset_ax, .config_bb_gain = rtw89_phy_config_bb_gain_ax, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_ax, diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index 532232892831..0963cdfb1594 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -353,25 +353,6 @@ struct rtw89_txpwr_byrate_cfg { u32 data; }; -struct rtw89_txpwr_track_cfg { - const s8 (*delta_swingidx_6gb_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6gb_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6ga_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6ga_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5gb_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5gb_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5ga_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5ga_p)[DELTA_SWINGIDX_SIZE]; - const s8 *delta_swingidx_2gb_n; - const s8 *delta_swingidx_2gb_p; - const s8 *delta_swingidx_2ga_n; - const s8 *delta_swingidx_2ga_p; - const s8 *delta_swingidx_2g_cck_b_n; - const s8 *delta_swingidx_2g_cck_b_p; - const s8 *delta_swingidx_2g_cck_a_n; - const s8 *delta_swingidx_2g_cck_a_p; -}; - struct rtw89_phy_dig_gain_cfg { const struct rtw89_reg_def *table; u8 size; @@ -502,6 +483,11 @@ struct rtw89_bb_wrap_regs { u32 pwr_macid_path; }; +struct rtw89_nctl_regs { + u32 cfg; + u32 rw; +}; + enum rtw89_bandwidth_section_num_ax { RTW89_BW20_SEC_NUM_AX = 8, RTW89_BW40_SEC_NUM_AX = 4, @@ -679,6 +665,7 @@ struct rtw89_phy_gen_def { const struct rtw89_physts_regs *physts; const struct rtw89_cfo_regs *cfo; const struct rtw89_bb_wrap_regs *bb_wrap; + const struct rtw89_nctl_regs *nctl; u32 (*phy0_phy1_offset)(struct rtw89_dev *rtwdev, u32 addr); void (*config_bb_gain)(struct rtw89_dev *rtwdev, const struct rtw89_reg2_def *reg, @@ -1163,6 +1150,9 @@ void rtw89_phy_rate_pattern_vif(struct rtw89_dev *rtwdev, bool rtw89_phy_c2h_chk_atomic(struct rtw89_dev *rtwdev, u8 class, u8 func); void rtw89_phy_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, u32 len, u8 class, u8 func); + +extern const char * const rtw89_rfk_report_names[]; + int rtw89_phy_rfk_pre_ntfy_and_wait(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, unsigned int ms); diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c index 99263355e2f1..c06927d1dc78 100644 --- a/drivers/net/wireless/realtek/rtw89/phy_be.c +++ b/drivers/net/wireless/realtek/rtw89/phy_be.c @@ -225,6 +225,16 @@ static const struct rtw89_bb_wrap_regs rtw89_bb_wrap_regs_be_v1 = { .pwr_macid_path = R_BE_PWR_MACID_PATH_BASE_V1, }; +static const struct rtw89_nctl_regs rtw89_nctl_regs_be = { + .cfg = R_NCTL_CFG, + .rw = R_NCTL_RW, +}; + +static const struct rtw89_nctl_regs rtw89_nctl_regs_be_v1 = { + .cfg = R_NCTL_CFG_BE4, + .rw = R_NCTL_RW_BE4, +}; + static u32 rtw89_phy0_phy1_offset_be(struct rtw89_dev *rtwdev, u32 addr) { u32 phy_page = addr >> 8; @@ -440,6 +450,28 @@ static void rtw89_phy_config_bb_gain_be(struct rtw89_dev *rtwdev, } } +static void rtw89_phy_preinit_rf_nctl_check_be(struct rtw89_dev *rtwdev) +{ + const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; + const struct rtw89_nctl_regs *nctl = phy->nctl; + int poll; + u32 val; + + rtw89_phy_write32(rtwdev, nctl->cfg, B_NCTL_CHK_EN); + + for (poll = 0; poll < 10000; poll++) { + rtw89_phy_write32(rtwdev, nctl->rw, B_NCTL_CHECK); + + fsleep(10); + + val = rtw89_phy_read32(rtwdev, nctl->rw); + if (val == B_NCTL_CHECK) + return; + } + + rtw89_warn(rtwdev, "NCTL INIT check 0x%x[2]TIMEOUT!\n", nctl->rw); +} + static void rtw89_phy_preinit_rf_nctl_be(struct rtw89_dev *rtwdev) { rtw89_phy_write32_mask(rtwdev, R_GOTX_IQKDPK_C0, B_GOTX_IQKDPK, 0x3); @@ -456,6 +488,11 @@ static void rtw89_phy_preinit_rf_nctl_be(struct rtw89_dev *rtwdev) rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_RST_C1, B_IQK_DPK_RST, 0x1); rtw89_phy_write32_mask(rtwdev, R_TXRFC_C1, B_TXRFC_RST, 0x1); } + + rtw89_phy_write32_mask(rtwdev, R_TX_COLLISION_T2R_ST, B_RX_CFIR_PATH_EN, 0x1); + rtw89_phy_write32_mask(rtwdev, R_TX_COLLISION_T2R_ST, B_RX_CFIR_PATH, 0x3); + + rtw89_phy_preinit_rf_nctl_check_be(rtwdev); } static void rtw89_phy_preinit_rf_nctl_be_v1(struct rtw89_dev *rtwdev) @@ -466,6 +503,8 @@ static void rtw89_phy_preinit_rf_nctl_be_v1(struct rtw89_dev *rtwdev) rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_RST_BE4, B_IQK_DPK_RST, 0x1); rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_PRST_BE4, B_IQK_DPK_PRST, 0x1); rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_PRST_C1_BE4, B_IQK_DPK_PRST, 0x1); + + rtw89_phy_preinit_rf_nctl_check_be(rtwdev); } static u32 rtw89_phy_bb_wrap_flush_addr(struct rtw89_dev *rtwdev, u32 addr) @@ -623,7 +662,7 @@ static u32 rtw89_phy_bb_wrap_be_bandedge_decision(struct rtw89_dev *rtwdev, case RTW89_BAND_2G: if (pri_ch == 1 || pri_ch == 13) val = BIT(1) | BIT(0); - else if (pri_ch == 3 || pri_ch == 11) + else if (pri_ch == 5 || pri_ch == 9) val = BIT(1); break; case RTW89_BAND_5G: @@ -637,8 +676,6 @@ static u32 rtw89_phy_bb_wrap_be_bandedge_decision(struct rtw89_dev *rtwdev, val = BIT(3); break; case RTW89_BAND_6G: - if (pri_ch == 233) - val = BIT(0); break; } @@ -670,13 +707,19 @@ void rtw89_phy_bb_wrap_set_rfsi_bandedge_ch(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { + bool cfr_manual_en = false; u32 reg; u32 val; val = rtw89_phy_bb_wrap_be_bandedge_decision(rtwdev, chan); + if (chan->band_type == RTW89_BAND_2G && + ((chan->primary_channel == 13 && chan->band_width == RTW89_CHANNEL_WIDTH_20) || + (chan->primary_channel == 11 && chan->band_width == RTW89_CHANNEL_WIDTH_40))) + cfr_manual_en = true; + rtw89_phy_write32_idx(rtwdev, R_TX_CFR_MANUAL_EN_BE4, B_TX_CFR_MANUAL_EN_BE4_M, - chan->primary_channel == 13, phy_idx); + cfr_manual_en, phy_idx); reg = rtw89_mac_reg_by_idx(rtwdev, R_BANDEDGE_DBWX_BE4, phy_idx); rtw89_write32_mask(rtwdev, reg, B_BANDEDGE_DBW20_BE4, val & BIT(0)); @@ -1907,6 +1950,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be = { .physts = &rtw89_physts_regs_be, .cfo = &rtw89_cfo_regs_be, .bb_wrap = &rtw89_bb_wrap_regs_be, + .nctl = &rtw89_nctl_regs_be, .phy0_phy1_offset = rtw89_phy0_phy1_offset_be, .config_bb_gain = rtw89_phy_config_bb_gain_be, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_be, @@ -1932,6 +1976,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be_v1 = { .physts = &rtw89_physts_regs_be_v1, .cfo = &rtw89_cfo_regs_be_v1, .bb_wrap = &rtw89_bb_wrap_regs_be_v1, + .nctl = &rtw89_nctl_regs_be_v1, .phy0_phy1_offset = rtw89_phy0_phy1_offset_be_v1, .config_bb_gain = rtw89_phy_config_bb_gain_be, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_be_v1, diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 086ef77ebb9f..abeeb61007dc 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -205,6 +205,8 @@ #define MAC_AX_HCI_SEL_PCIE_USB 3 #define MAC_AX_HCI_SEL_MULTI_SDIO 4 +#define R_AX_GPIO_16_TO_18_EXT_CTRL 0x0150 + #define R_AX_HALT_H2C_CTRL 0x0160 #define R_AX_HALT_H2C 0x0168 #define B_AX_HALT_H2C_TRIGGER BIT(0) @@ -3346,7 +3348,7 @@ #define DEFAULT_AX_RX_FLTR (B_AX_A_A1_MATCH | B_AX_A_BC | B_AX_A_MC | \ B_AX_A_UC_CAM_MATCH | B_AX_A_BC_CAM_MATCH | \ B_AX_A_PWR_MGNT | B_AX_A_FTM_REQ | \ - u32_encode_bits(3, B_AX_UID_FILTER_MASK) | \ + FIELD_PREP_CONST(B_AX_UID_FILTER_MASK, 3) | \ B_AX_A_BCN_CHK_EN) #define B_AX_RX_FLTR_CFG_MASK ((u32)~B_AX_RX_MPDU_MAX_LEN_MASK) @@ -4573,6 +4575,23 @@ #define R_BE_UDM2 0x01F8 #define B_BE_UDM2_EPC_RA_MASK GENMASK(31, 0) +#define R_BE_SPS_DIG_ON_CTRL0 0x0200 +#define B_BE_PFMCMP_IQ BIT(31) +#define B_BE_FREQ_MASK GENMASK(30, 27) +#define B_BE_OFF_END_SEL BIT(26) +#define B_BE_POW_MINOFF_L BIT(25) +#define B_BE_REG_BYPASS_L BIT(24) +#define B_BE_VREFPFM_L_MASK GENMASK(23, 19) +#define B_BE_REG_ZCDC_H_MASK GENMASK(18, 17) +#define B_BE_FORCE_ZCD_BIAS BIT(16) +#define B_BE_ZCD_SDZ_L_MASK GENMASK(15, 14) +#define B_BE_ISAW_MASK GENMASK(13, 10) +#define B_BE_SS_DIVSEL_MASK GENMASK(9, 8) +#define B_BE_REG_BG_H BIT(7) +#define B_BE_FPWM_L1 BIT(6) +#define B_BE_POW_ZCD_L BIT(5) +#define B_BE_PWMTUNE_MASK GENMASK(4, 0) + #define R_BE_SPS_DIG_ON_CTRL1 0x0204 #define B_BE_SN_N_L_MASK GENMASK(31, 28) #define B_BE_SP_N_L_MASK GENMASK(27, 24) @@ -6858,6 +6877,34 @@ #define B_BE_MUEDCA_EN_MASK GENMASK(1, 0) #define B_BE_MUEDCA_EN_0 BIT(0) +#define R_BE_MISC_1 0x1037C +#define R_BE_MISC_1_C1 0x1437C +#define B_BE_PPS_REMAIN_TIME_MODE BIT(31) +#define B_BE_PPS_IDLE_SORT_EN BIT(30) +#define B_BE_SR_TXOP_USE_SR_PERIOD_EN BIT(29) +#define B_BE_SCH_CCA_PIFS_CLK_GATING_DIS BIT(28) +#define B_BE_SR_TXOP_EN BIT(27) +#define B_BE_SCH_ABORT_CNT_SIFS_EN BIT(26) +#define B_BE_SCH_ABORT_CNT_TB_EN BIT(25) +#define B_BE_SCH_ABORT_CNT_CTN_EN BIT(24) +#define B_BE_SR_CCA_PER20_BITMAP_EN BIT(23) +#define B_BE_SR_CCA_S80_EN BIT(22) +#define B_BE_SR_CCA_S40_EN BIT(21) +#define B_BE_SR_CCA_S20_EN BIT(20) +#define B_BE_EN_TX_FINISH_PRD_RESP BIT(18) +#define B_BE_RESP_TX_ABORT_NON_IDLE BIT(17) +#define B_BE_RESP_TX_ABORT_QUICK_EN BIT(16) +#define B_BE_PREBKF_CHK_LINK_BUSY BIT(15) +#define B_BE_SCH_MSD_PRD_RST_EDCA_EN BIT(14) +#define B_BE_LINK_BUSY_RST_EDCA_EN_MASK GENMASK(13, 12) +#define B_BE_RX_TSFT_SYNC_BYPASS_FCS BIT(11) +#define B_BE_RX_TSFT_DIFF_THD_MASK GENMASK(10, 8) +#define B_BE_CAL_TBTT_OV_EN BIT(5) +#define B_BE_SUBBCN_MS_CNT_MODE BIT(3) +#define B_BE_CAL_ALWAYS_EN BIT(2) +#define B_BE_SIFS_TIMER_AUTO_RST_EN BIT(1) +#define B_BE_CHK_HAS_SIFS_TX_ABORT BIT(0) + #define R_BE_CTN_DRV_TXEN 0x10398 #define R_BE_CTN_DRV_TXEN_C1 0x14398 #define B_BE_CTN_TXEN_TWT_3 BIT(17) @@ -8162,6 +8209,10 @@ #define B_BE_A_BC BIT(2) #define B_BE_A_A1_MATCH BIT(1) #define B_BE_SNIFFER_MODE BIT(0) +#define DEFAULT_BE_RX_FLTR (B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | \ + B_BE_A_MC | B_BE_A_BC | B_BE_A_A1_MATCH | \ + FIELD_PREP_CONST(B_BE_UID_FILTER_MASK, 15) | \ + B_BE_A_FTM_REQ | B_BE_A_BCN_CHK_EN) #define R_BE_CTRL_FLTR 0x11424 #define R_BE_CTRL_FLTR_C1 0x15424 @@ -8718,6 +8769,7 @@ #define RR_VCO 0xb2 #define RR_VCO_VAL GENMASK(18, 14) #define RR_VCO_SEL GENMASK(9, 8) +#define RR_VCO_STS GENMASK(7, 0) #define RR_VCI 0xb3 #define RR_VCI_ON BIT(7) #define RR_LPF 0xb7 @@ -8977,6 +9029,8 @@ #define B_IQK_DPK_RST BIT(0) #define R_TX_COLLISION_T2R_ST 0x0C70 #define B_TX_COLLISION_T2R_ST_M GENMASK(25, 20) +#define B_RX_CFIR_PATH_EN BIT(9) +#define B_RX_CFIR_PATH GENMASK(6, 5) #define B_TXRX_FORCE_VAL GENMASK(9, 0) #define R_TXGATING 0x0C74 #define B_TXGATING_EN BIT(4) @@ -10070,6 +10124,9 @@ #define R_S1_DACKQ8 0x7E98 #define B_S1_DACKQ8_K GENMASK(15, 8) #define R_NCTL_CFG 0x8000 +#define R_NCTL_CFG_BE4 0x38000 +#define B_NCTL_CFG_BE4_SPAGE GENMASK(25, 24) +#define B_NCTL_CHK_EN BIT(3) #define B_NCTL_CFG_SPAGE GENMASK(2, 1) #define R_NCTL_RPT 0x8008 #define B_NCTL_RPT_FLG BIT(26) @@ -10103,6 +10160,8 @@ #define R_KIP_MOD 0x8078 #define B_KIP_MOD GENMASK(19, 0) #define R_NCTL_RW 0x8080 +#define R_NCTL_RW_BE4 0x38080 +#define B_NCTL_CHECK BIT(2) #define R_KIP_SYSCFG 0x8088 #define R_KIP_CLK 0x808C #define R_DPK_IDL 0x809C @@ -10754,6 +10813,7 @@ #define B_SYS_DBCC_24G_BAND_SEL_BE4 BIT(1) #define R_EMLSR_SWITCH_BE4 0x20044 #define B_EMLSR_SWITCH_BE4 GENMASK(27, 12) +#define B_EMLSR_CLR_FORCE_BE4 GENMASK(20, 19) #define B_EMLSR_BB_CLK_BE4 GENMASK(31, 30) #define R_CHINFO_SEG_BE4 0x200B4 #define B_CHINFO_SEG_LEN_BE4 GENMASK(12, 10) @@ -10794,6 +10854,13 @@ #define B_TXINFO_PATH_MB_BE4 BIT(19) #define R_SHAPER_COEFF_BE4 0x20CBC #define B_SHAPER_COEFF_BE4 BIT(19) + +#define R_CL_MODE_CNT_BE4 0x20DE0 +#define B_CL_MODE_NDP_CNT_PHY0_BE4 GENMASK(31, 24) +#define B_CL_MODE_NDP_CNT_PHY1_BE4 GENMASK(15, 8) +#define B_CL_MODE_CL_CNT_PHY0_BE4 GENMASK(23, 16) +#define B_CL_MODE_CL_CNT_PHY1_BE4 GENMASK(7, 0) + #define R_IFS_T1_AVG_BE4 0x20EDC #define B_IFS_T1_AVG_BE4 GENMASK(15, 0) #define B_IFS_T2_AVG_BE4 GENMASK(31, 16) @@ -10912,6 +10979,14 @@ #define R_GAIN_BIAS_BE4 0x260A0 #define B_GAIN_BIAS_BW20_BE4 GENMASK(11, 6) #define B_GAIN_BIAS_BW40_BE4 GENMASK(17, 12) + +#define R_BF_SMO_PDP_LMT_EHT_BE4 0x26568 +#define B_BF_SMO_PDP_LMT_EHT_BE4 BIT(30) +#define R_BF_SMO_PDP_LMT_HE_BE4 0x26568 +#define B_BF_SMO_PDP_LMT_HE_BE4 BIT(31) +#define R_BF_SMO_PDP_LMT_VHT_BE4 0x2656C +#define B_BF_SMO_PDP_LMT_VHT_BE4 BIT(31) + #define R_AWGN_DET_BE4 0x2668C #define B_AWGN_DET_BE4 GENMASK(17, 9) #define R_CSI_WGT_BE4 0x26770 @@ -10941,6 +11016,11 @@ #define R_RX_LDPC02_BE4 0x26834 #define B_RX_LDPC10_BE4 GENMASK(17, 12) #define B_RX_LDPC11_BE4 GENMASK(23, 18) +#define B_RX_LDPC12_BE4 GENMASK(29, 24) +#define R_RX_LDPC03_BE4 0x26838 +#define B_RX_LDPC13_BE4 GENMASK(5, 0) +#define B_RX_LDPC02_BE4 GENMASK(23, 18) +#define B_RX_LDPC03_BE4 GENMASK(29, 24) #define R_RX_LDPC00_BE4 0x2683C #define B_RX_LDPC04_BE4 GENMASK(5, 0) #define B_RX_LDPC05_BE4 GENMASK(11, 6) @@ -10953,6 +11033,15 @@ #define R_BSS_CLR_VLD_BE4 0x26920 #define B_BSS_CLR_VLD_BE4 BIT(2) +#define R_CL_MODE_TRIG_BE4 0x26F44 +#define B_CL_MODE_TRIG_BE4 BIT(31) +#define R_SELECTED_TONE_IDX_BE4 0x26F4C +#define B_SELECTED_TONE_IDX_BE4 GENMASK(11, 0) +#define R_OS_TRIG_BY_SW_BE4 0x26F50 +#define B_OS_TRIG_BY_SW_BE4 BIT(30) +#define R_OS_TRIG_SOURCE_BE4 0x26F6C +#define B_OS_TRIG_SOURCE_BE4 BIT(0) + #define R_SW_SI_DATA_BE4 0x2CF4C #define B_SW_SI_READ_DATA_BE4 GENMASK(19, 0) #define B_SW_SI_W_BUSY_BE4 BIT(24) @@ -10977,6 +11066,15 @@ #define B_KTBL0_MLD_IDX0 GENMASK(25, 24) #define B_KTBL0_MLD_IDX1 GENMASK(27, 26) #define B_KTBL0_RST BIT(31) +#define R_CFIR_CTRL_A_BE4 0x38124 +#define R_CFIR_CTRL_B_BE4 0x38224 +#define B_CFIR_CTRL_KIDX0_EN BIT(8) +#define B_CFIR_CTRL_KIDX1_EN BIT(24) +#define R_TX_IQC_A_BE4 0x38138 +#define R_TX_IQC_B_BE4 0x38238 +#define B_TX_IQC_X GENMASK(31, 20) +#define B_TX_IQC_Y GENMASK(19, 8) +#define B_TX_IQC_DPK BIT(3) #define R_KTBL1A_BE4 0x38154 #define R_KTBL1B_BE4 0x38254 #define B_KTBL1_TBL0 BIT(3) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 60f362593696..db37ad6653bc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -355,7 +355,7 @@ static const struct rtw89_pmac_regs rtw8851b_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8851b_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -367,7 +367,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8851b_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -1918,11 +1918,9 @@ static void rtw8851b_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8851b_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); @@ -2120,82 +2118,43 @@ static u8 rtw8851b_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8851b_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; - md->md_v7.ant.isolation = 10; - md->md_v7.kt_ver_adie = rtwdev->hal.acv; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->ant.isolation = 10; + md->kt_ver_adie = rtwdev->hal.acv; - if (md->md_v7.rfe_type == 0) - return; + if (md->rfe_type == 0) + return; - /* rfe_type 3*n+1: 1-Ant(shared), - * 3*n+2: 2-Ant+Div(non-shared), - * 3*n+3: 2-Ant+no-Div(non-shared) - */ - md->md_v7.ant.num = (md->md_v7.rfe_type % 3 == 1) ? 1 : 2; - /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ - md->md_v7.ant.single_pos = RF_PATH_A; - md->md_v7.ant.btg_pos = RF_PATH_A; - md->md_v7.ant.stream_cnt = 1; + /* rfe_type 3*n+1: 1-Ant(shared), + * 3*n+2: 2-Ant+Div(non-shared), + * 3*n+3: 2-Ant+no-Div(non-shared) + */ + md->ant.num = (md->rfe_type % 3 == 1) ? 1 : 2; + /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ + md->ant.single_pos = RF_PATH_A; + md->ant.btg_pos = RF_PATH_A; + md->ant.stream_cnt = 1; - if (md->md_v7.ant.num == 1) { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.wa_type = 1; - md->md_v7.ant.diversity = 0; - } else { /* ant.num == 2 */ - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - md->md_v7.switch_type = BTC_SWITCH_EXTERNAL; - md->md_v7.wa_type = 0; - if (md->md_v7.rfe_type % 3 == 2) - md->md_v7.ant.diversity = 1; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; - } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - md->md.ant.isolation = 10; - md->md.kt_ver_adie = rtwdev->hal.acv; - - if (md->md.rfe_type == 0) - return; - - /* rfe_type 3*n+1: 1-Ant(shared), - * 3*n+2: 2-Ant+Div(non-shared), - * 3*n+3: 2-Ant+no-Div(non-shared) - */ - md->md.ant.num = (md->md.rfe_type % 3 == 1) ? 1 : 2; - /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ - md->md.ant.single_pos = RF_PATH_A; - md->md.ant.btg_pos = RF_PATH_A; - md->md.ant.stream_cnt = 1; - - if (md->md.ant.num == 1) { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - md->md.wa_type = 1; - md->md.ant.diversity = 0; - } else { /* ant.num == 2 */ - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - md->md.switch_type = BTC_SWITCH_EXTERNAL; - md->md.wa_type = 0; - if (md->md.rfe_type % 3 == 2) - md->md.ant.diversity = 1; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + if (md->ant.num == 1) { + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->wa_type = 1; + md->ant.diversity = 0; + } else { /* ant.num == 2 */ + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_EXTERNAL; + md->wa_type = 0; + if (md->rfe_type % 3 == 2) + md->ant.diversity = 1; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static @@ -2219,9 +2178,8 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) }; const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - union rtw89_btc_module_info *md = &btc->mdinfo; - const struct rtw89_btc_ver *ver = btc->ver; - u8 path, path_min, path_max, str_cnt, ant_sing_pos; + struct rtw89_btc_module *md = &btc->mdinfo; + u8 path, path_min, path_max; /* PTA init */ rtw89_mac_coex_init(rtwdev, &coex_params); @@ -2230,17 +2188,9 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) chip->ops->btc_set_wl_pri(rtwdev, BTC_PRI_MASK_TX_RESP, true); chip->ops->btc_set_wl_pri(rtwdev, BTC_PRI_MASK_BEACON, true); - if (ver->fcxinit == 7) { - str_cnt = md->md_v7.ant.stream_cnt; - ant_sing_pos = md->md_v7.ant.single_pos; - } else { - str_cnt = md->md.ant.stream_cnt; - ant_sing_pos = md->md.ant.single_pos; - } - /* for 1-Ant && 1-ss case: only 1-path */ - if (str_cnt == 1) { - path_min = ant_sing_pos; + if (md->ant.stream_cnt == 1) { + path_min = md->ant.single_pos; path_max = path_min; } else { path_min = RF_PATH_A; @@ -2277,8 +2227,6 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - - btc->cx.wl.status.map.init_ok = true; } static @@ -2580,6 +2528,7 @@ static const struct rtw89_chip_ops rtw8851b_chip_ops = { .set_txpwr_ul_tb_offset = rtw8851b_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8851b_pwr_on_func, .pwr_off_func = rtw8851b_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, @@ -2636,7 +2585,8 @@ const struct rtw89_chip_info rtw8851b_chip_info = { }, .try_ce_fw = true, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8851B | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = NULL, .fifo_size = 196608, .small_fifo_size = true, @@ -2725,15 +2675,16 @@ const struct rtw89_chip_info rtw8851b_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8851b_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8851b_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8851b_mon_reg), .mon_reg = rtw89_btc_8851b_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8851b_rf_ul), - .rf_para_ulink = rtw89_btc_8851b_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8851b_rf_dl), - .rf_para_dlink = rtw89_btc_8851b_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8851b_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8851b_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8851b_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8851b_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -2779,6 +2730,7 @@ const struct rtw89_chip_info rtw8851b_chip_info = { #endif .xtal_info = &rtw8851b_xtal_info, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8851b_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c index e574a9950a3b..c63f0e6364da 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c @@ -1874,7 +1874,7 @@ static void _dpk_onoff(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, 0xf0000000, val); rtw89_debug(rtwdev, RTW89_DBG_RFK, "[DPK] S%d[%d] DPK %s !!!\n", path, - kidx, val == 0 ? "disable" : "enable"); + kidx, str_enable_disable(val)); } static void _dpk_init(struct rtw89_dev *rtwdev, enum rtw89_rf_path path) @@ -2823,6 +2823,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2833,23 +2834,26 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8851b_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_2ga_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c index a9c309c245c3..f875beaec555 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2022-2023 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8851b_table.h" @@ -14866,13 +14867,13 @@ const struct rtw89_txpwr_table rtw89_8851b_byr_table_type2 = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8851b_trk_cfg = { - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8851b_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8851b_dflt_parms = { @@ -14886,8 +14887,8 @@ const struct rtw89_rfe_parms rtw89_8851b_dflt_parms = { .lmt_ru = &rtw89_8851b_txpwr_lmt_ru_5g, }, .tx_shape = { - .lmt = &rtw89_8851b_tx_shape_lmt, - .lmt_ru = &rtw89_8851b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8851b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8851b_tx_shape_lmt_ru, }, }; @@ -14902,8 +14903,8 @@ static const struct rtw89_rfe_parms rtw89_8851b_rfe_parms_type2 = { .lmt_ru = &rtw89_8851b_txpwr_lmt_ru_5g_type2, }, .tx_shape = { - .lmt = &rtw89_8851b_tx_shape_lmt, - .lmt_ru = &rtw89_8851b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8851b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8851b_tx_shape_lmt_ru, }, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h index d8cf545d40a0..a73c0a6f03df 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h @@ -11,7 +11,7 @@ extern const struct rtw89_phy_table rtw89_8851b_phy_bb_table; extern const struct rtw89_phy_table rtw89_8851b_phy_bb_gain_table; extern const struct rtw89_phy_table rtw89_8851b_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8851b_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8851b_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8851b_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8851b_dflt_parms; extern const struct rtw89_rfe_parms_conf rtw89_8851b_rfe_parms_conf[]; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index 640672eb0d26..81c62296c32d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { static const struct rtw89_driver_info rtw89_8851be_info = { .chip = &rtw8851b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -93,6 +94,7 @@ static struct pci_driver rtw89_8851be_driver = { .id_table = rtw89_8851be_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c index 34ba5661d771..1e827205f254 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c @@ -30,6 +30,7 @@ static const struct rtw89_usb_info rtw8851b_usb_info = { static const struct rtw89_driver_info rtw89_8851bu_info = { .chip = &rtw8851b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 2c1f166e687f..5d7b5d1a24b1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -1827,56 +1827,30 @@ static u8 rtw8852a_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8852a_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static @@ -1945,7 +1919,6 @@ static void rtw8852a_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - btc->cx.wl.status.map.init_ok = true; } static @@ -2049,7 +2022,7 @@ s8 rtw8852a_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val + 6, -100, 0) + 100; } -static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_ul[] = { +static struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852a_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2061,7 +2034,7 @@ static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_ul[] = { {13, 1, 0, 7} }; -static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_dl[] = { +static struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852a_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2105,12 +2078,12 @@ void rtw8852a_btc_update_bt_cnt(struct rtw89_dev *rtwdev) return; val = rtw89_read32(rtwdev, R_AX_BT_STAST_HIGH); - cx->cnt_bt[BTC_BCNT_HIPRI_TX] = FIELD_GET(B_AX_STATIS_BT_HI_TX_MASK, val); - cx->cnt_bt[BTC_BCNT_HIPRI_RX] = FIELD_GET(B_AX_STATIS_BT_HI_RX_MASK, val); + cx->bt0.bcnt[BTC_BCNT_HIPRI_TX] = FIELD_GET(B_AX_STATIS_BT_HI_TX_MASK, val); + cx->bt0.bcnt[BTC_BCNT_HIPRI_RX] = FIELD_GET(B_AX_STATIS_BT_HI_RX_MASK, val); val = rtw89_read32(rtwdev, R_AX_BT_STAST_LOW); - cx->cnt_bt[BTC_BCNT_LOPRI_TX] = FIELD_GET(B_AX_STATIS_BT_LO_TX_1_MASK, val); - cx->cnt_bt[BTC_BCNT_LOPRI_RX] = FIELD_GET(B_AX_STATIS_BT_LO_RX_1_MASK, val); + cx->bt0.bcnt[BTC_BCNT_LOPRI_TX] = FIELD_GET(B_AX_STATIS_BT_LO_TX_1_MASK, val); + cx->bt0.bcnt[BTC_BCNT_LOPRI_RX] = FIELD_GET(B_AX_STATIS_BT_LO_RX_1_MASK, val); /* clock-gate off before reset counter*/ rtw89_write32_set(rtwdev, R_AX_BTC_CFG, B_AX_DIS_BTC_CLK_G); @@ -2212,7 +2185,7 @@ static void rtw8852a_query_ppdu(struct rtw89_dev *rtwdev, u8 raw; if (!status->signal) { - if (phy_ppdu->to_self) + if (phy_ppdu->to_self && ewma_rssi_read(&bb->bcn_rssi)) raw = ewma_rssi_read(&bb->bcn_rssi); else raw = max(rx_power[RF_PATH_A], rx_power[RF_PATH_B]); @@ -2326,6 +2299,7 @@ static const struct rtw89_chip_ops rtw8852a_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852a_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = NULL, .pwr_off_func = NULL, .query_rxdesc = rtw89_core_query_rxdesc, @@ -2373,7 +2347,8 @@ const struct rtw89_chip_info rtw8852a_chip_info = { }, .try_ce_fw = false, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8852A | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = NULL, .fifo_size = 458752, .small_fifo_size = false, @@ -2463,15 +2438,16 @@ const struct rtw89_chip_info rtw8852a_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852a_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852a_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852a_mon_reg), .mon_reg = rtw89_btc_8852a_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852a_rf_ul), - .rf_para_ulink = rtw89_btc_8852a_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852a_rf_dl), - .rf_para_dlink = rtw89_btc_8852a_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852a_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852a_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852a_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852a_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -2516,6 +2492,7 @@ const struct rtw89_chip_info rtw8852a_chip_info = { #endif .xtal_info = &rtw8852a_xtal_info, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852a_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c index 8679b21fd3fd..609cc300f24e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c @@ -2910,6 +2910,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2922,31 +2923,34 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852a_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c index ffdeb3801991..f1bcef64a91f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2020 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852a_table.h" @@ -50983,19 +50984,19 @@ const struct rtw89_txpwr_table rtw89_8852a_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852a_trk_cfg = { - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852a_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8852a_dflt_parms = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h index 58fe8575c1c9..501a8248d1ce 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h @@ -11,7 +11,7 @@ extern const struct rtw89_phy_table rtw89_8852a_phy_bb_table; extern const struct rtw89_phy_table rtw89_8852a_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852a_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852a_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852a_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852a_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852a_dflt_parms; #endif diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index 64306cdc1ee4..3defb0748648 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -70,6 +70,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { static const struct rtw89_driver_info rtw89_8852ae_info = { .chip = &rtw8852a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -95,6 +96,7 @@ static struct pci_driver rtw89_8852ae_driver = { .id_table = rtw89_8852ae_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852au.c b/drivers/net/wireless/realtek/rtw89/rtw8852au.c index 29b7f7769370..065f4e5b17af 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852au.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852au.c @@ -32,6 +32,7 @@ static const struct rtw89_usb_info rtw8852a_usb_info = { static const struct rtw89_driver_info rtw89_8852au_info = { .chip = &rtw8852a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 4e7b068aaa75..356623341f65 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -307,7 +307,7 @@ static const struct rtw89_pmac_regs rtw8852b_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852b_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -319,7 +319,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852b_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -750,56 +750,30 @@ static void rtw8852b_rfk_track(struct rtw89_dev *rtwdev) static void rtw8852b_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } union rtw8852b_btc_wl_txpwr_ctrl { @@ -907,6 +881,7 @@ static const struct rtw89_chip_ops rtw8852b_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852bx_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852b_pwr_on_func, .pwr_off_func = rtw8852b_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, @@ -967,7 +942,8 @@ const struct rtw89_chip_info rtw8852b_chip_info = { }, .try_ce_fw = true, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = &rtw89_fw_blacklist_default, .fifo_size = 196608, .small_fifo_size = true, @@ -1058,15 +1034,16 @@ const struct rtw89_chip_info rtw8852b_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852b_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852b_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852b_mon_reg), .mon_reg = rtw89_btc_8852b_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852b_rf_ul), - .rf_para_ulink = rtw89_btc_8852b_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852b_rf_dl), - .rf_para_dlink = rtw89_btc_8852b_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852b_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852b_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852b_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852b_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -1113,6 +1090,7 @@ const struct rtw89_chip_info rtw8852b_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852b_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c index df5fbae50ff5..7c68260dce70 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c @@ -1321,11 +1321,9 @@ static void rtw8852bx_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8852bx_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); @@ -1838,7 +1836,6 @@ static void __rtw8852bx_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - btc->cx.wl.status.map.init_ok = true; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c index 5cfacc10e7c8..29a82b1c4457 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c @@ -2784,6 +2784,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2796,31 +2797,34 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852b_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c index 07945d06dc59..1485a8fc4bde 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2020 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852b_table.h" @@ -22895,19 +22896,19 @@ const struct rtw89_txpwr_table rtw89_8852b_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852b_trk_cfg = { - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852b_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8852b_dflt_parms = { @@ -22921,7 +22922,7 @@ const struct rtw89_rfe_parms rtw89_8852b_dflt_parms = { .lmt_ru = &rtw89_8852b_txpwr_lmt_ru_5g, }, .tx_shape = { - .lmt = &rtw89_8852b_tx_shape_lmt, - .lmt_ru = &rtw89_8852b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8852b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8852b_tx_shape_lmt_ru, }, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h index da6c90e2ba93..d30a1fb43e96 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h @@ -12,7 +12,7 @@ extern const struct rtw89_phy_table rtw89_8852b_phy_bb_gain_table; extern const struct rtw89_phy_table rtw89_8852b_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852b_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852b_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852b_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852b_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852b_dflt_parms; #endif diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index 5bc0a6a99d1d..9b48e70c2b56 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { static const struct rtw89_driver_info rtw89_8852be_info = { .chip = &rtw8852b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -97,6 +98,7 @@ static struct pci_driver rtw89_8852be_driver = { .id_table = rtw89_8852be_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index 7fcc877f6ea0..3d93cee562c2 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -250,7 +250,7 @@ static const struct rtw89_pmac_regs rtw8852bt_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852bt_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -262,7 +262,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852bt_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -635,46 +635,41 @@ static void rtw8852bt_rfk_track(struct rtw89_dev *rtwdev) static void rtw8852bt_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.kt_ver_adie = rtwdev->hal.acv; - md->md_v7.bt_solo = 0; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; - md->md_v7.wa_type = 0; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->kt_ver_adie = rtwdev->hal.acv; + md->bt_solo = 0; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->wa_type = 0; - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.ant.num = 2; - md->md_v7.ant.isolation = 10; - md->md_v7.ant.diversity = 0; - /* WL 1-stream+1-Ant is located at 0:s0(path-A) or 1:s1(path-B) */ - md->md_v7.ant.single_pos = RF_PATH_A; - md->md_v7.ant.btg_pos = RF_PATH_B; + md->ant.type = BTC_ANT_SHARED; + md->ant.num = 2; + md->ant.isolation = 10; + md->ant.diversity = 0; + /* WL 1-stream+1-Ant is located at 0:s0(path-A) or 1:s1(path-B) */ + md->ant.single_pos = RF_PATH_A; + md->ant.btg_pos = RF_PATH_B; - if (md->md_v7.rfe_type == 0) { - rtwdev->btc.dm.error.map.rfe_type0 = true; - return; - } - - md->md_v7.ant.num = (md->md_v7.rfe_type % 2) ? 2 : 3; - md->md_v7.ant.stream_cnt = 2; - md->md_v7.wa_type |= BTC_WA_INIT_SCAN; - - if (md->md_v7.ant.num == 2) { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.wa_type |= BTC_WA_HFP_LAG; - } else { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } - } else { + if (md->rfe_type == 0) { + rtwdev->btc.dm.error.map.rfe_type0 = true; return; } + + md->ant.num = (md->rfe_type % 2) ? 2 : 3; + md->ant.stream_cnt = 2; + md->wa_type |= BTC_WA_INIT_SCAN; + + if (md->ant.num == 2) { + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->wa_type |= BTC_WA_HFP_LAG; + } else { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + } } static void @@ -753,6 +748,7 @@ static const struct rtw89_chip_ops rtw8852bt_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852bx_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852bt_pwr_on_func, .pwr_off_func = rtw8852bt_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, @@ -897,15 +893,16 @@ const struct rtw89_chip_info rtw8852bt_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852bt_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852bt_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852bt_mon_reg), .mon_reg = rtw89_btc_8852bt_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852bt_rf_ul), - .rf_para_ulink = rtw89_btc_8852bt_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852bt_rf_dl), - .rf_para_dlink = rtw89_btc_8852bt_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852bt_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852bt_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852bt_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852bt_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -950,6 +947,7 @@ const struct rtw89_chip_info rtw8852bt_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852bt_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index 49a72ca835ac..850ae9cbac18 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -78,6 +78,7 @@ static const struct rtw89_pci_info rtw8852bt_pci_info = { static const struct rtw89_driver_info rtw89_8852bte_info = { .chip = &rtw8852bt_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -99,6 +100,7 @@ static struct pci_driver rtw89_8852bte_driver = { .id_table = rtw89_8852bte_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c index 308d3d570ff3..de79a19a2824 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c @@ -30,6 +30,7 @@ static const struct rtw89_usb_info rtw8852b_usb_info = { static const struct rtw89_driver_info rtw89_8852bu_info = { .chip = &rtw8852b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 80821885b08e..c68959b63a01 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2193,11 +2193,9 @@ static void rtw8852c_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8852c_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); @@ -2610,56 +2608,30 @@ static u8 rtw8852c_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8852c_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static void rtw8852c_ctrl_btg_bt_rx(struct rtw89_dev *rtwdev, bool en, @@ -2761,7 +2733,6 @@ static void rtw8852c_btc_init_cfg(struct rtw89_dev *rtwdev) rtw89_write32_set(rtwdev, R_AX_BT_CNT_CFG, B_AX_BT_CNT_EN | B_AX_BT_CNT_RST_V1); - btc->cx.wl.status.map.init_ok = true; } static @@ -2863,7 +2834,7 @@ s8 rtw8852c_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val + 6, -100, 0) + 100; } -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852c_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2875,7 +2846,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852c_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -3125,6 +3096,7 @@ static const struct rtw89_chip_ops rtw8852c_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852c_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852c_pwr_on_func, .pwr_off_func = rtw8852c_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, @@ -3172,7 +3144,8 @@ const struct rtw89_chip_info rtw8852c_chip_info = { }, .try_ce_fw = false, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = &rtw89_fw_blacklist_default, .fifo_size = 458752, .small_fifo_size = false, @@ -3265,15 +3238,16 @@ const struct rtw89_chip_info rtw8852c_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852c_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852c_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852c_mon_reg), .mon_reg = rtw89_btc_8852c_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852c_rf_ul), - .rf_para_ulink = rtw89_btc_8852c_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852c_rf_dl), - .rf_para_dlink = rtw89_btc_8852c_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852c_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852c_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852c_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852c_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -3319,6 +3293,7 @@ const struct rtw89_chip_info rtw8852c_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852c_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c index cbee484dee30..8d03a4645947 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c @@ -2992,7 +2992,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) - struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -3005,91 +3005,62 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852c_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; case RTW89_CH_6G_BAND_IDX0: case RTW89_CH_6G_BAND_IDX1: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[0]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[0]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[0]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][0]; break; case RTW89_CH_6G_BAND_IDX2: case RTW89_CH_6G_BAND_IDX3: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[1]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[1]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[1]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][1]; break; case RTW89_CH_6G_BAND_IDX4: case RTW89_CH_6G_BAND_IDX5: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[2]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[2]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[2]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][2]; break; case RTW89_CH_6G_BAND_IDX6: case RTW89_CH_6G_BAND_IDX7: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[3]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[3]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[3]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[3]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][3]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][3]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][3]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][3]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c index 24c390b6f3d3..c5ede63e7e23 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2022 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852c_table.h" @@ -57109,23 +57110,23 @@ const struct rtw89_txpwr_table rtw89_8852c_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852c_trk_cfg = { - .delta_swingidx_6gb_n = _txpwr_track_delta_swingidx_6gb_n, - .delta_swingidx_6gb_p = _txpwr_track_delta_swingidx_6gb_p, - .delta_swingidx_6ga_n = _txpwr_track_delta_swingidx_6ga_n, - .delta_swingidx_6ga_p = _txpwr_track_delta_swingidx_6ga_p, - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852c_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N] = _txpwr_track_delta_swingidx_6gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P] = _txpwr_track_delta_swingidx_6gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N] = _txpwr_track_delta_swingidx_6ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P] = _txpwr_track_delta_swingidx_6ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_phy_tssi_dbw_table rtw89_8852c_tssi_dbw_table = { @@ -57153,7 +57154,7 @@ const struct rtw89_rfe_parms rtw89_8852c_dflt_parms = { .lmt_ru = &rtw89_8852c_txpwr_lmt_ru_6g, }, .tx_shape = { - .lmt = &rtw89_8852c_tx_shape_lmt, - .lmt_ru = &rtw89_8852c_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8852c_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8852c_tx_shape_lmt_ru, }, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h index 7c9f3ecdc4e7..69c95f35b535 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h @@ -13,7 +13,7 @@ extern const struct rtw89_phy_table rtw89_8852c_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852c_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852c_phy_nctl_table; extern const struct rtw89_phy_tssi_dbw_table rtw89_8852c_tssi_dbw_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852c_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852c_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852c_dflt_parms; #endif diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index 3c64c0539205..daff152f66bc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -101,6 +101,7 @@ static const struct dmi_system_id rtw8852c_pci_quirks[] = { static const struct rtw89_driver_info rtw89_8852ce_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = NULL, .quirks = rtw8852c_pci_quirks, .dev_id_quirks = 0, .bus = { @@ -122,6 +123,7 @@ static struct pci_driver rtw89_8852ce_driver = { .id_table = rtw89_8852ce_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c index 81ee96b0a048..2dec9b845481 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c @@ -32,6 +32,7 @@ static const struct rtw89_usb_info rtw8852c_usb_info = { static const struct rtw89_driver_info rtw89_8852cu_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -39,11 +40,65 @@ static const struct rtw89_driver_info rtw89_8852cu_info = { }, }; +static const struct rtw89_led_gpio_entry rtw8852cu_valve_led_gpios[] = { + { + .pin = 8, + .color = LED_COLOR_ID_WHITE, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO8_15_FUNC_SEL, + .mask = GENMASK(3, 0), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_EXT_CTRL + 2, .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_EXT_CTRL + 1, .data = BIT(0)}, + }, { + .pin = 18, + .color = LED_COLOR_ID_RED, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(11, 8), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(2) | BIT(10)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(2)}, + }, { + .pin = 16, + .color = LED_COLOR_ID_GREEN, + .intensity = 1, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(3, 0), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(0)}, + }, { + .pin = 17, + .color = LED_COLOR_ID_BLUE, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(7, 4), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(1) | BIT(9)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(1)}, + }, +}; + +static const struct rtw89_led_desc rtw8852cu_valve_led_desc = { + .gpios = rtw8852cu_valve_led_gpios, + .n_gpio = ARRAY_SIZE(rtw8852cu_valve_led_gpios), +}; + +static const struct rtw89_board_variant rtw89_8852cu_valve_board = { + .led_desc = &rtw8852cu_valve_led_desc, +}; + static const struct rtw89_driver_info rtw89_8852cu_valve_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = &rtw89_8852cu_valve_board, .quirks = NULL, - .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS), + .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS) | + BIT(RTW89_QUIRK_DISABLE_2GHZ), .bus = { .usb = &rtw8852c_usb_info, }, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index ad3618dfd57d..2d225e009b28 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2502,15 +2502,10 @@ static void rtw8922a_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; - const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 band = chan->band_type; u8 tx_shape_idx; - u8 band, regd; - - band = chan->band_type; - regd = rtw89_regd_get(rtwdev, band); - tx_shape_idx = (*tx_shape->lmt)[band][RTW89_RS_OFDM][regd]; + tx_shape_idx = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (tx_shape_idx == 0) rtw8922a_bb_tx_triangular(rtwdev, false, phy_idx); else @@ -2726,46 +2721,105 @@ static u32 rtw8922a_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev) { - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; - struct rtw89_btc_module_v7 *module = &md->md_v7; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_module *md = &btc->mdinfo; + u8 i, j; + u8 tx_path_pos, rx_path_pos; - module->rfe_type = rtwdev->efuse.rfe_type; - module->kt_ver = rtwdev->hal.cv; - module->bt_solo = 0; - module->switch_type = BTC_SWITCH_INTERNAL; - module->wa_type = 0; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->kt_ver_adie = rtwdev->hal.acv; + md->wa_type = 0; - module->ant.type = BTC_ANT_SHARED; - module->ant.num = 2; - module->ant.isolation = 10; - module->ant.diversity = 0; - module->ant.single_pos = RF_PATH_A; - module->ant.btg_pos = RF_PATH_B; + md->ant.num = 2; + md->ant.single_pos = BTC_RF_S0; - if (module->kt_ver <= 1) - module->wa_type |= BTC_WA_HFP_ZB; + if (md->kt_ver <= 1) + md->wa_type |= BTC_WA_HFP_ZB; - rtwdev->btc.cx.other.type = BTC_3CX_NONE; + btc->cx.bt0.ant_iso_to_wl = md->ant.isolation; + btc->cx.bt1.ant_iso_to_wl = md->ant.isolation; - if (module->rfe_type == 0) { + md->ant.stream_cnt = 2; + md->ant.btg_pos = RF_PATH_B; + md->ant.single_pos = RF_PATH_A; + + btc->cx.bt0.band_56G_support = 0; + btc->cx.bt1.band_56G_support = 0; + btc->cx.bt0.func_type = BTC_BTF_BT; + btc->cx.bt1.func_type = BTC_BTF_NONE; + + if (md->rfe_type == 0) { rtwdev->btc.dm.error.map.rfe_type0 = true; return; } - module->ant.num = (module->rfe_type % 2) ? 2 : 3; + md->ant.num = (md->rfe_type % 2) ? 2 : 3; + if (md->kt_ver == 0) + md->ant.num = 2; - if (module->kt_ver == 0) - module->ant.num = 2; + memset(btc->dm.ant_xmap, 0, sizeof(btc->dm.ant_xmap)); - if (module->ant.num == 3) { - module->ant.type = BTC_ANT_DEDICATED; - module->bt_pos = BTC_BT_ALONE; - } else { - module->ant.type = BTC_ANT_SHARED; - module->bt_pos = BTC_BT_BTG; + switch (md->ant.num) { + case 1: + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + md->ant.btg_pos = BTC_RF_S0; + btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; + md->ant.func[0] = BTC_EFMAP_BT0; + break; + case 2: /* 2-Ant */ + default: + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + btc->dm.wl_trx_nss_en = 1; + btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + md->ant.func[1] = BTC_EFMAP_BT0; + break; + case 3: /* 3-Ant, 3 different BT-configuration */ + /* WL-S0 + WL-S1 + BT0-S0 */ + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; } - rtwdev->btc.btg_pos = module->ant.btg_pos; - rtwdev->btc.ant_type = module->ant.type; + + tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss); + rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss); + /* Combine TX[7:4] and RX[3:0] into path_pos byte */ + md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) | + (rx_path_pos & BTC_ANT_RX_MASK); + + switch (btc->cx.bt_ext.chip_id) { + default: + case BTC_ESOC_NONE: + memset(&btc->cx.bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info)); + btc->cx.bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR_1; + btc->cx.bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + break; + case BTC_ESOC_8771: + btc->cx.bt_ext.func_type = BTC_BTF_THREAD; + btc->cx.bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA; + btc->cx.bt_ext.rf_band_map = 0x1; /* 2.4G only */ + btc->cx.bt_ext.link_weight[BTC_BT_B2G] = 10; + btc->cx.bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD; + /* use GPIO 12~15 for Ext-4-wire-PTA */ + btc->cx.bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15); + md->ant.func[2] = BTC_EFMAP_ZB; + break; + } + + btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0; + btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0; + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) + md->ant.ant_xmap[i][j] = btc->dm.ant_xmap[i][j]; + + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static @@ -2778,7 +2832,7 @@ void rtw8922a_set_trx_mask(struct rtw89_dev *rtwdev, u8 path, u8 group, u32 val) static void rtw8922a_btc_init_cfg(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_ant_info_v7 *ant = &btc->mdinfo.md_v7.ant; + struct rtw89_btc_ant_info *ant = &btc->mdinfo.ant; u32 wl_pri, path_min, path_max; u8 path; @@ -2828,7 +2882,6 @@ static void rtw8922a_btc_init_cfg(struct rtw89_dev *rtwdev) rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, 0xda5a5a5a); rtw89_write32(rtwdev, R_BTC_ZB_BREAK_TBL, 0xf0ffffff); - btc->cx.wl.status.map.init_ok = true; } static void @@ -2874,7 +2927,7 @@ s8 rtw8922a_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val, -100, 0) + 100; } -static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8922a_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2886,7 +2939,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8922a_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -3110,6 +3163,7 @@ static const struct rtw89_chip_ops rtw8922a_chip_ops = { .set_txpwr_ul_tb_offset = NULL, .digital_pwr_comp = rtw8922a_digital_pwr_comp, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8922a_pwr_on_func, .pwr_off_func = rtw8922a_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc_v2, @@ -3249,15 +3303,16 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8922a_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8922a_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8922a_mon_reg), .mon_reg = rtw89_btc_8922a_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8922a_rf_ul), - .rf_para_ulink = rtw89_btc_8922a_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8922a_rf_dl), - .rf_para_dlink = rtw89_btc_8922a_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8922a_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8922a_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8922a_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8922a_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, @@ -3302,6 +3357,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8922a_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index 5527a8db393b..e82c866dfa36 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -76,6 +76,7 @@ static const struct rtw89_pci_info rtw8922a_pci_info = { static const struct rtw89_driver_info rtw89_8922ae_info = { .chip = &rtw8922a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -86,6 +87,7 @@ static const struct rtw89_driver_info rtw89_8922ae_info = { static const struct rtw89_driver_info rtw89_8922ae_vs_info = { .chip = &rtw8922a_chip_info, .variant = &rtw8922ae_vs_variant, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -111,6 +113,7 @@ static struct pci_driver rtw89_8922ae_driver = { .id_table = rtw89_8922ae_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops_be, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922au.c b/drivers/net/wireless/realtek/rtw89/rtw8922au.c index 2b81de501d62..56c79b1ec865 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922au.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922au.c @@ -31,6 +31,7 @@ static const struct rtw89_usb_info rtw8922a_usb_info = { static const struct rtw89_driver_info rtw89_8922au_info = { .chip = &rtw8922a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 838c26231897..142e693c87bf 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -518,14 +518,15 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) } begin: - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_AFSM_WLSUS_EN | - B_BE_AFSM_PCIE_SUS_EN); - rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_DIS_WLBT_PDNSUSEN_SOPC); - rtw89_write32_set(rtwdev, R_BE_WLLPS_CTRL, B_BE_DIS_WLBT_LPSEN_LOPC); + val32 = rtw89_read32(rtwdev, R_BE_SYS_PW_CTRL); + val32 &= ~(B_BE_AFSM_WLSUS_EN | B_BE_AFSM_PCIE_SUS_EN | B_BE_APFM_SWLPS); + val32 |= B_BE_DIS_WLBT_PDNSUSEN_SOPC; if (hal->cid != RTL8922D_CID7090) - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APDM_HPDN); + val32 &= ~B_BE_APDM_HPDN; + rtw89_write32(rtwdev, R_BE_SYS_PW_CTRL, val32); + + rtw89_write32_set(rtwdev, R_BE_WLLPS_CTRL, B_BE_DIS_WLBT_LPSEN_LOPC); rtw89_write32_clr(rtwdev, R_BE_FWS1ISR, B_BE_FS_WL_HW_RADIO_OFF_INT); - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFM_SWLPS); ret = read_poll_timeout(rtw89_read32, val32, val32 & B_BE_RDY_SYSPWR, 1000, 3000000, false, rtwdev, R_BE_SYS_PW_CTRL); @@ -535,6 +536,19 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_EN_WLON); rtw89_write32_set(rtwdev, R_BE_WLRESUME_CTRL, B_BE_LPSROP_CMAC0 | B_BE_LPSROP_CMAC1); + + if (hal->aid == RTL8922D_AID7102) { + ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_AONLDO_CTRL, 0, 0x20); + if (ret) + return ret; + + udelay(1); + + ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_AONLDO_CTRL, 0, 0x40); + if (ret) + return ret; + } + rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFN_ONMAC); ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_APFN_ONMAC), @@ -568,14 +582,13 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) if (ret) return ret; - rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC1_1P3); + rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, + B_BE_SYM_PADPDN_WL_RFC1_1P3 | B_BE_SYM_PADPDN_WL_RFC0_1P3); ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x40, 0x40); if (ret) return ret; - rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC0_1P3); - ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x20, 0x20); if (ret) return ret; @@ -881,6 +894,28 @@ static int rtw8922d_read_efuse_rf(struct rtw89_dev *rtwdev, u8 *log_map) return 0; } +static int rtw8922d_read_sys(struct rtw89_dev *rtwdev, u8 *log_map) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + struct rtw89_hal *hal = &rtwdev->hal; + u16 digk; + u8 vmin; + + digk = log_map[0x200] | log_map[0x201] << 8; + hal->thermal_prot_vmax = u16_get_bits(digk, B_BE_PWMTUNE_MASK); + + if (efuse->dswr_valid) + vmin = efuse->dswr_vmin; + else if (efuse->vcore_valid) + vmin = hal->thermal_prot_vmax - efuse->vcore_vmax_reduce; + else + vmin = hal->thermal_prot_vmax - 3; + + hal->thermal_prot_vmin = vmin; + + return 0; +} + static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map, enum rtw89_efuse_block block) { @@ -891,6 +926,8 @@ static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map, return rtw8922d_read_efuse_usb(rtwdev, log_map); case RTW89_EFUSE_BLOCK_RF: return rtw8922d_read_efuse_rf(rtwdev, log_map); + case RTW89_EFUSE_BLOCK_SYS: + return rtw8922d_read_sys(rtwdev, log_map); default: return 0; } @@ -1136,8 +1173,10 @@ static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev, u32 sub_carr = rtw89_mac_reg_by_idx(rtwdev, R_BE_TX_SUB_BAND_VALUE, mac_idx); u32 chk_rate = rtw89_mac_reg_by_idx(rtwdev, R_BE_TXRATE_CHK, mac_idx); u32 rf_mod = rtw89_mac_reg_by_idx(rtwdev, R_BE_WMAC_RFMOD, mac_idx); + const struct rtw89_chip_info *chip = rtwdev->chip; u8 txsb20 = 0, txsb40 = 0, txsb80 = 0; u8 rf_mod_val, chk_rate_mask, sifs; + u16 tx_time = AMPDU_MAX_TIME_V1; u32 txsb; u32 reg; @@ -1218,6 +1257,12 @@ static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev, reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MUEDCA_EN, mac_idx); rtw89_write32_mask(rtwdev, reg, B_BE_SIFS_MACTXEN_TB_T1_DOT05US_MASK, sifs); + + if (chan->band_type == RTW89_BAND_2G && chip->txtime_limit_2ghz) + tx_time = min_t(u32, tx_time, chip->txtime_limit_2ghz * 1000 >> 15); + + reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_AMPDU_AGG_LIMIT, mac_idx); + rtw89_write32_mask(rtwdev, reg, B_BE_AMPDU_MAX_TIME_MASK, tx_time); } static const u32 rtw8922d_sco_barker_threshold[14] = { @@ -1634,10 +1679,6 @@ static void rtw8922d_calc_rx_gain_normal_cck(struct rtw89_dev *rtwdev, s8 rx_gain_offset; rx_gain_offset = -rtw8922d_get_rx_gain_by_chan(rtwdev, chan, path, true); - - if (chan->band_width == RTW89_CHANNEL_WIDTH_40) - rx_gain_offset += (3 << 2); /* compensate RPL loss of 3dB */ - calc->cck_mean_gain_bias = (rx_gain_offset & 0x3) << 1; calc->cck_rpl_ofst = (rx_gain_offset >> 2) + gain->cck_rpl_base[phy_idx]; } @@ -1720,6 +1761,73 @@ static void rtw8922d_calc_rx_gain_normal(struct rtw89_dev *rtwdev, rtw8922d_calc_rx_gain_normal_cck(rtwdev, chan, path, phy_idx, calc); } +static void rtw8922d_path_diff_update(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ +#define BF_SMOOTH_TH 80 + static const u32 path_diff_cnt_mask[] = {0xff0000, 0xff}; + static const u32 ndp_cnt_mask[] = {0xff000000, 0xff00}; + static const u16 he_eht_sel_tone[4] = {27, 54, 56, 56}; + static const u16 vht_sel_tone[4] = {11, 25, 25, 25}; + struct rtw89_hal *hal = &rtwdev->hal; + struct rtw89_entity_conf conf; + const struct rtw89_chan *chan; + u8 phy_idx = bb->phy_idx; + u32 path_diff_cnt; + u16 sel_tone = 0; + bool bf_smo_lmt; + u32 ndp_cnt; + + if (hal->cid != RTL8922D_CID7090) + return; + + ndp_cnt = rtw89_phy_read32_mask(rtwdev, R_CL_MODE_CNT_BE4, ndp_cnt_mask[phy_idx]); + path_diff_cnt = rtw89_phy_read32_mask(rtwdev, R_CL_MODE_CNT_BE4, + path_diff_cnt_mask[phy_idx]); + + bb->path_diff.raw = clamp(phy_div(path_diff_cnt * 100, ndp_cnt), 0, 255); + + if (ndp_cnt == 0) + ewma_path_diff_init(&bb->path_diff.avg); + else + ewma_path_diff_add(&bb->path_diff.avg, bb->path_diff.raw); + + rtw89_phy_write32_mask(rtwdev, R_CL_MODE_TRIG_BE4, B_CL_MODE_TRIG_BE4, 1); + rtw89_phy_write32_mask(rtwdev, R_CL_MODE_TRIG_BE4, B_CL_MODE_TRIG_BE4, 0); + + bf_smo_lmt = ewma_path_diff_read(&bb->path_diff.avg) < BF_SMOOTH_TH; + bb->path_diff.bf_smo_en = !bf_smo_lmt; + + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_EHT_BE4, + B_BF_SMO_PDP_LMT_EHT_BE4, bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_HE_BE4, + B_BF_SMO_PDP_LMT_HE_BE4, bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_VHT_BE4, + B_BF_SMO_PDP_LMT_VHT_BE4, bf_smo_lmt); + + rtw89_phy_write32_mask(rtwdev, R_OS_TRIG_BY_SW_BE4, + B_OS_TRIG_BY_SW_BE4, !bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_OS_TRIG_SOURCE_BE4, + B_OS_TRIG_SOURCE_BE4, !bf_smo_lmt); + + rtw89_entity_get_conf(rtwdev, &conf); + chan = conf.chans[phy_idx]; + if (chan->band_width <= RTW89_CHANNEL_WIDTH_160) { + if (bb->path_diff.link_mode >= 2) + sel_tone = he_eht_sel_tone[chan->band_width]; + else if (bb->path_diff.link_mode == 1) + sel_tone = vht_sel_tone[chan->band_width]; + rtw89_phy_write32_mask(rtwdev, R_SELECTED_TONE_IDX_BE4, + B_SELECTED_TONE_IDX_BE4, sel_tone); + } + + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, + "[PATH_DIFF] raw=%d%%, ma=%ld%%, bf_smo_en=%d, tone=%d\n", + bb->path_diff.raw, + ewma_path_diff_read(&bb->path_diff.avg), + bb->path_diff.bf_smo_en, sel_tone); +} + static void rtw8922d_set_cck_parameters(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) @@ -2130,38 +2238,42 @@ static int rtw8922d_ctrl_rx_path_tmac(struct rtw89_dev *rtwdev, rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS6_BE4, 3, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS7_BE4, 7, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS8_BE4, 2, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS9_BE4, 2, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN04_BE4, 4, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN07_BE4, 2, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN01_BE4, B_RX_AWGN09_BE4, 0, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN02_BE4, B_RX_AWGN11_BE4, 1, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 8, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 5, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 3, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 5, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC08_BE4, 1, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 2, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 4, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 2, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC02_BE4, 0x6, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC03_BE4, 0xf, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 0x10, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 0xa, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 0x8, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 0xc, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 0xb, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC12_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC13_BE4, 0x2d, phy_idx); } else { rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC0_BE4, B_RXCH_MCS4_BE4, 13, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS5_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS6_BE4, 6, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS7_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS8_BE4, 4, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS9_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN04_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN07_BE4, 3, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN01_BE4, B_RX_AWGN09_BE4, 1, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN02_BE4, B_RX_AWGN11_BE4, 0, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC02_BE4, 0x3, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC03_BE4, 0x9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 8, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 6, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 16, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC08_BE4, 4, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 7, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC12_BE4, 0x17, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC13_BE4, 0x1a, phy_idx); } return 0; @@ -2186,6 +2298,12 @@ static void rtw8922d_set_digital_pwr_comp(struct rtw89_dev *rtwdev, struct { __le32 vals[DIGITAL_PWR_COMP_VALS_NUM]; } sets[2][RTW89_TX_COMP_BAND_NR][BB_PATH_NUM_8922D]; + } *pwr_comp_v1; + const struct { + __le32 base[2][DIGITAL_PWR_COMP_BASE_NUM]; + struct { + __le32 vals[DIGITAL_PWR_COMP_VALS_NUM]; + } sets[2][RTW89_TX_COMP_BAND_NR][BB_PATH_NUM_8922D]; } *pwr_comp; struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info; const struct rtw89_fw_element_hdr *txcomp_elm = elm_info->tx_comp; @@ -2196,8 +2314,12 @@ static void rtw8922d_set_digital_pwr_comp(struct rtw89_dev *rtwdev, if (sizeof(*pwr_comp) == le32_to_cpu(txcomp_elm->size)) { pwr_comp = (const void *)txcomp_elm->u.common.contents; - comp_base = &pwr_comp->base; + comp_base = &pwr_comp->base[nss]; comp_vals = &pwr_comp->sets[nss][chan->tx_comp_band][path].vals; + } else if (sizeof(*pwr_comp_v1) == le32_to_cpu(txcomp_elm->size)) { + pwr_comp_v1 = (const void *)txcomp_elm->u.common.contents; + comp_base = &pwr_comp_v1->base; + comp_vals = &pwr_comp_v1->sets[nss][chan->tx_comp_band][path].vals; } else if (sizeof(*pwr_comp_v0) == le32_to_cpu(txcomp_elm->size)) { pwr_comp_v0 = (const void *)txcomp_elm->u.common.contents; comp_base = &pwr_comp_v0->sets[nss][chan->tx_comp_band][path].base; @@ -2246,17 +2368,16 @@ static void rtw8922d_digital_pwr_comp(struct rtw89_dev *rtwdev, } } -static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode mode, - bool pwr_comp) +static void rtw8922d_ctrl_mlo_mode_core(struct rtw89_dev *rtwdev, + enum rtw89_mlo_dbcc_mode mode) { - u32 reg0, reg1; - u8 cck_phy_idx; + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_CLR_FORCE_BE4, 0x3); if (mode == MLO_2_PLUS_0_1RF) { rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEBAD); udelay(1); @@ -2266,7 +2387,7 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEFFF); @@ -2276,7 +2397,7 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3AAB); @@ -2288,6 +2409,15 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x0); } +} + +static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode mode, + bool pwr_comp) +{ + u32 reg0, reg1; + u8 cck_phy_idx; + + rtw8922d_ctrl_mlo_mode_core(rtwdev, mode); if (pwr_comp) rtw8922d_digital_pwr_comp(rtwdev, RTW89_PHY_0); @@ -2411,25 +2541,10 @@ static void rtw8922d_pre_set_channel_bb(struct rtw89_dev *rtwdev, rtw89_phy_write32_mask(rtwdev, R_SYS_DBCC_BE4, B_SYS_DBCC_BE4, 0x0); - if (phy_idx == RTW89_PHY_0) { - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEBAD); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEAAD); - } else { - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEFFF); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEEFF); - } + if (phy_idx == RTW89_PHY_0) + rtw8922d_ctrl_mlo_mode_core(rtwdev, MLO_2_PLUS_0_1RF); + else + rtw8922d_ctrl_mlo_mode_core(rtwdev, MLO_0_PLUS_2_1RF); fsleep(1); } @@ -2650,14 +2765,74 @@ static void rtw8922d_rfk_band_changed(struct rtw89_dev *rtwdev, { } +static void __rtw8922d_txiqk_disable(struct rtw89_dev *rtwdev) +{ + struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; + u8 path, kidx; + + for (path = RF_PATH_A; path <= RF_PATH_B; path++) { + kidx = rtw89_phy_read32_mask(rtwdev, R_KTBL0A_BE4 + (path << 8), + B_KTBL0_IDX0); + if (kidx == 0) { + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + } else if (kidx == 1) { + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX1_EN); + } else { + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x1); + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x0); + } + + iqk_info->iqc_bak[path] = + rtw89_phy_read32(rtwdev, R_TX_IQC_A_BE4 + (path << 8)); + rtw89_phy_write32(rtwdev, R_TX_IQC_A_BE4 + (path << 8), 0x40000002); + } +} + +static void __rtw8922d_txiqk_enable(struct rtw89_dev *rtwdev) +{ + struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; + u8 path, kidx; + + for (path = RF_PATH_A; path <= RF_PATH_B; path++) { + kidx = rtw89_phy_read32_mask(rtwdev, R_KTBL0A_BE4 + (path << 8), + B_KTBL0_IDX0); + if (kidx == 0) { + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + } else if (kidx == 1) { + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX1_EN); + } else { + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x1); + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x0); + } + + rtw89_phy_write32(rtwdev, R_TX_IQC_A_BE4 + (path << 8), + iqk_info->iqc_bak[path]); + } +} + static void rtw8922d_rfk_scan(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, bool start) { - if (start) + if (start) { __rtw8922d_tssi_disable(rtwdev, rtwvif_link->phy_idx); - else + __rtw8922d_txiqk_disable(rtwdev); + } else { __rtw8922d_tssi_enable(rtwdev, rtwvif_link->phy_idx); + __rtw8922d_txiqk_enable(rtwdev); + } } static void rtw8922d_rfk_track(struct rtw89_dev *rtwdev) @@ -2739,16 +2914,11 @@ static void rtw8922d_set_tx_shape(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) { const struct rtw89_bb_wrap_data *d = rtwdev->phy_info.bb_wrap_data; - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; - const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 band = chan->band_type; u8 tx_shape_idx; - u8 band, regd; const u16 *th; - band = chan->band_type; - regd = rtw89_regd_get(rtwdev, band); - tx_shape_idx = (*tx_shape->lmt)[band][RTW89_RS_OFDM][regd]; - + tx_shape_idx = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (tx_shape_idx == 0) goto disable; @@ -2940,6 +3110,7 @@ static void rtw8922d_bb_cfg_txrx_path(struct rtw89_dev *rtwdev) static u8 rtw8922d_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_path) { + struct rtw89_power_trim_info *info = &rtwdev->pwr_trim; u8 val; rtw89_phy_write32_mask(rtwdev, R_TC_EN_BE4, B_TC_EN_BE4, 0x1); @@ -2949,6 +3120,7 @@ static u8 rtw8922d_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p fsleep(100); val = rtw89_phy_read32_mask(rtwdev, R_TC_VAL_BE4, B_TC_VAL_BE4); + val = clamp_t(int, val + info->thermal_k, 0, 255); return val; } @@ -2997,6 +3169,226 @@ static u32 rtw8922d_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) { + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_module *md = &btc->mdinfo; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; + u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos; + u8 is_combo, is_bt_share, is_spdt; + u8 tx_path_pos, rx_path_pos; + u8 i, j; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__); + + /* get from final capability of device */ + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->kt_ver_adie = rtwdev->hal.acv; + md->wa_type = 0; + dm->wl_trx_nss_en = 0; + + md->ant.num = 2; + md->ant.single_pos = BTC_RF_S0; /* WL 1ss+1Ant 0:s0(A)/ 1:s1(B) */ + + /* set default antenna isolation */ + cx->bt0.ant_iso_to_wl = md->ant.isolation; + cx->bt1.ant_iso_to_wl = md->ant.isolation; + + md->ant.stream_cnt = (rtwdev->hal.tx_nss << BTC_ANT_SHIFT) + + rtwdev->hal.rx_nss; + md->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */ + md->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */ + + cx->bt0.band_56G_support = 1; + cx->bt1.band_56G_support = 1; + cx->bt0.func_type = BTC_BTF_BT; + cx->bt1.func_type = BTC_BTF_BT; + + efuse_bt_func = rtwdev->efuse.bt_setting_2; + + is_spdt = !!(efuse_bt_func & BIT(5)); + + switch ((efuse_bt_func & 0xc0) >> 6) { /* 0xcd[7:6] */ + default: + case 0: + bt_sw_gpio_pos = 5; + break; + case 1: + bt_sw_gpio_pos = 11; + break; + case 2: + bt_sw_gpio_pos = 15; + break; + case 3: + bt_sw_gpio_pos = 20; + break; + } + + efuse_bt_func &= 0x1f; /* 0xcd[4:0] */ + efuse_ant_info = rtwdev->efuse.bt_setting_3; + md->ant.num = (efuse_ant_info & 0xe0) >> 5; /* 0xCE[7:5] */ + is_combo = (efuse_ant_info & 0xe) >> 1; /* 0xCE[3:1] */ + is_bt_share = efuse_ant_info & BIT(0); /* 0xCE[0] */ + + memset(dm->ant_xmap, 0, sizeof(dm->ant_xmap)); + + /* To-Do: "RFE_TYpe" to "ant.num" translation */ + switch (md->ant.num) { + case 1: /* 1-Ant WL-S0 only & BT0 only */ + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + md->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */ + md->ant.func[0] = BTC_EFMAP_BT0; + dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/ + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */ + break; + case 2: /* 2-Ant */ + default: + if (is_combo) { + if (efuse_bt_func == (BTC_EFMAP_BT0 | BTC_EFMAP_BT1)) + md->ant.func[0] = BTC_EFMAP_BT1; + + md->ant.func[1] = BTC_EFMAP_BT0; + } else { + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + } + + if (is_bt_share) { /* WL-S0 + (WL-S1 & BT0-S1) */ + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + } else { /* WL-S0 + BT0-S1 */ + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + } + + if (md->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */ + dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1; + if (is_spdt) { /* WL-S0 & BT1 by SPDT */ + md->bt1_pos = BTC_BT_ALONE; + /* Todo: set SPDT GPIO-ctrl */ + md->bt1_sw_type = bt_sw_gpio_pos; + } else { /* WL-S0 & BT1-S1 by BTGA */ + md->bt1_pos = BTC_BT_BTG; + md->bt1_sw_type = BTC_SWITCH_V1_INTERNAL; + } + } + + dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability if 1-BT */ + break; + case 3: /* 3-Ant, 3 different BT-configuration */ + if (is_bt_share) { + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_BT0; + md->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0); + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */ + } else { + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = efuse_bt_func; + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + } + + md->bt1_pos = BTC_BT_ALONE; /* BT1 may exist or not */ + md->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; + case 4: /* 4-Ant, WL-S0 + WL-S1 + BT0 + BT1 */ + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; + md->ant.func[3] = efuse_bt_func & (~BTC_EFMAP_BT0); + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->bt1_pos = BTC_BT_ALONE; + md->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; + case 5: + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; + md->ant.func[3] = BTC_EFMAP_BT1; + md->ant.func[4] = BTC_EFMAP_ZB; + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->bt1_pos = BTC_BT_ALONE; + md->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; + } + + tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss); + rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss); + /* Combine TX[7:4] and RX[3:0] into path_pos byte */ + md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) | + (rx_path_pos & BTC_ANT_RX_MASK); + + /* + * if only BT0 at BTGA: 2-Ant, 3-Ant(BT1 used dedicated-ant) + * can setup dm->wl_trx_nss_en = 1, WL MIMO-PS to 1T1R + * (WL at WL-S0 only, BT0 at WL-S1) + * tx_limit/rx_limit is decided by _set_trx_nss() + */ + if (dm->wl_trx_nss_en && + (dm->wl_trx_nss.tx_limit && dm->wl_trx_nss.rx_limit)) { + md->ant.type = BTC_ANT_DEDICATED; + md->ant.stream_cnt = 1; + dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 0; /* wl 1ss-> RF-S0 */ + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* BT0-> RF-S1 */ + dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 0; + dm->ant_xmap[BTC_RF_S1][BTC_BT_2ND] = 0; /* BT1 -> 3rd Ant */ + } + + /* + * Todo: call HALBB API to set BT0/1 at WL_S0 or WL_S1 + * r_sel_gnt_bt_rx_path0[1:0], r_sel_gnt_bt_rx_path1[1:0] + * WL_S0 with BT1 -> r_sel_gnt_bt_rx_path0[1:0]= 2b'10 + * WL_S1 with BT0 -> r_sel_gnt_bt_rx_path1[1:0]= 2b'01 + */ + + /* To Do: setup ext-SOC coex if exist */ + switch (cx->bt_ext.chip_id) { + default: + case BTC_ESOC_NONE: + memset(&cx->bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info)); + cx->bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR; + cx->bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + break; + case BTC_ESOC_8771: + cx->bt_ext.func_type = BTC_BTF_THREAD; + cx->bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA; + cx->bt_ext.rf_band_map = 0x1; /* 2.4GHz only */ + cx->bt_ext.link_weight[BTC_BT_B2G] = 10; + cx->bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD; + /* use GPIO 12~15 for Ext-4-wire-PTA */ + cx->bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15); + /* for Ext-SOC locate at Ant-2 */ + if (md->ant.num >= 5) + md->ant.func[4] = BTC_EFMAP_ZB; + else + md->ant.func[md->ant.num - 1] = BTC_EFMAP_ZB; + break; + } + + dm->ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0; + dm->ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0; + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) + md->ant.ant_xmap[i][j] = dm->ant_xmap[i][j]; + + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev) @@ -3252,6 +3644,7 @@ static const struct rtw89_chip_ops rtw8922d_chip_ops = { .set_txpwr_ul_tb_offset = NULL, .digital_pwr_comp = rtw8922d_digital_pwr_comp, .calc_rx_gain_normal = rtw8922d_calc_rx_gain_normal, + .path_diff_update = rtw8922d_path_diff_update, .pwr_on_func = rtw8922d_pwr_on_func, .pwr_off_func = rtw8922d_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc_v3, @@ -3318,7 +3711,7 @@ const struct rtw89_chip_info rtw8922d_chip_info = { .wde_qempty_acq_grpnum = 8, .wde_qempty_mgq_grpsel = 8, .rf_base_addr = {0x3e000, 0x3f000}, - .thermal_th = {0xac, 0xad}, + .thermal_th = {0xac, 0xb4}, .pwr_on_seq = NULL, .pwr_off_seq = NULL, .bb_table = NULL, @@ -3385,11 +3778,16 @@ const struct rtw89_chip_info rtw8922d_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8922d_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8922d_bt_rssi_thres, .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8922d_mon_reg), .mon_reg = rtw89_btc_8922d_mon_reg, + .rf_para_ulink_v0 = NULL, + .rf_para_dlink_v0 = NULL, + .rf_para_ulink_num_v0 = 0, + .rf_para_dlink_num_v0 = 0, .rf_para_ulink_v9 = rtw89_btc_8922d_rf_ul_v9, .rf_para_dlink_v9 = rtw89_btc_8922d_rf_dl_v9, .rf_para_ulink_num_v9 = ARRAY_SIZE(rtw89_btc_8922d_rf_ul_v9), @@ -3437,6 +3835,7 @@ const struct rtw89_chip_info rtw8922d_chip_info = { #endif .xtal_info = NULL, .default_quirks = BIT(RTW89_QUIRK_THERMAL_PROT_120C), + .txtime_limit_2ghz = 4608, }; EXPORT_SYMBOL(rtw8922d_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922de.c b/drivers/net/wireless/realtek/rtw89/rtw8922de.c index a1a81c338be3..a71e41f28544 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922de.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922de.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8922d_pci_info = { static const struct rtw89_driver_info rtw89_8922de_vs_info = { .chip = &rtw8922d_chip_info, .variant = &rtw8922de_vs_variant, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -82,6 +83,7 @@ static const struct rtw89_driver_info rtw89_8922de_vs_info = { static const struct rtw89_driver_info rtw89_8922de_info = { .chip = &rtw8922d_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -111,6 +113,7 @@ static struct pci_driver rtw89_8922de_driver = { .id_table = rtw89_8922de_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops_be, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c index 8dadd8df4fc6..2dfa24c54e7d 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.c +++ b/drivers/net/wireless/realtek/rtw89/wow.c @@ -358,7 +358,7 @@ static void rtw89_wow_get_key_info_iter(struct ieee80211_hw *hw, key_info->gtk_keyidx = key->keyidx; break; default: - rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupport cipher %x\n", + rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupported cipher %x\n", key->cipher); goto err; } @@ -443,7 +443,7 @@ static void rtw89_wow_set_key_info_iter(struct ieee80211_hw *hw, case WLAN_CIPHER_SUITE_WEP104: break; default: - rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupport cipher %x\n", + rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupported cipher %x\n", key->cipher); goto err; } @@ -1299,7 +1299,8 @@ static int rtw89_wow_swap_fw(struct rtw89_dev *rtwdev, bool wow) if (disable_intr_for_dlfw) rtw89_hci_enable_intr(rtwdev); - rtw89_phy_init_rf_reg(rtwdev, true); + if (chip->chip_gen == RTW89_CHIP_AX) + rtw89_phy_init_rf_reg(rtwdev, true); ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, rtwsta_link, RTW89_ROLE_FW_RESTORE); @@ -1456,9 +1457,20 @@ static int rtw89_wow_disable_trx_post(struct rtw89_dev *rtwdev) static void rtw89_fw_release_pno_pkt_list(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; struct rtw89_wow_param *rtw_wow = &rtwdev->wow; struct list_head *pkt_list = &rtw_wow->pno_pkt_list; struct rtw89_pktofld_info *info, *tmp; + u8 idx, wildcard_pkt_id; + + for (idx = NL80211_BAND_2GHZ; idx < NUM_NL80211_BANDS; idx++) { + if (!(rtwdev->chip->support_bands & BIT(idx))) + continue; + + wildcard_pkt_id = scan_info->wildcard_pkt_id[idx]; + if (wildcard_pkt_id != RTW89_SCANOFLD_PKT_NONE) + rtw89_fw_h2c_del_pkt_offload(rtwdev, wildcard_pkt_id); + } list_for_each_entry_safe(info, tmp, pkt_list, list) { rtw89_fw_h2c_del_pkt_offload(rtwdev, info->id); @@ -1479,6 +1491,11 @@ static int rtw89_pno_scan_update_probe_req(struct rtw89_dev *rtwdev, struct sk_buff *skb; int ret; + ret = rtw89_hw_scan_append_wildcard_probe_req(rtwdev, rtwvif_link, + rtwvif_link->mac_addr, true); + if (ret) + return ret; + for (i = 0; i < num; i++) { skb = ieee80211_probereq_get(rtwdev->hw, rtwvif_link->mac_addr, nd_config->match_sets[i].ssid.ssid, @@ -1522,6 +1539,10 @@ static int rtw89_pno_scan_offload(struct rtw89_dev *rtwdev, bool enable) int ret; if (enable) { + rtw89_hw_scan_calc_req_ssid(rtwdev, rtwvif_link, true); + memset(rtwdev->scan_info.wildcard_pkt_id, RTW89_SCANOFLD_PKT_NONE, + sizeof(rtwdev->scan_info.wildcard_pkt_id)); + ret = rtw89_pno_scan_update_probe_req(rtwdev, rtwvif_link); if (ret) { rtw89_err(rtwdev, "Update probe request failed\n"); diff --git a/drivers/net/wireless/rsi/rsi_91x_coex.c b/drivers/net/wireless/rsi/rsi_91x_coex.c index ee603a5173fb..023c539fc384 100644 --- a/drivers/net/wireless/rsi/rsi_91x_coex.c +++ b/drivers/net/wireless/rsi/rsi_91x_coex.c @@ -50,8 +50,9 @@ static void rsi_coex_sched_tx_pkts(struct rsi_coex_ctrl_block *coex_cb) } while (coex_q != RSI_COEX_Q_INVALID); } -static void rsi_coex_scheduler_thread(struct rsi_common *common) +static int rsi_coex_scheduler_thread(void *data) { + struct rsi_common *common = data; struct rsi_coex_ctrl_block *coex_cb = common->coex_cb; u32 timeout = EVENT_WAIT_FOREVER; diff --git a/drivers/net/wireless/rsi/rsi_91x_main.c b/drivers/net/wireless/rsi/rsi_91x_main.c index 662e42d1e5e8..2ce514766620 100644 --- a/drivers/net/wireless/rsi/rsi_91x_main.c +++ b/drivers/net/wireless/rsi/rsi_91x_main.c @@ -246,12 +246,13 @@ EXPORT_SYMBOL_GPL(rsi_read_pkt); /** * rsi_tx_scheduler_thread() - This function is a kernel thread to send the * packets to the device. - * @common: Pointer to the driver private structure. + * @data: Pointer to the driver private structure. * - * Return: None. + * Return: 0. */ -static void rsi_tx_scheduler_thread(struct rsi_common *common) +static int rsi_tx_scheduler_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; u32 timeout = EVENT_WAIT_FOREVER; diff --git a/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c index 597b238e2294..18a28aa97446 100644 --- a/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c +++ b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c @@ -62,8 +62,9 @@ int rsi_sdio_master_access_msword(struct rsi_hw *adapter, u16 ms_word) static void rsi_rx_handler(struct rsi_hw *adapter); -void rsi_sdio_rx_thread(struct rsi_common *common) +int rsi_sdio_rx_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; struct rsi_91x_sdiodev *sdev = adapter->rsi_dev; diff --git a/drivers/net/wireless/rsi/rsi_91x_usb_ops.c b/drivers/net/wireless/rsi/rsi_91x_usb_ops.c index 25c2b232394a..e899631b9aed 100644 --- a/drivers/net/wireless/rsi/rsi_91x_usb_ops.c +++ b/drivers/net/wireless/rsi/rsi_91x_usb_ops.c @@ -21,12 +21,13 @@ /** * rsi_usb_rx_thread() - This is a kernel thread to receive the packets from * the USB device. - * @common: Pointer to the driver private structure. + * @data: Pointer to the driver private structure. * - * Return: None. + * Return: 0. */ -void rsi_usb_rx_thread(struct rsi_common *common) +int rsi_usb_rx_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; struct rsi_91x_usbdev *dev = adapter->rsi_dev; int status; diff --git a/drivers/net/wireless/rsi/rsi_common.h b/drivers/net/wireless/rsi/rsi_common.h index 3cdf9ded876d..2a33a81f71a3 100644 --- a/drivers/net/wireless/rsi/rsi_common.h +++ b/drivers/net/wireless/rsi/rsi_common.h @@ -58,7 +58,7 @@ static inline void rsi_reset_event(struct rsi_event *event) static inline int rsi_create_kthread(struct rsi_common *common, struct rsi_thread *thread, - void *func_ptr, + int (*func_ptr)(void *data), u8 *name) { init_completion(&thread->completion); diff --git a/drivers/net/wireless/rsi/rsi_sdio.h b/drivers/net/wireless/rsi/rsi_sdio.h index 7c91b126b350..eb2b7f36a7e4 100644 --- a/drivers/net/wireless/rsi/rsi_sdio.h +++ b/drivers/net/wireless/rsi/rsi_sdio.h @@ -134,5 +134,5 @@ int rsi_sdio_master_access_msword(struct rsi_hw *adapter, u16 ms_word); void rsi_sdio_ack_intr(struct rsi_hw *adapter, u8 int_bit); int rsi_sdio_determine_event_timeout(struct rsi_hw *adapter); int rsi_sdio_check_buffer_status(struct rsi_hw *adapter, u8 q_num); -void rsi_sdio_rx_thread(struct rsi_common *common); +int rsi_sdio_rx_thread(void *data); #endif diff --git a/drivers/net/wireless/rsi/rsi_usb.h b/drivers/net/wireless/rsi/rsi_usb.h index 961851748bc4..78067eaffe8b 100644 --- a/drivers/net/wireless/rsi/rsi_usb.h +++ b/drivers/net/wireless/rsi/rsi_usb.h @@ -81,5 +81,5 @@ static inline int rsi_usb_event_timeout(struct rsi_hw *adapter) return EVENT_WAIT_FOREVER; } -void rsi_usb_rx_thread(struct rsi_common *common); +int rsi_usb_rx_thread(void *data); #endif diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index be583ae331c0..5595f7a1fc0c 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -6354,7 +6354,6 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, struct ieee80211_hw *hw; struct wl1271 *wl; int i, j, ret; - unsigned int order; hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops); if (!hw) { @@ -6434,8 +6433,7 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, mutex_init(&wl->flush_mutex); init_completion(&wl->nvs_loading_complete); - order = get_order(aggr_buf_size); - wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order); + wl->aggr_buf = kmalloc(round_up(aggr_buf_size, PAGE_SIZE), GFP_KERNEL); if (!wl->aggr_buf) { ret = -ENOMEM; goto err_wq; @@ -6449,7 +6447,7 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, } /* Allocate one page for the FW log */ - wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL); + wl->fwlog = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!wl->fwlog) { ret = -ENOMEM; goto err_dummy_packet; @@ -6474,13 +6472,13 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, kfree(wl->mbox); err_fwlog: - free_page((unsigned long)wl->fwlog); + kfree(wl->fwlog); err_dummy_packet: dev_kfree_skb(wl->dummy_packet); err_aggr: - free_pages((unsigned long)wl->aggr_buf, order); + kfree(wl->aggr_buf); err_wq: destroy_workqueue(wl->freezable_wq); @@ -6509,9 +6507,9 @@ int wlcore_free_hw(struct wl1271 *wl) kfree(wl->buffer_32); kfree(wl->mbox); - free_page((unsigned long)wl->fwlog); + kfree(wl->fwlog); dev_kfree_skb(wl->dummy_packet); - free_pages((unsigned long)wl->aggr_buf, get_order(wl->aggr_buf_size)); + kfree(wl->aggr_buf); wl1271_debugfs_exit(wl); diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 75caa97becc8..02b6d81cccd1 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2114,6 +2114,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, bool ack, unicast_data; enum nl80211_chan_width confbw = NL80211_CHAN_WIDTH_20_NOHT; u32 _portid, i; + int tx_link_id = -1; if (WARN_ON(skb->len < 10)) { /* Should not happen; just a sanity check for addr1 use */ @@ -2171,6 +2172,9 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, hdr, &link_sta); } + if (bss_conf) + tx_link_id = bss_conf->link_id; + if (unlikely(!bss_conf)) { /* if it's an MLO STA, it might have deactivated all * links temporarily - but we don't handle real PS in @@ -2282,6 +2286,12 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK) && ack) txi->flags |= IEEE80211_TX_STAT_ACK; + + if (tx_link_id >= 0) { + txi->status.link_valid = 1; + txi->status.link_id = tx_link_id; + } + ieee80211_tx_status_irqsafe(hw, skb); } @@ -2314,6 +2324,7 @@ static int mac80211_hwsim_start(struct ieee80211_hw *hw) static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) { struct mac80211_hwsim_data *data = hw->priv; + struct sk_buff *skb; int i; data->started = false; @@ -2321,8 +2332,8 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) for (i = 0; i < ARRAY_SIZE(data->link_data); i++) hrtimer_cancel(&data->link_data[i].beacon_timer); - while (!skb_queue_empty(&data->pending)) - ieee80211_free_txskb(hw, skb_dequeue(&data->pending)); + while ((skb = skb_dequeue(&data->pending))) + ieee80211_free_txskb(hw, skb); wiphy_dbg(hw->wiphy, "%s\n", __func__); } @@ -3841,9 +3852,6 @@ static void mac80211_hwsim_abort_pmsr(struct ieee80211_hw *hw, int err = 0; data = hw->priv; - _portid = READ_ONCE(data->wmediumd); - if (!_portid && !hwsim_virtio_enabled) - return; mutex_lock(&data->mutex); @@ -3852,6 +3860,13 @@ static void mac80211_hwsim_abort_pmsr(struct ieee80211_hw *hw, goto out; } + data->pmsr_request = NULL; + data->pmsr_request_wdev = NULL; + + _portid = READ_ONCE(data->wmediumd); + if (!_portid && !hwsim_virtio_enabled) + goto out; + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); if (!skb) { err = -ENOMEM; @@ -4207,6 +4222,15 @@ static int hwsim_pmsr_report_nl(struct sk_buff *msg, struct genl_info *info) if (!data) return -EINVAL; + if (!hwsim_virtio_enabled) { + if (hwsim_net_get_netgroup(genl_info_net(info)) != + data->netgroup) + return -EINVAL; + + if (info->snd_portid != data->wmediumd) + return -EPERM; + } + mutex_lock(&data->mutex); if (!data->pmsr_request) { err = -EINVAL; @@ -6103,6 +6127,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_PUNCT); + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_PROBE_AP); for (i = 0; i < ARRAY_SIZE(data->link_data); i++) { hrtimer_setup(&data->link_data[i].beacon_timer, mac80211_hwsim_beacon, @@ -6285,6 +6310,8 @@ static void mac80211_hwsim_free(void) struct mac80211_hwsim_data, list))) { list_del(&data->list); + rhashtable_remove_fast(&hwsim_radios_rht, &data->rht, + hwsim_rht_params); spin_unlock_bh(&hwsim_radio_lock); mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), NULL); @@ -6328,6 +6355,27 @@ static void hwsim_register_wmediumd(struct net *net, u32 portid) spin_unlock_bh(&hwsim_radio_lock); } +static int mac80211_hwsim_get_link_id(struct ieee80211_vif *vif, + struct ieee80211_hdr *hdr) +{ + int i; + + if (!vif || !ieee80211_vif_is_mld(vif)) + return -1; + + for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + struct ieee80211_bss_conf *link_conf; + + link_conf = rcu_dereference(vif->link_conf[i]); + if (!link_conf) + continue; + if (ether_addr_equal(link_conf->addr, hdr->addr2)) + return i; + } + + return -1; +} + static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, struct genl_info *info) { @@ -6408,13 +6456,18 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, txi->status.ack_signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); + hdr = (struct ieee80211_hdr *)skb->data; + i = mac80211_hwsim_get_link_id(txi->control.vif, hdr); + if (i >= 0) { + txi->status.link_valid = 1; + txi->status.link_id = i; + } + if (!(hwsim_flags & HWSIM_TX_CTL_NO_ACK) && (hwsim_flags & HWSIM_TX_STAT_ACK)) { - if (skb->len >= 16) { - hdr = (struct ieee80211_hdr *) skb->data; + if (skb->len >= 16) mac80211_hwsim_monitor_ack(data2->channel, hdr->addr2); - } txi->flags |= IEEE80211_TX_STAT_ACK; } diff --git a/drivers/net/wireless/zydas/zd1211rw/zd_usb.c b/drivers/net/wireless/zydas/zd1211rw/zd_usb.c index 966d8ccb0dbc..98102c663434 100644 --- a/drivers/net/wireless/zydas/zd1211rw/zd_usb.c +++ b/drivers/net/wireless/zydas/zd1211rw/zd_usb.c @@ -1353,6 +1353,14 @@ static int probe(struct usb_interface *intf, const struct usb_device_id *id) struct zd_usb *usb; struct ieee80211_hw *hw = NULL; + /* + * ZD1211 devices are single-function. Reject secondary interfaces + * to prevent multiple instances from conflicting on hardcoded endpoints + * and triggering recursive locking warnings. + */ + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + print_id(udev); if (id->driver_info & DEVICE_INSTALLER) diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index ccce2ad74128..ffbcf11e4e68 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -42,6 +42,7 @@ static struct dentry *wwan_debugfs_dir; /* WWAN port flags */ #define WWAN_PORT_TX_OFF 0 +#define WWAN_PORT_EXCLUSIVE 1 /** * struct wwan_device - The structure that defines a WWAN device @@ -748,6 +749,12 @@ static int wwan_port_op_start(struct wwan_port *port) goto out_unlock; } + if (test_bit(WWAN_PORT_EXCLUSIVE, &port->flags) && + !capable(CAP_SYS_ADMIN)) { + ret = -EBUSY; + goto out_unlock; + } + /* If port is already started, don't start again */ if (!port->start_count) ret = port->ops->start(port); @@ -769,6 +776,7 @@ static void wwan_port_op_stop(struct wwan_port *port) if (port->ops) port->ops->stop(port); skb_queue_purge(&port->rxq); + clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); } mutex_unlock(&port->ops_lock); } @@ -1031,6 +1039,22 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, break; } + case TIOCEXCL: + set_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + break; + + case TIOCNXCL: + clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + break; + + case TIOCGEXCL: + { + int excl = test_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + + ret = put_user(excl, (int __user *)arg); + break; + } + default: ret = -ENOIOCTLCMD; } @@ -1046,7 +1070,8 @@ static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd, struct wwan_port *port = filp->private_data; int res; - if (port->type == WWAN_PORT_AT) { /* AT port specific IOCTLs */ + if (port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) { + /* AT and QCDM port specific IOCTLs */ res = wwan_port_fops_at_ioctl(port, cmd, arg); if (res != -ENOIOCTLCMD) return res; diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index c1896a1d978c..e131f9933a5a 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -166,9 +166,36 @@ static int fdp_nci_i2c_read(struct fdp_i2c_phy *phy, struct sk_buff **skb) /* Packet that contains a length */ if (tmp[0] == 0 && tmp[1] == 0) { phy->next_read_size = (tmp[2] << 8) + tmp[3] + 3; + + /* + * next_read_size is taken from the device and is used + * as the i2c_master_recv() count for the next packet + * and as the data skb size. A value above the receive + * buffer overflows tmp[]; one below the minimum frame + * size runs the header/LRC strip and the length-field + * read past a short receive. Either way the packet is + * corrupt: drop it and force resynchronization. + */ + if (phy->next_read_size < FDP_NCI_I2C_MIN_PAYLOAD || + phy->next_read_size > FDP_NCI_I2C_MAX_PAYLOAD) { + dev_dbg(&client->dev, "%s: corrupted packet\n", + __func__); + phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + goto flush; + } } else { phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + /* + * Only one data packet is delivered per call; if the + * device sends another, do not overwrite and leak the + * skb allocated for the previous one. + */ + if (*skb) { + kfree_skb(*skb); + *skb = NULL; + } + *skb = alloc_skb(len, GFP_KERNEL); if (*skb == NULL) { r = -ENOMEM; @@ -349,8 +376,8 @@ static void fdp_nci_i2c_remove(struct i2c_client *client) } static const struct acpi_device_id fdp_nci_i2c_acpi_match[] = { - {"INT339A", 0}, - {} + { .id = "INT339A" }, + { } }; MODULE_DEVICE_TABLE(acpi, fdp_nci_i2c_acpi_match); diff --git a/drivers/nfc/microread/mei.c b/drivers/nfc/microread/mei.c index c256ae92d6b1..484e3ae0e875 100644 --- a/drivers/nfc/microread/mei.c +++ b/drivers/nfc/microread/mei.c @@ -48,10 +48,12 @@ static void microread_mei_remove(struct mei_cl_device *cldev) } static struct mei_cl_device_id microread_mei_tbl[] = { - { MICROREAD_DRIVER_NAME, MEI_NFC_UUID, MEI_CL_VERSION_ANY}, - - /* required last entry */ - { } + { + .name = MICROREAD_DRIVER_NAME, + .uuid = MEI_NFC_UUID, + .version = MEI_CL_VERSION_ANY, + }, + { /* required last entry */ } }; MODULE_DEVICE_TABLE(mei, microread_mei_tbl); diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index 4149c5d735bd..dfa2490db545 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -483,13 +483,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, switch (gate) { case MICROREAD_GATE_ID_MREAD_ISO_A: + if (skb->len <= MICROREAD_EMCF_A_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A_UID) { r = -EINVAL; goto exit_free; } @@ -497,13 +503,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_A_3: + if (skb->len <= MICROREAD_EMCF_A3_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A3_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A3_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A3_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A3_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A3_UID) { r = -EINVAL; goto exit_free; } @@ -511,11 +523,21 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_B: + if (skb->len < MICROREAD_EMCF_B_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_B_UID], 4); targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T1: + if (skb->len < MICROREAD_EMCF_T1_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_JEWEL_MASK; targets->sens_res = le16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_T1_ATQA]); @@ -523,6 +545,11 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T3: + if (skb->len < MICROREAD_EMCF_T3_UID + 8) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_FELICA_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T3_UID], 8); targets->nfcid1_len = 8; diff --git a/drivers/nfc/nfcmrvl/i2c.c b/drivers/nfc/nfcmrvl/i2c.c index 66877a7d03f2..068c5d278a35 100644 --- a/drivers/nfc/nfcmrvl/i2c.c +++ b/drivers/nfc/nfcmrvl/i2c.c @@ -245,9 +245,9 @@ static void nfcmrvl_i2c_remove(struct i2c_client *client) } -static const struct of_device_id of_nfcmrvl_i2c_match[] __maybe_unused = { - { .compatible = "marvell,nfc-i2c", }, - {}, +static const struct of_device_id of_nfcmrvl_i2c_match[] = { + { .compatible = "marvell,nfc-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_nfcmrvl_i2c_match); diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index 9c8cde1250fb..8b8f00dac0f8 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -168,6 +168,10 @@ static int nfcmrvl_spi_probe(struct spi_device *spi) drv_data->nci_spi = nci_spi_allocate_spi(drv_data->spi, 0, 10, drv_data->priv->ndev); + if (!drv_data->nci_spi) { + nfcmrvl_nci_unregister_dev(drv_data->priv); + return -ENOMEM; + } /* Init completion for slave handshake */ init_completion(&drv_data->handshake_completion); @@ -181,14 +185,14 @@ static void nfcmrvl_spi_remove(struct spi_device *spi) nfcmrvl_nci_unregister_dev(drv_data->priv); } -static const struct of_device_id of_nfcmrvl_spi_match[] __maybe_unused = { - { .compatible = "marvell,nfc-spi", }, - {}, +static const struct of_device_id of_nfcmrvl_spi_match[] = { + { .compatible = "marvell,nfc-spi" }, + { } }; MODULE_DEVICE_TABLE(of, of_nfcmrvl_spi_match); static const struct spi_device_id nfcmrvl_spi_id_table[] = { - { "nfcmrvl_spi", 0 }, + { .name = "nfcmrvl_spi" }, { } }; MODULE_DEVICE_TABLE(spi, nfcmrvl_spi_id_table); diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c index 4babde8e4249..c7f2afe00b93 100644 --- a/drivers/nfc/nfcmrvl/usb.c +++ b/drivers/nfc/nfcmrvl/usb.c @@ -17,7 +17,6 @@ static struct usb_device_id nfcmrvl_table[] = { USB_CLASS_VENDOR_SPEC, 4, 1) }, { } /* Terminating entry */ }; - MODULE_DEVICE_TABLE(usb, nfcmrvl_table); #define NFCMRVL_USB_BULK_RUNNING 1 diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c index faebc89a7ef5..92ce096e9b18 100644 --- a/drivers/nfc/nxp-nci/i2c.c +++ b/drivers/nfc/nxp-nci/i2c.c @@ -334,8 +334,10 @@ static int nxp_nci_i2c_probe(struct i2c_client *client) nxp_nci_i2c_irq_thread_fn, irqflags | IRQF_ONESHOT, NXP_NCI_I2C_DRIVER_NAME, phy); - if (r < 0) + if (r < 0) { nfc_err(&client->dev, "Unable to register IRQ handler\n"); + nxp_nci_remove(phy->ndev); + } return r; } @@ -355,16 +357,16 @@ static const struct i2c_device_id nxp_nci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, nxp_nci_i2c_id_table); static const struct of_device_id of_nxp_nci_i2c_match[] = { - { .compatible = "nxp,nxp-nci-i2c", }, - {} + { .compatible = "nxp,nxp-nci-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_nxp_nci_i2c_match); #ifdef CONFIG_ACPI static const struct acpi_device_id acpi_id[] = { - { "NXP1001" }, - { "NXP1002" }, - { "NXP7471" }, + { .id = "NXP1001" }, + { .id = "NXP1002" }, + { .id = "NXP7471" }, { } }; MODULE_DEVICE_TABLE(acpi, acpi_id); diff --git a/drivers/nfc/pn533/i2c.c b/drivers/nfc/pn533/i2c.c index 94aca9119f0f..66d201c14a40 100644 --- a/drivers/nfc/pn533/i2c.c +++ b/drivers/nfc/pn533/i2c.c @@ -236,15 +236,15 @@ static void pn533_i2c_remove(struct i2c_client *client) pn53x_common_clean(phy->priv); } -static const struct of_device_id of_pn533_i2c_match[] __maybe_unused = { - { .compatible = "nxp,pn532", }, +static const struct of_device_id of_pn533_i2c_match[] = { + { .compatible = "nxp,pn532" }, /* * NOTE: The use of the compatibles with the trailing "...-i2c" is * deprecated and will be removed. */ - { .compatible = "nxp,pn533-i2c", }, - { .compatible = "nxp,pn532-i2c", }, - {}, + { .compatible = "nxp,pn533-i2c" }, + { .compatible = "nxp,pn532-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_pn533_i2c_match); diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index d7bdbc82e2ba..f5a6a7c20d5a 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -434,6 +434,18 @@ static int pn533_send_async_complete(struct pn533 *dev) return rc; } +static int pn533_send_cmd_frame(struct pn533 *dev, struct pn533_cmd *cmd) +{ + struct sk_buff *req = cmd->req; + int rc; + + skb_get(req); + dev->cmd = cmd; + rc = dev->phy_ops->send_frame(dev, req); + dev_kfree_skb(req); + return rc; +} + static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, struct sk_buff *req, pn533_send_async_complete_t complete_cb, @@ -458,8 +470,7 @@ static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, mutex_lock(&dev->cmd_lock); if (!dev->cmd_pending) { - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc) { dev->cmd = NULL; goto error; @@ -529,8 +540,7 @@ static int pn533_send_cmd_direct_async(struct pn533 *dev, u8 cmd_code, pn533_build_cmd_frame(dev, cmd_code, req); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; kfree(cmd); @@ -569,8 +579,7 @@ static void pn533_wq_cmd(struct work_struct *work) mutex_unlock(&dev->cmd_lock); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, cmd->req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; dev_kfree_skb(cmd->req); @@ -740,8 +749,10 @@ static int pn533_target_found_type_a(struct nfc_target *nfc_tgt, u8 *tgt_data, struct pn533_target_felica { u8 pol_res; - u8 opcode; - u8 nfcid2[NFC_NFCID2_MAXSIZE]; + struct_group(sensf_res, + u8 opcode; + u8 nfcid2[NFC_NFCID2_MAXSIZE]; + ); u8 pad[8]; /* optional */ u8 syst_code[]; @@ -778,8 +789,8 @@ static int pn533_target_found_felica(struct nfc_target *nfc_tgt, u8 *tgt_data, else nfc_tgt->supported_protocols = NFC_PROTO_FELICA_MASK; - memcpy(nfc_tgt->sensf_res, &tgt_felica->opcode, 9); - nfc_tgt->sensf_res_len = 9; + memcpy(nfc_tgt->sensf_res, &tgt_felica->sensf_res, sizeof(tgt_felica->sensf_res)); + nfc_tgt->sensf_res_len = sizeof(tgt_felica->sensf_res); memcpy(nfc_tgt->nfcid2, tgt_felica->nfcid2, NFC_NFCID2_MAXSIZE); nfc_tgt->nfcid2_len = NFC_NFCID2_MAXSIZE; @@ -2799,6 +2810,7 @@ void pn53x_common_clean(struct pn533 *priv) destroy_workqueue(priv->wq); skb_queue_purge(&priv->resp_q); + skb_queue_purge(&priv->fragment_skb); list_for_each_entry_safe(cmd, n, &priv->cmd_queue, queue) { list_del(&cmd->queue); diff --git a/drivers/nfc/pn533/uart.c b/drivers/nfc/pn533/uart.c index e0d67cd2ac9b..83c1ccda0af6 100644 --- a/drivers/nfc/pn533/uart.c +++ b/drivers/nfc/pn533/uart.c @@ -238,8 +238,8 @@ static const struct serdev_device_ops pn532_serdev_ops = { }; static const struct of_device_id pn532_uart_of_match[] = { - { .compatible = "nxp,pn532", }, - {}, + { .compatible = "nxp,pn532" }, + { } }; MODULE_DEVICE_TABLE(of, pn532_uart_of_match); diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index dcfa96bd4345..7fde3aefae70 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -47,14 +47,12 @@ static const struct i2c_device_id pn544_hci_i2c_id_table[] = { { .name = "pn544" }, { } }; - MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); -static const struct acpi_device_id pn544_hci_i2c_acpi_match[] __maybe_unused = { - {"NXP5440", 0}, - {} +static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { + { .id = "NXP5440" }, + { } }; - MODULE_DEVICE_TABLE(acpi, pn544_hci_i2c_acpi_match); #define PN544_HCI_I2C_DRIVER_NAME "pn544_hci_i2c" @@ -939,9 +937,9 @@ static void pn544_hci_i2c_remove(struct i2c_client *client) pn544_hci_i2c_disable(phy); } -static const struct of_device_id of_pn544_i2c_match[] __maybe_unused = { - { .compatible = "nxp,pn544-i2c", }, - {}, +static const struct of_device_id of_pn544_i2c_match[] = { + { .compatible = "nxp,pn544-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_pn544_i2c_match); diff --git a/drivers/nfc/pn544/mei.c b/drivers/nfc/pn544/mei.c index 3d3755cfa71e..7ca117186d3e 100644 --- a/drivers/nfc/pn544/mei.c +++ b/drivers/nfc/pn544/mei.c @@ -47,10 +47,12 @@ static void pn544_mei_remove(struct mei_cl_device *cldev) } static struct mei_cl_device_id pn544_mei_tbl[] = { - { PN544_DRIVER_NAME, MEI_NFC_UUID, MEI_CL_VERSION_ANY}, - - /* required last entry */ - { } + { + .name = PN544_DRIVER_NAME, + .uuid = MEI_NFC_UUID, + .version = MEI_CL_VERSION_ANY, + }, + { /* required last entry */ } }; MODULE_DEVICE_TABLE(mei, pn544_mei_tbl); diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index 5ae61d7ebcfe..b613f5e2fd57 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -1480,8 +1480,8 @@ static const struct nfc_digital_ops port100_digital_ops = { }; static const struct usb_device_id port100_table[] = { - { USB_DEVICE(SONY_VENDOR_ID, RCS380S_PRODUCT_ID), }, - { USB_DEVICE(SONY_VENDOR_ID, RCS380P_PRODUCT_ID), }, + { USB_DEVICE(SONY_VENDOR_ID, RCS380S_PRODUCT_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, RCS380P_PRODUCT_ID) }, { } }; MODULE_DEVICE_TABLE(usb, port100_table); diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c index e9a34d27a369..4ba762611711 100644 --- a/drivers/nfc/s3fwrn5/i2c.c +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -210,9 +210,9 @@ static const struct i2c_device_id s3fwrn5_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table); -static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = { - { .compatible = "samsung,s3fwrn5-i2c", }, - {} +static const struct of_device_id of_s3fwrn5_i2c_match[] = { + { .compatible = "samsung,s3fwrn5-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match); diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c index e17c599a2da5..8f142a255101 100644 --- a/drivers/nfc/s3fwrn5/uart.c +++ b/drivers/nfc/s3fwrn5/uart.c @@ -85,8 +85,8 @@ static const struct serdev_device_ops s3fwrn82_serdev_ops = { }; static const struct of_device_id s3fwrn82_uart_of_match[] = { - { .compatible = "samsung,s3fwrn82", }, - {}, + { .compatible = "samsung,s3fwrn82" }, + { } }; MODULE_DEVICE_TABLE(of, s3fwrn82_uart_of_match); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 9ae839a6f5cc..152c20b6bb01 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -262,18 +262,18 @@ static const struct i2c_device_id st_nci_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table); -static const struct acpi_device_id st_nci_i2c_acpi_match[] __maybe_unused = { - {"SMO2101"}, - {"SMO2102"}, - {} +static const struct acpi_device_id st_nci_i2c_acpi_match[] = { + { .id = "SMO2101" }, + { .id = "SMO2102" }, + { } }; MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match); -static const struct of_device_id of_st_nci_i2c_match[] __maybe_unused = { - { .compatible = "st,st21nfcb-i2c", }, - { .compatible = "st,st21nfcb_i2c", }, - { .compatible = "st,st21nfcc-i2c", }, - {} +static const struct of_device_id of_st_nci_i2c_match[] = { + { .compatible = "st,st21nfcb-i2c" }, + { .compatible = "st,st21nfcb_i2c" }, + { .compatible = "st,st21nfcc-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 169eacc0a32a..7948c7e0c88c 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -271,21 +271,21 @@ static void st_nci_spi_remove(struct spi_device *dev) } static struct spi_device_id st_nci_spi_id_table[] = { - {ST_NCI_SPI_DRIVER_NAME, 0}, - {"st21nfcb-spi", 0}, - {} + { .name = ST_NCI_SPI_DRIVER_NAME }, + { .name = "st21nfcb-spi" }, + { } }; MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); -static const struct acpi_device_id st_nci_spi_acpi_match[] __maybe_unused = { - {"SMO2101", 0}, - {} +static const struct acpi_device_id st_nci_spi_acpi_match[] = { + { .id = "SMO2101" }, + { } }; MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); -static const struct of_device_id of_st_nci_spi_match[] __maybe_unused = { - { .compatible = "st,st21nfcb-spi", }, - {} +static const struct of_device_id of_st_nci_spi_match[] = { + { .compatible = "st,st21nfcb-spi" }, + { } }; MODULE_DEVICE_TABLE(of, of_st_nci_spi_match); diff --git a/drivers/nfc/st21nfca/dep.c b/drivers/nfc/st21nfca/dep.c index 3425b68f0ddc..a5fab4fd5129 100644 --- a/drivers/nfc/st21nfca/dep.c +++ b/drivers/nfc/st21nfca/dep.c @@ -205,6 +205,9 @@ static int st21nfca_tm_recv_atr_req(struct nfc_hci_dev *hdev, if (atr_req->length < sizeof(struct st21nfca_atr_req)) return -EPROTO; + if (atr_req->length > skb->len) + return -EPROTO; + r = st21nfca_tm_send_atr_res(hdev, atr_req); if (r) return r; diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index aa5f4922b6b0..a4c93ff7c5b0 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -577,16 +577,16 @@ static const struct i2c_device_id st21nfca_hci_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, st21nfca_hci_i2c_id_table); -static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] __maybe_unused = { - {"SMO2100", 0}, - {} +static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { + { .id = "SMO2100" }, + { } }; MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); -static const struct of_device_id of_st21nfca_i2c_match[] __maybe_unused = { - { .compatible = "st,st21nfca-i2c", }, - { .compatible = "st,st21nfca_i2c", }, - {} +static const struct of_device_id of_st21nfca_i2c_match[] = { + { .compatible = "st,st21nfca-i2c" }, + { .compatible = "st,st21nfca_i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_st21nfca_i2c_match); diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index ffe5b4eab457..4d772a308bff 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -450,19 +450,19 @@ static int st95hf_select_protocol(struct st95hf_context *stcontext, int type) static void st95hf_send_st95enable_negativepulse(struct st95hf_context *st95con) { /* First make irq_in pin high */ - gpiod_set_value(st95con->enable_gpiod, HIGH); + gpiod_set_value_cansleep(st95con->enable_gpiod, HIGH); /* wait for 1 milisecond */ usleep_range(1000, 2000); /* Make irq_in pin low */ - gpiod_set_value(st95con->enable_gpiod, LOW); + gpiod_set_value_cansleep(st95con->enable_gpiod, LOW); /* wait for minimum interrupt pulse to make st95 active */ usleep_range(1000, 2000); /* At end make it high */ - gpiod_set_value(st95con->enable_gpiod, HIGH); + gpiod_set_value_cansleep(st95con->enable_gpiod, HIGH); } /* @@ -1049,14 +1049,14 @@ static const struct nfc_digital_ops st95hf_nfc_digital_ops = { }; static const struct spi_device_id st95hf_id[] = { - { "st95hf", 0 }, - {} + { .name = "st95hf" }, + { } }; MODULE_DEVICE_TABLE(spi, st95hf_id); -static const struct of_device_id st95hf_spi_of_match[] __maybe_unused = { +static const struct of_device_id st95hf_spi_of_match[] = { { .compatible = "st,st95hf" }, - {}, + { } }; MODULE_DEVICE_TABLE(of, st95hf_spi_of_match); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index f22e091019de..60883001fa5d 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -938,7 +938,7 @@ static irqreturn_t trf7970a_irq(int irq, void *dev_id) if (!trf->timeout) { trf->ignore_timeout = !cancel_delayed_work(&trf->timeout_work); - trf->rx_skb = ERR_PTR(0); + trf->rx_skb = NULL; trf7970a_send_upstream(trf); break; } @@ -2303,18 +2303,16 @@ static const struct dev_pm_ops trf7970a_pm_ops = { trf7970a_pm_runtime_resume, NULL) }; -static const struct of_device_id trf7970a_of_match[] __maybe_unused = { - {.compatible = "ti,trf7970a",}, - {}, +static const struct of_device_id trf7970a_of_match[] = { + { .compatible = "ti,trf7970a" }, + { } }; - MODULE_DEVICE_TABLE(of, trf7970a_of_match); static const struct spi_device_id trf7970a_id_table[] = { - {"trf7970a", 0}, - {} + { .name = "trf7970a" }, + { } }; - MODULE_DEVICE_TABLE(spi, trf7970a_id_table); static struct spi_driver trf7970a_spi_driver = { diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index 04241df8fd59..59a806004d40 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -20,9 +20,6 @@ #include "pcie-designware.h" -#define PCI_VENDOR_ID_SPACEMIT 0x201f -#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 - /* Offsets and field definitions for link management registers */ #define K1_PHY_AHB_IRQ_EN 0x0000 #define PCIE_INTERRUPT_EN BIT(0) diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index b93640ca08b7..feb50f8cc406 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -78,6 +78,7 @@ config DP83640_PHY depends on PHYLIB depends on PTP_1588_CLOCK select CRC32 + select PHY_PACKAGE help Supports the DP83640 PHYTER with IEEE 1588 features. diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c index d6f54ccaf93b..4111342d64f0 100644 --- a/drivers/ptp/ptp_clock.c +++ b/drivers/ptp/ptp_clock.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -159,7 +160,18 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx) delta = ktime_to_ns(kt); err = ops->adjtime(ops, delta); } else if (tx->modes & ADJ_FREQUENCY) { - long ppb = scaled_ppm_to_ppb(tx->freq); + long ppb; + s64 tmp; + + /* + * scaled_ppm_to_ppb() multiplies (1 + freq) by 125 in s64; + * reject a ->freq large enough to overflow that, which would + * otherwise wrap the result back into the max_adj range. + */ + if (check_add_overflow((s64)tx->freq, (s64)1, &tmp) || + check_mul_overflow(tmp, (s64)125, &tmp)) + return -ERANGE; + ppb = scaled_ppm_to_ppb(tx->freq); if (ppb > ops->max_adj || ppb < -ops->max_adj) return -ERANGE; err = ops->adjfine(ops, tx->freq); diff --git a/drivers/ptp/ptp_fc3.c b/drivers/ptp/ptp_fc3.c index f0e000428a3f..02b973995d7c 100644 --- a/drivers/ptp/ptp_fc3.c +++ b/drivers/ptp/ptp_fc3.c @@ -665,8 +665,6 @@ static int idtfc3_init_timecounter(struct idtfc3 *idtfc3) if (err) return err; - ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); - return 0; } @@ -825,6 +823,14 @@ static int idtfc3_enable_ptp(struct idtfc3 *idtfc3) idtfc3->caps = idtfc3_caps; snprintf(idtfc3->caps.name, sizeof(idtfc3->caps.name), "IDT FC3W"); + err = idtfc3_set_overhead(idtfc3); + if (err) + return err; + + err = idtfc3_init_timecounter(idtfc3); + if (err) + return err; + idtfc3->ptp_clock = ptp_clock_register(&idtfc3->caps, NULL); if (IS_ERR(idtfc3->ptp_clock)) { @@ -833,13 +839,7 @@ static int idtfc3_enable_ptp(struct idtfc3 *idtfc3) return err; } - err = idtfc3_set_overhead(idtfc3); - if (err) - return err; - - err = idtfc3_init_timecounter(idtfc3); - if (err) - return err; + ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); dev_info(idtfc3->dev, "TIME_SYNC_CHANNEL registered as ptp%d", idtfc3->ptp_clock->index); diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 1c20d7efab92..59db08e189e6 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -482,6 +482,9 @@ static int net_timer_enable_perout(struct netc_timer *priv, netc_timer_enable_periodic_pulse(priv, channel); } else { + if (!pp->enabled) + goto unlock_spinlock; + netc_timer_disable_periodic_pulse(priv, channel); priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id); memset(pp, 0, sizeof(*pp)); diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index eebdcd5ebc08..bb0e14bac9f2 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -372,6 +372,12 @@ static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) return -EROFS; + /* + * Restrict the read-only mapping so it cannot be upgraded to + * writable later with mprotect(). + */ + vm_flags_clear(vma, VM_MAYWRITE); + if (vma->vm_end - vma->vm_start != PAGE_SIZE || vma->vm_pgoff) return -EINVAL; diff --git a/drivers/s390/net/Makefile b/drivers/s390/net/Makefile index 537514cc52fb..038ba3e4005d 100644 --- a/drivers/s390/net/Makefile +++ b/drivers/s390/net/Makefile @@ -3,6 +3,8 @@ # S/390 network devices # +CONTEXT_ANALYSIS := y + ctcm-y += ctcm_main.o ctcm_fsms.o ctcm_mpc.o ctcm_sysfs.o ctcm_dbug.o obj-$(CONFIG_CTCM) += ctcm.o fsm.o obj-$(CONFIG_SMSGIUCV) += smsgiucv.o diff --git a/drivers/s390/net/ctcm_fsms.c b/drivers/s390/net/ctcm_fsms.c index bf917f426453..84fd394d3525 100644 --- a/drivers/s390/net/ctcm_fsms.c +++ b/drivers/s390/net/ctcm_fsms.c @@ -545,6 +545,7 @@ static void chx_rxidle(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; int rc; @@ -563,8 +564,6 @@ static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is undeterministic in - * static view. => ignore sparse warnings here. */ rc = ccw_device_start(ch->cdev, &ch->ccw[6], 0, 0xff, 0); if (event == CTC_EVENT_TIMER) /* see above comments */ @@ -648,6 +647,7 @@ static void ctcm_chx_start(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; unsigned long saveflags = 0; @@ -662,15 +662,12 @@ static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) if (event == CTC_EVENT_STOP) /* only for STOP not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is undeterministic in - * static view. => ignore sparse warnings here. */ oldstate = fsm_getstate(fi); fsm_newstate(fi, CTC_STATE_TERM); rc = ccw_device_halt(ch->cdev, 0); if (event == CTC_EVENT_STOP) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - /* see remark above about conditional locking */ if (rc != 0 && rc != -EBUSY) { fsm_deltimer(&ch->timer); @@ -824,6 +821,7 @@ static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -842,9 +840,6 @@ static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg) fsm_newstate(fi, CTC_STATE_STARTWAIT); if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is a known problem for - * sparse because its undeterministic in static view. - * Warnings should be ignored here. */ rc = ccw_device_halt(ch->cdev, 0); if (event == CTC_EVENT_TIMER) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); @@ -999,6 +994,7 @@ static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1042,9 +1038,6 @@ static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); if (event == CTC_EVENT_TIMER) /* for TIMER not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is a known problem for - * sparse because its undeterministic in static view. - * Warnings should be ignored here. */ if (do_debug_ccw) ctcmpc_dumpit((char *)&ch->ccw[3], sizeof(struct ccw1) * 3); @@ -1383,6 +1376,7 @@ static void ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1462,7 +1456,7 @@ static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) spin_lock_irqsave( get_ccwdev_lock(ch->cdev), saveflags); rc = ccw_device_start(ch->cdev, &ch->ccw[0], 0, 0xff, 0); - if (dolock) /* see remark about conditional locking */ + if (dolock) spin_unlock_irqrestore( get_ccwdev_lock(ch->cdev), saveflags); if (rc != 0) @@ -1539,6 +1533,7 @@ static void ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1566,7 +1561,6 @@ void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) ch->ccw[1].count = ch->max_bufsize; CTCM_CCW_DUMP((char *)&ch->ccw[0], sizeof(struct ccw1) * 3); if (event == CTC_EVENT_START) - /* see remark about conditional locking */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); rc = ccw_device_start(ch->cdev, &ch->ccw[0], 0, 0xff, 0); if (event == CTC_EVENT_START) diff --git a/drivers/s390/net/ctcm_mpc.c b/drivers/s390/net/ctcm_mpc.c index aeb102537e7f..08e36685e578 100644 --- a/drivers/s390/net/ctcm_mpc.c +++ b/drivers/s390/net/ctcm_mpc.c @@ -1647,6 +1647,7 @@ static int mpc_validate_xid(struct mpcg_info *mpcginfo) * CTCM_PROTO_MPC only */ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; int rc = 0; @@ -1774,9 +1775,6 @@ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) CTCM_D3_DUMP((char *)ch->xid_id, 4); if (!in_hardirq()) { - /* Such conditional locking is a known problem for - * sparse because its static undeterministic. - * Warnings should be ignored here. */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); gotlock = 1; } @@ -1784,7 +1782,7 @@ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) fsm_addtimer(&ch->timer, 5000 , CTC_EVENT_TIMER, ch); rc = ccw_device_start(ch->cdev, &ch->ccw[8], 0, 0xff, 0); - if (gotlock) /* see remark above about conditional locking */ + if (gotlock) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); if (rc != 0) { diff --git a/drivers/s390/net/fsm.h b/drivers/s390/net/fsm.h index 16dc071a2973..6a0b47ca87f0 100644 --- a/drivers/s390/net/fsm.h +++ b/drivers/s390/net/fsm.h @@ -11,18 +11,18 @@ #include #include -/** +/* * Define this to get debugging messages. */ #define FSM_DEBUG 0 -/** +/* * Define this to get debugging massages for * timer handling. */ #define FSM_TIMER_DEBUG 0 -/** +/* * Define these to record a history of * Events/Statechanges and print it if a * action_function is not found. @@ -32,12 +32,12 @@ struct fsm_instance_t; -/** +/* * Definition of an action function, called by a FSM */ typedef void (*fsm_function_t)(struct fsm_instance_t *, int, void *); -/** +/* * Internal jump table for a FSM */ typedef struct { @@ -49,7 +49,7 @@ typedef struct { } fsm; #if FSM_DEBUG_HISTORY -/** +/* * Element of State/Event history used for debugging. */ typedef struct { @@ -58,7 +58,7 @@ typedef struct { } fsm_history; #endif -/** +/* * Representation of a FSM */ typedef struct fsm_instance_t { @@ -75,7 +75,7 @@ typedef struct fsm_instance_t { #endif } fsm_instance; -/** +/* * Description of a state-event combination */ typedef struct { @@ -84,7 +84,7 @@ typedef struct { fsm_function_t function; } fsm_node; -/** +/* * Description of a FSM Timer. */ typedef struct { @@ -95,50 +95,52 @@ typedef struct { } fsm_timer; /** - * Creates an FSM + * init_fsm - Creates a finite state machine + * @name: Name of this instance for logging purposes + * @state_names: Array of names for all states for logging purposes + * @event_names: Array of names for all events for logging purposes + * @nr_states: Number of states for this instance + * @nr_events: Number of events for this instance + * @tmpl: Pointer to fsm_node array describing this FSM + * @tmpl_len: Number of entries in the tmpl array + * @order: GFP flags for memory allocation (e.g. GFP_KERNEL) * - * @param name Name of this instance for logging purposes. - * @param state_names An array of names for all states for logging purposes. - * @param event_names An array of names for all events for logging purposes. - * @param nr_states Number of states for this instance. - * @param nr_events Number of events for this instance. - * @param tmpl An array of fsm_nodes, describing this FSM. - * @param tmpl_len Length of the describing array. - * @param order Parameter for allocation of the FSM data structs. + * Allocates and initializes a finite state machine instance with the + * specified states, events, and transition table. + * + * Return: Pointer to initialized FSM instance, or NULL on failure */ -extern fsm_instance * -init_fsm(char *name, const char **state_names, - const char **event_names, - int nr_states, int nr_events, const fsm_node *tmpl, - int tmpl_len, gfp_t order); +fsm_instance *init_fsm(char *name, const char **state_names, + const char **event_names, int nr_states, + int nr_events, const fsm_node *tmpl, + int tmpl_len, gfp_t order); /** - * Releases an FSM + * kfree_fsm - Releases a finite state machine + * @fi: Pointer to FSM instance, previously created with init_fsm() * - * @param fi Pointer to an FSM, previously created with init_fsm. + * Frees all memory associated with the FSM instance. */ -extern void kfree_fsm(fsm_instance *fi); +void kfree_fsm(fsm_instance *fi); #if FSM_DEBUG_HISTORY -extern void -fsm_print_history(fsm_instance *fi); +void fsm_print_history(fsm_instance *fi); -extern void -fsm_record_history(fsm_instance *fi, int state, int event); +void fsm_record_history(fsm_instance *fi, int state, int event); #endif /** - * Emits an event to a FSM. - * If an action function is defined for the current state/event combination, - * this function is called. + * fsm_event - Emits an event to a finite state machine + * @fi: Pointer to FSM which should receive the event + * @event: The event to be delivered + * @arg: Generic argument, passed to the action function * - * @param fi Pointer to FSM which should receive the event. - * @param event The event do be delivered. - * @param arg A generic argument, handed to the action function. + * If an action function is defined for the current state/event + * combination, that function is called with the provided arguments. * - * @return 0 on success, - * 1 if current state or event is out of range - * !0 if state and event in range, but no action defined. + * Return: + * * 0 - Success, action function was called + * * 1 - State/event out of range, or no action function defined */ static inline int fsm_event(fsm_instance *fi, int event, void *arg) @@ -182,11 +184,12 @@ fsm_event(fsm_instance *fi, int event, void *arg) } /** - * Modifies the state of an FSM. - * This does not trigger an event or calls an action function. + * fsm_newstate - Modifies the state of a finite state machine + * @fi: Pointer to FSM + * @newstate: The new state for this FSM * - * @param fi Pointer to FSM - * @param state The new state for this FSM. + * This does not trigger an event or call an action function. + * Wakes up any processes waiting on the FSM's wait queue. */ static inline void fsm_newstate(fsm_instance *fi, int newstate) @@ -203,11 +206,10 @@ fsm_newstate(fsm_instance *fi, int newstate) } /** - * Retrieves the state of an FSM + * fsm_getstate - Retrieves the current state of a finite state machine + * @fi: Pointer to FSM * - * @param fi Pointer to FSM - * - * @return The current state of the FSM. + * Return: Current state number */ static inline int fsm_getstate(fsm_instance *fi) @@ -216,51 +218,53 @@ fsm_getstate(fsm_instance *fi) } /** - * Retrieves the name of the state of an FSM + * fsm_getstate_str - Retrieves the name of the current FSM state + * @fi: Pointer to FSM * - * @param fi Pointer to FSM - * - * @return The current state of the FSM in a human readable form. + * Return: State name string, or "Invalid" if state is out of range */ -extern const char *fsm_getstate_str(fsm_instance *fi); +const char *fsm_getstate_str(fsm_instance *fi); /** - * Initializes a timer for an FSM. - * This prepares an fsm_timer for usage with fsm_addtimer. + * fsm_settimer - Initializes a timer for a finite state machine + * @fi: Pointer to FSM + * @this: The timer to be initialized * - * @param fi Pointer to FSM - * @param timer The timer to be initialized. + * Prepares an fsm_timer for usage with fsm_addtimer(). */ -extern void fsm_settimer(fsm_instance *fi, fsm_timer *); +void fsm_settimer(fsm_instance *fi, fsm_timer *this); /** - * Clears a pending timer of an FSM instance. + * fsm_deltimer - Clears a pending timer of an FSM instance + * @timer: The timer to clear * - * @param timer The timer to clear. + * Stops and removes the timer. Safe to call on an inactive timer. */ -extern void fsm_deltimer(fsm_timer *timer); +void fsm_deltimer(fsm_timer *timer); /** - * Adds and starts a timer to an FSM instance. + * fsm_addtimer - Adds and starts a timer for an FSM instance + * @timer: The timer to be added (timer->fi must point to the FSM instance) + * @millisec: Duration in milliseconds after which the timer expires + * @event: Event to trigger when timer expires + * @arg: Generic argument provided to the event handler * - * @param timer The timer to be added. The field fi of that timer - * must have been set to point to the instance. - * @param millisec Duration, after which the timer should expire. - * @param event Event, to trigger if timer expires. - * @param arg Generic argument, provided to expiry function. + * Starts a timer that will trigger the specified event after the given + * duration. The timer must have been initialized with fsm_settimer(). * - * @return 0 on success, -1 if timer is already active. + * Return: Always returns 0 */ -extern int fsm_addtimer(fsm_timer *timer, int millisec, int event, void *arg); +int fsm_addtimer(fsm_timer *timer, int millisec, int event, void *arg); /** - * Modifies a timer of an FSM. + * fsm_modtimer - Modifies a timer of a finite state machine + * @timer: The timer to modify + * @millisec: New duration in milliseconds after which the timer expires + * @event: Event to trigger when timer expires + * @arg: Generic argument provided to the event handler * - * @param timer The timer to modify. - * @param millisec Duration, after which the timer should expire. - * @param event Event, to trigger if timer expires. - * @param arg Generic argument, provided to expiry function. + * Stops the existing timer and restarts it with new parameters. */ -extern void fsm_modtimer(fsm_timer *timer, int millisec, int event, void *arg); +void fsm_modtimer(fsm_timer *timer, int millisec, int event, void *arg); #endif /* _FSM_H_ */ diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 9fa3131feb66..bb74d8cd7659 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -2534,7 +2534,7 @@ static int _cfg80211_rtw_mgmt_tx(struct adapter *padapter, u8 tx_ch, const u8 *b static int cfg80211_rtw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { struct net_device *ndev = wdev_to_ndev(wdev); struct ieee80211_channel *chan = params->chan; @@ -2556,11 +2556,8 @@ static int cfg80211_rtw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, padapter = rtw_netdev_priv(ndev); - /* cookie generation */ - *cookie = (unsigned long)buf; - /* indicate ack before issue frame to avoid racing with rsp frame */ - rtw_cfg80211_mgmt_tx_status(padapter, *cookie, buf, len, ack, GFP_KERNEL); + rtw_cfg80211_mgmt_tx_status(padapter, cookie, buf, len, ack, GFP_KERNEL); if (!rtw_action_frame_parse(buf, len, &category, &action)) goto exit; diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index 429ac20a8999..636f7886fc26 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -1233,8 +1233,6 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, struct usb_interface *intf) { struct cxacru_data *instance = usbatm_instance->driver_data; - int is_polling = 1; - usb_dbg(usbatm_instance, "cxacru_unbind entered\n"); if (!instance) { @@ -1245,17 +1243,11 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, mutex_lock(&instance->poll_state_serialize); BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); - /* ensure that status polling continues unless - * it has already stopped */ - if (instance->poll_state == CXPOLL_STOPPED) - is_polling = 0; - /* stop polling from being stopped or started */ instance->poll_state = CXPOLL_SHUTDOWN; mutex_unlock(&instance->poll_state_serialize); - if (is_polling) - cancel_delayed_work_sync(&instance->poll_work); + cancel_delayed_work_sync(&instance->poll_work); usb_kill_urb(instance->snd_urb); usb_kill_urb(instance->rcv_urb); diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 4266a0cb7e3b..61723e7ab351 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -2463,7 +2463,8 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, if (ifnum != UEA_INTR_IFACE_NO) return -ENODEV; - usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT); + usbatm->flags = (modem_index < NB_MODEM && sync_wait[modem_index]) ? + 0 : UDSL_SKIP_HEAVY_INIT; /* interface 1 is for outbound traffic */ ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 6949b704166d..3e72b9c6af0c 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -176,13 +176,21 @@ static void *vhost_net_buf_consume(struct vhost_net_buf *rxq) return ret; } -static int vhost_net_buf_produce(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_produce(struct sock *sk, + struct vhost_net_virtqueue *nvq) { + struct file *file = sk->sk_socket->file; struct vhost_net_buf *rxq = &nvq->rxq; rxq->head = 0; - rxq->tail = ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, - VHOST_NET_BATCH); + spin_lock(&nvq->rx_ring->consumer_lock); + rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, + VHOST_NET_BATCH); + + if (rxq->tail) + tun_wake_queue(file, rxq->tail); + + spin_unlock(&nvq->rx_ring->consumer_lock); return rxq->tail; } @@ -209,14 +217,15 @@ static int vhost_net_buf_peek_len(void *ptr) return __skb_array_len_with_tag(ptr); } -static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_peek(struct sock *sk, + struct vhost_net_virtqueue *nvq) { struct vhost_net_buf *rxq = &nvq->rxq; if (!vhost_net_buf_is_empty(rxq)) goto out; - if (!vhost_net_buf_produce(nvq)) + if (!vhost_net_buf_produce(sk, nvq)) return 0; out: @@ -1004,7 +1013,7 @@ static int peek_head_len(struct vhost_net_virtqueue *rvq, struct sock *sk) unsigned long flags; if (rvq->rx_ring) - return vhost_net_buf_peek(rvq); + return vhost_net_buf_peek(sk, rvq); spin_lock_irqsave(&sk->sk_receive_queue.lock, flags); head = skb_peek(&sk->sk_receive_queue); diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h index 016c74fb747c..802b143de83d 100644 --- a/include/cxl/cxl.h +++ b/include/cxl/cxl.h @@ -226,4 +226,6 @@ struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev, struct cxl_memdev *devm_cxl_probe_mem(struct cxl_dev_state *cxlds, struct range *range); + +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity); #endif /* __CXL_CXL_H__ */ diff --git a/include/cxl/pci.h b/include/cxl/pci.h new file mode 100644 index 000000000000..3e0000015871 --- /dev/null +++ b/include/cxl/pci.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2020 Intel Corporation. All rights reserved. */ + +#ifndef __CXL_CXL_PCI_H__ +#define __CXL_CXL_PCI_H__ + +/* Register Block Identifier (RBI) */ +enum cxl_regloc_type { + CXL_REGLOC_RBI_EMPTY = 0, + CXL_REGLOC_RBI_COMPONENT, + CXL_REGLOC_RBI_VIRT, + CXL_REGLOC_RBI_MEMDEV, + CXL_REGLOC_RBI_PMU, + CXL_REGLOC_RBI_TYPES +}; + +struct cxl_register_map; +struct pci_dev; + +int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, + struct cxl_register_map *map); +#endif diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index fe21d2f547b5..92c2e20e7416 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -229,9 +229,6 @@ static inline void atm_dev_put(struct atm_dev *dev) int atm_charge(struct atm_vcc *vcc,int truesize); -struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc,int pdu_size, - gfp_t gfp_flags); -int atm_pcr_goal(const struct atm_trafprm *tp); void vcc_release_async(struct atm_vcc *vcc, int reply); diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index d3b6c476b91a..764ef2892608 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -75,8 +75,10 @@ struct ntmp_user { /* NTMP table bitmaps for resource management */ u32 ett_bitmap_size; u32 ect_bitmap_size; + u16 maft_num_entries; unsigned long *ett_gid_bitmap; /* only valid for switch */ unsigned long *ect_gid_bitmap; /* only valid for switch */ + unsigned long *maft_eid_bitmap; /* only valid for ENETC */ }; struct maft_entry_data { diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index c109722b1969..b62297a978e7 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -481,6 +481,7 @@ struct ieee80211_multi_link_elem { #define IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP 0x0100 #define IEEE80211_MLC_BASIC_PRES_MLD_ID 0x0200 #define IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP 0x0400 +#define IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD 0x0800 #define IEEE80211_MED_SYNC_DELAY_DURATION 0x00ff #define IEEE80211_MED_SYNC_DELAY_SYNC_OFDM_ED_THRESH 0x0f00 @@ -809,6 +810,49 @@ static inline u16 ieee80211_mle_get_ext_mld_capa_op(const u8 *data) return get_unaligned_le16(common); } +/** + * ieee80211_mle_get_enh_crit_upd_info - returns the enhanced critical + * updates information + * @data: pointer to the multi-link element + * Return: the enhanced critical updates information field, or %NULL + * + * The element is assumed to be of the correct type (BASIC) and big enough, + * this must be checked using ieee80211_mle_type_ok(). + */ +static inline const struct ieee80211_enh_crit_upd * +ieee80211_mle_get_enh_crit_upd_info(const u8 *data) +{ + const struct ieee80211_multi_link_elem *mle = (const void *)data; + u16 control = le16_to_cpu(mle->control); + const u8 *common = mle->variable; + + /* + * common points now at the beginning of + * ieee80211_mle_basic_common_info + */ + common += sizeof(struct ieee80211_mle_basic_common_info); + + if (!(control & IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD)) + return NULL; + + if (control & IEEE80211_MLC_BASIC_PRES_LINK_ID) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_BSS_PARAM_CH_CNT) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_MED_SYNC_DELAY) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_EML_CAPA) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_MLD_ID) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP) + common += 2; + + return (const void *)common; +} + /** * ieee80211_mle_get_mld_id - returns the MLD ID * @data: pointer to the multi-link element @@ -882,6 +926,8 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += 1; if (control & IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP) common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD) + common += 1; break; case IEEE80211_ML_CONTROL_TYPE_PREQ: common += sizeof(struct ieee80211_mle_preq_common_info); @@ -955,6 +1001,8 @@ enum ieee80211_mle_subelems { #define IEEE80211_MLE_STA_CONTROL_NSTR_LINK_PAIR_PRESENT 0x0200 #define IEEE80211_MLE_STA_CONTROL_NSTR_BITMAP_SIZE 0x0400 #define IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT 0x0800 +#define IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT 0x1000 +#define IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT 0x2000 struct ieee80211_mle_per_sta_profile { __le16 control; @@ -1000,6 +1048,12 @@ static inline bool ieee80211_mle_basic_sta_prof_size_ok(const u8 *data, if (control & IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT) info_len += 1; + if (control & IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT) + info_len += 1; + + if (control & IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT) + info_len += 1; + return prof->sta_info_len >= info_len && fixed + prof->sta_info_len - 1 <= len; } @@ -1040,6 +1094,44 @@ ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(const struct ieee80211_mle_per_sta return *pos; } +/** + * ieee80211_mle_basic_sta_prof_enh_crit_upd - get per-STA profile enhanced + * critical updates field + * @prof: the per-STA profile, having been checked with + * ieee80211_mle_basic_sta_prof_size_ok() for the correct length + * + * Return: The enhanced critical updates field if present, %NULL otherwise. + */ +static inline const struct ieee80211_enh_crit_upd * +ieee80211_mle_basic_sta_prof_enh_crit_upd(const struct ieee80211_mle_per_sta_profile *prof) +{ + u16 control = le16_to_cpu(prof->control); + const u8 *pos = prof->variable; + + if (!(control & IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT)) + return NULL; + + if (control & IEEE80211_MLE_STA_CONTROL_STA_MAC_ADDR_PRESENT) + pos += 6; + if (control & IEEE80211_MLE_STA_CONTROL_BEACON_INT_PRESENT) + pos += 2; + if (control & IEEE80211_MLE_STA_CONTROL_TSF_OFFS_PRESENT) + pos += 8; + if (control & IEEE80211_MLE_STA_CONTROL_DTIM_INFO_PRESENT) + pos += 2; + if (control & IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE && + control & IEEE80211_MLE_STA_CONTROL_NSTR_LINK_PAIR_PRESENT) { + if (control & IEEE80211_MLE_STA_CONTROL_NSTR_BITMAP_SIZE) + pos += 2; + else + pos += 1; + } + if (control & IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT) + pos += 1; + + return (const void *)pos; +} + #define IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID 0x000f #define IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE 0x0010 #define IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT 0x0020 diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 597c9e559261..665d4b3a5b41 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -17,6 +17,11 @@ #define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0004 #define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0008 #define IEEE80211_UHR_OPER_PARAMS_DBE_BW 0x0070 +#define IEEE80211_UHR_OPER_PARAMS_DUO_PRES 0x0080 +#define IEEE80211_UHR_OPER_PARAMS_DPS_PRES 0x0100 +#define IEEE80211_UHR_OPER_PARAMS_NPCA_PRES 0x0200 +#define IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES 0x0400 +#define IEEE80211_UHR_OPER_PARAMS_DBE_PRES 0x0800 struct ieee80211_uhr_operation { __le16 params; @@ -265,8 +270,7 @@ struct ieee80211_uhr_p_edca_info { __le16 params; } __packed; -static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, - bool beacon) +static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len) { const struct ieee80211_uhr_operation *oper = (const void *)data; u8 needed = sizeof(*oper); @@ -274,19 +278,15 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, if (len < needed) return false; - /* nothing else present in beacons */ - if (beacon) - return true; - /* DPS Operation Parameters (fixed 4 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) { needed += sizeof(struct ieee80211_uhr_dps_info); if (len < needed) return false; } /* NPCA Operation Parameters (fixed 4 bytes + optional 2 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES)) { const struct ieee80211_uhr_npca_info *npca = (const void *)(data + needed); @@ -303,14 +303,14 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, } /* P-EDCA Operation Parameters (fixed 3 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES)) { needed += sizeof(struct ieee80211_uhr_p_edca_info); if (len < needed) return false; } /* DBE Operation Parameters (fixed 1 byte + optional 2 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_PRES)) { const struct ieee80211_uhr_dbe_info *dbe = (const void *)(data + needed); @@ -329,19 +329,19 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, return len >= needed; } -/* - * Note: cannot call this on the element coming from a beacon, - * must ensure ieee80211_uhr_oper_size_ok(..., false) first - */ +/* Note: must ensure ieee80211_uhr_oper_size_ok(...) first */ static inline const struct ieee80211_uhr_npca_info * ieee80211_uhr_npca_info(const struct ieee80211_uhr_operation *oper) { const u8 *pos = oper->variable; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES))) + return NULL; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA))) return NULL; - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); return (const void *)pos; @@ -360,22 +360,22 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper) return npca->dis_subch_bmap; } -/* - * Note: cannot call this on the element coming from a beacon, - * must ensure ieee80211_uhr_oper_size_ok(..., false) first - */ +/* Note: must ensure ieee80211_uhr_oper_size_ok(...) first */ static inline const struct ieee80211_uhr_dbe_info * ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) { const u8 *pos = oper->variable; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_PRES))) + return NULL; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA))) return NULL; - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES)) { const struct ieee80211_uhr_npca_info *npca = (const void *)pos; pos += sizeof(*npca); @@ -383,7 +383,7 @@ ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) pos += sizeof(npca->dis_subch_bmap[0]); } - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES)) pos += sizeof(struct ieee80211_uhr_p_edca_info); return (const void *)pos; diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index d40484451e9a..26e674038865 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -556,6 +556,17 @@ static inline bool ieee80211_is_reassoc_resp(__le16 fc) cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_REASSOC_RESP); } +/** + * ieee80211_is_assoc - check if (Re)association request/response frame + * @fc: frame control bytes in little-endian byteorder + * Return: whether or not the frame is an (re)association request or response + */ +static inline bool ieee80211_is_assoc(__le16 fc) +{ + return ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc) || + ieee80211_is_assoc_resp(fc) || ieee80211_is_reassoc_resp(fc); +} + /** * ieee80211_is_probe_req - check if IEEE80211_FTYPE_MGMT && IEEE80211_STYPE_PROBE_REQ * @fc: frame control bytes in little-endian byteorder @@ -2616,6 +2627,7 @@ static inline int ieee80211_get_tdls_action(struct sk_buff *skb) /* convert frequencies */ #define MHZ_TO_KHZ(freq) ((freq) * 1000) #define KHZ_TO_MHZ(freq) ((freq) / 1000) +#define KHZ_TO_HZ(x) ((x) * 1000) #define PR_KHZ(f) KHZ_TO_MHZ(f), f % 1000 #define KHZ_F "%d.%03d" diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 80166eb62f41..eeb9ed3c5a23 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -22,6 +22,8 @@ struct tun_msg_ctl { #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE) struct socket *tun_get_socket(struct file *); struct ptr_ring *tun_get_tx_ring(struct file *file); +/* Callers must hold the consumer_lock of the ring of file */ +void tun_wake_queue(struct file *file, int consumed); static inline bool tun_is_xdp_frame(void *ptr) { @@ -55,6 +57,8 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f) return ERR_PTR(-EINVAL); } +static inline void tun_wake_queue(struct file *f, int consumed) {} + static inline bool tun_is_xdp_frame(void *ptr) { return false; diff --git a/include/linux/mdio.h b/include/linux/mdio.h index f4f9d9609448..a7d9e3ae362a 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -8,7 +8,6 @@ #include #include -#include struct gpio_desc; struct mii_bus; @@ -87,6 +86,11 @@ static inline void *mdiodev_get_drvdata(struct mdio_device *mdio) return dev_get_drvdata(&mdio->dev); } +static inline bool mdiodev_has_reset(struct mdio_device *mdio) +{ + return (mdio->reset_gpio || mdio->reset_ctrl); +} + void mdio_device_free(struct mdio_device *mdiodev); struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr); int mdio_device_register(struct mdio_device *mdiodev); diff --git a/include/linux/mlx5/device.h b/include/linux/mlx5/device.h index 07a25f264292..8cb321a9fb3d 100644 --- a/include/linux/mlx5/device.h +++ b/include/linux/mlx5/device.h @@ -1171,6 +1171,7 @@ enum { MLX5_MATCH_MISC_PARAMETERS_3 = 1 << 4, MLX5_MATCH_MISC_PARAMETERS_4 = 1 << 5, MLX5_MATCH_MISC_PARAMETERS_5 = 1 << 6, + MLX5_MATCH_MISC_PARAMETERS_6 = 1 << 7, }; enum { diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index b1871c0821d0..83d0a83bbfbc 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -548,6 +548,7 @@ struct mlx5_debugfs_entries { struct dentry *cq_debugfs; struct dentry *cmdif_debugfs; struct dentry *frag_buf_dma_pools_debugfs; + struct dentry *db_dma_pools_debugfs; struct dentry *pages_debugfs; struct dentry *lag_debugfs; }; @@ -569,6 +570,7 @@ enum mlx5_page_mgt_mode { }; struct mlx5_frag_buf_node_pools; +struct mlx5_dma_pool; struct mlx5_ft_pool; struct mlx5_priv { /* IRQ table valid only for real pci devices PF or VF */ @@ -598,10 +600,8 @@ struct mlx5_priv { struct mutex alloc_mutex; int numa_node; - struct mutex pgdir_mutex; - struct list_head pgdir_list; - struct mlx5_frag_buf_node_pools **frag_buf_node_pools; + struct mlx5_dma_pool **db_node_pools; /* end: alloc stuff */ struct mlx5_adev **adev; @@ -806,7 +806,7 @@ struct mlx5_core_dev { struct mlx5_db { __be32 *db; union { - struct mlx5_db_pgdir *pgdir; + struct mlx5_dma_pool_page *pool_page; struct mlx5_ib_user_db_page *user_page; } u; dma_addr_t dma; diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 8f18a508320d..bc4b8734ec40 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -508,7 +508,8 @@ struct mlx5_ifc_flow_table_prop_layout_bits { u8 reformat_l2_to_l3_audp_tunnel[0x1]; u8 reformat_l3_audp_tunnel_to_l2[0x1]; u8 ignore_flow_level_rtc_valid[0x1]; - u8 reserved_at_70[0x8]; + u8 reserved_at_70[0x7]; + u8 reformat_del_psp_transport[0x1]; u8 log_max_ft_num[0x8]; u8 reserved_at_80[0x10]; @@ -798,6 +799,15 @@ struct mlx5_ifc_fte_match_set_misc5_bits { u8 reserved_at_100[0x100]; }; +struct mlx5_ifc_fte_match_set_misc6_bits { + u8 reserved_at_0[0x1a]; + u8 psp_version[0x4]; + u8 reserved_at_1e[0x2]; + + u8 reserved_at_20[0x1e0]; +}; + + struct mlx5_ifc_cmd_pas_bits { u8 pa_h[0x20]; @@ -1042,20 +1052,6 @@ struct mlx5_ifc_wqe_based_flow_table_cap_bits { u8 reserved_at_1c1[0x1f]; }; -struct mlx5_ifc_esw_cap_bits { - u8 reserved_at_0[0x1d]; - u8 merged_eswitch[0x1]; - u8 reserved_at_1e[0x2]; - - u8 reserved_at_20[0x40]; - - u8 esw_manager_vport_number_valid[0x1]; - u8 reserved_at_61[0xf]; - u8 esw_manager_vport_number[0x10]; - - u8 reserved_at_80[0x780]; -}; - enum { MLX5_COUNTER_SOURCE_ESWITCH = 0x0, MLX5_COUNTER_FLOW_ESWITCH = 0x1, @@ -1096,7 +1092,11 @@ struct mlx5_ifc_e_switch_cap_bits { u8 log_max_esw_sf[0x5]; u8 esw_sf_base_id[0x10]; - u8 reserved_at_60[0x7a0]; + u8 esw_manager_vport_number_valid[0x1]; + u8 reserved_at_61[0xf]; + u8 esw_manager_vport_number[0x10]; + + u8 reserved_at_80[0x780]; }; @@ -2352,7 +2352,7 @@ struct mlx5_ifc_fte_match_param_bits { struct mlx5_ifc_fte_match_set_misc5_bits misc_parameters_5; - u8 reserved_at_e00[0x200]; + struct mlx5_ifc_fte_match_set_misc6_bits misc_parameters_6; }; enum { @@ -3859,7 +3859,6 @@ union mlx5_ifc_hca_cap_union_bits { struct mlx5_ifc_flow_table_nic_cap_bits flow_table_nic_cap; struct mlx5_ifc_flow_table_eswitch_cap_bits flow_table_eswitch_cap; struct mlx5_ifc_wqe_based_flow_table_cap_bits wqe_based_flow_table_cap; - struct mlx5_ifc_esw_cap_bits esw_cap; struct mlx5_ifc_e_switch_cap_bits e_switch_cap; struct mlx5_ifc_port_selection_cap_bits port_selection_cap; struct mlx5_ifc_qos_cap_bits qos_cap; @@ -6999,6 +6998,7 @@ enum { MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_3 = 0x4, MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_4 = 0x5, MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_5 = 0x6, + MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_6 = 0x7, }; struct mlx5_ifc_query_flow_group_out_bits { @@ -7260,6 +7260,7 @@ enum mlx5_reformat_ctx_type { MLX5_REFORMAT_TYPE_REMOVE_HDR = 0x10, MLX5_REFORMAT_TYPE_ADD_MACSEC = 0x11, MLX5_REFORMAT_TYPE_DEL_MACSEC = 0x12, + MLX5_REFORMAT_TYPE_REMOVE_PSP_TRANSPORT = 0x16, }; struct mlx5_ifc_alloc_packet_reformat_context_in_bits { @@ -7383,6 +7384,7 @@ enum { MLX5_ACTION_IN_FIELD_OUT_EMD_47_32 = 0x6F, MLX5_ACTION_IN_FIELD_OUT_EMD_31_0 = 0x70, MLX5_ACTION_IN_FIELD_PSP_SYNDROME = 0x71, + MLX5_ACTION_IN_FIELD_PSP_HEADER_1 = 0x78, }; struct mlx5_ifc_alloc_modify_header_context_out_bits { diff --git a/include/linux/mlx5/vport.h b/include/linux/mlx5/vport.h index ee34d3ed335f..577168a4ca0c 100644 --- a/include/linux/mlx5/vport.h +++ b/include/linux/mlx5/vport.h @@ -78,7 +78,8 @@ int mlx5_query_nic_vport_mtu(struct mlx5_core_dev *mdev, u16 *mtu); int mlx5_modify_nic_vport_mtu(struct mlx5_core_dev *mdev, u16 mtu); int mlx5_query_nic_vport_system_image_guid(struct mlx5_core_dev *mdev, u64 *system_image_guid); -int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group); +int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group, + u8 *sd_group_size); int mlx5_query_nic_vport_node_guid(struct mlx5_core_dev *mdev, u16 vport, bool other_vport, u64 *node_guid); int mlx5_modify_nic_vport_node_guid(struct mlx5_core_dev *mdev, diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 0685dd717e85..afae93f6f245 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -117,7 +117,11 @@ #define SDIO_VENDOR_ID_MICROCHIP_WILC 0x0296 #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 +#define SDIO_VENDOR_ID_MORSEMICRO 0x325b +#define SDIO_DEVICE_ID_MORSEMICRO_MM8108 0x0809 + #define SDIO_VENDOR_ID_NXP 0x0471 +#define SDIO_DEVICE_ID_NXP_IW61X_BASE 0x0204 #define SDIO_DEVICE_ID_NXP_IW61X 0x0205 #define SDIO_VENDOR_ID_REALTEK 0x024c diff --git a/include/linux/net.h b/include/linux/net.h index fdcf9956805c..3d82966e2243 100644 --- a/include/linux/net.h +++ b/include/linux/net.h @@ -47,6 +47,29 @@ typedef struct sockopt { int optlen; } sockopt_t; +/* + * Initialize a user-backed sockopt_t from the (optval, optlen) __user pair of + * a getsockopt() callback. Used by transitional __user getsockopt wrappers + * while the proto-layer callbacks are converted to take a sockopt_t; the + * caller writes opt->optlen back to the user optlen after the callback. + */ +static inline int sockopt_init_user(sockopt_t *opt, char __user *optval, + int __user *optlen) +{ + int len; + + if (get_user(len, optlen)) + return -EFAULT; + if (len < 0) + return -EINVAL; + + iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval, len); + iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval, len); + opt->optlen = len; + + return 0; +} + struct poll_table_struct; struct pipe_inode_info; struct inode; diff --git a/include/linux/net/intel/libie/controlq.h b/include/linux/net/intel/libie/controlq.h new file mode 100644 index 000000000000..175227d4eef1 --- /dev/null +++ b/include/linux/net/intel/libie/controlq.h @@ -0,0 +1,436 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef __LIBIE_CONTROLQ_H +#define __LIBIE_CONTROLQ_H + +#include +#include + +#include +#include + +/* Default mailbox control queue */ +#define LIBIE_CTLQ_MBX_ID -1 +#define LIBIE_CTLQ_MAX_BUF_LEN SZ_4K + +/** + * enum libie_ctlq_type - control queue type + * @LIBIE_CTLQ_TYPE_TX: basic Tx control queue + * @LIBIE_CTLQ_TYPE_RX: basic Rx control queue + */ +enum libie_ctlq_type { + LIBIE_CTLQ_TYPE_TX = 0, + LIBIE_CTLQ_TYPE_RX = 1, +}; + +/* Opcode used to send controlq message to the control plane */ +#define LIBIE_CTLQ_SEND_MSG_TO_CP 0x801 +#define LIBIE_CTLQ_SEND_MSG_TO_PEER 0x804 + +#define LIBIE_CP_TX_COPYBREAK 128 + +/** + * struct libie_ctlq_ctx - contains controlq info and MMIO region info + * @mmio_info: MMIO region info structure + * @ctlqs: list that stores all the control queues + * @ctlqs_lock: lock for control queue list + */ +struct libie_ctlq_ctx { + struct libie_mmio_info mmio_info; + struct list_head ctlqs; + spinlock_t ctlqs_lock; /* protects the ctlqs list */ +}; + +/** + * struct libie_ctlq_reg - structure representing virtual addresses of the + * controlq registers and masks + * @head: controlq head register address + * @tail: controlq tail register address + * @len: register address to write controlq length and enable bit + * @addr_high: register address to write the upper 32b of ring physical address + * @addr_low: register address to write the lower 32b of ring physical address + * @len_mask: mask to read the controlq length + * @len_ena_mask: mask to write the controlq enable bit + * @head_mask: mask to read the head value + */ +struct libie_ctlq_reg { + void __iomem *head; + void __iomem *tail; + void __iomem *len; + void __iomem *addr_high; + void __iomem *addr_low; + u32 len_mask; + u32 len_ena_mask; + u32 head_mask; +}; + +/** + * struct libie_cp_dma_mem - structure for DMA memory + * @va: virtual address + * @pa: physical address + * @size: memory size + * @direction: memory to device or device to memory + */ +struct libie_cp_dma_mem { + void *va; + dma_addr_t pa; + size_t size; + int direction; +}; + +/** + * struct libie_ctlq_msg - control queue message data + * @flags: refer to 'Flags sub-structure' definitions + * @opcode: infrastructure message opcode + * @data_len: size of the payload + * @func_id: queue id for mailbox selection, 0 for default mailbox (Tx) + * @hw_retval: execution status from the HW (Rx) + * @chnl_opcode: virtchnl message opcode + * @chnl_retval: virtchnl return value + * @param0: indirect message raw parameter0 + * @sw_cookie: used to verify the response of the sent virtchnl message + * @virt_flags: virtchnl capability flags + * @addr_param: additional parameters in place of the address, given no buffer + * @recv_mem: virtual address and size of the buffer that contains + * the indirect response + * @send_mem: physical and virtual address of the DMA buffer, + * used for sending + */ +struct libie_ctlq_msg { + u16 flags; + u16 opcode; + u16 data_len; + union { + u16 func_id; + u16 hw_retval; + }; + u32 chnl_opcode; + u32 chnl_retval; + u32 param0; + u16 sw_cookie; + u16 virt_flags; + u64 addr_param; + union { + struct kvec recv_mem; + struct libie_cp_dma_mem send_mem; + }; +}; + +/** + * struct libie_ctlq_create_info - control queue create information + * @type: control queue type (Rx or Tx) + * @id: queue offset passed as input, -1 for default mailbox + * @reg: registers accessed by control queue + * @len: controlq length + */ +struct libie_ctlq_create_info { + enum libie_ctlq_type type; + int id; + struct libie_ctlq_reg reg; + u16 len; +}; + +/** + * struct libie_ctlq_info - control queue information + * @list: used to add a controlq to the list of queues in libie_ctlq_ctx + * @type: control queue type + * @qid: queue identifier + * @lock: control queue lock + * @ring_mem: descriptor ring DMA memory + * @descs: array of descriptors + * @rx_fqes: array of controlq Rx buffers + * @tx_msg: Tx messages sent to hardware + * @reg: registers used by control queue + * @dev: device that owns this control queue + * @pp: page pool for controlq Rx buffers + * @truesize: size to allocate per buffer + * @next_to_clean: next descriptor to be cleaned + * @next_to_use: next available slot to send buffer (Tx queue) + * @next_to_post: next available slot to post buffers to (Rx queue) + * @ring_len: length of the descriptor ring + */ +struct libie_ctlq_info { + struct list_head list; + enum libie_ctlq_type type; + int qid; + spinlock_t lock; /* for concurrent processing */ + struct libie_cp_dma_mem ring_mem; + struct libie_ctlq_desc *descs; + union { + struct libeth_fqe *rx_fqes; + struct libie_ctlq_msg **tx_msg; + }; + struct libie_ctlq_reg reg; + struct device *dev; + struct page_pool *pp; + u32 truesize; + u32 next_to_clean; + union { + u32 next_to_use; + u32 next_to_post; + }; + u32 ring_len; +}; + +#define LIBIE_CTLQ_MBX_ATQ_LEN GENMASK(9, 0) + +/* libie controlq descriptor qword0 details */ + +/* Flags sub-structure + * |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 | + * |DD |CMP|ERR| * RSV * |FTYPE | *RSV* |RD |VFC|BUF| HOST_ID | + */ +#define LIBIE_CTLQ_DESC_FLAG_DD BIT(0) +#define LIBIE_CTLQ_DESC_FLAG_CMP BIT(1) +#define LIBIE_CTLQ_DESC_FLAG_ERR BIT(2) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE_VM BIT(6) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE_PF BIT(7) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE GENMASK(7, 6) +#define LIBIE_CTLQ_DESC_FLAG_RD BIT(10) +#define LIBIE_CTLQ_DESC_FLAG_VFC BIT(11) +#define LIBIE_CTLQ_DESC_FLAG_BUF BIT(12) +#define LIBIE_CTLQ_DESC_FLAG_HOST_ID GENMASK(15, 13) + +#define LIBIE_CTLQ_DESC_FLAGS GENMASK(15, 0) +#define LIBIE_CTLQ_DESC_INFRA_OPCODE GENMASK_ULL(31, 16) +#define LIBIE_CTLQ_DESC_DATA_LEN GENMASK_ULL(47, 32) +#define LIBIE_CTLQ_DESC_HW_RETVAL GENMASK_ULL(63, 48) + +#define LIBIE_CTLQ_DESC_PFID_VFID GENMASK_ULL(63, 48) + +/* libie controlq descriptor qword1 details */ +#define LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE GENMASK(27, 0) +#define LIBIE_CTLQ_DESC_VIRTCHNL_DESC_TYPE GENMASK_ULL(31, 28) +#define LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL GENMASK_ULL(63, 32) + +/* libie controlq descriptor qword2 details */ +#define LIBIE_CTLQ_DESC_MSG_PARAM0 GENMASK_ULL(31, 0) +#define LIBIE_CTLQ_DESC_SW_COOKIE GENMASK_ULL(47, 32) +#define LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS GENMASK_ULL(63, 48) + +/* libie controlq descriptor qword3 details */ +#define LIBIE_CTLQ_DESC_DATA_ADDR_HIGH GENMASK_ULL(31, 0) +#define LIBIE_CTLQ_DESC_DATA_ADDR_LOW GENMASK_ULL(63, 32) + +/** + * struct libie_ctlq_desc - control queue descriptor format + * @qword0: flags, message opcode, data length etc + * @qword1: virtchnl opcode, descriptor type and return value + * @qword2: indirect message parameters + * @qword3: indirect message buffer address + */ +struct libie_ctlq_desc { + __le64 qword0; + __le64 qword1; + __le64 qword2; + __le64 qword3; +}; + +/** + * struct libie_ctlq_clean_params - cleaning parameters for Tx messages + * @rel_dma_mem: non-sleeping callback to put the DMA buffer after send + * @rel_ctx: additional context for release callback + * @ctlq: control queue information + * @num_msgs: number of messages to be cleaned + * @force: clean even if DD is not yet set, use only for final cleanup + */ +struct libie_ctlq_clean_params { + void (*rel_dma_mem)(const void *ctx, struct libie_cp_dma_mem *dma_mem); + const void *rel_ctx; + struct libie_ctlq_info *ctlq; + u16 num_msgs; + bool force; +}; + +/** + * libie_ctlq_release_rx_buf - Release Rx buffer for a specific control queue + * @rx_buf: Rx buffer to be freed + * + * Driver uses this function to post back the Rx buffer after the usage. + */ +static inline void libie_ctlq_release_rx_buf(struct kvec *rx_buf) +{ + netmem_ref netmem; + + if (!rx_buf->iov_base) + return; + + netmem = virt_to_netmem(rx_buf->iov_base); + page_pool_put_full_netmem(netmem_get_pp(netmem), netmem, false); +} + +int libie_ctlq_init(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo, u32 numq); +void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx); + +struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx, + enum libie_ctlq_type type, + int id); + +u32 libie_ctlq_send_desc_avail(const struct libie_ctlq_info *ctlq); +void libie_ctlq_send(struct libie_ctlq_info *ctlq, u32 num_q_msg); +u32 libie_ctlq_send_clean(const struct libie_ctlq_clean_params *params); +u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, + u32 num_q_msg); + +int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq); + +/* Only 8 bits are available in descriptor for Xn index */ +#define LIBIE_CTLQ_MAX_XN_ENTRIES 256 +#define LIBIE_CTLQ_XN_COOKIE_M GENMASK(15, 8) +#define LIBIE_CTLQ_XN_INDEX_M GENMASK(7, 0) + +/** + * enum libie_ctlq_xn_state - Transaction state of a virtchnl message + * @LIBIE_CTLQ_XN_IDLE: transaction is available to use + * @LIBIE_CTLQ_XN_WAITING: waiting for transaction to complete + * @LIBIE_CTLQ_XN_COMPLETED_SUCCESS: transaction completed with success + * @LIBIE_CTLQ_XN_COMPLETED_FAILED: transaction completed with failure + * @LIBIE_CTLQ_XN_ASYNC: asynchronous virtchnl message transaction type + * @LIBIE_CTLQ_XN_SHUTDOWN: transaction cannot be used anymore + */ +enum libie_ctlq_xn_state { + LIBIE_CTLQ_XN_IDLE = 0, + LIBIE_CTLQ_XN_WAITING, + LIBIE_CTLQ_XN_COMPLETED_SUCCESS, + LIBIE_CTLQ_XN_COMPLETED_FAILED, + LIBIE_CTLQ_XN_ASYNC, + LIBIE_CTLQ_XN_SHUTDOWN, +}; + +/** + * struct libie_ctlq_xn - structure representing a virtchnl transaction entry + * @resp_cb: non-sleeping callback to handle the response to an async message + * @xn_lock: lock to protect the transaction entry state + * @cmd_completion_event: wait until reply is received or xn is terminated + * @small_dma_mem: DMA memory for copying small send buffers from stack, + * is recycled when response is received or on timeout + * @send_dma_mem: DMA memory of send buffer + * @recv_mem: receive buffer + * @send_ctx: context for callback function + * @timeout_ms: Xn transaction timeout in msecs + * @timestamp: timestamp to record the Xn send + * @tx_msg: control queue Tx message slot to track small DMA usage + * @virtchnl_opcode: virtchnl command opcode used for Xn transaction + * @state: transaction state of a virtchnl message + * @cookie: unique message identifier, incremented every time the slot is used + * @index: index of the transaction entry + */ +struct libie_ctlq_xn { + void (*resp_cb)(void *ctx, struct kvec *mem, int status); + spinlock_t xn_lock; /* protects state */ + struct completion cmd_completion_event; + struct libie_cp_dma_mem small_dma_mem; + struct libie_cp_dma_mem send_dma_mem; + struct kvec recv_mem; + void *send_ctx; + u64 timeout_ms; + ktime_t timestamp; + struct libie_ctlq_msg *tx_msg; + u32 virtchnl_opcode; + enum libie_ctlq_xn_state state; + u8 cookie; + u8 index; +}; + +/** + * struct libie_ctlq_xn_manager - structure representing the array of virtchnl + * transaction entries + * @ctx: pointer to controlq context structure + * @free_xns_bm_lock: lock to protect the free Xn entries bit map + * @free_xns_bm: bitmap that represents the free Xn entries + * @ring: array of Xn entries + * @small_buff_pool: DMA pool for small send buffers + * @can_destroy: completion, triggered by the last released transaction + * @shutdown: shutdown process has been started, no new transactions allowed + */ +struct libie_ctlq_xn_manager { + struct libie_ctlq_ctx *ctx; + spinlock_t free_xns_bm_lock; /* get/check entries */ + DECLARE_BITMAP(free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES); + struct libie_ctlq_xn ring[LIBIE_CTLQ_MAX_XN_ENTRIES]; + struct dma_pool *small_buff_pool; + struct completion can_destroy; + bool shutdown; +}; + +/** + * struct libie_ctlq_xn_send_params - structure representing send Xn entry + * @resp_cb: non-sleeping callback to handle the response to an async message + * @rel_tx_buf: non-sleeping callback for freeing the send buffer + * @xnm: Xn manager to process Xn entries + * @ctlq: send control queue information + * @ctlq_msg: control queue message information + * @send_buf: buffer that carries outgoing message data, buffers larger than + * LIBIE_CP_TX_COPYBREAK bytes will always be consumed + * @recv_mem: receive buffer + * @send_ctx: context for callback function + * @timeout_ms: virtchnl transaction timeout in msecs + * @chnl_opcode: virtchnl message opcode + */ +struct libie_ctlq_xn_send_params { + void (*resp_cb)(void *ctx, struct kvec *mem, int status); + void (*rel_tx_buf)(const void *buf_va); + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *ctlq; + struct libie_ctlq_msg *ctlq_msg; + struct kvec send_buf; + struct kvec recv_mem; + void *send_ctx; + u64 timeout_ms; + u32 chnl_opcode; +}; + +/** + * libie_cp_can_send_onstack - can a message be sent using a stack variable + * @size: ctlq data buffer size + * + * Return: %true if the message size is small enough for caller to pass + * an on-stack buffer, %false if kmalloc is needed + */ +static inline bool libie_cp_can_send_onstack(u32 size) +{ + return size <= LIBIE_CP_TX_COPYBREAK; +} + +/** + * struct libie_ctlq_xn_recv_params - request to receive xn responses + * @ctlq_msg_handler: handler for Rx messages with no matching xn (mandatory) + * @xnm: Xn manager to process Xn entries + * @ctlq: control queue information + * @budget: maximum number of messages to process + */ +struct libie_ctlq_xn_recv_params { + void (*ctlq_msg_handler)(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *msg); + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *ctlq; + u32 budget; +}; + +/** + * struct libie_ctlq_xn_init_params - xn transaction manager parameters + * @cctlq_info: control queue information + * @ctx: pointer to controlq context structure + * @xnm: Xn manager to process Xn entries + * @num_qs: number of control queues to be initialized + */ +struct libie_ctlq_xn_init_params { + struct libie_ctlq_create_info *cctlq_info; + struct libie_ctlq_ctx *ctx; + struct libie_ctlq_xn_manager *xnm; + u32 num_qs; +}; + +int libie_ctlq_xn_init(struct libie_ctlq_xn_init_params *params); +void libie_ctlq_xn_deinit(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_ctx *ctx); +void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm); +int libie_ctlq_xn_send(struct libie_ctlq_xn_send_params *params); +u32 libie_ctlq_xn_recv(struct libie_ctlq_xn_recv_params *params); +u32 libie_ctlq_xn_send_clean(struct libie_ctlq_info *ctlq, + void (*rel_tx_buf)(const void *buf_va), + bool force); + +#endif /* __LIBIE_CONTROLQ_H */ diff --git a/include/linux/net/intel/libie/pci.h b/include/linux/net/intel/libie/pci.h new file mode 100644 index 000000000000..effd072c55c8 --- /dev/null +++ b/include/linux/net/intel/libie/pci.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef __LIBIE_PCI_H +#define __LIBIE_PCI_H + +#include + +/** + * struct libie_pci_mmio_region - structure for MMIO region info + * @list: used to add a MMIO region to the list of MMIO regions in + * libie_mmio_info + * @addr: virtual address of MMIO region start + * @offset: start offset of the MMIO region + * @size: size of the MMIO region + * @bar_idx: BAR index to which the MMIO region belongs to + */ +struct libie_pci_mmio_region { + struct list_head list; + void __iomem *addr; + resource_size_t offset; + resource_size_t size; + u16 bar_idx; +}; + +/** + * struct libie_mmio_info - contains list of MMIO regions + * @pdev: PCI device pointer + * @mmio_list: list of MMIO regions + */ +struct libie_mmio_info { + struct pci_dev *pdev; + struct list_head mmio_list; +}; + +#define libie_pci_map_mmio_region(mmio_info, offset, size, ...) \ + __libie_pci_map_mmio_region(mmio_info, offset, size, \ + COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__) + +#define libie_pci_get_mmio_addr(mmio_info, offset, ...) \ + __libie_pci_get_mmio_addr(mmio_info, offset, \ + COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__) + +bool __libie_pci_map_mmio_region(struct libie_mmio_info *mmio_info, + resource_size_t offset, resource_size_t size, + int num_args, ...); +void __iomem *__libie_pci_get_mmio_addr(struct libie_mmio_info *mmio_info, + resource_size_t offset, + int num_args, ...); +void libie_pci_unmap_all_mmio_regions(struct libie_mmio_info *mmio_info); +void libie_pci_unmap_fltr_regs(struct libie_mmio_info *mmio_info, + bool (*fltr)(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg)); +int libie_pci_init_dev(struct pci_dev *pdev); + +#endif /* __LIBIE_PCI_H */ diff --git a/include/linux/avf/virtchnl.h b/include/linux/net/intel/virtchnl.h similarity index 100% rename from include/linux/avf/virtchnl.h rename to include/linux/net/intel/virtchnl.h diff --git a/drivers/net/ethernet/intel/idpf/virtchnl2.h b/include/linux/net/intel/virtchnl2.h similarity index 100% rename from drivers/net/ethernet/intel/idpf/virtchnl2.h rename to include/linux/net/intel/virtchnl2.h diff --git a/drivers/net/ethernet/intel/idpf/virtchnl2_lan_desc.h b/include/linux/net/intel/virtchnl2_lan_desc.h similarity index 100% rename from drivers/net/ethernet/intel/idpf/virtchnl2_lan_desc.h rename to include/linux/net/intel/virtchnl2_lan_desc.h diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 8840b126979f..87cafc932e9e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -832,8 +832,13 @@ struct xps_dev_maps { #define TC_BITMASK 15 /* HW offloaded queuing disciplines txq count and offset maps */ struct netdev_tc_txq { - u16 count; - u16 offset; + union { + struct { + u16 count; + u16 offset; + }; + u32 combined; + }; }; #if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE) @@ -894,6 +899,7 @@ struct net_device_path { u8 h_dest[ETH_ALEN]; } encap; struct { + struct dst_entry *dst; union { struct in_addr src_v4; struct in6_addr src_v6; @@ -903,7 +909,7 @@ struct net_device_path { struct in6_addr dst_v6; }; - u8 l3_proto; + u8 inner_proto; } tun; struct { enum { @@ -940,6 +946,7 @@ struct net_device_path_stack { struct net_device_path_ctx { const struct net_device *dev; u8 daddr[ETH_ALEN]; + __be16 ether_type; int num_vlans; struct { @@ -1847,6 +1854,8 @@ enum netdev_reg_state { * @napi_list: List entry used for polling NAPI devices * @unreg_list: List entry when we are unregistering the * device; see the function unregister_netdev + * @unreg_list_net:List entry when we are unregistering the cross-netns + * device; see the function unregister_netdevice_queue_net() * @close_list: List entry used when we are closing the device * @ptype_all: Device-specific packet handlers for all protocols * @ptype_specific: Device-specific, protocol-specific packet handlers @@ -2243,6 +2252,9 @@ struct net_device { struct list_head dev_list; struct list_head napi_list; struct list_head unreg_list; +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + struct list_head unreg_list_net; +#endif struct list_head close_list; struct list_head ptype_all; @@ -2660,16 +2672,16 @@ static inline bool netif_elide_gro(const struct net_device *dev) static inline int netdev_get_prio_tc_map(const struct net_device *dev, u32 prio) { - return dev->prio_tc_map[prio & TC_BITMASK]; + return READ_ONCE(dev->prio_tc_map[prio & TC_BITMASK]); } static inline int netdev_set_prio_tc_map(struct net_device *dev, u8 prio, u8 tc) { - if (tc >= dev->num_tc) + if (tc >= READ_ONCE(dev->num_tc)) return -EINVAL; - dev->prio_tc_map[prio & TC_BITMASK] = tc & TC_BITMASK; + WRITE_ONCE(dev->prio_tc_map[prio & TC_BITMASK], tc & TC_BITMASK); return 0; } @@ -2679,9 +2691,9 @@ int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset); int netdev_set_num_tc(struct net_device *dev, u8 num_tc); static inline -int netdev_get_num_tc(struct net_device *dev) +int netdev_get_num_tc(const struct net_device *dev) { - return dev->num_tc; + return READ_ONCE(dev->num_tc); } static inline void net_prefetch(void *p) @@ -2708,7 +2720,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, int netdev_set_sb_channel(struct net_device *dev, u16 channel); static inline int netdev_get_sb_channel(struct net_device *dev) { - return max_t(int, -dev->num_tc, 0); + return max_t(int, -READ_ONCE(dev->num_tc), 0); } static inline @@ -3422,8 +3434,9 @@ void dev_remove_offload(struct packet_offload *po); int dev_get_iflink(const struct net_device *dev); int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb); -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +int dev_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path_stack *stack); +void dev_fill_forward_path_release(struct net_device_path_stack *stack); struct net_device *dev_get_by_name(struct net *net, const char *name); struct net_device *dev_get_by_name_rcu(struct net *net, const char *name); struct net_device *__dev_get_by_name(struct net *net, const char *name); @@ -3474,6 +3487,25 @@ static inline void unregister_netdevice(struct net_device *dev) unregister_netdevice_queue(dev, NULL); } +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL +void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, + struct list_head *head); +void unregister_netdevice_many_net(struct net *net); +void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head); +#else +static inline void unregister_netdevice_queue_net(struct net *net, + struct net_device *dev, + struct list_head *head) +{ + unregister_netdevice_queue(dev, head); +} + +static inline void unregister_netdevice_queue_many_net(struct net *net, + struct list_head *head) +{ +} +#endif + int netdev_refcnt_read(const struct net_device *dev); void free_netdev(struct net_device *dev); diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h index efbbfa770d66..e99afc1414cd 100644 --- a/include/linux/netfilter.h +++ b/include/linux/netfilter.h @@ -93,6 +93,7 @@ enum nf_hook_ops_type { NF_HOOK_OP_NF_TABLES, NF_HOOK_OP_BPF, NF_HOOK_OP_NFT_FT, + NF_HOOK_OP_NAT, }; struct nf_hook_ops { @@ -140,6 +141,12 @@ struct nf_hook_entries { */ }; +struct nf_nat_lookup_hook_priv { + struct nf_hook_entries __rcu *entries; + + struct rcu_head rcu_head; +}; + #ifdef CONFIG_NETFILTER static inline struct nf_hook_ops **nf_hook_entries_get_hook_ops(const struct nf_hook_entries *e) { diff --git a/include/linux/netfilter/nf_conntrack_amanda.h b/include/linux/netfilter/nf_conntrack_amanda.h index 1719987e8fd8..deb560bb79c4 100644 --- a/include/linux/netfilter/nf_conntrack_amanda.h +++ b/include/linux/netfilter/nf_conntrack_amanda.h @@ -9,6 +9,7 @@ typedef unsigned int nf_nat_amanda_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, diff --git a/include/linux/netfilter/nf_conntrack_ftp.h b/include/linux/netfilter/nf_conntrack_ftp.h index 7b62446ccec4..712702183b94 100644 --- a/include/linux/netfilter/nf_conntrack_ftp.h +++ b/include/linux/netfilter/nf_conntrack_ftp.h @@ -28,6 +28,7 @@ struct nf_ct_ftp_master { * connection we should expect. */ typedef unsigned int nf_nat_ftp_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, enum nf_ct_ftp_type type, unsigned int protoff, diff --git a/include/linux/netfilter/nf_conntrack_h323.h b/include/linux/netfilter/nf_conntrack_h323.h index 81286c499325..b15f37604cde 100644 --- a/include/linux/netfilter/nf_conntrack_h323.h +++ b/include/linux/netfilter/nf_conntrack_h323.h @@ -9,8 +9,6 @@ #include #include -#define RAS_PORT 1719 -#define Q931_PORT 1720 #define H323_RTP_CHANNEL_MAX 4 /* Audio, video, FAX and other */ /* This structure exists only once per master */ diff --git a/include/linux/netfilter/nf_conntrack_irc.h b/include/linux/netfilter/nf_conntrack_irc.h index ce07250afb4e..c73b3b44a0b7 100644 --- a/include/linux/netfilter/nf_conntrack_irc.h +++ b/include/linux/netfilter/nf_conntrack_irc.h @@ -10,6 +10,7 @@ typedef unsigned int nf_nat_irc_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, diff --git a/include/linux/netfilter/nf_conntrack_pptp.h b/include/linux/netfilter/nf_conntrack_pptp.h index c3bdb4370938..c0b305ce7c3c 100644 --- a/include/linux/netfilter/nf_conntrack_pptp.h +++ b/include/linux/netfilter/nf_conntrack_pptp.h @@ -50,8 +50,6 @@ struct nf_nat_pptp { __be16 pac_call_id; /* NAT'ed PAC call id */ }; -#define PPTP_CONTROL_PORT 1723 - #define PPTP_PACKET_CONTROL 1 #define PPTP_PACKET_MGMT 2 diff --git a/include/linux/netfilter/nf_conntrack_sane.h b/include/linux/netfilter/nf_conntrack_sane.h index 46c7acd1b4a7..8501035d7335 100644 --- a/include/linux/netfilter/nf_conntrack_sane.h +++ b/include/linux/netfilter/nf_conntrack_sane.h @@ -3,8 +3,6 @@ #define _NF_CONNTRACK_SANE_H /* SANE tracking. */ -#define SANE_PORT 6566 - enum sane_state { SANE_STATE_NORMAL, SANE_STATE_START_REQUESTED, diff --git a/include/linux/netfilter/nf_conntrack_tftp.h b/include/linux/netfilter/nf_conntrack_tftp.h index 90b334bbce3c..802cb7fc19cd 100644 --- a/include/linux/netfilter/nf_conntrack_tftp.h +++ b/include/linux/netfilter/nf_conntrack_tftp.h @@ -2,8 +2,6 @@ #ifndef _NF_CONNTRACK_TFTP_H #define _NF_CONNTRACK_TFTP_H -#define TFTP_PORT 69 - #include #include #include @@ -21,6 +19,7 @@ struct tftphdr { typedef unsigned int nf_nat_tftp_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, struct nf_conntrack_expect *exp); diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 88f7daa8560e..1c6b1eec5efd 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -21,20 +21,6 @@ union inet_addr { struct in6_addr in6; }; -/* - * Maximum payload netpoll's preallocated skb pool can carry. Keep this in - * sync with the buffer size used by refill_skbs() in net/core/netpoll.c; - * callers (e.g. netconsole) use it to detect requests the pool can never - * satisfy and avoid dequeuing a pooled skb that would later trip - * skb_over_panic() in skb_put(). - */ -#define MAX_UDP_CHUNK 1460 -#define MAX_SKB_SIZE \ - (sizeof(struct ethhdr) + \ - sizeof(struct iphdr) + \ - sizeof(struct udphdr) + \ - MAX_UDP_CHUNK) - struct netpoll { struct net_device *dev; netdevice_tracker dev_tracker; @@ -46,13 +32,6 @@ struct netpoll { char dev_name[IFNAMSIZ]; u8 dev_mac[ETH_ALEN]; const char *name; - - union inet_addr local_ip, remote_ip; - bool ipv6; - u16 local_port, remote_port; - u8 remote_mac[ETH_ALEN]; - struct sk_buff_head skb_pool; - struct work_struct refill_wq; }; #define np_info(np, fmt, ...) \ @@ -84,12 +63,12 @@ static inline void netpoll_poll_enable(struct net_device *dev) { return; } #endif int __netpoll_setup(struct netpoll *np, struct net_device *ndev); -int netpoll_setup(struct netpoll *np); void __netpoll_free(struct netpoll *np); void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); +unsigned int netpoll_get_carrier_timeout(void); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 15f58e3c56c7..27f652d4920b 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -10,15 +10,86 @@ #include #include +/* OPEN Alliance TC6 registers */ +/* Standard Capabilities Register */ +#define OA_TC6_REG_STDCAP 0x0002 +#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) + +/* Reset Control and Status Register */ +#define OA_TC6_REG_RESET 0x0003 +#define OA_TC6_RESET_SWRESET BIT(0) /* Software Reset */ + +/* Configuration Register #0 */ +#define OA_TC6_REG_CONFIG0 0x0004 +#define OA_TC6_CONFIG0_SYNC BIT(15) +#define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12) +#define OA_TC6_CONFIG0_PROTE BIT(5) + +/* Configuration Register #2 */ +#define OA_TC6_REG_CONFIG2 0x0006 + +/* Status Register #0 */ +#define OA_TC6_REG_STATUS0 0x0008 +#define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */ +#define OA_TC6_STATUS0_HEADER_ERROR BIT(5) +#define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR BIT(4) +#define OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) +#define OA_TC6_STATUS0_TX_PROTOCOL_ERROR BIT(0) + +/* Buffer Status Register */ +#define OA_TC6_REG_BUFFER_STATUS 0x000B +#define OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) +#define OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) + +/* Interrupt Mask Register #0 */ +#define OA_TC6_REG_INT_MASK0 0x000C +#define OA_TC6_INT_MASK0_HEADER_ERR_MASK BIT(5) +#define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) +#define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) +#define OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) +#define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ + GENMASK(12, 7)) + +/* PHY Clause 22 registers base address and mask */ +#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 +#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F + +/* Memory map selector (MMS) values as per table 6 in the + * OPEN Alliance specification. + */ +#define OA_TC6_MAC_MMS1 1 +#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ +#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ +#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ +#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ +#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ + struct oa_tc6; -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev); +enum oa_tc6_quirk_flag { + OA_TC6_BROKEN_PHY = BIT(0), +}; + +struct oa_tc6_quirks { + enum oa_tc6_quirk_flag quirk_flags; +}; + +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks); void oa_tc6_exit(struct oa_tc6 *tc6); int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value); +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value); int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value); +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value); int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb); int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6); +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum); +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val); diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 2c17239aacea..2f72956c864d 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2640,6 +2640,10 @@ #define PCI_VENDOR_ID_SUNIX 0x1fd4 #define PCI_DEVICE_ID_SUNIX_1999 0x1999 +#define PCI_VENDOR_ID_SPACEMIT 0x201f +#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 +#define PCI_DEVICE_ID_SPACEMIT_K3 0x0002 + #define PCI_VENDOR_ID_PICOHEART 0x20fa #define PCI_VENDOR_ID_HINT 0x3388 diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h index 17a87c1a55d7..901e1e628f89 100644 --- a/include/linux/pds/pds_core_if.h +++ b/include/linux/pds/pds_core_if.h @@ -40,6 +40,14 @@ enum pds_core_cmd_opcode { PDS_CORE_CMD_FW_DOWNLOAD = 4, PDS_CORE_CMD_FW_CONTROL = 5, + PDS_CORE_CMD_GET_COMPONENT_INFO = 6, + PDS_CORE_CMD_SEND_PKG_DATA = 7, + PDS_CORE_CMD_SEND_COMPONENT_TBL = 8, + PDS_CORE_CMD_SEND_COMPONENT = 9, + PDS_CORE_CMD_FINALIZE_UPDATE = 10, + PDS_CORE_CMD_MATCH_RECORD_DESC = 11, + PDS_CORE_CMD_HOST_MEM = 12, + /* SR/IOV commands */ PDS_CORE_CMD_VF_GETATTR = 60, PDS_CORE_CMD_VF_SETATTR = 61, @@ -100,6 +108,16 @@ struct pds_core_drv_identity { char driver_ver_str[32]; }; +/** + * enum pds_core_dev_capability - Device capabilities + * @PDS_CORE_DEV_CAP_PLDM_FW_UPDATE: Device only supports FW update via PLDM + * @PDS_CORE_DEV_CAP_HOST_MEM: Device supports host memory for fw use + */ +enum pds_core_dev_capability { + PDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0), + PDS_CORE_DEV_CAP_HOST_MEM = BIT(1), +}; + #define PDS_DEV_TYPE_MAX 16 /** * struct pds_core_dev_identity - Device identity information @@ -119,6 +137,11 @@ struct pds_core_drv_identity { * value in usecs to device units using: * device units = usecs * mult / div * @vif_types: How many of each VIF device type is supported + * @max_fw_slots: Number of firmware components reported by device + * only supported on version >= PDS_CORE_IDENTITY_VERSION_2 + * @rsvd2: Word boundary padding + * @capabilities: Device capabilities + * only supported on version >= PDS_CORE_IDENTITY_VERSION_2 */ struct pds_core_dev_identity { u8 version; @@ -131,9 +154,13 @@ struct pds_core_dev_identity { __le32 intr_coal_mult; __le32 intr_coal_div; __le16 vif_types[PDS_DEV_TYPE_MAX]; + __le16 max_fw_slots; + u8 rsvd2[6]; + __le64 capabilities; }; #define PDS_CORE_IDENTITY_VERSION_1 1 +#define PDS_CORE_IDENTITY_VERSION_2 2 /** * struct pds_core_dev_identify_cmd - Driver/device identify command @@ -275,11 +302,20 @@ enum pds_core_fw_control_oper { PDS_CORE_FW_GET_LIST = 7, }; +/** + * enum pds_core_fw_slot - Firmware slot identifiers + * @PDS_CORE_FW_SLOT_INVALID: Let firmware select slot based on package metadata + * @PDS_CORE_FW_SLOT_A: Primary firmware slot A + * @PDS_CORE_FW_SLOT_B: Primary firmware slot B + * @PDS_CORE_FW_SLOT_GOLD: Gold/recovery firmware slot + * @PDS_CORE_FW_SLOT_MAX: Sentinel value indicating no slot resolved + */ enum pds_core_fw_slot { PDS_CORE_FW_SLOT_INVALID = 0, PDS_CORE_FW_SLOT_A = 1, PDS_CORE_FW_SLOT_B = 2, PDS_CORE_FW_SLOT_GOLD = 3, + PDS_CORE_FW_SLOT_MAX = 0xff, }; /** @@ -446,6 +482,423 @@ struct pds_core_vf_ctrl_comp { u8 status; }; +/** + * struct pds_core_send_pkg_data_cmd - Send package data command + * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA + * @ver: Driver's max support version of this command + * @total_len: Total length of the package data + * @offset: Offset in the package data, non-zero if multiple commands are + * needed for sending the package data + * @data_len: Length of data stored at data_pa + * @data_pa: Data physical address for DMA to device + * + * The package data may be too large to store in a single buffer, so multiple + * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed. + */ +struct pds_core_send_pkg_data_cmd { + u8 opcode; + u8 ver; + __le16 total_len; + __le16 offset; + __le16 data_len; + __le64 data_pa; +}; + +/** + * struct pds_core_send_pkg_data_comp - Send package data completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_send_pkg_data_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_component_tbl - Component table details + * @comparison_stamp: Comparison stamp used for component version checks + * @classification: Vendor specific classification info + * @identifier: Component's ID + * @transfer_flag: Part of the component table this request represents + * @version_str_type: The types of strings used + * @version_str_len: Length of @version_str + * @version_str: Component version information + */ +struct pds_core_component_tbl { + __le32 comparison_stamp; + __le16 classification; + __le16 identifier; + u8 transfer_flag; + u8 version_str_type; + u8 version_str_len; + u8 version_str[]; +}; + +/** + * struct pds_core_send_component_tbl_cmd - Send component table command + * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL + * @ver: Driver's max support version of this command + * @slot_id: enum pds_core_fw_slot + * @rsvd: Word boundary padding + * + * Expects to find component table info (struct pds_core_component_tbl) + * in cmd_regs->data. Driver should keep the devcmd interface locked + * while preparing the component table info. + */ +struct pds_core_send_component_tbl_cmd { + u8 opcode; + u8 ver; + u8 slot_id; + u8 rsvd; +}; + +enum pds_core_component_resp_code { + PDS_CORE_COMPONENT_VALID = 0x0, + PDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1, + PDS_CORE_COMPONENT_STAMP_LOWER = 0x2, + PDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3, + PDS_CORE_COMPONENT_CONFLICT = 0x4, + PDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5, + PDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6, + PDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0, +}; + +/** + * struct pds_core_send_component_tbl_comp - Send component table completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @completion_code: Component completion code + * @response: Component response + * @response_code: Component response code + * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot) + * @rsvd: Word boundary padding + */ +struct pds_core_send_component_tbl_comp { + u8 status; + u8 ver; + u8 completion_code; + u8 response; + u8 response_code; + u8 slot_id; + u8 rsvd[2]; +}; + +/** + * enum pds_core_send_component_op - PDS_CORE_CMD_SEND_COMPONENT operation + * @PDS_CORE_SEND_COMPONENT_START: Initial operation to start transfer + * @PDS_CORE_SEND_COMPONENT_STATUS: Subsequent calls to check on status + */ +enum pds_core_send_component_op { + PDS_CORE_SEND_COMPONENT_START = 0, + PDS_CORE_SEND_COMPONENT_STATUS = 1, +}; + +#define PDS_CORE_FW_COMPONENT_ID_INVALID 0xFFFF +/** + * struct pds_core_flash_component - Component details + * @comparison_stamp: Comparison stamp used for component version checks + * @image_size: Component image size + * @classification: Vendor specific classification info + * @identifier: Component's ID + * @options: Component options + * @rsvd: Word boundary padding + * @version_str_type: The types of strings used + * @version_str_len: Length of @version_str + * @version_str: Component version information + */ +struct pds_core_flash_component { + __le32 comparison_stamp; + __le32 image_size; + __le16 classification; + __le16 identifier; + __le16 options; + u8 rsvd[3]; + u8 version_str_type; + u8 version_str_len; + u8 version_str[]; +}; + +/** + * struct pds_core_send_component_cmd - Send component command + * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT + * @ver: Driver's max supported version of this command + * @slot_id: enum pds_core_fw_slot + * @operation: enum pds_core_send_component_op + * @offset: Offset into the component, non-zero if multiple commands + * are needed for a single component + * @data_len: Length of this part of the component stored at @data_pa + * @rsvd: Word boundary padding + * @data_pa: DMA address of the component + * + * A component may be too large to store in a single buffer, so multiple + * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed. + * + * Expects to find flash component info (struct pds_core_flash_component) + * in cmd_regs->data. Driver should keep the devcmd interface locked + * while preparing and sending the flash component info. + */ +struct pds_core_send_component_cmd { + u8 opcode; + u8 ver; + u8 slot_id; + u8 operation; + __le32 offset; + __le32 data_len; + u8 rsvd[4]; + __le64 data_pa; +}; + +/** + * struct pds_core_send_component_comp - Send component completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @completion_code: Completion code + * @compat_response: Compatibility response (0 = Component can be updated) + * @compat_response_code: Compatibility response code + * @rsvd: Word boundary padding + */ +struct pds_core_send_component_comp { + u8 status; + u8 ver; + u8 completion_code; + u8 compat_response; + u8 compat_response_code; + u8 rsvd[3]; +}; + +/** + * enum pds_core_fw_component_type - Firmware component type + * @PDS_CORE_FW_TYPE_UNKNOWN: Unknown component type + * @PDS_CORE_FW_TYPE_MAIN: Main firmware + * @PDS_CORE_FW_TYPE_BOOT: Boot loader + * @PDS_CORE_FW_TYPE_CPLD: CPLD firmware + * @PDS_CORE_FW_TYPE_SECURE: Secure firmware + * @PDS_CORE_FW_TYPE_FPGA: FPGA configuration + * @PDS_CORE_FW_TYPE_SUC_MAIN: System Unit Controller firmware + * @PDS_CORE_FW_TYPE_SUC_BOOT: System Unit Controller bootloader + * @PDS_CORE_FW_TYPE_UBOOT: U-Boot bootloader + * + * Gold/recovery variants are identified by slot_id == PDS_CORE_FW_SLOT_GOLD + * and reported with a ".gold" suffix (e.g., fw.gold). + */ +enum pds_core_fw_component_type { + PDS_CORE_FW_TYPE_UNKNOWN = 0, + PDS_CORE_FW_TYPE_MAIN = 1, + PDS_CORE_FW_TYPE_BOOT = 2, + PDS_CORE_FW_TYPE_CPLD = 3, + PDS_CORE_FW_TYPE_SECURE = 4, + PDS_CORE_FW_TYPE_FPGA = 5, + PDS_CORE_FW_TYPE_SUC_MAIN = 6, + PDS_CORE_FW_TYPE_SUC_BOOT = 7, + PDS_CORE_FW_TYPE_UBOOT = 8, +}; + +/** + * enum pds_core_component_info_flags - Component info flags + * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running + * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot + * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated + * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated + * by name + */ +enum pds_core_component_info_flags { + PDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0), + PDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1), + PDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2), + PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3), +}; + +/** + * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry + * @name: Component's name + * @component_type: enum pds_core_fw_component_type + * @rsvd: Word boundary padding + * @flags: enum pds_core_component_info_flags + * @identifier: Component's identifier + * @slot_id: Component's slot identifier + * @version: Component's version + */ +struct pds_core_fw_component_info { +#define PDS_CORE_FW_COMPONENT_NAME_BUFLEN 24 + char name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN]; + u8 component_type; + u8 rsvd[3]; + __le16 flags; + u8 identifier; + u8 slot_id; +#define PDS_CORE_FW_COMPONENT_VER_BUFLEN 32 + char version[PDS_CORE_FW_COMPONENT_VER_BUFLEN]; +}; + +#define PDS_CORE_FW_COMPONENT_LIST_LEN ((PDS_PAGE_SIZE - 8) / \ + sizeof(struct pds_core_fw_component_info)) + +/** + * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data + * @num_components: Number of valid components + * @rsvd: Word boundary padding + * @info: List of valid components + */ +struct pds_core_component_list_info { + u8 num_components; + u8 rsvd[7]; + struct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN]; +}; + +/** + * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command + * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO + * @ver: Driver's max supported version of this command + * @data_len: Length of data at data_pa + * @rsvd: Word boundary padding + * @data_pa: DMA address of data + * + * FW populates struct pds_core_component_list_info pointed to by @data_pa + */ +struct pds_core_get_component_info_cmd { + u8 opcode; + u8 ver; + __le16 data_len; + u8 rsvd[4]; + __le64 data_pa; +}; + +/** + * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_get_component_info_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command + * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE + * @ver: Driver's max support version of this command + * @rsvd: Word boundary padding + * + * Driver sends at the end of updating all components to finalize the update + */ +struct pds_core_finalize_update_cmd { + u8 opcode; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_finalize_update_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command + * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC + * @ver: Driver's max supported version of this command + * @type: PLDM Descriptor Identifier Type + * @size: Length of the Descriptor Identifier Value + * @rsvd: Word boundary padding + * + * Expects to find the Descriptor Identifier Data in cmd_regs->data. Driver + * should keep the devcmd interface locked while preparing and sending this + * command. + */ +struct pds_core_match_record_desc_cmd { + u8 opcode; + u8 ver; + __le16 type; + __le16 size; + u8 rsvd[2]; +}; + +/** + * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @match: Whether or not the Record Descriptor matches the device + * @rsvd: Word boundary padding + * + * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's + * undefined. + */ +struct pds_core_match_record_desc_comp { + u8 status; + u8 ver; + u8 match; + u8 rsvd; +}; + +/** + * enum pds_core_host_mem_oper - HOST_MEM sub-operations + * @PDS_CORE_HOST_MEM_GET_COUNT: Query number of memory requests + * @PDS_CORE_HOST_MEM_QUERY: Query details of a memory request + * @PDS_CORE_HOST_MEM_ADD: Provide allocated memory to firmware + * @PDS_CORE_HOST_MEM_DEL: Notify firmware of memory deallocation + */ +enum pds_core_host_mem_oper { + PDS_CORE_HOST_MEM_GET_COUNT = 0, + PDS_CORE_HOST_MEM_QUERY = 1, + PDS_CORE_HOST_MEM_ADD = 2, + PDS_CORE_HOST_MEM_DEL = 3, +}; + +/** + * struct pds_core_host_mem_cmd - HOST_MEM command + * @opcode: Opcode PDS_CORE_CMD_HOST_MEM + * @oper: Operation (enum pds_core_host_mem_oper) + * @index: Memory request index (GET_COUNT: max_count, QUERY: index) + * @tag: Tag for this memory request (ADD/DEL) + * @reason: Reason for deletion (DEL only) + * @rsvd: Reserved + * @max_contig: Maximum contiguous memory size (GET_COUNT only) + * @size: Size of memory in bytes (ADD only) + * @buf_pa: DMA address of memory (ADD only) + * + * Unified command for all host memory operations. Fields are reused + * across operations to minimize opcode space usage. + */ +struct pds_core_host_mem_cmd { + u8 opcode; + u8 oper; + __le16 index; + __le16 tag; + u8 reason; + u8 rsvd; + __le32 max_contig; + __le32 size; + __le64 buf_pa; +}; + +/** + * struct pds_core_host_mem_comp - HOST_MEM completion + * @status: Status of the command (enum pds_core_status_code) + * @oper: Operation that was performed + * @count: Number of memory requests (GET_COUNT) + * @size: Size of memory request in bytes (QUERY) + * @tag: Tag for this memory request (QUERY/DEL) + * @rsvd: Reserved + */ +struct pds_core_host_mem_comp { + u8 status; + u8 oper; + __le16 count; + __le32 size; + __le16 tag; + u8 rsvd[6]; +}; + /* * union pds_core_dev_cmd - Overlay of core device command structures */ @@ -462,6 +915,14 @@ union pds_core_dev_cmd { struct pds_core_vf_setattr_cmd vf_setattr; struct pds_core_vf_getattr_cmd vf_getattr; struct pds_core_vf_ctrl_cmd vf_ctrl; + + struct pds_core_get_component_info_cmd get_component_info; + struct pds_core_send_pkg_data_cmd send_pkg_data; + struct pds_core_send_component_tbl_cmd send_component_tbl; + struct pds_core_send_component_cmd send_component; + struct pds_core_finalize_update_cmd finalize_update; + struct pds_core_match_record_desc_cmd match_record_desc; + struct pds_core_host_mem_cmd host_mem; }; /* @@ -480,6 +941,14 @@ union pds_core_dev_comp { struct pds_core_vf_setattr_comp vf_setattr; struct pds_core_vf_getattr_comp vf_getattr; struct pds_core_vf_ctrl_comp vf_ctrl; + + struct pds_core_get_component_info_comp get_component_info; + struct pds_core_send_pkg_data_comp send_pkg_data; + struct pds_core_send_component_tbl_comp send_component_tbl; + struct pds_core_send_component_comp send_component; + struct pds_core_finalize_update_comp finalize_update; + struct pds_core_match_record_desc_comp match_record_desc; + struct pds_core_host_mem_comp host_mem; }; /** diff --git a/include/linux/phy.h b/include/linux/phy.h index fc680901275b..5f8d65868e0f 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2231,6 +2231,11 @@ static inline void phy_device_reset(struct phy_device *phydev, int value) mdio_device_reset(&phydev->mdio, value); } +static inline bool phy_device_has_reset(struct phy_device *phydev) +{ + return mdiodev_has_reset(&phydev->mdio); +} + #define phydev_err(_phydev, format, args...) \ dev_err(&_phydev->mdio.dev, format, ##args) @@ -2297,6 +2302,9 @@ static inline int genphy_no_config_intr(struct phy_device *phydev) { return 0; } +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum); +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val); int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad, u16 regnum); int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum, @@ -2309,6 +2317,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed); /* Clause 45 PHY */ int genphy_c45_restart_aneg(struct phy_device *phydev); int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart); +int genphy_c45_pma_soft_reset(struct phy_device *phydev); int genphy_c45_aneg_done(struct phy_device *phydev); int genphy_c45_read_link(struct phy_device *phydev); int genphy_c45_read_lpa(struct phy_device *phydev); diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 2bc0db3d52ac..1dda5c7ed5f1 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -791,6 +791,7 @@ static inline int phylink_get_link_timer_ns(phy_interface_t interface) { switch (interface) { case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_USXGMII: case PHY_INTERFACE_MODE_10G_QXGMII: diff --git a/include/linux/ptr_ring.h b/include/linux/ptr_ring.h index d2c3629bbe45..631c43fde440 100644 --- a/include/linux/ptr_ring.h +++ b/include/linux/ptr_ring.h @@ -96,6 +96,26 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) return ret; } +/* Report whether the next __ptr_ring_produce() has room for one entry: + * 0 means the single slot at r->queue[r->producer] is free, -ENOSPC means + * the ring is full, which is transient, and -EINVAL means r->size is 0, + * which is permanent. A caller that stops producing and waits for space + * must therefore do so only for -ENOSPC. + * + * Note: callers invoking this in a loop must use a compiler barrier, + * for example cpu_relax(). Callers must hold producer_lock. + */ +static inline int __ptr_ring_check_produce(struct ptr_ring *r) +{ + if (unlikely(!r->size)) + return -EINVAL; + + if (data_race(r->queue[r->producer])) + return -ENOSPC; + + return 0; +} + /* Note: callers invoking this in a loop must use a compiler barrier, * for example cpu_relax(). Callers must hold producer_lock. * Callers are responsible for making sure pointer that is being queued @@ -103,8 +123,10 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) */ static inline int __ptr_ring_produce(struct ptr_ring *r, void *ptr) { - if (unlikely(!r->size) || data_race(r->queue[r->producer])) - return -ENOSPC; + int ret = __ptr_ring_check_produce(r); + + if (ret) + return ret; /* Make sure the pointer we are storing points to a valid data. */ /* Pairs with the dependency ordering in __ptr_ring_consume. */ diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index 6816e4c5f3f0..deea02a034c3 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -100,7 +100,8 @@ struct rfkill * __must_check rfkill_alloc(const char *name, int __must_check rfkill_register(struct rfkill *rfkill); /** - * rfkill_pause_polling(struct rfkill *rfkill) + * rfkill_pause_polling - Pause polling + * @rfkill: rfkill struct * * Pause polling -- say transmitter is off for other reasons. * NOTE: not necessary for suspend/resume -- in that case the @@ -110,9 +111,9 @@ int __must_check rfkill_register(struct rfkill *rfkill); void rfkill_pause_polling(struct rfkill *rfkill); /** - * rfkill_resume_polling(struct rfkill *rfkill) + * rfkill_resume_polling - Resume polling + * @rfkill: rfkill struct * - * Resume polling * NOTE: not necessary for suspend/resume -- in that case the * core stops polling anyway */ @@ -325,6 +326,8 @@ static inline enum rfkill_type rfkill_find_type(const char *name) #ifdef CONFIG_RFKILL_LEDS /** * rfkill_get_led_trigger_name - Get the LED trigger name for the button's LED. + * @rfkill: rfkill struct + * * This function might return a NULL pointer if registering of the * LED trigger failed. Use this as "default_trigger" for the LED. */ diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h index ea39dd23a197..95729339e7a5 100644 --- a/include/linux/rtnetlink.h +++ b/include/linux/rtnetlink.h @@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net); bool lockdep_rtnl_net_is_held(struct net *net); +void rtnl_net_queue_work(struct net *net); +void rtnl_net_flush_workqueue(void); +void rtnl_net_work_func(struct work_struct *work); + #define rcu_dereference_rtnl_net(net, p) \ rcu_dereference_check(p, lockdep_rtnl_net_is_held(net)) #define rtnl_net_dereference(net, p) \ @@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net) ASSERT_RTNL(); } +static inline void rtnl_net_flush_workqueue(void) +{ +} + #define rcu_dereference_rtnl_net(net, p) \ rcu_dereference_rtnl(p) #define rtnl_net_dereference(net, p) \ diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 22eda1d54a0e..95184183180f 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -5004,6 +5004,7 @@ static inline unsigned long skb_get_nfct(const struct sk_buff *skb) static inline void skb_set_nfct(struct sk_buff *skb, unsigned long nfct) { #if IS_ENABLED(CONFIG_NF_CONNTRACK) + DEBUG_NET_WARN_ON_ONCE(skb->_nfct & NFCT_PTRMASK); skb->slow_gro |= !!nfct; skb->_nfct = nfct; #endif diff --git a/include/linux/sonet.h b/include/linux/sonet.h deleted file mode 100644 index 2b802b6d12ad..000000000000 --- a/include/linux/sonet.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* sonet.h - SONET/SHD physical layer control */ -#ifndef LINUX_SONET_H -#define LINUX_SONET_H - - -#include -#include - -struct k_sonet_stats { -#define __HANDLE_ITEM(i) atomic_t i - __SONET_ITEMS -#undef __HANDLE_ITEM -}; - -extern void sonet_copy_stats(struct k_sonet_stats *from,struct sonet_stats *to); -extern void sonet_subtract_stats(struct k_sonet_stats *from, - struct sonet_stats *to); - -#endif diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 8a6807082672..6a8c77719322 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -274,7 +274,7 @@ struct tcp_sock { u32 write_seq; /* Tail(+1) of data held in tcp send buffer */ u32 pushed_seq; /* Last pushed seq, required to talk to windows */ u32 lsndtime; /* timestamp of last sent data packet (for restart window) */ - u32 mdev_us; /* medium deviation */ + u32 mdev_us; /* mean deviation of RTT, scaled by 4 (<< 2) in usecs */ u32 rtt_seq; /* sequence number to update rttvar */ u32 max_packets_out; /* max packets_out in last window */ u32 cwnd_usage_seq; /* right edge of cwnd usage tracking flight */ diff --git a/include/linux/udp.h b/include/linux/udp.h index ce56ebcee5cb..998906ec3b32 100644 --- a/include/linux/udp.h +++ b/include/linux/udp.h @@ -23,6 +23,33 @@ static inline struct udphdr *udp_hdr(const struct sk_buff *skb) return (struct udphdr *)skb_transport_header(skb); } +static inline unsigned int udp_get_len(const struct sk_buff *skb, + const struct udphdr *uh, + unsigned int dataoff) +{ + if (uh->len) + return ntohs(uh->len); + if (skb_is_gso(skb)) /* BIG TCP */ + return skb->len - dataoff; + return 0; +} + +static inline unsigned int udp_get_len_short(const struct udphdr *uh) +{ + return ntohs(uh->len); +} + +static inline void udp_set_len(struct udphdr *uh, unsigned int len) +{ + uh->len = len < GRO_LEGACY_MAX_SIZE ? htons(len) : 0; +} + +static inline void udp_set_len_short(struct udphdr *uh, unsigned int len) +{ + DEBUG_NET_WARN_ON_ONCE(len >= GRO_LEGACY_MAX_SIZE); + uh->len = htons(len); +} + #define UDP_HTABLE_SIZE_MIN_PERNET 128 #define UDP_HTABLE_SIZE_MIN (IS_ENABLED(CONFIG_BASE_SMALL) ? 128 : 256) #define UDP_HTABLE_SIZE_MAX 65536 diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index a2f6f1e04efb..4bb04a371105 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -2,7 +2,7 @@ /* * mctp-usb.h - MCTP USB transport binding: common definitions, * based on DMTF0283 specification: - * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf * * These are protocol-level definitions, that may be shared between host * and gadget drivers. @@ -13,18 +13,96 @@ #ifndef __LINUX_USB_MCTP_USB_H #define __LINUX_USB_MCTP_USB_H +#include +#include #include +/* + * MCTP-over-USB transport header. DSP0283 v1.0 has an 8-bit length field + * (preceded by 8 reserved bits), v1.1 has a 13-bit length field (preceded by + * 3 reserved bits). We use a be16 for our length to handle the larger v1.1 + * representation, and mask as appropriate. + */ struct mctp_usb_hdr { __be16 id; - u8 rsvd; - u8 len; + __be16 len; } __packed; -#define MCTP_USB_XFER_SIZE 512 +/* max transfer size for DSP0283 v1.0 */ +#define MCTP_USB_1_0_XFER_SIZE 512 #define MCTP_USB_BTU 68 #define MCTP_USB_MTU_MIN MCTP_USB_BTU -#define MCTP_USB_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) +#define MCTP_USB_1_0_PKTLEN_MAX U8_MAX +#define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) +#define MCTP_USB_1_1_PKTLEN_MAX GENMASK(12, 0) +#define MCTP_USB_1_1_MTU_MAX (MCTP_USB_1_1_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) #define MCTP_USB_DMTF_ID 0x1ab4 +/* mctp-usblib */ + +/* + * RX handle: drivers will typically create one on init, which persists for + * the life of the driver. The same handle is used for progressive + * prepare -> complete operations (for each incoming USB transfer), which + * result in netif_rx()-ing the MCTP packets received + */ +struct mctp_usblib_rx { + struct sk_buff *skb; + u16 ep_pktlen; + bool span; +}; + +int mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span); +void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx); + +int mctp_usblib_rx_prepare(struct net_device *netdev, + struct mctp_usblib_rx *rx, + void **bufp, size_t *lenp, gfp_t gfp); + +int mctp_usblib_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len); + +void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx); + +/* + * TX handle: created by mctp_usblib_tx_push() during the tx path, and + * may persist across multiple packet transmits. + */ +struct mctp_usblib_tx_ctx; + +struct mctp_usblib_tx_ops { + /* Start a USB TX for @data. On returning success, the implementation + * must arrange for mctp_usblib_tx_send_complete() to be called at some + * later point (eg., on urb completion). + */ + int (*send)(struct mctp_usblib_tx_ctx *tx_ctx, void *data, size_t len); +}; + +struct mctp_usblib_tx { + struct mctp_usblib_tx_ops ops; + void *priv; + bool span; + /* protects access to cur_ctx */ + spinlock_t lock; + /* context to which we are adding packets, cleared on send */ + struct mctp_usblib_tx_ctx *cur_ctx; +}; + +void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, + const struct mctp_usblib_tx_ops *ops, void *priv, + bool span); +void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx); + +void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx); + +int mctp_usblib_tx_push(struct net_device *dev, + struct mctp_usblib_tx *tx, + struct sk_buff *skb, bool more); + +void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx, + struct net_device *dev, bool ok); + +void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev, + enum skb_drop_reason reason); + #endif /* __LINUX_USB_MCTP_USB_H */ diff --git a/include/net/af_unix.h b/include/net/af_unix.h index 34f53dde65ce..bb1b3dee02e8 100644 --- a/include/net/af_unix.h +++ b/include/net/af_unix.h @@ -49,6 +49,7 @@ struct unix_sock { struct scm_stat scm_stat; int inq_len; bool recvmsg_inq; + bool scm_rights_notrunc; #if IS_ENABLED(CONFIG_AF_UNIX_OOB) struct sk_buff *oob_skb; #endif diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 30046a3c20f7..3357ee62d10b 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -52,13 +52,10 @@ struct vsock_sock { * The listening socket is the head for both lists. Sockets created * for connection requests are placed in the pending list until they * are connected, at which point they are put in the accept queue list - * so they can be accepted in accept(). If accept() cannot accept the - * connection, it is marked as rejected so the cleanup function knows - * to clean up the socket. + * so they can be accepted in accept(). */ struct list_head pending_links; struct list_head accept_queue; - bool rejected; struct delayed_work connect_work; struct delayed_work pending_work; struct delayed_work close_work; diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index 72f51b587a04..1f071ab55416 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -8,6 +8,13 @@ #define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ +/* + * Max header size, shared by both the devcoredump core and + * the dmp_hdr() registered by driver via hci_devcd_register() + */ +#define HCI_DEVCD_HDR_SIZE_MAX 512 +#define HCI_DEVCD_HDR_END_MARKER "--- Start dump ---\n" + typedef void (*coredump_t)(struct hci_dev *hdev); typedef void (*dmp_hdr_t)(struct hci_dev *hdev, struct sk_buff *skb); typedef void (*notify_change_t)(struct hci_dev *hdev, int state); @@ -60,6 +67,8 @@ struct hci_devcoredump { #ifdef CONFIG_DEV_COREDUMP +const char *hci_devcd_state_name(enum devcoredump_state state); + void hci_devcd_reset(struct hci_dev *hdev); void hci_devcd_rx(struct work_struct *work); void hci_devcd_timeout(struct work_struct *work); @@ -74,6 +83,11 @@ int hci_devcd_abort(struct hci_dev *hdev); #else +static inline const char *hci_devcd_state_name(enum devcoredump_state state) +{ + return ""; +} + static inline void hci_devcd_reset(struct hci_dev *hdev) {} static inline void hci_devcd_rx(struct work_struct *work) {} static inline void hci_devcd_timeout(struct work_struct *work) {} diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 50f0eef71fb1..1641d879dbda 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -653,6 +653,8 @@ enum { #define HCI_LE_LL_EXT_FEATURE 0x80 #define HCI_LE_CS 0x40 #define HCI_LE_CS_HOST 0x80 +#define HCI_LE_SCI 0x01 /* byte 9 - Shorter Connection Intervals */ +#define HCI_LE_SCI_HOST 0x02 /* byte 9 - Shorter Connection Intervals (Host) */ /* Connection modes */ #define HCI_CM_ACTIVE 0x0000 @@ -2489,6 +2491,46 @@ struct hci_cp_le_set_host_feature_v2 { __u8 bit_value; } __packed; +#define HCI_OP_LE_CONN_RATE 0x20a1 +struct hci_cp_le_conn_rate { + __le16 handle; + __le16 interval_min; + __le16 interval_max; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_SET_DEF_RATE 0x20a2 +struct hci_cp_le_set_def_rate { + __le16 interval_min; + __le16 interval_max; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_READ_CONN_INTERVAL 0x20a3 +struct hci_le_conn_interval_group { + __le16 min; + __le16 max; + __le16 stride; +} __packed; + +struct hci_rp_le_read_conn_interval { + __u8 status; + __u8 num_grps; + struct hci_le_conn_interval_group grps[]; +} __packed; + /* ---- HCI Events ---- */ struct hci_ev_status { __u8 status; @@ -3303,6 +3345,17 @@ struct hci_evt_le_cs_test_end_complete { __u8 status; } __packed; +#define HCI_EVT_LE_CONN_RATE_CHANGE 0x37 +struct hci_evt_le_conn_rate_change { + __u8 status; + __le16 handle; + __le16 interval; + __le16 subrate; + __le16 latency; + __le16 cont_number; + __le16 supv_timeout; +} __packed; + #define HCI_EV_VENDOR 0xff /* Internal events generated by Bluetooth stack */ @@ -3329,6 +3382,7 @@ struct hci_ev_si_security { /* ---- HCI Packet structures ---- */ #define HCI_COMMAND_HDR_SIZE 3 #define HCI_EVENT_HDR_SIZE 2 +#define HCI_MAX_EVENT_PLEN 255 #define HCI_ACL_HDR_SIZE 4 #define HCI_SCO_HDR_SIZE 3 #define HCI_ISO_HDR_SIZE 4 @@ -3407,6 +3461,16 @@ static inline struct hci_iso_hdr *hci_iso_hdr(const struct sk_buff *skb) #define hci_handle(h) (h & 0x0fff) #define hci_flags(h) (h >> 12) +static inline __u16 hci_acl_handle(const struct sk_buff *skb) +{ + return hci_handle(__le16_to_cpu(hci_acl_hdr(skb)->handle)); +} + +static inline __u16 hci_acl_dlen(const struct sk_buff *skb) +{ + return __le16_to_cpu(hci_acl_hdr(skb)->dlen); +} + /* ISO handle and flags pack/unpack */ #define hci_iso_flags_pb(f) (f & 0x0003) #define hci_iso_flags_ts(f) ((f >> 2) & 0x0001) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3df59849dcbe..e07418a5adce 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -416,6 +416,7 @@ struct hci_dev { __u16 le_conn_max_interval; __u16 le_conn_latency; __u16 le_supv_timeout; + __u16 le_min_rate_interval; __u16 le_def_tx_len; __u16 le_def_tx_time; __u16 le_max_tx_len; @@ -645,6 +646,8 @@ struct hci_dev { int (*setup)(struct hci_dev *hdev); int (*shutdown)(struct hci_dev *hdev); int (*send)(struct hci_dev *hdev, struct sk_buff *skb); + /* Handle HCI_EV_VENDOR; return true if handled, false otherwise */ + bool (*handle_ev_vendor)(struct hci_dev *hdev, struct sk_buff *skb); void (*notify)(struct hci_dev *hdev, unsigned int evt); void (*hw_error)(struct hci_dev *hdev, u8 code); int (*post_init)(struct hci_dev *hdev); @@ -720,6 +723,11 @@ struct hci_conn { __u16 le_conn_interval; __u16 le_conn_latency; __u16 le_supv_timeout; + __u16 le_rate_interval; + __u16 le_subrate; + __u16 le_rate_latency; + __u16 le_cont_num; + __u16 le_rate_supv_timeout; __u8 le_adv_data[HCI_MAX_EXT_AD_LENGTH]; __u8 le_adv_data_len; __u8 le_per_adv_data[HCI_MAX_PER_AD_TOT_LEN]; @@ -769,7 +777,7 @@ struct hci_conn { struct hci_dev *hdev; spinlock_t proto_lock; /* lock guarding protocol data */ - void *l2cap_data; + void *l2cap_data __guarded_by(&proto_lock, &hdev->lock); void *sco_data; void *iso_data __guarded_by(&proto_lock); @@ -812,6 +820,14 @@ struct hci_conn_params { u16 conn_latency; u16 supervision_timeout; + u16 rate_min_interval; + u16 rate_max_interval; + u16 subrate_min; + u16 subrate_max; + u16 max_latency; + u16 cont_num; + u16 rate_supv_timeout; + enum { HCI_AUTO_CONN_DISABLED, HCI_AUTO_CONN_REPORT, @@ -1771,7 +1787,13 @@ int hci_register_suspend_notifier(struct hci_dev *hdev); int hci_unregister_suspend_notifier(struct hci_dev *hdev); int hci_suspend_dev(struct hci_dev *hdev); int hci_resume_dev(struct hci_dev *hdev); -int hci_reset_dev(struct hci_dev *hdev); +int __hci_reset_dev(struct hci_dev *hdev, u8 hw_err_code); + +static inline int hci_reset_dev(struct hci_dev *hdev) +{ + return __hci_reset_dev(hdev, 0); +} + int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb); int hci_recv_diag(struct hci_dev *hdev, struct sk_buff *skb); __printf(2, 3) void hci_set_hw_info(struct hci_dev *hdev, const char *fmt, ...); @@ -2079,6 +2101,11 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define le_cs_host_capable(dev) \ ((dev)->le_features[5] & HCI_LE_CS_HOST) +#define le_sci_capable(dev) \ + ((dev)->le_features[9] & HCI_LE_SCI) +#define le_sci_enabled(dev) \ + (le_enabled(dev) && le_sci_capable(dev)) + #define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \ (!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG))) @@ -2494,6 +2521,8 @@ void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev, int mgmt_phy_configuration_changed(struct hci_dev *hdev, struct sock *skip); void mgmt_adv_monitor_device_lost(struct hci_dev *hdev, u16 handle, bdaddr_t *bdaddr, u8 addr_type); +void mgmt_conn_subrate_notify(struct hci_dev *hdev, struct hci_conn *conn, + u8 status); int hci_abort_conn(struct hci_conn *conn, u8 reason); void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index 73e494b2591d..a6579a868678 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -59,6 +59,7 @@ int __hci_cmd_sync_status(struct hci_dev *hdev, u16 opcode, u32 plen, int __hci_cmd_sync_status_sk(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param, u8 event, u32 timeout, struct sock *sk); +int __hci_reset_sync(struct hci_dev *hdev); int hci_cmd_sync_status(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param, u32 timeout); @@ -84,9 +85,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev, struct hci_cmd_sync_work_entry *entry); bool hci_cmd_sync_dequeue(struct hci_dev *hdev, hci_cmd_sync_work_func_t func, void *data, hci_cmd_sync_work_destroy_t destroy); -bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev, - hci_cmd_sync_work_func_t func, void *data, - hci_cmd_sync_work_destroy_t destroy); int hci_update_eir_sync(struct hci_dev *hdev); int hci_update_class_sync(struct hci_dev *hdev); @@ -185,6 +183,7 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn, struct hci_conn_params *params); +int hci_le_conn_rate_request(struct hci_dev *hdev, struct hci_conn *conn); int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn); diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index 08daed7a96d5..1e22eab1081c 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -118,6 +118,7 @@ struct mgmt_rp_read_index_list { #define MGMT_SETTING_LL_PRIVACY BIT(22) #define MGMT_SETTING_PAST_SENDER BIT(23) #define MGMT_SETTING_PAST_RECEIVER BIT(24) +#define MGMT_SETTING_SCI BIT(25) #define MGMT_OP_READ_INFO 0x0004 #define MGMT_READ_INFO_SIZE 0 @@ -893,6 +894,23 @@ struct mgmt_cp_hci_cmd_sync { } __packed; #define MGMT_HCI_CMD_SYNC_SIZE 6 +#define MGMT_OP_LOAD_CONN_SUBRATE 0x005C +struct mgmt_conn_subrate { + struct mgmt_addr_info addr; + __le16 min_interval; + __le16 max_interval; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; +} __packed; +struct mgmt_cp_load_conn_subrate { + __le16 param_count; + struct mgmt_conn_subrate params[] __counted_by_le(param_count); +} __packed; +#define MGMT_LOAD_CONN_SUBRATE_SIZE 2 + #define MGMT_EV_CMD_COMPLETE 0x0001 struct mgmt_ev_cmd_complete { __le16 opcode; @@ -1192,3 +1210,14 @@ struct mgmt_ev_mesh_device_found { struct mgmt_ev_mesh_pkt_cmplt { __u8 handle; } __packed; + +#define MGMT_EV_CONN_SUBRATE 0x0033 +struct mgmt_ev_conn_subrate { + struct mgmt_addr_info addr; + __u8 status; + __le16 interval; + __le16 subrate; + __le16 latency; + __le16 cont_num; + __le16 supv_timeout; +} __packed; diff --git a/include/net/bond_3ad.h b/include/net/bond_3ad.h index 05572c19e14b..ef667dff2972 100644 --- a/include/net/bond_3ad.h +++ b/include/net/bond_3ad.h @@ -302,8 +302,8 @@ void bond_3ad_state_machine_handler(struct work_struct *); void bond_3ad_initiate_agg_selection(struct bonding *bond, int timeout); void bond_3ad_adapter_speed_duplex_changed(struct slave *slave); void bond_3ad_handle_link_change(struct slave *slave, char link); -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info); -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); int bond_3ad_lacpdu_recv(const struct sk_buff *skb, struct bonding *bond, struct slave *slave); diff --git a/include/net/bonding.h b/include/net/bonding.h index 2c54a36a8477..598d56b1bc97 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -345,14 +345,14 @@ static inline bool bond_mode_uses_primary(int mode) mode == BOND_MODE_ALB; } -static inline bool bond_uses_primary(struct bonding *bond) +static inline bool bond_uses_primary(const struct bonding *bond) { return bond_mode_uses_primary(BOND_MODE(bond)); } -static inline struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond) +static inline struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond) { - struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); + const struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); return bond_uses_primary(bond) && slave ? slave->dev : NULL; } @@ -703,7 +703,7 @@ void bond_setup(struct net_device *bond_dev); unsigned int bond_get_num_tx_queues(void); int bond_netlink_init(void); void bond_netlink_fini(void); -struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond); +struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond); const char *bond_slave_link_status(s8 link); struct bond_vlan_tag *bond_verify_device_path(struct net_device *start_dev, struct net_device *end_dev, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index f5abf1db7558..97c16d4ff127 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1236,6 +1236,26 @@ ieee80211_chandef_max_power(struct cfg80211_chan_def *chandef) return chandef->chan->max_power; } +/** + * cfg80211_chandef_s1g_pri_width - return S1G primary width in MHz + * + * An S1G interface may have a primary channel width of either 1 + * or 2MHz depending on whether chandef::s1g_primary_2mhz is set. + * + * Note: There is _always_ a 1MHz primary subchannel, regardless + * of the primary width. So chandef::chan always points to this + * 1MHz primary channel. + * + * @chandef: the chandef to use + * + * Returns: width in MHz of the S1G primary channel in use + */ +static inline int +cfg80211_chandef_s1g_pri_width(struct cfg80211_chan_def *chandef) +{ + return chandef->s1g_primary_2mhz ? 2 : 1; +} + /** * cfg80211_any_usable_channels - check for usable channels * @wiphy: the wiphy to check for @@ -2803,7 +2823,7 @@ struct mesh_setup { u8 path_metric; u8 auth_id; const u8 *ie; - u8 ie_len; + size_t ie_len; bool is_authenticated; bool is_secure; bool user_mpm; @@ -3722,6 +3742,7 @@ enum wiphy_params_flags { /* The per TXQ device queue limit in airtime */ #define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L 5000 #define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H 12000 +#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC 50000 /* The per interface airtime threshold to switch to lower queue limit */ #define IEEE80211_AQL_THRESHOLD 24000 @@ -5086,8 +5107,9 @@ struct mgmt_frame_regs { * @tdls_mgmt: Transmit a TDLS management frame. * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * - * @probe_client: probe an associated client, must return a cookie that it - * later passes to cfg80211_probe_status(). + * @probe_peer: probe a connected peer (AP: STA MAC required; STA: no MAC), + * must use the @cookie as provided which is later passed to + * cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. * @@ -5198,7 +5220,8 @@ struct mgmt_frame_regs { * user space * * @tx_control_port: TX a control port frame (EAPoL). The noencrypt parameter - * tells the driver that the frame should not be encrypted. + * tells the driver that the frame should not be encrypted. A @cookie + * value of 0 means the caller does not want TX status reporting. * * @get_ftm_responder_stats: Retrieve FTM responder statistics, if available. * Statistics should be cumulative, currently no way to reset is provided. @@ -5435,14 +5458,14 @@ struct cfg80211_ops { struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr); + u64 cookie, const u8 *rx_addr); int (*cancel_remain_on_channel)(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); int (*mgmt_tx)(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); int (*mgmt_tx_cancel_wait)(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); @@ -5488,8 +5511,8 @@ struct cfg80211_ops { int (*tdls_oper)(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper); - int (*probe_client)(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie); + int (*probe_peer)(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 cookie); int (*set_noack_map)(struct wiphy *wiphy, struct net_device *dev, @@ -5587,7 +5610,7 @@ struct cfg80211_ops { const u8 *buf, size_t len, const u8 *dest, const __be16 proto, const bool noencrypt, int link_id, - u64 *cookie); + u64 cookie); int (*get_ftm_responder_stats)(struct wiphy *wiphy, struct net_device *dev, @@ -5690,7 +5713,6 @@ struct cfg80211_ops { * set this flag to update channels on beacon hints. * @WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY: support connection to non-primary link * of an NSTR mobile AP MLD. - * @WIPHY_FLAG_DISABLE_WEXT: disable wireless extensions for this device */ enum wiphy_flags { WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK = BIT(0), @@ -5702,7 +5724,7 @@ enum wiphy_flags { WIPHY_FLAG_4ADDR_STATION = BIT(6), WIPHY_FLAG_CONTROL_PORT_PROTOCOL = BIT(7), WIPHY_FLAG_IBSS_RSN = BIT(8), - WIPHY_FLAG_DISABLE_WEXT = BIT(9), + /* reuse bit 9 */ WIPHY_FLAG_MESH_AUTH = BIT(10), WIPHY_FLAG_SUPPORTS_EXT_KCK_32 = BIT(11), WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY = BIT(12), @@ -8405,6 +8427,8 @@ cfg80211_inform_bss(struct wiphy *wiphy, * @bss_type: type of BSS, see &enum ieee80211_bss_type * @privacy: privacy filter, see &enum ieee80211_privacy * @use_for: indicates which use is intended + * @extack: (optional) extack that is filled with the reason when no + * usable entry was found; may be %NULL * * Return: Reference-counted BSS on success. %NULL on error. */ @@ -8414,7 +8438,8 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for); + u32 use_for, + struct netlink_ext_ack *extack); /** * cfg80211_get_bss - get a BSS reference @@ -8438,7 +8463,7 @@ cfg80211_get_bss(struct wiphy *wiphy, struct ieee80211_channel *channel, { return __cfg80211_get_bss(wiphy, channel, bssid, ssid, ssid_len, bss_type, privacy, - NL80211_BSS_USE_FOR_NORMAL); + NL80211_BSS_USE_FOR_NORMAL, NULL); } static inline struct cfg80211_bss * @@ -9846,15 +9871,17 @@ bool cfg80211_rx_unexpected_4addr_frame(struct net_device *dev, const u8 *addr, /** * cfg80211_probe_status - notify userspace about probe status * @dev: the device the probe was sent on - * @addr: the address of the peer - * @cookie: the cookie filled in @probe_client previously + * @peer: The peer MAC address (or MLD address for MLO) or %NULL if not + * applicable (e.g. for STA/P2P-client) + * @cookie: the cookie filled in @probe_peer previously + * @link_id: The link ID on which the probe was sent (or -1 for non-MLO) * @acked: indicates whether probe was acked or not * @ack_signal: signal strength (in dBm) of the ACK frame. * @is_valid_ack_signal: indicates the ack_signal is valid or not. * @gfp: allocation flags */ -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp); /** diff --git a/include/net/devlink.h b/include/net/devlink.h index dd546dbd57cf..7abd23376319 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -36,7 +36,7 @@ struct devlink_port_phys_attrs { * struct devlink_port_pci_pf_attrs - devlink port's PCI PF attributes * @controller: Associated controller number * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_pf_attrs { u32 controller; @@ -50,7 +50,7 @@ struct devlink_port_pci_pf_attrs { * @pf: associated PCI function number for the devlink port instance * @vf: associated PCI VF number of a PF for the devlink port instance; * VF number starts from 0 for the first PCI virtual function - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_vf_attrs { u32 controller; @@ -64,7 +64,7 @@ struct devlink_port_pci_vf_attrs { * @controller: Associated controller number * @sf: associated SF number of a PF for the devlink port instance * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_sf_attrs { u32 controller; @@ -554,6 +554,7 @@ enum devlink_param_generic_id { DEVLINK_PARAM_GENERIC_ID_TOTAL_VFS, DEVLINK_PARAM_GENERIC_ID_NUM_DOORBELLS, DEVLINK_PARAM_GENERIC_ID_MAX_MAC_PER_VF, + DEVLINK_PARAM_GENERIC_ID_MAX_SFS, /* add new param generic ids above here*/ __DEVLINK_PARAM_GENERIC_ID_MAX, @@ -627,6 +628,9 @@ enum devlink_param_generic_id { #define DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_NAME "max_mac_per_vf" #define DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_TYPE DEVLINK_PARAM_TYPE_U32 +#define DEVLINK_PARAM_GENERIC_MAX_SFS_NAME "max_sfs" +#define DEVLINK_PARAM_GENERIC_MAX_SFS_TYPE DEVLINK_PARAM_TYPE_U32 + #define DEVLINK_PARAM_GENERIC(_id, _cmodes, _get, _set, _validate) \ { \ .id = DEVLINK_PARAM_GENERIC_ID_##_id, \ @@ -1594,6 +1598,15 @@ struct devlink_ops { struct devlink_rate *parent, void *priv_child, void *priv_parent, struct netlink_ext_ack *extack); + /* Indicates if cross-device rate nodes are supported. + * This also requires a shared common ancestor object all devices that + * could share rate nodes are nested in. + * If enabled, rate operations may be called on an instance with only + * the common ancestor lock held and *without that instance lock held*. + * It is the driver's responsibility to ensure proper serialization + * with other operations. + */ + bool supported_cross_device_rate_nodes; /** * selftests_check() - queries if selftest is supported * @devlink: devlink instance diff --git a/include/net/dsa.h b/include/net/dsa.h index 8c16ef23cc10..7507d632e7c6 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -59,6 +59,8 @@ struct tc_action; #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 #define DSA_TAG_PROTO_MXL862_VALUE 32 #define DSA_TAG_PROTO_NETC_VALUE 33 +#define DSA_TAG_PROTO_KSZ8463_VALUE 34 +#define DSA_TAG_PROTO_MT7628_VALUE 35 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -95,6 +97,8 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE, + DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE, + DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE, }; struct dsa_switch; diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 7dee0ae616e3..c6b94790fa81 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -82,7 +82,7 @@ struct fib_rules_ops { struct fib_rule_hdr *, struct nlattr **, struct netlink_ext_ack *); - int (*delete)(struct fib_rule *); + void (*delete)(struct fib_rule *); int (*compare)(struct fib_rule *, struct fib_rule_hdr *, struct nlattr **); @@ -93,11 +93,13 @@ struct fib_rules_ops { /* Called after modifications to the rules set, must flush * the route cache if one exists. */ void (*flush_cache)(struct fib_rules_ops *ops); + bool (*need_rtnl)(struct net *net); int nlgroup; struct list_head rules_list; struct module *owner; struct net *fro_net; + struct mutex lock; struct rcu_head rcu; }; diff --git a/include/net/geneve.h b/include/net/geneve.h index 5c96827a487e..ba2c14d61e90 100644 --- a/include/net/geneve.h +++ b/include/net/geneve.h @@ -68,9 +68,4 @@ static inline bool netif_is_geneve(const struct net_device *dev) !strcmp(dev->rtnl_link_ops->kind, "geneve"); } -#ifdef CONFIG_INET -struct net_device *geneve_dev_create_fb(struct net *net, const char *name, - u8 name_assign_type, u16 dst_port); -#endif /*ifdef CONFIG_INET */ - #endif /*ifdef__NET_GENEVE_H */ diff --git a/include/net/gre.h b/include/net/gre.h index ccd293203284..b55f67ecd2fc 100644 --- a/include/net/gre.h +++ b/include/net/gre.h @@ -32,8 +32,6 @@ struct gre_protocol { int gre_add_protocol(const struct gre_protocol *proto, u8 version); int gre_del_protocol(const struct gre_protocol *proto, u8 version); -struct net_device *gretap_fb_dev_create(struct net *net, const char *name, - u8 name_assign_type); int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, bool *csum_err, __be16 proto, int nhs); diff --git a/include/net/ieee80211_radiotap.h b/include/net/ieee80211_radiotap.h index c60867e7e43c..8bbaf77da7cf 100644 --- a/include/net/ieee80211_radiotap.h +++ b/include/net/ieee80211_radiotap.h @@ -95,6 +95,8 @@ enum ieee80211_radiotap_presence { IEEE80211_RADIOTAP_EXT = 31, IEEE80211_RADIOTAP_EHT_USIG = 33, IEEE80211_RADIOTAP_EHT = 34, + IEEE80211_RADIOTAP_UHR_ELR = 37, + IEEE80211_RADIOTAP_UHR = 38, }; /* for IEEE80211_RADIOTAP_FLAGS */ @@ -602,6 +604,194 @@ enum ieee80211_radiotap_eht_usig_tb { IEEE80211_RADIOTAP_EHT_USIG2_TB_B20_B25_TAIL = 0xfc000000, }; +/* + * ieee80211_radiotap_uhr_elr - content of UHR-ELR TLV (type 37) + * see https://www.radiotap.org/fields/UHR-ELR for details + */ +struct ieee80211_radiotap_uhr_elr { + __le32 known; + __le32 sig1, sig2, mark; +} __packed; + +enum ieee80211_radiotap_uhr_elr_known { + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_VERSION_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_UL_DL = 0x00000002, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_MCS = 0x00000004, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_CODING = 0x00000008, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_LENGTH = 0x00000010, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_LDPC_EXTRA_OFDM_SYM = 0x00000020, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_CRC = 0x00000040, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_TAIL = 0x00000080, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_STA_ID = 0x00000100, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_DISREGARD = 0x00000200, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_CRC = 0x00000400, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_TAIL = 0x00000800, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_CRC_CHECKED = 0x00001000, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_CRC_CHECKED = 0x00002000, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_MARK_BSS_COLOR = 0x00010000, +}; + +enum ieee80211_radiotap_uhr_elr_sig1 { + IEEE80211_RADIOTAP_UHR_ELR_SIG1_VERSION_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_UL_DL = 0x00000002, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_MCS = 0x00000004, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CODING = 0x00000008, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_LENGTH = 0x00001FF0, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_LDPC_EXTRA_OFDM_SYM = 0x00002000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CRC = 0x0003C000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_TAIL = 0x00FC0000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CRC_VALID = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_elr_sig2 { + IEEE80211_RADIOTAP_UHR_ELR_SIG2_STA_ID = 0x000007FF, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_DISREGARD = 0x00003800, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_CRC = 0x0003C000, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_TAIL = 0x00FC0000, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_CRC_VALID = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_elr_mark { + IEEE80211_RADIOTAP_UHR_ELR_MARK_BSS_COLOR = 0x0000003F, +}; + +/* + * ieee80211_radiotap_uhr - content of UHR TLV (type 38) + * see https://www.radiotap.org/fields/UHR for details + */ +struct ieee80211_radiotap_uhr { + __le32 known; + __le32 data[9]; + struct { + __le32 known, info; + } user[]; +} __packed; + +enum ieee80211_radiotap_uhr_known { + IEEE80211_RADIOTAP_UHR_KNOWN_SPATIAL_REUSE = 0x00000001, + IEEE80211_RADIOTAP_UHR_KNOWN_GI_LTF_SIZE = 0x00000002, + IEEE80211_RADIOTAP_UHR_KNOWN_NUMBER_OF_UHR_LTF_SYMBOLS = 0x00000004, + IEEE80211_RADIOTAP_UHR_KNOWN_LDPC_EXTRA_SYMBOL_SEGMENT = 0x00000008, + IEEE80211_RADIOTAP_UHR_KNOWN_PRE_FEC_PADDING_FACTOR = 0x00000010, + IEEE80211_RADIOTAP_UHR_KNOWN_PE_DISAMBIGUITY = 0x00000020, + IEEE80211_RADIOTAP_UHR_KNOWN_DISREGARD_OFDMA = 0x00000040, + IEEE80211_RADIOTAP_UHR_KNOWN_CRC1 = 0x00000080, + IEEE80211_RADIOTAP_UHR_KNOWN_TAIL1 = 0x00000100, + IEEE80211_RADIOTAP_UHR_KNOWN_CRC2 = 0x00000200, + IEEE80211_RADIOTAP_UHR_KNOWN_TAIL2 = 0x00000400, + IEEE80211_RADIOTAP_UHR_KNOWN_INTERFERENCE_MITIGATION = 0x00000800, + IEEE80211_RADIOTAP_UHR_KNOWN_DISREGARD_NON_OFDMA = 0x00001000, + IEEE80211_RADIOTAP_UHR_KNOWN_NUMBER_OF_NON_OFDMA_USERS = 0x00002000, + IEEE80211_RADIOTAP_UHR_KNOWN_COMMON_ENCODING_BLOCK_CRC = 0x00004000, + IEEE80211_RADIOTAP_UHR_KNOWN_COMMON_ENCODING_BLOCK_TAIL = 0x00008000, + IEEE80211_RADIOTAP_UHR_KNOWN_RU_MRU_DRU_SIZE = 0x00010000, + IEEE80211_RADIOTAP_UHR_KNOWN_RU_MRU_INDEX = 0x00020000, + IEEE80211_RADIOTAP_UHR_KNOWN_DRU_RRU_ALLOC_TB_FMT = 0x00040000, + IEEE80211_RADIOTAP_UHR_KNOWN_PRI80_CHAN_POS = 0x00080000, +}; + +enum ieee80211_radiotap_uhr_data { + /* data[0] */ + IEEE80211_RADIOTAP_UHR_DATA0_SPATIAL_REUSE = 0x0000000F, + IEEE80211_RADIOTAP_UHR_DATA0_GI_LTF_SIZE = 0x00000030, + IEEE80211_RADIOTAP_UHR_DATA0_NUMBER_OF_LTF_SYMBOLS = 0x00000700, + IEEE80211_RADIOTAP_UHR_DATA0_LDPC_EXTRA_SYMBOL_SEGMENT = 0x00000800, + IEEE80211_RADIOTAP_UHR_DATA0_PRE_FEC_PADDING_FACTOR = 0x00003000, + IEEE80211_RADIOTAP_UHR_DATA0_PE_DISAMBIGUITY = 0x00004000, + IEEE80211_RADIOTAP_UHR_DATA0_DISREGARD_OFDMA = 0x00078000, + IEEE80211_RADIOTAP_UHR_DATA0_CRC1 = 0x00780000, + IEEE80211_RADIOTAP_UHR_DATA0_TAIL1 = 0x1f800000, + /* data[1] */ + IEEE80211_RADIOTAP_UHR_DATA1_RU_MRU_DRU_SIZE = 0x0000001f, + IEEE80211_RADIOTAP_UHR_DATA1_RU_MRU_INDEX = 0x00001fe0, + IEEE80211_RADIOTAP_UHR_DATA1_RU_ALLOC_CC_1_1_1 = 0x003fe000, + IEEE80211_RADIOTAP_UHR_DATA1_RU_ALLOC_CC_1_1_1_KNOWN = 0x00400000, + IEEE80211_RADIOTAP_UHR_DATA1_PRI80_CHAN_POS = 0xc0000000, + /* data[2] */ + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_1 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_1_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_1_1_2 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_1_1_2_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_2 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_2_KNOWN = 0x20000000, + /* data[3] */ + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_1 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_1_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_2_2_1 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_2_2_1_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_2 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_2_KNOWN = 0x20000000, + /* data[4] */ + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_2 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_2_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_1_2_3 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_1_2_3_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_3 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_3_KNOWN = 0x20000000, + /* data[5] */ + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_4 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_4_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_2_2_4 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_2_2_4_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_5 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_5_KNOWN = 0x20000000, + /* data[6] */ + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_5 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_5_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_1_2_6 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_1_2_6_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_6 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_6_KNOWN = 0x20000000, + /* data[7] */ + IEEE80211_RADIOTAP_UHR_DATA7_CRC2 = 0x0000000f, + IEEE80211_RADIOTAP_UHR_DATA7_TAIL2 = 0x000003f0, + IEEE80211_RADIOTAP_UHR_DATA7_INTERFERENCE_MITIGATION = 0x00000400, + IEEE80211_RADIOTAP_UHR_DATA7_DISREGARD_NON_OFDMA = 0x00001800, + IEEE80211_RADIOTAP_UHR_DATA7_NUMBER_OF_NON_OFDMA_USERS = 0x0000e000, + IEEE80211_RADIOTAP_UHR_DATA7_COMMON_ENCODING_BLOCK_CRC = 0x000f0000, + IEEE80211_RADIOTAP_UHR_DATA7_COMMON_ENCODING_BLOCK_TAIL = 0x03f00000, + /* data[8] */ + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_PS_160= 0x00000001, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_B0 = 0x00000002, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_B7_B1 = 0x000001fc, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_INDICATION = 0x00000200, +}; + +enum ieee80211_radiotap_uhr_user_known { + IEEE80211_RADIOTAP_UHR_USER_KNOWN_STA_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_MCS = 0x00000002, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_NSS = 0x00000004, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_UEQM = 0x00000008, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_BF = 0x00000010, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_CODING = 0x00000020, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_UEQM_PATTERN = 0x00000040, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_2X_LDPC = 0x00000080, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_SPATIAL_CONFIG = 0x00000100, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DISREGARD = 0x00000200, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_BSS_COLOR_INDICATION = 0x00000400, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USR_ENC_BLK_CRC = 0x00000800, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USR_ENC_BLK_TAIL = 0x00001000, + /* really 'known' but actual data */ + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DATA_USR_ENC_BLK_CRC = 0x000f0000, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DATA_USR_ENC_BLK_TAIL = 0x03f00000, + /* indicates this user was captured */ + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USER_CAPTURED = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_user_info { + IEEE80211_RADIOTAP_UHR_USER_INFO_STA_ID = 0x000007ff, + IEEE80211_RADIOTAP_UHR_USER_INFO_MCS = 0x0000f800, + IEEE80211_RADIOTAP_UHR_USER_INFO_NSS = 0x00070000, + IEEE80211_RADIOTAP_UHR_USER_INFO_SPATIAL_CONFIG = 0x000f0000, + IEEE80211_RADIOTAP_UHR_USER_INFO_UEQM = 0x00100000, + IEEE80211_RADIOTAP_UHR_USER_INFO_DISREGARD = 0x00100000, + IEEE80211_RADIOTAP_UHR_USER_INFO_BF = 0x00200000, + IEEE80211_RADIOTAP_UHR_USER_INFO_BSS_COLOR_INDICATION = 0x00200000, + IEEE80211_RADIOTAP_UHR_USER_INFO_UEQM_PATTERN = 0x00c00000, + IEEE80211_RADIOTAP_UHR_USER_INFO_CODING = 0x01000000, + IEEE80211_RADIOTAP_UHR_USER_INFO_2X_LDPC = 0x02000000, +}; + /** * ieee80211_get_radiotap_len - get radiotap header length * @data: pointer to the header diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 9cd27e1b9b69..232289b439c3 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -468,7 +468,8 @@ void fib6_clean_all_skip_notify(struct net *net, int fib6_add(struct fib6_node *root, struct fib6_info *rt, struct nl_info *info, struct netlink_ext_ack *extack); -int fib6_del(struct fib6_info *rt, struct nl_info *info); +int fib6_del(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason); static inline void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr) @@ -532,6 +533,8 @@ static inline void fib6_rt_update(struct net *net, struct fib6_info *rt, #endif void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, unsigned int flags); +void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason); void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args, unsigned long now); diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 09ffe0f13ce7..cc045705862d 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -126,6 +126,8 @@ int ipv6_route_ioctl(struct net *net, unsigned int cmd, int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, struct netlink_ext_ack *extack); int ip6_ins_rt(struct net *net, struct fib6_info *f6i); +int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i, + enum rt_del_reason del_reason); #if IS_ENABLED(CONFIG_IPV6) int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify); #else diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h index c63a3c4967ae..0a35355fb0f3 100644 --- a/include/net/ip_fib.h +++ b/include/net/ip_fib.h @@ -302,7 +302,8 @@ static inline struct fib_table *fib_get_table(struct net *net, u32 id) &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] : &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]; - tb_hlist = rcu_dereference_rtnl(hlist_first_rcu(ptr)); + /* Only fib4_rules_init() adds fib_table. */ + tb_hlist = rcu_dereference_protected(hlist_first_rcu(ptr), true); return hlist_entry(tb_hlist, struct fib_table, tb_hlist); } diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..7c9aadfe8fe3 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -412,7 +412,6 @@ bool ip_tunnel_parm_from_user(struct ip_tunnel_parm_kern *kp, bool ip_tunnel_parm_to_user(void __user *data, struct ip_tunnel_parm_kern *kp); int ip_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr, void __user *data, int cmd); -int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict); int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu); struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn, @@ -629,8 +628,7 @@ struct metadata_dst *iptunnel_metadata_reply(struct metadata_dst *md, int skb_tunnel_check_pmtu(struct sk_buff *skb, struct dst_entry *encap_dst, int headroom, bool reply); -static inline void ip_tunnel_adj_headroom(struct net_device *dev, - unsigned int headroom) +static inline unsigned int ip_tunnel_limit_headroom(unsigned int headroom) { /* we must cap headroom to some upperlimit, else pskb_expand_head * will overflow header offsets in skb_headers_offset_update(). @@ -640,6 +638,14 @@ static inline void ip_tunnel_adj_headroom(struct net_device *dev, if (headroom > max_allowed) headroom = max_allowed; + return headroom; +} + +static inline void ip_tunnel_adj_headroom(struct net_device *dev, + unsigned int headroom) +{ + headroom = ip_tunnel_limit_headroom(headroom); + if (headroom > READ_ONCE(dev->needed_headroom)) WRITE_ONCE(dev->needed_headroom, headroom); } diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index be3a6617adf4..32fde731bceb 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2131,7 +2131,7 @@ static inline void ip_vs_notrack(struct sk_buff *skb) struct nf_conn *ct = nf_ct_get(skb, &ctinfo); if (ct) { - nf_conntrack_put(&ct->ct_general); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); } #endif @@ -2154,7 +2154,7 @@ void ip_vs_update_conntrack(struct sk_buff *skb, struct ip_vs_conn *cp, int outin); int ip_vs_confirm_conntrack(struct sk_buff *skb); void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs); void ip_vs_conn_drop_conntrack(struct ip_vs_conn *cp); diff --git a/include/net/libeth/rx.h b/include/net/libeth/rx.h index 5d991404845e..0e736846c5e8 100644 --- a/include/net/libeth/rx.h +++ b/include/net/libeth/rx.h @@ -69,6 +69,7 @@ enum libeth_fqe_type { * @type: type of the buffers this queue has * @hsplit: flag whether header split is enabled * @xdp: flag indicating whether XDP is enabled + * @no_napi: the queue is not a data queue and does not have NAPI * @buf_len: HW-writeable length per each buffer * @nid: ID of the closest NUMA node with memory */ @@ -85,12 +86,13 @@ struct libeth_fq { enum libeth_fqe_type type:2; bool hsplit:1; bool xdp:1; + bool no_napi:1; u32 buf_len; int nid; }; -int libeth_rx_fq_create(struct libeth_fq *fq, struct napi_struct *napi); +int libeth_rx_fq_create(struct libeth_fq *fq, void *napi_dev); void libeth_rx_fq_destroy(struct libeth_fq *fq); /** diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 4f95da023746..9d1fac6e8082 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -790,6 +790,10 @@ struct ieee80211_bss_npca_params { * be updated to 1, even if bss_param_ch_cnt didn't change. This allows * the link to know that it heard the latest value from its own beacon * (as opposed to hearing its value from another link's beacon). + * @enh_bss_param_ch_cnt: In BSS-mode, the enhanced BSS parameters change + * counter. See @bss_param_ch_cnt, it works the same way. + * @enh_bss_param_ch_cnt_link_id: In BSS-mode, the link_id for the enhanced + * BSS parameter change counter, see @bss_param_ch_cnt_link_id. * @s1g_long_beacon_period: number of beacon intervals between each long * beacon transmission. * @npca: NPCA parameters @@ -894,6 +898,8 @@ struct ieee80211_bss_conf { u8 bss_param_ch_cnt; u8 bss_param_ch_cnt_link_id; + u8 enh_bss_param_ch_cnt; + u8 enh_bss_param_ch_cnt_link_id; u8 s1g_long_beacon_period; @@ -1317,6 +1323,7 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate) * @status_data: internal data for TX status handling, assigned privately, * see also &enum ieee80211_status_data for the internal documentation * @status_data_idr: indicates status data is IDR allocated ID for ack frame + * @tx_time_mc: TX time estimate is for a multicast frame, used internally * @tx_time_est: TX time estimate in units of 4us, used internally * @control: union part for control data * @control.rates: TX rates array to try @@ -1342,6 +1349,11 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate) * @status.tx_time: airtime consumed for transmission; note this is only * used for WMM AC, not for airtime fairness * @status.flags: status flags, see &enum mac80211_tx_status_flags + * @status.link_valid: if the link which is identified by @status.link_id is + * valid. This flag is set by the driver in the TX status callback when the + * connection is MLO and the driver knows which link was used for TX. + * @status.link_id: id of the link used to transmit the packet. This is used + * along with @status.link_valid. * @status.status_driver_data: driver use area * @ack: union part for pure ACK data * @ack.cookie: cookie for the ACK @@ -1354,8 +1366,8 @@ struct ieee80211_tx_info { status_data_idr:1, status_data:13, hw_queue:4, + tx_time_mc:1, tx_time_est:10; - /* 1 free bit */ union { struct { @@ -1396,7 +1408,7 @@ struct ieee80211_tx_info { u8 pad; u16 tx_time; u8 flags; - u8 pad2; + u8 link_valid:1, link_id:4; void *status_driver_data[16 / sizeof(void *)]; } status; struct { @@ -6876,6 +6888,17 @@ void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid, bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +/** + * ieee80211_txq_aql_pending - get pending AQL airtime for a txq + * + * @hw: pointer obtained from ieee80211_alloc_hw() + * @txq: pointer obtained from station or virtual interface + * + * Return: pending airtime (in usec) for the given txq. + */ +u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw, + struct ieee80211_txq *txq); + /** * ieee80211_iter_keys - iterate keys programmed into the device * @hw: pointer obtained from ieee80211_alloc_hw() diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 0c395917b214..9d57a0ea0e5e 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -47,6 +47,7 @@ enum gdma_queue_type { GDMA_RQ, GDMA_CQ, GDMA_EQ, + GDMA_DIM, }; enum gdma_work_request_flags { @@ -126,6 +127,17 @@ union gdma_doorbell_entry { u64 tail_ptr : 31; u64 arm : 1; } eq; + + struct { + u64 id : 24; + u64 reserved : 8; + u64 mod_usec : 10; + u64 reserve1 : 5; + u64 mod_usec_vld : 1; + u64 mod_comps : 8; + u64 reserve2 : 7; + u64 mod_comps_vld: 1; + } dim; }; /* HW DATA */ struct gdma_msg_hdr { @@ -170,6 +182,7 @@ struct gdma_general_req { #define GDMA_MESSAGE_V2 2 #define GDMA_MESSAGE_V3 3 #define GDMA_MESSAGE_V4 4 +#define GDMA_MESSAGE_V5 5 struct gdma_general_resp { struct gdma_resp_hdr hdr; @@ -228,6 +241,14 @@ struct gdma_mem_info { void *virt_addr; u64 length; + /* Scattered fallback: when @nr_pages > 0 the ring is that many + * PAGE_SIZE coherent allocations in @pages_va/@pages_dma, not + * @virt_addr/@dma_handle. + */ + void **pages_va; + dma_addr_t *pages_dma; + unsigned int nr_pages; + /* Allocated by the PF driver */ u64 dma_region_handle; }; @@ -416,6 +437,9 @@ struct gdma_context { /* L2 MTU */ u16 adapter_mtu; + /* NIC supports CQE x8 coalescing */ + bool cqe8_coalescing_sup; + /* This maps a CQ index to the queue structure. */ unsigned int max_num_cqs; struct gdma_queue **cq_table; @@ -500,8 +524,14 @@ int mana_gd_poll_cq(struct gdma_queue *cq, struct gdma_comp *comp, int num_cqe); void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit); +ssize_t mana_gd_read_ring(struct gdma_queue *q, char __user *buf, + size_t count, loff_t *pos); + int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld); + struct gdma_wqe { u32 reserved :24; u32 last_vbytes :8; @@ -650,6 +680,12 @@ enum { /* Driver supports self recovery on Hardware Channel timeouts */ #define GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY BIT(25) +/* Driver supports dynamic interrupt moderation - DIM */ +#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28) + +/* Driver supports non-contiguous queue buffers */ +#define GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS BIT(30) + #define GDMA_DRV_CAP_FLAGS1 \ (GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \ GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \ @@ -665,7 +701,9 @@ enum { GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \ - GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT) + GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \ + GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION | \ + GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS) #define GDMA_DRV_CAP_FLAGS2 0 @@ -701,6 +739,9 @@ struct gdma_verify_ver_req { u8 os_ver_str4[128]; }; /* HW DATA */ +/* HW supports dynamic interrupt moderation - DIM */ +#define GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(15) + struct gdma_verify_ver_resp { struct gdma_resp_hdr hdr; u64 gdma_protocol_ver; @@ -1023,7 +1064,7 @@ void mana_gd_wq_ring_doorbell(struct gdma_context *gc, struct gdma_queue *queue); int mana_gd_alloc_memory(struct gdma_context *gc, unsigned int length, - struct gdma_mem_info *gmi); + struct gdma_mem_info *gmi, bool allow_scatter); void mana_gd_free_memory(struct gdma_mem_info *gmi); diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 04acb6791dbd..83b7eff4646e 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -4,6 +4,7 @@ #ifndef _MANA_H #define _MANA_H +#include #include #include @@ -30,6 +31,12 @@ enum TRI_STATE { TRI_STATE_TRUE = 1 }; +/* MANA ethtool private flag bit positions */ +enum mana_priv_flag_bits { + MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF = 0, + MANA_PRIV_FLAG_MAX, +}; + /* Number of entries for hardware indirection table must be in power of 2 */ #define MANA_INDIRECT_TABLE_MAX_SIZE 512 #define MANA_INDIRECT_TABLE_DEF_SIZE 64 @@ -61,11 +68,27 @@ enum TRI_STATE { #define MAX_PORTS_IN_MANA_DEV 256 -/* Maximum number of packets per coalesced CQE */ +/* Maximum number of PPIs per coalesced CQE */ #define MANA_RXCOMP_OOB_NUM_PPI 4 +/* 8-pkt mode packs up to 2 packets per PPI entry */ +#define MANA_CQE_COAL_PKTS_8 8 + +/* Default/max interrupt moderation settings */ +#define MANA_INTR_MODR_USEC_DEF 0 +#define MANA_INTR_MODR_COMP_DEF 0 + +#define MANA_ADAPTIVE_RX_DEF true +#define MANA_ADAPTIVE_TX_DEF true + +/* DIM doorbell value field layout */ +#define MANA_INTR_MODR_USEC_MAX GENMASK(9, 0) +#define MANA_INTR_MODR_USEC_VLD BIT(15) +#define MANA_INTR_MODR_COMP_MAX GENMASK(7, 0) +#define MANA_INTR_MODR_COMP_MASK GENMASK(23, 16) + /* Update this count whenever the respective structures are changed */ -#define MANA_STATS_RX_COUNT (6 + MANA_RXCOMP_OOB_NUM_PPI - 1) +#define MANA_STATS_RX_COUNT (6 + MANA_CQE_COAL_PKTS_8 - 1) #define MANA_STATS_TX_COUNT 11 #define MANA_RX_FRAG_ALIGNMENT 64 @@ -77,7 +100,7 @@ struct mana_stats_rx { u64 xdp_tx; u64 xdp_redirect; u64 pkt_len0_err; - u64 coalesced_cqe[MANA_RXCOMP_OOB_NUM_PPI - 1]; + u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1]; struct u64_stats_sync syncp; }; @@ -188,6 +211,7 @@ enum mana_cqe_type { CQE_RX_COALESCED_4 = 2, CQE_RX_OBJECT_FENCE = 3, CQE_RX_TRUNCATED = 4, + CQE_RX_COALESCED_8 = 7, CQE_TX_OKAY = 32, CQE_TX_SA_DROP = 33, @@ -224,12 +248,26 @@ struct mana_cqe_header { #define MANA_HASH_L4 \ (NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | NDIS_HASH_TCP_IPV6 | \ NDIS_HASH_UDP_IPV6 | NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_UDP_IPV6_EX) +#define MANA_HASH_ENABLE_SUPPORTED \ + (NDIS_HASH_IPV4 | NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | \ + NDIS_HASH_IPV6 | NDIS_HASH_TCP_IPV6 | NDIS_HASH_UDP_IPV6) -struct mana_rxcomp_perpkt_info { - u32 pkt_len : 16; - u32 reserved1 : 16; - u32 reserved2; - u32 pkt_hash; +/* Read PPI in two different layouts based on cqe_type */ +union mana_rxcomp_perpkt_info { + struct { + u32 pkt_len : 16; + u32 reserved1 : 16; + u32 reserved2; + u32 pkt_hash; + }; + + /* Up to two pkts per PPI entry */ + struct { + u32 pkt_hash0; + u16 pkt_len0; + u16 pkt_len1; + u32 pkt_hash1; + }; }; /* HW DATA */ /* Receive completion OOB */ @@ -250,7 +288,7 @@ struct mana_rxcomp_oob { u32 rx_udp_csum_fail : 1; u32 reserved2 : 1; - struct mana_rxcomp_perpkt_info ppi[MANA_RXCOMP_OOB_NUM_PPI]; + union mana_rxcomp_perpkt_info ppi[MANA_RXCOMP_OOB_NUM_PPI]; u32 rx_wqe_offset; }; /* HW DATA */ @@ -297,6 +335,17 @@ struct mana_cq { int work_done; int work_done_since_doorbell; int budget; + + /* DIM - Dynamic Interrupt Moderation */ + struct dim dim; + u16 dim_event_ctr; + + /* Cumulative TX completions fed to DIM. Updated and read only in + * NAPI context (mana_poll_tx_cq() / mana_update_tx_dim()), so they + * measure the hardware completion rate and need no u64_stats_sync. + */ + u64 tx_dim_pkts; + u64 tx_dim_bytes; }; struct mana_recv_buf_oob { @@ -505,6 +554,9 @@ struct mana_port_context { struct net_device *ndev; struct work_struct queue_reset_work; + /* Debug knob to log TX timeout but skip recovery reset */ + bool tx_timeout_skip_reset; + u8 mac_addr[ETH_ALEN]; struct mana_eq *eqs; @@ -540,6 +592,8 @@ struct mana_port_context { u32 rxbpre_headroom; u32 rxbpre_frag_count; + u32 priv_flags; + struct bpf_prog *bpf_prog; /* Create num_queues EQs, SQs, SQ-CQs, RQs and RQ-CQs, respectively. */ @@ -579,8 +633,18 @@ struct mana_port_context { bool port_st_save; /* Saved port state */ u8 cqe_coalescing_enable; + u8 cqe8_coalescing_enable; u32 cqe_coalescing_timeout_ns; + /* Interrupt moderation settings */ + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + + bool rx_dim_enabled; + bool tx_dim_enabled; + struct mana_ethtool_stats eth_stats; struct mana_ethtool_phy_stats phy_stats; @@ -606,6 +670,8 @@ int mana_alloc_queues(struct net_device *ndev); int mana_attach(struct net_device *ndev); int mana_detach(struct net_device *ndev, bool from_close); +void mana_dim_change(struct mana_cq *cq, bool enable); + int mana_probe(struct gdma_dev *gd, bool resuming); void mana_remove(struct gdma_dev *gd, bool suspending); @@ -641,6 +707,9 @@ struct mana_obj_spec { u32 queue_size; u32 attached_eq; u32 modr_ctx_id; + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; }; enum mana_command_code { @@ -709,6 +778,8 @@ struct mana_query_device_cfg_req { u32 reserved; }; /* HW DATA */ +#define MANA_PF_FLAG_1_CQE_8_COALESCING_SUPPORTED BIT(5) + struct mana_query_device_cfg_resp { struct gdma_resp_hdr hdr; @@ -772,6 +843,15 @@ struct mana_create_wqobj_req { u32 cq_size; u32 cq_moderation_ctx_id; u32 cq_parent_qid; + + /* V2 */ + u8 allow_rqwqe_chain; + + /* V3 */ + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 reserved2[2]; }; /* HW DATA */ struct mana_create_wqobj_resp { @@ -779,6 +859,12 @@ struct mana_create_wqobj_resp { u32 wq_id; u32 cq_id; mana_handle_t wq_obj; + + /* V2 */ + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 cq_moderation_enabled; + u8 reserved1[3]; }; /* HW DATA */ /* Destroy WQ Object */ @@ -929,7 +1015,10 @@ struct mana_cfg_rx_steer_req_v2 { mana_handle_t default_rxobj; u8 hashkey[MANA_HASH_KEY_SIZE]; u8 cqe_coalescing_enable; - u8 reserved2[7]; + u8 reserved2[3]; + u16 rss_hash_types; + u8 cqe8_coalescing_enable; /* v5 message */ + u8 reserved3; mana_handle_t indir_tab[] __counted_by(num_indir_entries); }; /* HW DATA */ diff --git a/include/net/mptcp.h b/include/net/mptcp.h index 71b9fc5a5796..485d55b66ea6 100644 --- a/include/net/mptcp.h +++ b/include/net/mptcp.h @@ -19,10 +19,6 @@ struct seq_file; /* MPTCP sk_buff extension data */ struct mptcp_ext { - union { - u64 data_ack; - u32 data_ack32; - }; u64 data_seq; u32 subflow_seq; u16 data_len; diff --git a/include/net/ndisc.h b/include/net/ndisc.h index 3da1a6f8d3f9..9e5379ad2d8e 100644 --- a/include/net/ndisc.h +++ b/include/net/ndisc.h @@ -430,6 +430,8 @@ void ndisc_update(const struct net_device *dev, struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags, u8 icmp6_type, struct ndisc_options *ndopts); +int ndisc_check_ns_na(struct sk_buff *skb); + /* * IGMP */ diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 80de5e98a66d..e5ee673b9fcf 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -197,6 +197,9 @@ struct net { #ifdef CONFIG_DEBUG_NET_SMALL_RTNL /* Move to a better place when the config guard is removed. */ struct mutex rtnl_mutex; + struct work_struct rtnl_work; + struct list_head dev_unreg_head; + spinlock_t dev_unreg_lock; #endif #if IS_ENABLED(CONFIG_VSOCKETS) struct netns_vsock vsock; @@ -522,12 +525,13 @@ struct ctl_table; #ifdef CONFIG_SYSCTL int net_sysctl_init(void); struct ctl_table_header *register_net_sysctl_sz(struct net *net, const char *path, - struct ctl_table *table, size_t table_size); + const struct ctl_table *table, + size_t table_size); void unregister_net_sysctl_table(struct ctl_table_header *header); #else static inline int net_sysctl_init(void) { return 0; } static inline struct ctl_table_header *register_net_sysctl_sz(struct net *net, - const char *path, struct ctl_table *table, size_t table_size) + const char *path, const struct ctl_table *table, size_t table_size) { return NULL; } diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index 3939b816b001..330517a1cb5c 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -68,21 +68,52 @@ struct net_shaper { * The operations are serialized via a per device lock. * * Device not supporting any kind of nesting should not provide the - * group operation. + * @group operation. * * Each shaper is uniquely identified within the device with a 'handle' * comprising the shaper scope and a scope-specific id. + * + * Driver ops vs uAPI + * ------------------ + * Members of the driver ops mirror the Netlink uAPI but driver calls do not + * map 1:1 to user calls. Drivers need to be careful when assuming that calls + * disallowed at the uAPI level will never be made at the driver level. + * The shaper core performs automatic reparenting and cleanup, generating + * additional calls. Notably: + * + * - @group calls in the driver facing API may have nodes as leaves (user is + * only allowed to construct groups with queues as leaves) + * - @group calls may update leaf's parent if the parent is about + * to be removed (re-parenting nodes explicitly is not supported in the uAPI) + * + * Implicit creation + * ----------------- + * Shapers are created implicitly, meaning that @set and @group operations + * are called both for existing and new shapers. The driver has to infer + * whether the operation is an update or a creation by tracking the handles. + * Removal of shapers is explicit and done with a @delete call. + * + * The @set operation implicitly creates NET_SHAPER_SCOPE_NETDEV and + * NET_SHAPER_SCOPE_QUEUE shapers. + * The @group operation implicitly creates NET_SHAPER_SCOPE_NETDEV and + * NET_SHAPER_SCOPE_NODE shapers (the group shaper itself), as well as + * NET_SHAPER_SCOPE_QUEUE shapers (leaves). */ struct net_shaper_ops { /** - * @group: create the specified shapers scheduling group + * @group: create a scheduling group or add leaves * - * Nest the @leaves shapers identified under the * @node shaper. + * Nest the @leaves shapers identified under the @node shaper. * All the shapers belong to the device specified by @binding. - * The @leaves arrays size is specified by @leaves_count. - * Create either the @leaves and the @node shaper; or if they already - * exists, links them together in the desired way. - * @leaves scope must be NET_SHAPER_SCOPE_QUEUE. + * The @leaves array's size is specified by @leaves_count. + * + * @node and @leaves may or may not already exist + * (see the "Implicit creation" note). If @node already exists, + * the @leaves should be *added* to its children. In this case, + * the @leaves array only holds new/modified leaves, not the full list. + * + * Re-parenting @leaves is implemented by a @group call on a new parent. + * There's no explicit call to remove the children from the old parent. */ int (*group)(struct net_shaper_binding *binding, int leaves_count, const struct net_shaper *leaves, @@ -103,6 +134,13 @@ struct net_shaper_ops { * * Removes the shaper configuration as identified by the given @handle * on the device specified by @binding, restoring the default behavior. + * + * Note that a @delete call on a NET_SHAPER_SCOPE_QUEUE shaper also + * implicitly removes the associated queue from the scheduling + * hierarchy. The driver must take care of that step. + * @delete calls on NET_SHAPER_SCOPE_NODE should not require any + * implicit re-parenting in the driver as core will re-parent the leaves + * first, before deleting the SCOPE_NODE shaper. */ int (*delete)(struct net_shaper_binding *binding, const struct net_shaper_handle *handle, diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h index 26d6babd92fc..8c978b9a467d 100644 --- a/include/net/netfilter/nf_conntrack_expect.h +++ b/include/net/netfilter/nf_conntrack_expect.h @@ -42,6 +42,9 @@ struct nf_conntrack_expect { /* Expectation class */ unsigned int class; + /* Event filter mask */ + u16 event_mask; + /* Function to call after setup and insertion */ void (*expectfn)(struct nf_conn *new, struct nf_conntrack_expect *this); diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index c761cd8158b2..335b8c43694f 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -43,11 +43,10 @@ struct nf_conntrack_helper { refcount_t ct_refcnt; - /* Tuple of things we will help (compared against server response) */ - struct nf_conntrack_tuple tuple; + u8 nfproto; /* NFPROTO_*, can be NFPROTO_UNSPEC */ + u8 l4proto; /* IPPROTO_UDP/TCP */ - /* Function to call when data passes; return verdict, or -1 to - invalidate. */ + /* Function to call when data passes; return verdict */ int __rcu (*help)(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct, enum ip_conntrack_info conntrackinfo); @@ -94,8 +93,7 @@ struct nf_conntrack_helper *nf_conntrack_helper_try_module_get(const char *name, void nf_conntrack_helper_put(struct nf_conntrack_helper *helper); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, + u8 l3num, u16 protonum, const char *name, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, @@ -108,6 +106,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, int nf_conntrack_helper_register(struct nf_conntrack_helper *, struct nf_conntrack_helper **); int __nf_conntrack_helper_register(struct nf_conntrack_helper *); void nf_conntrack_helper_unregister(struct nf_conntrack_helper *); +void nf_conntrack_helper_release(struct nf_conntrack_helper *); int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int, struct nf_conntrack_helper **); diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index ce414118962f..f2e2771f188f 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -117,7 +117,7 @@ struct flow_offload_tunnel { struct in6_addr dst_v6; }; - u8 l3_proto; + u8 inner_proto; }; struct flow_offload_tuple { @@ -310,6 +310,14 @@ int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow) void flow_offload_refresh(struct nf_flowtable *flow_table, struct flow_offload *flow, bool force); +static inline bool nf_flow_dst_check(struct flow_offload_tuple *tuple) +{ + if (!tuple->dst_cache) + return true; + + return dst_check(tuple->dst_cache, tuple->dst_cookie); +} + struct flow_offload_tuple_rhash *flow_offload_lookup(struct nf_flowtable *flow_table, struct flow_offload_tuple *tuple); void nf_flow_table_gc_run(struct nf_flowtable *flow_table); diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 6e27c56514df..cb7f8bf15671 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -118,6 +118,7 @@ struct netns_ipv4 { struct fib_rules_ops *rules_ops; struct fib_table __rcu *fib_main; struct fib_table __rcu *fib_default; + spinlock_t fib_table_hash_lock; unsigned int fib_rules_require_fldissect; bool fib_has_custom_rules; #endif diff --git a/include/net/nexthop.h b/include/net/nexthop.h index 572e69cda476..f86c115074d7 100644 --- a/include/net/nexthop.h +++ b/include/net/nexthop.h @@ -28,6 +28,7 @@ struct nh_config { u8 nh_protocol; u8 nh_blackhole; u8 nh_fdb; + __be16 nh_dst_port; u32 nh_flags; int nh_ifindex; @@ -63,6 +64,7 @@ struct nh_info { u8 family; bool reject_nh; bool fdb_nh; + __be16 dst_port; union { struct fib_nh_common fib_nhc; @@ -574,7 +576,8 @@ struct fib_nh_common *nexthop_fdb_nhc(struct nexthop *nh) } static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh, - int hash) + int hash, + __be16 *dst_port) { struct nh_info *nhi; struct nexthop *nhp; @@ -583,6 +586,7 @@ static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh, if (unlikely(!nhp)) return NULL; nhi = rcu_dereference(nhp->nh_info); + *dst_port = nhi->dst_port; return &nhi->fib_nhc; } #endif diff --git a/include/net/scm.h b/include/net/scm.h index c52519669349..86ae6bc109ec 100644 --- a/include/net/scm.h +++ b/include/net/scm.h @@ -50,8 +50,8 @@ struct scm_cookie { #endif }; -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm); -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm); +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc); +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc); int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm); void __scm_destroy(struct scm_cookie *scm); struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl); @@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg, void scm_recv_unix(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm, int flags); -static inline int scm_recv_one_fd(struct file *f, int __user *ufd, - unsigned int flags) -{ - if (!ufd) - return -EFAULT; - return receive_fd(f, ufd, flags); -} +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags, + bool notrunc); #endif /* __LINUX_NET_SCM_H */ diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h index 74b3790e2a3d..85efa65d3912 100644 --- a/include/net/sctp/auth.h +++ b/include/net/sctp/auth.h @@ -86,9 +86,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, const struct sctp_association *asoc); int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc); -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp); +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp); void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); diff --git a/include/net/udp.h b/include/net/udp.h index 8262e2b215b4..1fee17274745 100644 --- a/include/net/udp.h +++ b/include/net/udp.h @@ -430,7 +430,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb, netdev_features_t features, bool is_ipv6); int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen); + sockopt_t *opt); int udp_lib_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval, unsigned int optlen, int (*push_pending_frames)(struct sock *)); diff --git a/include/net/vxlan.h b/include/net/vxlan.h index dfba89695efc..7b8207505523 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -359,9 +359,6 @@ struct vxlan_dev { VXLAN_F_MC_ROUTE | \ 0) -struct net_device *vxlan_dev_create(struct net *net, const char *name, - u8 name_assign_type, struct vxlan_config *conf); - static inline netdev_features_t vxlan_features_check(struct sk_buff *skb, netdev_features_t features) { @@ -567,8 +564,9 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh, struct vxlan_rdst *rdst) { struct fib_nh_common *nhc; + __be16 dst_port = 0; - nhc = nexthop_path_fdb_result(nh, hash >> 1); + nhc = nexthop_path_fdb_result(nh, hash >> 1, &dst_port); if (unlikely(!nhc)) return false; @@ -583,6 +581,8 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh, break; } + rdst->remote_port = dst_port; + return true; } diff --git a/include/trace/events/icmp.h b/include/trace/events/icmp.h index 31559796949a..09ae115099df 100644 --- a/include/trace/events/icmp.h +++ b/include/trace/events/icmp.h @@ -44,7 +44,7 @@ TRACE_EVENT(icmp_send, } else { __entry->sport = ntohs(uh->source); __entry->dport = ntohs(uh->dest); - __entry->ulen = ntohs(uh->len); + __entry->ulen = udp_get_len_short(uh); } p32 = (__be32 *) __entry->saddr; diff --git a/include/trace/events/mptcp.h b/include/trace/events/mptcp.h index 04521acba483..22882bd03459 100644 --- a/include/trace/events/mptcp.h +++ b/include/trace/events/mptcp.h @@ -75,7 +75,6 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, TP_ARGS(mpext), TP_STRUCT__entry( - __field(u64, data_ack) __field(u64, data_seq) __field(u32, subflow_seq) __field(u16, data_len) @@ -94,7 +93,6 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, ), TP_fast_assign( - __entry->data_ack = mpext->ack64 ? mpext->data_ack : mpext->data_ack32; __entry->data_seq = mpext->data_seq; __entry->subflow_seq = mpext->subflow_seq; __entry->data_len = mpext->data_len; @@ -112,8 +110,8 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, __entry->infinite_map = mpext->infinite_map; ), - TP_printk("data_ack=%llu data_seq=%llu subflow_seq=%u data_len=%u csum=%x use_map=%u dsn64=%u data_fin=%u use_ack=%u ack64=%u mpc_map=%u frozen=%u reset_transient=%u reset_reason=%u csum_reqd=%u infinite_map=%u", - __entry->data_ack, __entry->data_seq, + TP_printk("data_seq=%llu subflow_seq=%u data_len=%u csum=%x use_map=%u dsn64=%u data_fin=%u use_ack=%u ack64=%u mpc_map=%u frozen=%u reset_transient=%u reset_reason=%u csum_reqd=%u infinite_map=%u", + __entry->data_seq, __entry->subflow_seq, __entry->data_len, __entry->csum, __entry->use_map, __entry->dsn64, __entry->data_fin, diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h index 53b5a8c002b1..84ea7b92936e 100644 --- a/include/uapi/asm-generic/socket.h +++ b/include/uapi/asm-generic/socket.h @@ -150,6 +150,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__)) diff --git a/include/uapi/linux/batadv_packet.h b/include/uapi/linux/batadv_packet.h index 1241285b866c..32436560ecc8 100644 --- a/include/uapi/linux/batadv_packet.h +++ b/include/uapi/linux/batadv_packet.h @@ -192,13 +192,19 @@ enum batadv_tvlv_type { }; #pragma pack(2) -/* the destination hardware field in the ARP frame is used to - * transport the claim type and the group id +/** + * struct batadv_bla_claim_dst - layout of the destination MAC of a BLA claim + * frame + * @magic: fixed magic prefix (FF:43:05) identifying claim frames + * @type: claim frame type, see &enum batadv_bla_claimframe + * @group: group identifier of the announcing backbone gateway + * + * used in the destination hardware field of the ARP frame */ struct batadv_bla_claim_dst { - __u8 magic[3]; /* FF:43:05 */ - __u8 type; /* bla_claimframe */ - __be16 group; /* group id */ + __u8 magic[3]; + __u8 type; + __be16 group; }; /** diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ca713bcc47b9..a6801feb7744 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -648,6 +648,8 @@ enum devlink_attr { DEVLINK_ATTR_INDEX, /* uint */ DEVLINK_ATTR_RESOURCE_SCOPE_MASK, /* u32 */ + DEVLINK_ATTR_PARENT_DEV, /* nested */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 55eaa82f5f98..85b898b1db5e 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -129,6 +129,9 @@ enum dpll_type { * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator. + * When connected as a DPLL input, the DPLL enters NCO mode where the output + * frequency is adjusted by the host via the PTP clock interface. */ enum dpll_pin_type { DPLL_PIN_TYPE_MUX = 1, @@ -136,6 +139,7 @@ enum dpll_pin_type { DPLL_PIN_TYPE_SYNCE_ETH_PORT, DPLL_PIN_TYPE_INT_OSCILLATOR, DPLL_PIN_TYPE_GNSS, + DPLL_PIN_TYPE_INT_NCO, /* private: */ __DPLL_PIN_TYPE_MAX, @@ -208,11 +212,15 @@ enum dpll_pin_operstate { * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed + * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to + * connected regardless of current DPLL device mode, overriding the active + * input selection. Requires state-can-change to be set as well. */ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8, }; #define DPLL_PHASE_OFFSET_DIVIDER 1000 diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index a2091d4e00f3..986d70caec33 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2013,7 +2013,13 @@ enum ethtool_link_mode_bit_indices { ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT = 39, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT = 40, ETHTOOL_LINK_MODE_1000baseX_Full_BIT = 41, + + /* Despite the "baseCR" in 10000baseCR, this is not an IEEE 802.3 baseCR + * It represents SFF-8431 Appendix-E SFP+ Direct Attach (10G-SFI-DA). + * The name is kept as-is for uAPI backward compatibility. + */ ETHTOOL_LINK_MODE_10000baseCR_Full_BIT = 42, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT = 43, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT = 44, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT = 45, diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h index aa7958b4e41d..7fb630b7fe31 100644 --- a/include/uapi/linux/if_addr.h +++ b/include/uapi/linux/if_addr.h @@ -36,6 +36,7 @@ enum { IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ IFA_TARGET_NETNSID, IFA_PROTO, /* u8, address protocol */ + IFA_MC_USERS, /* u32, multicast group users */ __IFA_MAX, }; diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h index 79d53c7a1ebd..a0ddc50a7534 100644 --- a/include/uapi/linux/if_tun.h +++ b/include/uapi/linux/if_tun.h @@ -69,6 +69,10 @@ #define IFF_NAPI_FRAGS 0x0020 /* Used in TUNSETIFF to bring up tun/tap without carrier */ #define IFF_NO_CARRIER 0x0040 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index b2541c948fc1..06f4bc3c20c7 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -332,8 +332,13 @@ #define MDIO_AN_10GBT_CTRL_ADV2_5G 0x0080 /* Advertise 2.5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV5G 0x0100 /* Advertise 5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV10G 0x1000 /* Advertise 10GBASE-T */ +#define MDIO_AN_10GBT_CTRL_MS_ENABLE 0x8000 /* Master/slave manual config enable */ +#define MDIO_AN_10GBT_CTRL_MS_VALUE 0x4000 /* Master/slave config value (1=Master) */ +#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE 0x2000 /* Master Preferred Type */ /* AN 10GBASE-T status register. */ +#define MDIO_AN_10GBT_STAT_MS_FAULT 0x8000 /* Master/slave fault */ +#define MDIO_AN_10GBT_STAT_MS_RES 0x4000 /* Master/slave resolution (1=Master) */ #define MDIO_AN_10GBT_STAT_LP2_5G 0x0020 /* LP is 2.5GBT capable */ #define MDIO_AN_10GBT_STAT_LP5G 0x0040 /* LP is 5GBT capable */ #define MDIO_AN_10GBT_STAT_LPTRR 0x0200 /* LP training reset req. */ diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h index bc49baf4a267..59cf1cee93bd 100644 --- a/include/uapi/linux/nexthop.h +++ b/include/uapi/linux/nexthop.h @@ -83,6 +83,9 @@ enum { /* u32; read-only; whether any driver collects HW stats */ NHA_HW_STATS_USED, + /* be16; UDP destination port for an fdb nexthop (e.g. VXLAN) */ + NHA_DST_PORT, + __NHA_MAX, }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 9998f6c0a665..020387d76412 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -922,13 +922,15 @@ * and wasn't already in a 4-addr VLAN. The event will be sent similarly * to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener. * - * @NL80211_CMD_PROBE_CLIENT: Probe an associated station on an AP interface - * by sending a null data frame to it and reporting when the frame is - * acknowledged. This is used to allow timing out inactive clients. Uses - * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a - * direct reply with an %NL80211_ATTR_COOKIE that is later used to match - * up the event with the request. The event includes the same data and - * has %NL80211_ATTR_ACK set if the frame was ACKed. + * @NL80211_CMD_PROBE_PEER: Probe a connected peer by sending a null data + * frame and reporting when the frame is acknowledged. + * In AP/GO mode, %NL80211_ATTR_MAC is required to identify the client. + * In STA/P2P-client mode, %NL80211_ATTR_MAC must be omitted (the AP is + * implied); the driver must advertise %NL80211_EXT_FEATURE_PROBE_AP. + * The command returns a direct reply with an %NL80211_ATTR_COOKIE that + * is later used to match up the event with the request. The event + * includes the same data and has %NL80211_ATTR_ACK set if the frame + * was ACKed. * * @NL80211_CMD_REGISTER_BEACONS: Register this socket to receive beacons from * other BSSes when any interfaces are in AP mode. This helps implement @@ -1558,7 +1560,7 @@ enum nl80211_commands { NL80211_CMD_UNEXPECTED_FRAME, - NL80211_CMD_PROBE_CLIENT, + NL80211_CMD_PROBE_PEER, NL80211_CMD_REGISTER_BEACONS, @@ -1729,6 +1731,7 @@ enum nl80211_commands { #define NL80211_CMD_GET_MESH_PARAMS NL80211_CMD_GET_MESH_CONFIG #define NL80211_CMD_SET_MESH_PARAMS NL80211_CMD_SET_MESH_CONFIG #define NL80211_MESH_SETUP_VENDOR_PATH_SEL_IE NL80211_MESH_SETUP_IE +#define NL80211_CMD_PROBE_CLIENT NL80211_CMD_PROBE_PEER /** * enum nl80211_attrs - nl80211 netlink attributes @@ -3165,6 +3168,23 @@ enum nl80211_commands { * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) * + * @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION + * (dump mode only). When set on an MLD station, the dump produces two + * %NL80211_CMD_NEW_STATION messages per station per dump call: + * + * 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO + * contains MLO-combined statistics (same content as a dump without + * this flag). + * + * 2. For each active link, a per-link message containing + * %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds + * %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC, + * and %NL80211_ATTR_STA_INFO with per-link statistics (see + * &enum nl80211_sta_info). + * + * The aggregated message always precedes the per-link messages for the + * same station within a dump sequence. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3763,6 +3783,8 @@ enum nl80211_attrs { NL80211_ATTR_NPCA_PRIMARY_FREQ, NL80211_ATTR_NPCA_PUNCT_BITMAP, + NL80211_ATTR_STA_DUMP_LINK_STATS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -4474,8 +4496,8 @@ enum nl80211_mpath_info { * capabilities IE * @NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE: HE PPE thresholds information as * defined in HE capabilities IE - * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities (__le16), - * given for all 6 GHz band channels + * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities, + * given for all 6 GHz band channels (binary, element content) * @NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS: vendor element capabilities that are * advertised on this band/for this iftype (binary) * @NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC: EHT MAC capabilities as in EHT @@ -7085,6 +7107,9 @@ enum nl80211_feature_flags { * LTF key seed via %NL80211_KEY_LTF_SEED. The seed is used to generate * secure LTF keys for secure LTF measurement sessions. * + * @NL80211_EXT_FEATURE_PROBE_AP: Driver supports probing the associated AP + * in STA mode using @NL80211_CMD_PROBE_PEER. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7166,6 +7191,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_IEEE8021X_AUTH, NL80211_EXT_FEATURE_ROC_ADDR_FILTER, NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, + NL80211_EXT_FEATURE_PROBE_AP, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h index aa2acdbda8f8..440825e65837 100644 --- a/include/uapi/linux/openvswitch.h +++ b/include/uapi/linux/openvswitch.h @@ -244,13 +244,33 @@ enum ovs_vport_cmd { OVS_VPORT_CMD_SET }; +/** + * enum ovs_vport_type - OVS vport types for %OVS_VPORT_ATTR_TYPE. + * @OVS_VPORT_TYPE_NETDEV: Existing network device attached as a vport. + * @OVS_VPORT_TYPE_INTERNAL: Network device implemented by the OVS datapath. + * @OVS_VPORT_TYPE_GRE: Legacy GRE tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_VXLAN: Legacy VXLAN tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_GENEVE: Legacy Geneve tunnel. Not supported, see below. + * + * The tunnel vport types are not supported. Instead, create the tunnel device + * using %RTM_NEWLINK with the appropriate %IFLA_INFO_KIND (e.g. ``gre``, + * ``gretap``, ``vxlan``, ``geneve``, or other tunnel types) and add it as + * %OVS_VPORT_TYPE_NETDEV. To match and set tunnel parameters on a per-flow + * basis, the tunnel device should collect metadata. To do that, some tunnel + * types require an explicit flag such as %IFLA_VXLAN_COLLECT_METADATA for + * ``vxlan``, while others such as ``bareudp`` collect metadata + * unconditionally. + */ enum ovs_vport_type { + /* private: */ OVS_VPORT_TYPE_UNSPEC, + /* public: */ OVS_VPORT_TYPE_NETDEV, /* network device */ OVS_VPORT_TYPE_INTERNAL, /* network device implemented by datapath */ - OVS_VPORT_TYPE_GRE, /* GRE tunnel. */ - OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel. */ - OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */ + OVS_VPORT_TYPE_GRE, /* GRE tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel (legacy, not supported). */ + /* private: */ __OVS_VPORT_TYPE_MAX }; @@ -284,7 +304,7 @@ enum ovs_vport_type { * %OVS_VPORT_ATTR_NAME attributes are required. %OVS_VPORT_ATTR_PORT_NO is * optional; if not specified a free port number is automatically selected. * Whether %OVS_VPORT_ATTR_OPTIONS is required or optional depends on the type - * of vport. + * of vport. None of currently supported vport types support options. * * For other requests, if %OVS_VPORT_ATTR_NAME is specified then it is used to * look up the vport to operate on; otherwise dp_idx from the &struct @@ -336,7 +356,8 @@ enum { #define OVS_VXLAN_EXT_MAX (__OVS_VXLAN_EXT_MAX - 1) -/* OVS_VPORT_ATTR_OPTIONS attributes for tunnels. +/* OVS_VPORT_ATTR_OPTIONS attributes for legacy tunnel vports. + * Not supported, see the note for enum ovs_vport_type. */ enum { OVS_TUNNEL_ATTR_UNSPEC, diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index 27265fd31e5f..3988b51db539 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -399,6 +399,7 @@ enum rtattr_type_t { RTA_DPORT, RTA_NH_ID, RTA_FLOWLABEL, + RTA_DEL_REASON, __RTA_MAX }; @@ -407,6 +408,30 @@ enum rtattr_type_t { #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) +/* RTA_DEL_REASON: why the kernel deleted the route. u32. + * Emitted only on RTM_DELROUTE notifications, and only when the deletion + * path records a cause. Absence means either an older kernel or a + * deletion path that does not (yet) record its cause - consumers must + * treat "absent" and "unspec" identically. New causes may be appended. + * Currently only IPv6 deletion paths record a cause. + * + * The attribute is notification-only: the kernel rejects it in + * requests, so a notification must not be echoed back verbatim. + * + * The value space is family-agnostic: a value must never be + * reinterpreted per address family. A cause that only one family can + * produce still gets its own value rather than reusing another + * family's. + */ +enum rt_del_reason { + RT_DEL_REASON_UNSPEC, /* cause not recorded */ + RT_DEL_REASON_EXPIRED, /* RTF_EXPIRES lifetime ran out (GC) */ + RT_DEL_REASON_RA_WITHDRAWN, /* zero-lifetime RA / PIO / RIO */ + __RT_DEL_REASON_MAX +}; + +#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1) + /* RTM_MULTIPATH --- array of struct rtnexthop. * * "struct rtnexthop" describes all necessary nexthop information, diff --git a/include/uapi/linux/seg6_iptunnel.h b/include/uapi/linux/seg6_iptunnel.h index 485889b19900..e1964b3a4bb0 100644 --- a/include/uapi/linux/seg6_iptunnel.h +++ b/include/uapi/linux/seg6_iptunnel.h @@ -21,6 +21,7 @@ enum { SEG6_IPTUNNEL_UNSPEC, SEG6_IPTUNNEL_SRH, SEG6_IPTUNNEL_SRC, /* struct in6_addr */ + SEG6_IPTUNNEL_TABLE, /* __u32 FIB table for post-encap SID lookup */ __SEG6_IPTUNNEL_MAX, }; #define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 4fa569db8ec7..2fbff2618a1e 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -171,6 +171,7 @@ int irq_can_set_affinity(unsigned int irq) { return __irq_can_set_affinity(irq_to_desc(irq)); } +EXPORT_SYMBOL_GPL(irq_can_set_affinity); /** * irq_can_set_affinity_usr - Check if affinity of a irq can be set from user space diff --git a/lib/tests/blackhole_dev_kunit.c b/lib/tests/blackhole_dev_kunit.c index 06834ab35f43..fa3e0533038d 100644 --- a/lib/tests/blackhole_dev_kunit.c +++ b/lib/tests/blackhole_dev_kunit.c @@ -46,7 +46,7 @@ static void test_blackholedev(struct kunit *test) uh = (struct udphdr *)skb_push(skb, sizeof(struct udphdr)); skb_set_transport_header(skb, 0); uh->source = uh->dest = htons(UDP_PORT); - uh->len = htons(data_len); + udp_set_len_short(uh, data_len); uh->check = 0; /* (Network) IPv6 */ ip6h = (struct ipv6hdr *)skb_push(skb, sizeof(struct ipv6hdr)); diff --git a/net/6lowpan/nhc_udp.c b/net/6lowpan/nhc_udp.c index 0a506c77283d..ed4227e6db74 100644 --- a/net/6lowpan/nhc_udp.c +++ b/net/6lowpan/nhc_udp.c @@ -88,16 +88,16 @@ static int udp_uncompress(struct sk_buff *skb, size_t needed) switch (lowpan_dev(skb->dev)->lltype) { case LOWPAN_LLTYPE_IEEE802154: if (lowpan_802154_cb(skb)->d_size) - uh.len = htons(lowpan_802154_cb(skb)->d_size - - sizeof(struct ipv6hdr)); + udp_set_len_short(&uh, lowpan_802154_cb(skb)->d_size - + sizeof(struct ipv6hdr)); else - uh.len = htons(skb->len + sizeof(struct udphdr)); + udp_set_len_short(&uh, skb->len + sizeof(struct udphdr)); break; default: - uh.len = htons(skb->len + sizeof(struct udphdr)); + udp_set_len_short(&uh, skb->len + sizeof(struct udphdr)); break; } - pr_debug("uncompressed UDP length: src = %d", ntohs(uh.len)); + pr_debug("uncompressed UDP length: src = %d", udp_get_len_short(&uh)); /* replace the compressed UDP head by the uncompressed UDP * header diff --git a/net/atm/atm_misc.c b/net/atm/atm_misc.c index a30b83c1cb3f..c9d7609da184 100644 --- a/net/atm/atm_misc.c +++ b/net/atm/atm_misc.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -22,81 +21,3 @@ int atm_charge(struct atm_vcc *vcc, int truesize) return 0; } EXPORT_SYMBOL(atm_charge); - -struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc, int pdu_size, - gfp_t gfp_flags) -{ - struct sock *sk = sk_atm(vcc); - int guess = SKB_TRUESIZE(pdu_size); - - atm_force_charge(vcc, guess); - if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) { - struct sk_buff *skb = alloc_skb(pdu_size, gfp_flags); - - if (skb) { - atomic_add(skb->truesize-guess, - &sk->sk_rmem_alloc); - return skb; - } - } - atm_return(vcc, guess); - atomic_inc(&vcc->stats->rx_drop); - return NULL; -} -EXPORT_SYMBOL(atm_alloc_charge); - - -/* - * atm_pcr_goal returns the positive PCR if it should be rounded up, the - * negative PCR if it should be rounded down, and zero if the maximum available - * bandwidth should be used. - * - * The rules are as follows (* = maximum, - = absent (0), x = value "x", - * (x+ = x or next value above x, x- = x or next value below): - * - * min max pcr result min max pcr result - * - - - * (UBR only) x - - x+ - * - - * * x - * * - * - - z z- x - z z- - * - * - * x * - x+ - * - * * * x * * * - * - * z z- x * z z- - * - y - y- x y - x+ - * - y * y- x y * y- - * - y z z- x y z z- - * - * All non-error cases can be converted with the following simple set of rules: - * - * if pcr == z then z- - * else if min == x && pcr == - then x+ - * else if max == y then y- - * else * - */ - -int atm_pcr_goal(const struct atm_trafprm *tp) -{ - if (tp->pcr && tp->pcr != ATM_MAX_PCR) - return -tp->pcr; - if (tp->min_pcr && !tp->pcr) - return tp->min_pcr; - if (tp->max_pcr != ATM_MAX_PCR) - return -tp->max_pcr; - return 0; -} -EXPORT_SYMBOL(atm_pcr_goal); - -void sonet_copy_stats(struct k_sonet_stats *from, struct sonet_stats *to) -{ -#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i) - __SONET_ITEMS -#undef __HANDLE_ITEM -} -EXPORT_SYMBOL(sonet_copy_stats); - -void sonet_subtract_stats(struct k_sonet_stats *from, struct sonet_stats *to) -{ -#define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i) - __SONET_ITEMS -#undef __HANDLE_ITEM -} -EXPORT_SYMBOL(sonet_subtract_stats); diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c index 49e5861b58ec..93cd77680e64 100644 --- a/net/batman-adv/bat_algo.c +++ b/net/batman-adv/bat_algo.c @@ -42,7 +42,8 @@ void batadv_algo_init(void) */ struct batadv_algo_ops *batadv_algo_get(const char *name) { - struct batadv_algo_ops *bat_algo_ops = NULL, *bat_algo_ops_tmp; + struct batadv_algo_ops *bat_algo_ops = NULL; + struct batadv_algo_ops *bat_algo_ops_tmp; hlist_for_each_entry(bat_algo_ops_tmp, &batadv_algo_list, list) { if (strcmp(bat_algo_ops_tmp->name, name) != 0) @@ -116,12 +117,23 @@ int batadv_algo_select(struct batadv_priv *bat_priv, const char *name) return 0; } +/** + * batadv_param_set_ra() - Validate and store routing_algo module parameter + * @val: new value for the routing_algo module parameter + * @kp: kernel parameter description used to store the value + * + * Check that the requested algorithm is known to batman-adv and then store + * the name as the new default routing algorithm. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_param_set_ra(const char *val, const struct kernel_param *kp) { struct batadv_algo_ops *bat_algo_ops; char *algo_name = (char *)val; - size_t name_len = strlen(algo_name); + size_t name_len; + name_len = strlen(algo_name); if (name_len > 0 && algo_name[name_len - 1] == '\n') algo_name[name_len - 1] = '\0'; diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index bb2f012b454e..53fbdbbe8f4f 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -106,8 +106,8 @@ static u8 batadv_ring_buffer_avg(const u8 lq_recv[]) { const u8 *ptr; u16 count = 0; - u16 i = 0; u16 sum = 0; + u16 i = 0; ptr = lq_recv; @@ -171,6 +171,14 @@ batadv_iv_ogm_orig_get(struct batadv_priv *bat_priv, const u8 *addr) return NULL; } +/** + * batadv_iv_ogm_neigh_new() - retrieve or create a B.A.T.M.A.N. IV neighbour + * @hard_iface: the interface where the neighbour is connected to + * @neigh_addr: the mac address of the neighbour + * @orig_node: originator object representing the neighbour + * + * Return: pointer to the neigh_node or NULL in case of failure + */ static struct batadv_neigh_node * batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, const u8 *neigh_addr, @@ -183,6 +191,14 @@ batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, return neigh_node; } +/** + * batadv_iv_ogm_iface_enable() - prepare an interface for B.A.T.M.A.N. IV + * @hard_iface: the interface to prepare + * + * Allocate and prepare the per-interface OGM buffer + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -220,6 +236,14 @@ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) return 0; } +/** + * batadv_iv_ogm_iface_disable() - release B.A.T.M.A.N. IV resources of an + * interface + * @hard_iface: the interface which is shutting down + * + * Free the per-interface OGM buffer and cancel a possibly pending OGM + * rescheduling work. + */ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) { mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); @@ -233,6 +257,11 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) disable_delayed_work_sync(&hard_iface->bat_iv.reschedule_work); } +/** + * batadv_iv_ogm_iface_update_mac() - update the originator MAC stored in the + * per-interface OGM buffer + * @hard_iface: the interface for which the OGM buffer should be updated + */ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -254,6 +283,14 @@ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_primary_iface_set() - apply primary interface state to + * a batadv_hard_iface + * @hard_iface: interface which just became the primary + * + * The primary interface uses the full TTL for its own OGMs, so adjust the TTL + * stored in the OGM template buffer accordingly. + */ static void batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) { @@ -273,7 +310,17 @@ batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } -/* when do we schedule our own ogm to be sent */ +/** + * batadv_iv_ogm_emit_send_time() - calculate the jiffies when an own OGM + * should be sent next + * @bat_priv: the bat priv with all the mesh interface information + * + * The next emission point is the configured orig_interval randomised by + * +/- BATADV_JITTER milliseconds to reduce chance of potential collisions + * between neighbours. + * + * Return: jiffies value when the next OGM should be transmitted + */ static unsigned long batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) { @@ -285,13 +332,24 @@ batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) return jiffies + msecs_to_jiffies(msecs); } -/* when do we schedule a ogm packet to be sent */ +/** + * batadv_iv_ogm_fwd_send_time() - calculate the jiffies when a forwarded OGM + * should be sent next + * + * Return: jiffies value at which a forwarded OGM should be transmitted + */ static unsigned long batadv_iv_ogm_fwd_send_time(void) { return jiffies + msecs_to_jiffies(get_random_u32_below(BATADV_JITTER / 2)); } -/* apply hop penalty for a normal link */ +/** + * batadv_hop_penalty() - apply the configured hop penalty to a TQ value + * @tq: input TQ value to be reduced + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: the TQ value after the hop penalty has been applied + */ static u8 batadv_hop_penalty(u8 tq, const struct batadv_priv *bat_priv) { int hop_penalty = READ_ONCE(bat_priv->hop_penalty); @@ -337,17 +395,25 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len, return next_buff_pos <= packet_len; } -/* send a batman ogm to a given interface */ +/** + * batadv_iv_ogm_send_to_if() - send a batman OGM to a given interface + * @forw_packet: forward packet containing the OGM(s) to be transmitted + * @hard_iface: interface to send the OGM out on + * + * Update the direct link flags of each aggregated OGM for the outgoing + * interface, log the transmission and finally hand a clone of the skb to the + * lower layer for broadcast. + */ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - const char *fwd_str; - u8 packet_num; - int buff_pos; struct batadv_ogm_packet *batadv_ogm_packet; + const char *fwd_str; struct sk_buff *skb; u8 *packet_pos; + u8 packet_num; + int buff_pos; if (hard_iface->if_status != BATADV_IF_ACTIVE) return; @@ -401,26 +467,23 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, } } -/* send a batman ogm packet */ +/** + * batadv_iv_ogm_emit() - emit an (aggregated) OGM packet + * @forw_packet: forward packet which should be emitted + * + * The @forw_packet will be emitted but not consumed. When the interface is + * no longer active, the transmission will be skipped. + */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { - struct net_device *mesh_iface; - if (!forw_packet->if_incoming) { pr_err("Error - can't forward packet: incoming iface not specified\n"); return; } - mesh_iface = forw_packet->if_incoming->mesh_iface; - if (WARN_ON(!forw_packet->if_outgoing)) return; - if (forw_packet->if_outgoing->mesh_iface != mesh_iface) { - pr_warn("%s: mesh interface switch for queued OGM\n", __func__); - return; - } - if (forw_packet->if_incoming->if_status != BATADV_IF_ACTIVE) return; @@ -451,13 +514,13 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet, const struct batadv_hard_iface *if_outgoing, const struct batadv_forw_packet *forw_packet) { - struct batadv_ogm_packet *batadv_ogm_packet; unsigned int aggregated_bytes = forw_packet->packet_len + packet_len; + struct batadv_ogm_packet *batadv_ogm_packet; struct batadv_hard_iface *primary_if = NULL; u8 packet_num = forw_packet->num_packets; - bool res = false; unsigned long aggregation_end_time; unsigned int max_bytes; + bool res = false; batadv_ogm_packet = (struct batadv_ogm_packet *)forw_packet->skb->data; aggregation_end_time = send_time; @@ -564,10 +627,10 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_forw_packet *forw_packet_aggr; - struct sk_buff *skb; unsigned char *skb_buff; unsigned int skb_size; - atomic_t *queue_left = own_packet ? NULL : &bat_priv->batman_queue_left; + atomic_t *queue_left; + struct sk_buff *skb; if (READ_ONCE(bat_priv->aggregated_ogms)) skb_size = max_t(unsigned int, BATADV_MAX_AGGREGATION_BYTES, @@ -581,6 +644,7 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, if (!skb) return false; + queue_left = own_packet ? NULL : &bat_priv->batman_queue_left; forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing, queue_left, bat_priv, skb); if (!forw_packet_aggr) { @@ -612,7 +676,14 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, return true; } -/* aggregate a new packet into the existing ogm packet */ +/** + * batadv_iv_ogm_aggregate() - append an OGM to an existing aggregated forward + * packet + * @forw_packet_aggr: aggregated forward packet to extend + * @packet_buff: pointer to the OGM to append + * @packet_len: length of the OGM to append + * @direct_link: true if @packet_buff was received as a direct link OGM + */ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr, const unsigned char *packet_buff, int packet_len, bool direct_link) @@ -653,9 +724,9 @@ static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, struct batadv_forw_packet *forw_packet_aggr = NULL; struct batadv_forw_packet *forw_packet_pos = NULL; struct batadv_ogm_packet *batadv_ogm_packet; - bool direct_link; unsigned long max_aggregation_jiffies; bool aggregated_ogms; + bool direct_link; batadv_ogm_packet = (struct batadv_ogm_packet *)packet_buff; direct_link = !!(batadv_ogm_packet->flags & BATADV_DIRECTLINK); @@ -708,6 +779,21 @@ static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, } } +/** + * batadv_iv_ogm_forward() - rebroadcast a received OGM + * @orig_node: originator that sent the OGM + * @ethhdr: ethernet header of the OGM packet + * @batadv_ogm_packet: OGM packet to be forwarded + * @is_single_hop_neigh: true if the OGM was received via a one-hop neighbour + * @is_from_best_next_hop: true if the sender is the currently selected best + * next hop towards @orig_node + * @if_incoming: interface where the packet was received + * @if_outgoing: interface for which the retransmission should be considered + * + * Decrement the TTL, apply the hop penalty and queue the OGM for + * retransmission. OGMs that do not arrive over the best next hop are only + * forwarded for link-quality measurement reasons (and marked accordingly). + */ static void batadv_iv_ogm_forward(struct batadv_orig_node *orig_node, const struct ethhdr *ethhdr, struct batadv_ogm_packet *batadv_ogm_packet, @@ -771,9 +857,9 @@ batadv_iv_ogm_slide_own_bcast_window(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; - struct batadv_orig_node *orig_node; struct batadv_orig_ifinfo *orig_ifinfo; + struct batadv_orig_node *orig_node; + struct hlist_head *head; unsigned long *word; u32 i; u8 *w; @@ -811,13 +897,14 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_ogm_buf *ogm_buff = &hard_iface->bat_iv.ogm_buff; struct batadv_ogm_packet *batadv_ogm_packet; - struct batadv_hard_iface *primary_if, *tmp_hard_iface; - struct list_head *iter; - u32 seqno; - u16 tvlv_len = 0; + struct batadv_hard_iface *tmp_hard_iface; + struct batadv_hard_iface *primary_if; unsigned long send_time; bool reschedule = false; + struct list_head *iter; + u16 tvlv_len = 0; bool scheduled; + u32 seqno; int ret; lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex); @@ -908,10 +995,16 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_iv_ogm_schedule() - schedule the next OGM transmission on an + * interface + * @hard_iface: interface for which the next OGM should be scheduled + * + * Take the OGM buffer mutex and prepare the next OGM for transmission. + */ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) return; mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); @@ -919,6 +1012,13 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_reschedule() - work-queue helper to rerun batadv_iv_ogm_schedule() + * @work: work item embedded in the hard interface + * + * Invoked from the per-interface reschedule_work delayed work when the + * previous attempt to enqueue the own OGM failed. + */ static void batadv_iv_ogm_reschedule(struct work_struct *work) { struct delayed_work *delayed_work = to_delayed_work(work); @@ -1006,13 +1106,14 @@ batadv_iv_ogm_orig_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_outgoing, enum batadv_dup_status dup_status) { - struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *neigh_node = NULL; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; struct batadv_neigh_node *tmp_neigh_node = NULL; + struct batadv_neigh_node *neigh_node = NULL; struct batadv_neigh_node *router = NULL; - u8 sum_orig, sum_neigh; u8 *neigh_addr; + u8 sum_neigh; + u8 sum_orig; u8 tq_avg; batadv_dbg(BATADV_DBG_BATMAN, bat_priv, @@ -1143,15 +1244,21 @@ static bool batadv_iv_ogm_calc_tq(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_neigh_node *neigh_node = NULL, *tmp_neigh_node; - struct batadv_neigh_ifinfo *neigh_ifinfo; - u8 total_count; - u8 orig_eq_count, neigh_rq_count, neigh_rq_inv, tq_own; unsigned int tq_iface_hop_penalty = BATADV_TQ_MAX_VALUE; - unsigned int neigh_rq_inv_cube, neigh_rq_max_cube; - unsigned int tq_asym_penalty, inv_asym_penalty; + struct batadv_neigh_node *neigh_node = NULL; + struct batadv_neigh_node *tmp_neigh_node; + struct batadv_neigh_ifinfo *neigh_ifinfo; + unsigned int neigh_rq_inv_cube; + unsigned int neigh_rq_max_cube; + unsigned int inv_asym_penalty; + unsigned int tq_asym_penalty; unsigned int combined_tq; + u8 neigh_rq_count; + u8 orig_eq_count; bool ret = false; + u8 neigh_rq_inv; + u8 total_count; + u8 tq_own; /* find corresponding one hop neighbor */ rcu_read_lock(); @@ -1284,19 +1391,19 @@ batadv_iv_ogm_update_seqnos(const struct ethhdr *ethhdr, struct batadv_hard_iface *if_outgoing) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_orig_node *orig_node; struct batadv_orig_ifinfo *orig_ifinfo = NULL; - struct batadv_neigh_node *neigh_node; - struct batadv_neigh_ifinfo *neigh_ifinfo; - bool is_dup; - s32 seq_diff; - bool need_update = false; - int set_mark; - enum batadv_dup_status ret = BATADV_NO_DUP; u32 seqno = ntohl(batadv_ogm_packet->seqno); - u8 *neigh_addr; - u8 packet_count; + enum batadv_dup_status ret = BATADV_NO_DUP; + struct batadv_neigh_ifinfo *neigh_ifinfo; + struct batadv_neigh_node *neigh_node; + struct batadv_orig_node *orig_node; + bool need_update = false; unsigned long *bitmap; + u8 packet_count; + u8 *neigh_addr; + s32 seq_diff; + int set_mark; + bool is_dup; orig_node = batadv_iv_ogm_orig_get(bat_priv, batadv_ogm_packet->orig); if (!orig_node) @@ -1413,21 +1520,22 @@ batadv_iv_ogm_process_per_outif(const struct sk_buff *skb, int ogm_offset, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_hardif_neigh_node *hardif_neigh = NULL; - struct batadv_neigh_node *router = NULL; - struct batadv_neigh_node *router_router = NULL; - struct batadv_orig_node *orig_neigh_node; - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *orig_neigh_router = NULL; struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_node *router_router = NULL; + struct batadv_orig_node *orig_neigh_node; + struct batadv_neigh_node *router = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_ogm_packet *ogm_packet; - enum batadv_dup_status dup_status; bool is_from_best_next_hop = false; + enum batadv_dup_status dup_status; bool is_single_hop_neigh = false; - bool sameseq, similar_ttl; struct sk_buff *skb_priv; struct ethhdr *ethhdr; - u8 *prev_sender; + bool similar_ttl; bool is_bidirect; + u8 *prev_sender; + bool sameseq; /* create a private copy of the skb, as some functions change tq value * and/or flags. @@ -1649,16 +1757,17 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, struct batadv_hard_iface *if_incoming) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_orig_node *orig_neigh_node, *orig_node; + struct batadv_orig_node *orig_neigh_node; struct batadv_hard_iface *hard_iface; struct batadv_ogm_packet *ogm_packet; - u32 if_incoming_seqno; - bool has_directlink_flag; - struct ethhdr *ethhdr; + struct batadv_orig_node *orig_node; bool is_my_oldorig = false; + bool has_directlink_flag; bool is_my_addr = false; bool is_my_orig = false; struct list_head *iter; + u32 if_incoming_seqno; + struct ethhdr *ethhdr; ogm_packet = (struct batadv_ogm_packet *)(skb->data + ogm_offset); ethhdr = eth_hdr(skb); @@ -1775,10 +1884,18 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, batadv_orig_node_put(orig_node); } +/** + * batadv_iv_send_outstanding_bat_ogm_packet() - work-queue helper to emit a + * queued forward packet + * @work: work item embedded in the forward packet + * + * Emit the queued OGM forward packet and, for own primary-interface packets, + * schedule the next periodic OGM. The forward packet is freed afterwards. + */ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) { - struct delayed_work *delayed_work; struct batadv_forw_packet *forw_packet; + struct delayed_work *delayed_work; struct batadv_priv *bat_priv; bool dropped = false; @@ -1813,15 +1930,26 @@ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) batadv_forw_packet_free(forw_packet, dropped); } +/** + * batadv_iv_ogm_receive() - receive a B.A.T.M.A.N. IV OGM packet + * @skb: skb containing the OGM packet + * @if_incoming: interface where the packet was received + * + * Validate the packet, then split the aggregated OGM packet into individual + * OGMs and hand each of them to batadv_iv_ogm_process(). Ownership of @skb is + * always taken over by this function. + * + * Return: NET_RX_SUCCESS or NET_RX_DROP + */ static int batadv_iv_ogm_receive(struct sk_buff *skb, struct batadv_hard_iface *if_incoming) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_ogm_packet *ogm_packet; + int ret = NET_RX_DROP; u8 *packet_pos; int ogm_offset; bool res; - int ret = NET_RX_DROP; res = batadv_check_management_packet(skb, if_incoming, BATADV_OGM_HLEN); if (!res) @@ -1911,9 +2039,9 @@ batadv_iv_ogm_orig_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_neigh_node *neigh_node, bool best) { + unsigned int last_seen_msecs; void *hdr; u8 tq_avg; - unsigned int last_seen_msecs; last_seen_msecs = jiffies_to_msecs(jiffies - orig_node->last_seen); @@ -1975,9 +2103,9 @@ batadv_iv_ogm_orig_dump_entry(struct sk_buff *msg, u32 portid, u32 seq, { struct batadv_neigh_node *neigh_node_best; struct batadv_neigh_node *neigh_node; + u8 tq_avg_best; int sub = 0; bool best; - u8 tq_avg_best; neigh_node_best = batadv_orig_router_get(orig_node, if_outgoing); if (!neigh_node_best) @@ -2070,11 +2198,11 @@ batadv_iv_ogm_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_hard_iface *if_outgoing) { struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; + int portid = NETLINK_CB(cb->skb).portid; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; while (bucket < hash->size) { head = &hash->table[bucket]; @@ -2113,9 +2241,11 @@ static bool batadv_iv_ogm_neigh_diff(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing2, int *diff) { - struct batadv_neigh_ifinfo *neigh1_ifinfo, *neigh2_ifinfo; - u8 tq1, tq2; + struct batadv_neigh_ifinfo *neigh1_ifinfo; + struct batadv_neigh_ifinfo *neigh2_ifinfo; bool ret = true; + u8 tq1; + u8 tq2; neigh1_ifinfo = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); neigh2_ifinfo = batadv_neigh_ifinfo_get(neigh2, if_outgoing2); @@ -2149,8 +2279,8 @@ static int batadv_iv_ogm_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_hardif_neigh_node *hardif_neigh) { - void *hdr; unsigned int last_seen_msecs; + void *hdr; last_seen_msecs = jiffies_to_msecs(jiffies - hardif_neigh->last_seen); @@ -2228,12 +2358,12 @@ batadv_iv_ogm_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_priv *bat_priv, struct batadv_hard_iface *single_hardif) { - struct batadv_hard_iface *hard_iface; - struct list_head *iter; - int i_hardif = 0; - int i_hardif_s = cb->args[0]; - int idx = cb->args[1]; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + int i_hardif_s = cb->args[0]; + struct list_head *iter; + int idx = cb->args[1]; + int i_hardif = 0; rcu_read_lock(); if (single_hardif) { @@ -2320,6 +2450,12 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1, return ret; } +/** + * batadv_iv_iface_enabled() - notification handler for activated interfaces + * @hard_iface: interface that was just activated + * + * Set up the per-interface reschedule work and start sending periodic OGMs. + */ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface) { INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule); @@ -2338,17 +2474,26 @@ static void batadv_iv_init_sel_class(struct batadv_priv *bat_priv) WRITE_ONCE(bat_priv->gw.sel_class, 20); } +/** + * batadv_iv_gw_get_best_gw_node() - retrieve the best gateway node based on + * the B.A.T.M.A.N. IV metric and the configured GW selection class + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: gateway node with the highest score for the current selection class, + * or NULL if no eligible gateway exists. + */ static struct batadv_gw_node * batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) { - struct batadv_neigh_node *router; struct batadv_neigh_ifinfo *router_ifinfo; - struct batadv_gw_node *gw_node, *curr_gw = NULL; + struct batadv_gw_node *curr_gw = NULL; + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *router; + struct batadv_gw_node *gw_node; u64 max_gw_factor = 0; u64 tmp_gw_factor = 0; u8 max_tq = 0; u8 tq_avg; - struct batadv_orig_node *orig_node; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.gateway_list, list) { @@ -2415,6 +2560,19 @@ batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) return curr_gw; } +/** + * batadv_iv_gw_is_eligible() - check whether a new gateway should replace the + * currently selected one + * @bat_priv: the bat priv with all the mesh interface information + * @curr_gw_orig: originator of the currently selected gateway + * @orig_node: originator of the gateway candidate + * + * Compare the TQ values of @curr_gw_orig and @orig_node, taking the configured + * gateway selection class into account. + * + * Return: true if @orig_node should take over as the active gateway, false + * otherwise + */ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) @@ -2422,10 +2580,11 @@ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_neigh_ifinfo *router_orig_ifinfo = NULL; struct batadv_neigh_ifinfo *router_gw_ifinfo = NULL; u32 sel_class = READ_ONCE(bat_priv->gw.sel_class); - struct batadv_neigh_node *router_gw = NULL; struct batadv_neigh_node *router_orig = NULL; - u8 gw_tq_avg, orig_tq_avg; + struct batadv_neigh_node *router_gw = NULL; bool ret = false; + u8 orig_tq_avg; + u8 gw_tq_avg; /* dynamic re-election is performed only on fast or late switch */ if (sel_class <= 2) @@ -2496,8 +2655,8 @@ static int batadv_iv_gw_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_gw_node *gw_node) { struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *router; struct batadv_gw_node *curr_gw = NULL; + struct batadv_neigh_node *router; int ret = 0; void *hdr; @@ -2643,3 +2802,11 @@ int __init batadv_iv_init(void) out: return ret; } + +/** + * batadv_iv_deinit() - B.A.T.M.A.N. IV deinitialization function + */ +void batadv_iv_deinit(void) +{ + batadv_recv_handler_unregister(BATADV_IV_OGM); +} diff --git a/net/batman-adv/bat_iv_ogm.h b/net/batman-adv/bat_iv_ogm.h index 04b01bd684e8..90318801aeaf 100644 --- a/net/batman-adv/bat_iv_ogm.h +++ b/net/batman-adv/bat_iv_ogm.h @@ -10,5 +10,6 @@ #include "main.h" int batadv_iv_init(void); +void batadv_iv_deinit(void); #endif /* _NET_BATMAN_ADV_BAT_IV_OGM_H_ */ diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index db6f5bdcaa98..0c27447cf688 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -41,6 +41,13 @@ #include "netlink.h" #include "originator.h" +/** + * batadv_v_iface_activate() - finalise the activation of a hard interface + * @hard_iface: interface to activate + * + * Reuse the currently selected primary interface to seed the ELP packet. + * Immediately activate the interface. + */ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); @@ -61,6 +68,16 @@ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) hard_iface->if_status = BATADV_IF_ACTIVE; } +/** + * batadv_v_iface_enable() - enable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to enable + * + * Enable the ELP and OGM components for @hard_iface. On error, the partially + * enabled state is rolled back before returning. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) { int ret; @@ -76,12 +93,21 @@ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) return ret; } +/** + * batadv_v_iface_disable() - disable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to disable + */ static void batadv_v_iface_disable(struct batadv_hard_iface *hard_iface) { batadv_v_ogm_iface_disable(hard_iface); batadv_v_elp_iface_disable(hard_iface); } +/** + * batadv_v_primary_iface_set() - apply primary interface state + * @hard_iface: interface which just became the primary + */ static void batadv_v_primary_iface_set(struct batadv_hard_iface *hard_iface) { batadv_v_elp_primary_iface_set(hard_iface); @@ -109,6 +135,11 @@ static void batadv_v_iface_update_mac(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_v_hardif_neigh_init() - initialise the B.A.T.M.A.N. V state of a + * hard interface neighbour + * @hardif_neigh: hard interface neighbour to initialise + */ static void batadv_v_hardif_neigh_init(struct batadv_hardif_neigh_node *hardif_neigh) { @@ -128,9 +159,9 @@ static int batadv_v_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_hardif_neigh_node *hardif_neigh) { - void *hdr; unsigned int last_seen_msecs; u32 throughput; + void *hdr; last_seen_msecs = jiffies_to_msecs(jiffies - hardif_neigh->last_seen); throughput = ewma_throughput_read(&hardif_neigh->bat_v.throughput); @@ -211,12 +242,12 @@ batadv_v_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_priv *bat_priv, struct batadv_hard_iface *single_hardif) { - struct batadv_hard_iface *hard_iface; - struct list_head *iter; - int i_hardif = 0; - int i_hardif_s = cb->args[0]; - int idx = cb->args[1]; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + int i_hardif_s = cb->args[0]; + struct list_head *iter; + int idx = cb->args[1]; + int i_hardif = 0; rcu_read_lock(); if (single_hardif) { @@ -421,11 +452,11 @@ batadv_v_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_hard_iface *if_outgoing) { struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; + int portid = NETLINK_CB(cb->skb).portid; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; while (bucket < hash->size) { head = &hash->table[bucket]; @@ -444,12 +475,23 @@ batadv_v_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, cb->args[2] = sub; } +/** + * batadv_v_neigh_cmp() - compare two B.A.T.M.A.N. V neighbours by throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: a positive value if @neigh1 is better, a negative value if @neigh2 + * is better and 0 if both have equal throughput + */ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, struct batadv_hard_iface *if_outgoing2) { - struct batadv_neigh_ifinfo *ifinfo1, *ifinfo2; + struct batadv_neigh_ifinfo *ifinfo1; + struct batadv_neigh_ifinfo *ifinfo2; int ret = 0; ifinfo1 = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); @@ -469,14 +511,26 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, return ret; } +/** + * batadv_v_neigh_is_sob() - check whether two B.A.T.M.A.N. V neighbours have + * a similar or better throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: true if the throughput of @neigh2 is at least 3/4 of the + * @neigh1 throughput + */ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, struct batadv_hard_iface *if_outgoing2) { - struct batadv_neigh_ifinfo *ifinfo1, *ifinfo2; - u32 threshold; + struct batadv_neigh_ifinfo *ifinfo1; + struct batadv_neigh_ifinfo *ifinfo2; bool ret = false; + u32 threshold; ifinfo1 = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); if (!ifinfo1) @@ -558,8 +612,10 @@ static int batadv_v_gw_throughput_get(struct batadv_gw_node *gw_node, u32 *bw) static struct batadv_gw_node * batadv_v_gw_get_best_gw_node(struct batadv_priv *bat_priv) { - struct batadv_gw_node *gw_node, *curr_gw = NULL; - u32 max_bw = 0, bw; + struct batadv_gw_node *curr_gw = NULL; + struct batadv_gw_node *gw_node; + u32 max_bw = 0; + u32 bw; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.gateway_list, list) { @@ -598,9 +654,12 @@ static bool batadv_v_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *curr_gw, *orig_gw = NULL; - u32 gw_throughput, orig_throughput, threshold; + struct batadv_gw_node *orig_gw = NULL; + struct batadv_gw_node *curr_gw; + u32 orig_throughput; + u32 gw_throughput; bool ret = false; + u32 threshold; threshold = READ_ONCE(bat_priv->gw.sel_class); @@ -656,8 +715,8 @@ static int batadv_v_gw_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_gw_node *gw_node) { struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *router; struct batadv_gw_node *curr_gw = NULL; + struct batadv_neigh_node *router; int ret = 0; void *hdr; @@ -888,3 +947,12 @@ int __init batadv_v_init(void) return ret; } + +/** + * batadv_v_deinit() - B.A.T.M.A.N. V deinitialization function + */ +void batadv_v_deinit(void) +{ + batadv_recv_handler_unregister(BATADV_OGM2); + batadv_recv_handler_unregister(BATADV_ELP); +} diff --git a/net/batman-adv/bat_v.h b/net/batman-adv/bat_v.h index 964431f4dc8d..76bd969e79a2 100644 --- a/net/batman-adv/bat_v.h +++ b/net/batman-adv/bat_v.h @@ -12,6 +12,7 @@ #ifdef CONFIG_BATMAN_ADV_BATMAN_V int batadv_v_init(void); +void batadv_v_deinit(void); void batadv_v_hardif_init(struct batadv_hard_iface *hardif); int batadv_v_mesh_init(struct batadv_priv *bat_priv); void batadv_v_mesh_free(struct batadv_priv *bat_priv); @@ -23,6 +24,10 @@ static inline int batadv_v_init(void) return 0; } +static inline void batadv_v_deinit(void) +{ +} + static inline void batadv_v_hardif_init(struct batadv_hard_iface *hardif) { } diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index 4841f0f1a9b1..6ad6042a5d9b 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -90,12 +90,6 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh, u32 throughput; int ret; - /* don't query throughput when no longer associated with any - * batman-adv interface - */ - if (!mesh_iface) - return false; - /* if the user specified a customised value for this interface, then * return it directly */ @@ -236,11 +230,14 @@ static bool batadv_v_elp_wifi_neigh_probe(struct batadv_hardif_neigh_node *neigh) { struct batadv_hard_iface *hard_iface = neigh->if_incoming; - struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + struct batadv_priv *bat_priv; unsigned long last_tx_diff; struct sk_buff *skb; - int probe_len, i; int elp_skb_len; + int probe_len; + int i; + + bat_priv = netdev_priv(hard_iface->mesh_iface); /* this probing routine is for Wifi neighbours only */ if (!batadv_is_wifi_hardif(hard_iface)) @@ -295,8 +292,8 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) struct batadv_v_metric_queue_entry *metric_entry; struct batadv_v_metric_queue_entry *metric_safe; struct batadv_hardif_neigh_node *hardif_neigh; - struct batadv_hard_iface *hard_iface; struct batadv_hard_iface_bat_v *bat_v; + struct batadv_hard_iface *hard_iface; struct batadv_elp_packet *elp_packet; struct list_head metric_queue; struct batadv_priv *bat_priv; @@ -311,8 +308,7 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) goto out; /* we are in the process of shutting this interface down */ - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) goto out; /* the interface was enabled but may not be ready yet */ @@ -402,9 +398,9 @@ int batadv_v_elp_iface_enable(struct batadv_hard_iface *hard_iface) static const size_t tvlv_padding = sizeof(__be32); struct batadv_elp_packet *elp_packet; unsigned char *elp_buff; + int res = -ENOMEM; u32 random_seqno; size_t size; - int res = -ENOMEM; size = ETH_HLEN + NET_IP_ALIGN + BATADV_ELP_HLEN + tvlv_padding; hard_iface->bat_v.elp_skb = dev_alloc_skb(size); @@ -507,11 +503,11 @@ static void batadv_v_elp_neigh_update(struct batadv_priv *bat_priv, struct batadv_elp_packet *elp_packet) { - struct batadv_neigh_node *neigh; - struct batadv_orig_node *orig_neigh; struct batadv_hardif_neigh_node *hardif_neigh; - s32 seqno_diff; + struct batadv_orig_node *orig_neigh; + struct batadv_neigh_node *neigh; s32 elp_latest_seqno; + s32 seqno_diff; orig_neigh = batadv_v_ogm_orig_get(bat_priv, elp_packet->orig); if (!orig_neigh) @@ -563,8 +559,8 @@ int batadv_v_elp_packet_recv(struct sk_buff *skb, struct batadv_elp_packet *elp_packet; struct batadv_hard_iface *primary_if; struct ethhdr *ethhdr; - bool res; int ret = NET_RX_DROP; + bool res; res = batadv_check_management_packet(skb, if_incoming, BATADV_ELP_HLEN); if (!res) diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index 037921aad35d..70846c997410 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -101,6 +101,7 @@ static void batadv_v_ogm_start_queue_timer(struct batadv_hard_iface *hard_iface) static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) { unsigned long msecs; + /* this function may be invoked in different contexts (ogm rescheduling * or hard_iface activation), but the work timer should not be reset */ @@ -115,14 +116,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) /** * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to send * @hard_iface: the interface to use to send the OGM */ -static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_send_to_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + if (hard_iface->if_status != BATADV_IF_ACTIVE) { kfree_skb(skb); return; @@ -189,7 +190,6 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) /** * batadv_v_ogm_aggr_send() - flush & send aggregation queue - * @bat_priv: the bat priv with all the mesh interface information * @hard_iface: the interface with the aggregation queue to flush * * Aggregates all OGMv2 packets currently in the aggregation queue into a @@ -199,8 +199,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) * * Caller needs to hold the hard_iface->bat_v.aggr_list.lock. */ -static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, - struct batadv_hard_iface *hard_iface) +static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface) { unsigned int aggr_len = hard_iface->bat_v.aggr_len; struct sk_buff *skb_aggr; @@ -230,26 +229,21 @@ static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, consume_skb(skb); } - batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface); + batadv_v_ogm_send_to_if(skb_aggr, hard_iface); } /** * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to queue * @hard_iface: the interface to queue the OGM on */ -static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_queue_on_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { - if (hard_iface->mesh_iface != bat_priv->mesh_iface) { - kfree_skb(skb); - return; - } + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); if (!READ_ONCE(bat_priv->aggregated_ogms)) { - batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface); + batadv_v_ogm_send_to_if(skb, hard_iface); return; } @@ -260,7 +254,7 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, } if (!batadv_v_ogm_queue_left(skb, hard_iface)) - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb); __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb); @@ -275,11 +269,12 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, */ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) { - struct batadv_hard_iface *hard_iface; struct batadv_ogm2_packet *ogm_packet; + struct batadv_hard_iface *hard_iface; struct batadv_ogm_buf *ogm_buff; - struct sk_buff *skb, *skb_tmp; + struct sk_buff *skb_tmp; struct list_head *iter; + struct sk_buff *skb; u16 tvlv_len; int ret; @@ -357,7 +352,7 @@ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) break; } - batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface); + batadv_v_ogm_queue_on_if(skb_tmp, hard_iface); batadv_hardif_put(hard_iface); } rcu_read_unlock(); @@ -397,14 +392,12 @@ void batadv_v_ogm_aggr_work(struct work_struct *work) { struct batadv_hard_iface_bat_v *batv; struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work); hard_iface = container_of(batv, struct batadv_hard_iface, bat_v); - bat_priv = netdev_priv(hard_iface->mesh_iface); spin_lock_bh(&hard_iface->bat_v.aggr_list.lock); - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock); batadv_v_ogm_start_queue_timer(hard_iface); @@ -601,7 +594,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv, if_outgoing->net_dev->name, ntohl(ogm_forward->throughput), ogm_forward->ttl, if_incoming->net_dev->name); - batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing); + batadv_v_ogm_queue_on_if(skb, if_outgoing); out: batadv_orig_ifinfo_put(orig_ifinfo); @@ -630,11 +623,11 @@ static int batadv_v_ogm_metric_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_incoming, struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; bool protection_started = false; - int ret = -EINVAL; u32 path_throughput; + int ret = -EINVAL; s32 seq_diff; orig_ifinfo = batadv_orig_ifinfo_new(orig_node, if_outgoing); @@ -712,15 +705,17 @@ static bool batadv_v_ogm_route_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_incoming, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_node *router = NULL; - struct batadv_orig_node *orig_neigh_node; struct batadv_neigh_node *orig_neigh_router = NULL; - struct batadv_neigh_ifinfo *router_ifinfo = NULL, *neigh_ifinfo = NULL; - u32 router_throughput, neigh_throughput; + struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_orig_node *orig_neigh_node; + struct batadv_neigh_node *router = NULL; + u32 router_throughput; u32 router_last_seqno; + u32 neigh_throughput; u32 neigh_last_seqno; - s32 neigh_seq_diff; bool forward = false; + s32 neigh_seq_diff; orig_neigh_node = batadv_v_ogm_orig_get(bat_priv, ethhdr->h_source); if (!orig_neigh_node) @@ -880,14 +875,16 @@ static void batadv_v_ogm_process(const struct sk_buff *skb, int ogm_offset, struct batadv_hard_iface *if_incoming) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct ethhdr *ethhdr; - struct batadv_orig_node *orig_node = NULL; struct batadv_hardif_neigh_node *hardif_neigh = NULL; struct batadv_neigh_node *neigh_node = NULL; - struct batadv_hard_iface *hard_iface; + struct batadv_orig_node *orig_node = NULL; struct batadv_ogm2_packet *ogm_packet; - u32 ogm_throughput, link_throughput, path_throughput; + struct batadv_hard_iface *hard_iface; struct list_head *iter; + struct ethhdr *ethhdr; + u32 link_throughput; + u32 path_throughput; + u32 ogm_throughput; int ret; ethhdr = eth_hdr(skb); @@ -1013,9 +1010,9 @@ int batadv_v_ogm_packet_recv(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_ogm2_packet *ogm_packet; struct ethhdr *ethhdr; + int ret = NET_RX_DROP; int ogm_offset; u8 *packet_pos; - int ret = NET_RX_DROP; /* did we receive a OGM2 packet on an interface that does not have * B.A.T.M.A.N. V enabled ? diff --git a/net/batman-adv/bitarray.c b/net/batman-adv/bitarray.c index 67cb356332bf..f814756d9533 100644 --- a/net/batman-adv/bitarray.c +++ b/net/batman-adv/bitarray.c @@ -11,7 +11,14 @@ #include "log.h" -/* shift the packet array by n places. */ +/** + * batadv_bitmap_shift_left() - shift the sequence number bitmap left + * @seq_bits: the sequence number bitmap to shift + * @n: number of positions to shift left + * + * Shift @seq_bits by @n positions. No-op if @n is not within the bounds of + * the bitmap. + */ static void batadv_bitmap_shift_left(unsigned long *seq_bits, s32 n) { if (n <= 0 || n >= BATADV_TQ_LOCAL_WINDOW_SIZE) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 5c73f6ba16cf..94e074235e15 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -177,8 +177,8 @@ static void batadv_backbone_gw_put(struct batadv_bla_backbone_gw *backbone_gw) */ static void batadv_claim_release(struct kref *ref) { - struct batadv_bla_claim *claim; struct batadv_bla_backbone_gw *old_backbone_gw; + struct batadv_bla_claim *claim; claim = container_of(ref, struct batadv_bla_claim, refcount); @@ -220,9 +220,9 @@ batadv_claim_hash_find(struct batadv_priv *bat_priv, struct batadv_bla_claim *data) { struct batadv_hashtable *hash = bat_priv->bla.claim_hash; - struct hlist_head *head; - struct batadv_bla_claim *claim; struct batadv_bla_claim *claim_tmp = NULL; + struct batadv_bla_claim *claim; + struct hlist_head *head; int index; if (!hash) @@ -260,9 +260,10 @@ batadv_backbone_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; - struct hlist_head *head; - struct batadv_bla_backbone_gw search_entry, *backbone_gw; struct batadv_bla_backbone_gw *backbone_gw_tmp = NULL; + struct batadv_bla_backbone_gw search_entry; + struct batadv_bla_backbone_gw *backbone_gw; + struct hlist_head *head; int index; if (!hash) @@ -298,12 +299,12 @@ batadv_backbone_hash_find(struct batadv_priv *bat_priv, const u8 *addr, static void batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) { + spinlock_t *list_lock; /* protects write access to the hash lists */ + struct batadv_bla_claim *claim; struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; - struct batadv_bla_claim *claim; int i; - spinlock_t *list_lock; /* protects write access to the hash lists */ hash = backbone_gw->bat_priv->bla.claim_hash; if (!hash) @@ -341,13 +342,12 @@ batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, unsigned short vid, int claimtype) { - struct sk_buff *skb; - struct ethhdr *ethhdr; - struct batadv_hard_iface *primary_if; - struct net_device *mesh_iface; - u8 *hw_src; struct batadv_bla_claim_dst local_claim_dest; + struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; + struct sk_buff *skb; __be32 zeroip = 0; + u8 *hw_src; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -357,14 +357,10 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, sizeof(local_claim_dest)); local_claim_dest.type = claimtype; - mesh_iface = READ_ONCE(primary_if->mesh_iface); - if (!mesh_iface) - goto out; - skb = arp_create(ARPOP_REPLY, ETH_P_ARP, /* IP DST: 0.0.0.0 */ zeroip, - mesh_iface, + primary_if->mesh_iface, /* IP SRC: 0.0.0.0 */ zeroip, /* Ethernet DST: Broadcast */ @@ -442,7 +438,7 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, } skb_reset_mac_header(skb); - skb->protocol = eth_type_trans(skb, mesh_iface); + skb->protocol = eth_type_trans(skb, primary_if->mesh_iface); batadv_inc_counter(bat_priv, BATADV_CNT_RX); batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, skb->len + ETH_HLEN); @@ -598,10 +594,10 @@ static void batadv_bla_answer_request(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if, unsigned short vid) { - struct hlist_head *head; - struct batadv_hashtable *hash; - struct batadv_bla_claim *claim; struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim; + struct batadv_hashtable *hash; + struct hlist_head *head; int i; batadv_dbg(BATADV_DBG_BLA, bat_priv, @@ -697,8 +693,8 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv, struct batadv_bla_backbone_gw *backbone_gw) { struct batadv_bla_backbone_gw *old_backbone_gw; - struct batadv_bla_claim *claim; struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim; bool remove_crc = false; int hash_added; @@ -805,9 +801,10 @@ batadv_bla_claim_get_backbone_gw(struct batadv_bla_claim *claim) static void batadv_bla_del_claim(struct batadv_priv *bat_priv, const u8 *mac, const unsigned short vid) { - struct batadv_bla_claim search_claim, *claim; struct batadv_bla_claim *claim_removed_entry; struct hlist_node *claim_removed_node; + struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim; ether_addr_copy(search_claim.addr, mac); search_claim.vid = vid; @@ -847,7 +844,8 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr, u8 *backbone_addr, unsigned short vid) { struct batadv_bla_backbone_gw *backbone_gw; - u16 backbone_crc, crc; + u16 backbone_crc; + u16 crc; if (memcmp(an_addr, batadv_announce_mac, 4) != 0) return false; @@ -1024,9 +1022,10 @@ static int batadv_check_claim_group(struct batadv_priv *bat_priv, u8 *hw_src, u8 *hw_dst, struct ethhdr *ethhdr) { - u8 *backbone_addr; + struct batadv_bla_claim_dst *bla_dst_own; + struct batadv_bla_claim_dst *bla_dst; struct batadv_orig_node *orig_node; - struct batadv_bla_claim_dst *bla_dst, *bla_dst_own; + u8 *backbone_addr; bla_dst = (struct batadv_bla_claim_dst *)hw_dst; bla_dst_own = &bat_priv->bla.claim_dest; @@ -1083,6 +1082,11 @@ static int batadv_check_claim_group(struct batadv_priv *bat_priv, * @primary_if: the primary hard interface of this batman mesh interface * @skb: the frame to be checked * + * Warning: This function may reallocate the skb data buffer via + * batadv_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if it was a claim frame, otherwise return false to * tell the callee that it can use the frame on its own. */ @@ -1090,15 +1094,18 @@ static bool batadv_bla_process_claim(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if, struct sk_buff *skb) { - struct batadv_bla_claim_dst *bla_dst, *bla_dst_own; - u8 *hw_src, *hw_dst; - struct vlan_hdr *vhdr, vhdr_buf; + struct batadv_bla_claim_dst *bla_dst_own; + struct batadv_bla_claim_dst *bla_dst; + struct vlan_hdr vhdr_buf; + struct vlan_hdr *vhdr; struct ethhdr *ethhdr; struct arphdr *arphdr; unsigned short vid; int vlan_depth = 0; __be16 proto; int headlen; + u8 *hw_src; + u8 *hw_dst; int ret; vid = batadv_get_vid(skb, 0); @@ -1230,11 +1237,11 @@ static bool batadv_bla_process_claim(struct batadv_priv *bat_priv, */ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; - struct batadv_hashtable *hash; - spinlock_t *list_lock; /* protects write access to the hash lists */ bool purged; int i; @@ -1307,8 +1314,8 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv, { struct batadv_bla_backbone_gw *backbone_gw; struct batadv_bla_claim *claim; - struct hlist_head *head; struct batadv_hashtable *hash; + struct hlist_head *head; int i; hash = bat_priv->bla.claim_hash; @@ -1370,8 +1377,8 @@ void batadv_bla_update_orig_address(struct batadv_priv *bat_priv, struct batadv_hard_iface *oldif) { struct batadv_bla_backbone_gw *backbone_gw; - struct hlist_head *head; struct batadv_hashtable *hash; + struct hlist_head *head; __be16 group; int i; @@ -1464,14 +1471,14 @@ void batadv_bla_status_update(struct net_device *net_dev) */ static void batadv_bla_periodic_work(struct work_struct *work) { - struct delayed_work *delayed_work; - struct batadv_priv *bat_priv; - struct batadv_priv_bla *priv_bla; - struct hlist_head *head; struct batadv_bla_backbone_gw *backbone_gw; - struct batadv_hashtable *hash; struct batadv_hard_iface *primary_if; + struct delayed_work *delayed_work; + struct batadv_priv_bla *priv_bla; + struct batadv_hashtable *hash; + struct batadv_priv *bat_priv; bool send_loopdetect = false; + struct hlist_head *head; int i; delayed_work = to_delayed_work(work); @@ -1573,11 +1580,11 @@ static struct lock_class_key batadv_backbone_hash_lock_class_key; */ int batadv_bla_init(struct batadv_priv *bat_priv) { - int i; u8 claim_dest[ETH_ALEN] = {0xff, 0x43, 0x05, 0x00, 0x00, 0x00}; struct batadv_hard_iface *primary_if; - u16 crc; unsigned long entrytime; + u16 crc; + int i; spin_lock_init(&bat_priv->bla.bcast_duplist_lock); @@ -1656,8 +1663,9 @@ static bool batadv_bla_check_duplist(struct batadv_priv *bat_priv, struct batadv_bcast_duplist_entry *entry; bool ret = false; int payload_len; - int i, curr; + int curr; u32 crc; + int i; /* calculate the crc ... */ payload_len = skb->len - payload_offset; @@ -1779,8 +1787,8 @@ bool batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, u8 *orig, unsigned short vid) { struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; - struct hlist_head *head; struct batadv_bla_backbone_gw *backbone_gw; + struct hlist_head *head; int i; if (!READ_ONCE(bat_priv->bridge_loop_avoidance)) @@ -1812,6 +1820,11 @@ bool batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, u8 *orig, * @orig_node: the orig_node of the frame * @hdr_size: maximum length of the frame * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_get_vid()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the orig_node is also a gateway on the mesh interface, * otherwise it returns false. */ @@ -1941,9 +1954,10 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid, int packet_type) { struct batadv_bla_backbone_gw *backbone_gw; - struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim, *claim = NULL; + struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; bool own_claim; bool ret; @@ -2066,7 +2080,10 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, * * in these cases, the skb is further handled by this function. * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * batadv_bla_process_claim()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Return: true if handled, otherwise it returns false and the caller shall * further process the skb. @@ -2074,10 +2091,11 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid) { - struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim, *claim = NULL; struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; bool client_roamed; bool ret = false; @@ -2178,10 +2196,10 @@ batadv_bla_claim_dump_entry(struct sk_buff *msg, u32 portid, { const u8 *primary_addr = primary_if->net_dev->dev_addr; struct batadv_bla_backbone_gw *backbone_gw; + int ret = -EINVAL; u16 backbone_crc; bool is_own; void *hdr; - int ret = -EINVAL; hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq, &batadv_netlink_family, NLM_F_MULTI, @@ -2340,11 +2358,11 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_bla_backbone_gw *backbone_gw) { const u8 *primary_addr = primary_if->net_dev->dev_addr; + int ret = -EINVAL; u16 backbone_crc; bool is_own; int msecs; void *hdr; - int ret = -EINVAL; hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq, &batadv_netlink_family, NLM_F_MULTI, @@ -2499,10 +2517,10 @@ int batadv_bla_backbone_dump(struct sk_buff *msg, struct netlink_callback *cb) bool batadv_bla_check_claim(struct batadv_priv *bat_priv, u8 *addr, unsigned short vid) { - struct batadv_bla_backbone_gw *backbone_gw; - struct batadv_bla_claim search_claim; - struct batadv_bla_claim *claim = NULL; struct batadv_hard_iface *primary_if = NULL; + struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; bool ret = true; if (!READ_ONCE(bat_priv->bridge_loop_avoidance)) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index a6fe4820f65b..0d5a9cb0affe 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -50,27 +50,65 @@ #include "translation-table.h" #include "tvlv.h" +/** + * enum batadv_bootpop - BOOTP/DHCP message op codes + */ enum batadv_bootpop { + /** @BATADV_BOOTREPLY: server-to-client reply */ BATADV_BOOTREPLY = 2, }; +/** + * enum batadv_boothtype - BOOTP/DHCP hardware address types + */ enum batadv_boothtype { + /** @BATADV_HTYPE_ETHERNET: Ethernet (10Mb) */ BATADV_HTYPE_ETHERNET = 1, }; +/** + * enum batadv_dhcpoptioncode - DHCP option codes relevant for batman-adv DAT + */ enum batadv_dhcpoptioncode { + /** @BATADV_DHCP_OPT_PAD: pad option */ BATADV_DHCP_OPT_PAD = 0, + + /** @BATADV_DHCP_OPT_MSG_TYPE: DHCP message type option */ BATADV_DHCP_OPT_MSG_TYPE = 53, + + /** @BATADV_DHCP_OPT_END: end of options marker */ BATADV_DHCP_OPT_END = 255, }; +/** + * enum batadv_dhcptype - DHCP message types relevant for batman-adv DAT + */ enum batadv_dhcptype { + /** @BATADV_DHCPACK: DHCPACK message */ BATADV_DHCPACK = 5, }; /* { 99, 130, 83, 99 } */ #define BATADV_DHCP_MAGIC 1669485411 +/** + * struct batadv_dhcp_packet - BOOTP/DHCP packet header + * @op: message op code / message type + * @htype: hardware address type + * @hlen: hardware address length + * @hops: number of relay hops + * @xid: transaction identifier + * @secs: seconds elapsed since client started trying to boot + * @flags: BOOTP/DHCP flags + * @ciaddr: client IP address + * @yiaddr: "your" (client) IP address as assigned by the server + * @siaddr: IP address of next server to use in bootstrap + * @giaddr: relay agent IP address + * @chaddr: client hardware address + * @sname: optional server host name + * @file: boot file name + * @magic: BOOTP/DHCP magic cookie identifying the start of options + */ struct batadv_dhcp_packet { __u8 op; __u8 htype; @@ -90,6 +128,34 @@ struct batadv_dhcp_packet { /* __u8 options[]; */ }; +/** + * struct batadv_dhcp_header - minimal BOOTP/DHCP packet header + */ +struct batadv_dhcp_header { + /** @op: message op code / message type */ + __u8 op; + + /** @htype: hardware address type */ + __u8 htype; + + /** @hlen: hardware address length */ + __u8 hlen; + + /** @hops: number of relay hops */ + __u8 hops; +}; + +/** + * struct batadv_dhcp_option_header - BOOTP/DHCP option header + */ +struct batadv_dhcp_option_header { + /** @type: type of option */ + __u8 type; + + /** @len: length of option */ + __u8 len; +}; + #define BATADV_DHCP_YIADDR_LEN sizeof(((struct batadv_dhcp_packet *)0)->yiaddr) #define BATADV_DHCP_CHADDR_LEN sizeof(((struct batadv_dhcp_packet *)0)->chaddr) @@ -288,9 +354,9 @@ static __be32 batadv_arp_ip_dst(struct sk_buff *skb, int hdr_size) */ static u32 batadv_hash_dat(const void *data, u32 size) { - u32 hash = 0; const struct batadv_dat_entry *dat = data; const unsigned char *key; + u32 hash = 0; __be16 vid; u32 i; @@ -329,9 +395,11 @@ static struct batadv_dat_entry * batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip, unsigned short vid) { - struct hlist_head *head; - struct batadv_dat_entry to_find, *dat_entry, *dat_entry_tmp = NULL; struct batadv_hashtable *hash = bat_priv->dat.hash; + struct batadv_dat_entry *dat_entry_tmp = NULL; + struct batadv_dat_entry *dat_entry; + struct batadv_dat_entry to_find; + struct hlist_head *head; u32 index; if (!hash) @@ -432,7 +500,8 @@ static void batadv_dbg_arp(struct batadv_priv *bat_priv, struct sk_buff *skb, struct batadv_unicast_4addr_packet *unicast_4addr_packet; struct batadv_bcast_packet *bcast_pkt; u8 *orig_addr; - __be32 ip_src, ip_dst; + __be32 ip_src; + __be32 ip_dst; if (msg) batadv_dbg(BATADV_DBG_DAT, bat_priv, "%s\n", msg); @@ -567,10 +636,11 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, int select, batadv_dat_addr_t ip_key, batadv_dat_addr_t *last_max) { - batadv_dat_addr_t max = 0; - batadv_dat_addr_t tmp_max = 0; - struct batadv_orig_node *orig_node, *max_orig_node = NULL; struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *max_orig_node = NULL; + struct batadv_orig_node *orig_node; + batadv_dat_addr_t tmp_max = 0; + batadv_dat_addr_t max = 0; struct hlist_head *head; int i; @@ -634,10 +704,11 @@ static struct batadv_dat_candidate * batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst, unsigned short vid) { - int select; - batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX, ip_key; + batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX; struct batadv_dat_candidate *res; struct batadv_dat_entry dat; + batadv_dat_addr_t ip_key; + int select; if (!bat_priv->orig_hash) return NULL; @@ -680,12 +751,12 @@ static bool batadv_dat_forward_data(struct batadv_priv *bat_priv, struct sk_buff *skb, __be32 ip, unsigned short vid, int packet_subtype) { - int i; + struct batadv_neigh_node *neigh_node = NULL; + struct batadv_dat_candidate *cand; + struct sk_buff *tmp_skb; bool ret = false; int send_status; - struct batadv_neigh_node *neigh_node = NULL; - struct sk_buff *tmp_skb; - struct batadv_dat_candidate *cand; + int i; cand = batadv_dat_select_candidates(bat_priv, ip, vid); if (!cand) @@ -993,6 +1064,11 @@ int batadv_dat_cache_dump(struct sk_buff *msg, struct netlink_callback *cb) * @skb: packet to analyse * @hdr_size: size of the possible header before the ARP packet in the skb * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. obtained from skb->data + * or eth_hdr()) before this call must be considered invalid afterwards and has + * to be reacquired. + * * Return: the ARP type if the skb contains a valid ARP packet, 0 otherwise. */ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, @@ -1000,9 +1076,11 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, { struct arphdr *arphdr; struct ethhdr *ethhdr; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; + __be32 ip_src; + __be32 ip_dst; u16 type = 0; + u8 *hw_src; + u8 *hw_dst; /* pull the ethernet header */ if (unlikely(!pskb_may_pull(skb, hdr_size + ETH_HLEN))) @@ -1069,6 +1147,11 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, * The caller must ensure that at least @hdr_size + ETH_HLEN bytes are * accessible after skb->data. * + * Warning: This function calls batadv_get_vid() and may therefore reallocate + * the skb data buffer. Any pointer into the skb data (e.g. obtained from + * skb->data or eth_hdr()) before this call must be considered invalid + * afterwards and has to be reacquired. + * * Return: If the packet embedded in the skb is vlan tagged this function * returns the VID with the BATADV_VLAN_HAS_TAG flag. Otherwise BATADV_NO_FLAGS * is returned. @@ -1131,6 +1214,11 @@ batadv_dat_arp_create_reply(struct batadv_priv *bat_priv, __be32 ip_src, * @bat_priv: the bat priv with all the mesh interface information * @skb: packet to check * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/.... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the message has been sent to the dht candidates, false * otherwise. In case of a positive return value the message has to be enqueued * to permit the fallback. @@ -1138,15 +1226,16 @@ batadv_dat_arp_create_reply(struct batadv_priv *bat_priv, __be32 ip_src, bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb) { - u16 type = 0; - __be32 ip_dst, ip_src; - u8 *hw_src; - bool ret = false; + struct net_device *mesh_iface = bat_priv->mesh_iface; struct batadv_dat_entry *dat_entry = NULL; struct sk_buff *skb_new; - struct net_device *mesh_iface = bat_priv->mesh_iface; - int hdr_size = 0; unsigned short vid; + bool ret = false; + int hdr_size = 0; + __be32 ip_dst; + __be32 ip_src; + u16 type = 0; + u8 *hw_src; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; @@ -1233,18 +1322,24 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, * @skb: packet to check * @hdr_size: size of the encapsulation header * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the request has been answered, false otherwise. */ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { - u16 type; - __be32 ip_src, ip_dst; - u8 *hw_src; - struct sk_buff *skb_new; struct batadv_dat_entry *dat_entry = NULL; - bool ret = false; + struct sk_buff *skb_new; unsigned short vid; + bool ret = false; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u16 type; int err; if (!READ_ONCE(bat_priv->distributed_arp_table)) @@ -1277,17 +1372,9 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, if (!skb_new) goto out; - /* To preserve backwards compatibility, the node has choose the outgoing - * format based on the incoming request packet type. The assumption is - * that a node not using the 4addr packet format doesn't support it. - */ - if (hdr_size == sizeof(struct batadv_unicast_4addr_packet)) - err = batadv_send_skb_via_tt_4addr(bat_priv, skb_new, - BATADV_P_DAT_CACHE_REPLY, - NULL, vid); - else - err = batadv_send_skb_via_tt(bat_priv, skb_new, NULL, vid); - + err = batadv_send_skb_via_tt_4addr(bat_priv, skb_new, + BATADV_P_DAT_CACHE_REPLY, + NULL, vid); if (err != NET_XMIT_DROP) { batadv_inc_counter(bat_priv, BATADV_CNT_DAT_CACHED_REPLY_TX); ret = true; @@ -1303,15 +1390,22 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, * batadv_dat_snoop_outgoing_arp_reply() - snoop the ARP reply and fill the DHT * @bat_priv: the bat priv with all the mesh interface information * @skb: packet to check + * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. */ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb) { - u16 type; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; - int hdr_size = 0; unsigned short vid; + int hdr_size = 0; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u8 *hw_dst; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) return; @@ -1352,6 +1446,11 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, * @skb: packet to check * @hdr_size: size of the encapsulation header * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the packet was snooped and consumed by DAT. False if the * packet has to be delivered to the interface */ @@ -1359,11 +1458,13 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { struct batadv_dat_entry *dat_entry = NULL; - u16 type; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; bool dropped = false; unsigned short vid; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u8 *hw_dst; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; @@ -1454,8 +1555,10 @@ static bool batadv_dat_check_dhcp_ipudp(struct sk_buff *skb, __be32 *ip_src) { unsigned int offset = skb_network_offset(skb); - struct udphdr *udphdr, _udphdr; - struct iphdr *iphdr, _iphdr; + struct udphdr *udphdr; + struct udphdr _udphdr; + struct iphdr *iphdr; + struct iphdr _iphdr; iphdr = skb_header_pointer(skb, offset, sizeof(_iphdr), &_iphdr); if (!iphdr || iphdr->version != 4 || iphdr->ihl * 4 < sizeof(_iphdr)) @@ -1493,14 +1596,11 @@ batadv_dat_check_dhcp_ipudp(struct sk_buff *skb, __be32 *ip_src) static int batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) { - __be32 *magic, _magic; + struct batadv_dhcp_header *dhcp_h; + struct batadv_dhcp_header _dhcp_h; unsigned int offset; - struct { - __u8 op; - __u8 htype; - __u8 hlen; - __u8 hops; - } *dhcp_h, _dhcp_h; + __be32 *magic; + __be32 _magic; if (proto != htons(ETH_P_IP)) return -EINVAL; @@ -1541,11 +1641,10 @@ batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) static int batadv_dat_get_dhcp_message_type(struct sk_buff *skb) { unsigned int offset = skb_transport_offset(skb) + sizeof(struct udphdr); - u8 *type, _type; - struct { - u8 type; - u8 len; - } *tl, _tl; + struct batadv_dhcp_option_header *tl; + struct batadv_dhcp_option_header _tl; + u8 *type; + u8 _type; offset += sizeof(struct batadv_dhcp_packet); @@ -1737,7 +1836,8 @@ void batadv_dat_snoop_outgoing_dhcp_ack(struct batadv_priv *bat_priv, unsigned short vid) { u8 chaddr[BATADV_DHCP_CHADDR_LEN]; - __be32 ip_src, yiaddr; + __be32 ip_src; + __be32 yiaddr; if (!READ_ONCE(bat_priv->distributed_arp_table)) return; @@ -1758,15 +1858,21 @@ void batadv_dat_snoop_outgoing_dhcp_ack(struct batadv_priv *bat_priv, * This function first checks whether the given skb is a valid DHCPACK. If * so then its source MAC and IP as well as its DHCP Client Hardware Address * field and DHCP Your IP Address field are added to the local DAT cache. + * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_dat_get_vid()/... Any pointer into the skb data + * (e.g.obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. */ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { u8 chaddr[BATADV_DHCP_CHADDR_LEN]; struct ethhdr *ethhdr; - __be32 ip_src, yiaddr; unsigned short vid; int hdr_size_tmp; + __be32 ip_src; + __be32 yiaddr; __be16 proto; u8 *hw_src; @@ -1805,17 +1911,22 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, * @bat_priv: the bat priv with all the mesh interface information * @forw_packet: the broadcast packet * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the node can drop the packet, false otherwise. */ bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, struct batadv_forw_packet *forw_packet) { - u16 type; - __be32 ip_dst; - struct batadv_dat_entry *dat_entry = NULL; - bool ret = false; int hdr_size = sizeof(struct batadv_bcast_packet); + struct batadv_dat_entry *dat_entry = NULL; unsigned short vid; + bool ret = false; + __be32 ip_dst; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c index 2e20a2cb64cb..493af3091aa8 100644 --- a/net/batman-adv/fragmentation.c +++ b/net/batman-adv/fragmentation.c @@ -139,15 +139,17 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node, struct sk_buff *skb, struct hlist_head *chain_out) { - struct batadv_frag_table_entry *chain; - struct batadv_frag_list_entry *frag_entry_new = NULL, *frag_entry_curr; struct batadv_frag_list_entry *frag_entry_last = NULL; + struct batadv_frag_list_entry *frag_entry_new = NULL; + u16 hdr_size = sizeof(struct batadv_frag_packet); + struct batadv_frag_list_entry *frag_entry_curr; struct batadv_frag_packet *frag_packet; - u8 bucket; - u16 seqno, hdr_size = sizeof(struct batadv_frag_packet); + struct batadv_frag_table_entry *chain; bool overflow = false; bool ret = false; size_t data_len; + u8 bucket; + u16 seqno; /* Linearize packet to avoid linearizing 16 packets in a row when doing * the later merge. Non-linear merge should be added to remove this @@ -258,11 +260,12 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node, static struct sk_buff * batadv_frag_merge_packets(struct hlist_head *chain) { - struct batadv_frag_packet *packet; + int hdr_size = sizeof(struct batadv_frag_packet); struct batadv_frag_list_entry *entry; + struct batadv_frag_packet *packet; struct sk_buff *skb_out; - int size, hdr_size = sizeof(struct batadv_frag_packet); bool dropped = false; + int size; /* Remove first entry, as this is the destination for the rest of the * fragments. @@ -348,8 +351,8 @@ static bool batadv_skb_is_frag(struct sk_buff *skb) bool batadv_frag_skb_buffer(struct sk_buff **skb, struct batadv_orig_node *orig_node_src) { - struct sk_buff *skb_out = NULL; struct hlist_head head = HLIST_HEAD_INIT; + struct sk_buff *skb_out = NULL; bool ret = false; /* Add packet to buffer and table entry if merge is possible. */ @@ -403,8 +406,8 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_neigh_node *neigh_node = NULL; struct batadv_frag_packet *packet; - u16 total_size; bool ret = false; + u16 total_size; packet = (struct batadv_frag_packet *)skb->data; @@ -468,10 +471,11 @@ static struct sk_buff *batadv_frag_create(struct net_device *net_dev, { unsigned int ll_reserved = LL_RESERVED_SPACE(net_dev); unsigned int tailroom = net_dev->needed_tailroom; - struct sk_buff *skb_fragment; unsigned int header_size = sizeof(*frag_head); - unsigned int mtu = fragment_size + header_size; + struct sk_buff *skb_fragment; + unsigned int mtu; + mtu = fragment_size + header_size; skb_fragment = dev_alloc_skb(ll_reserved + mtu + tailroom); if (!skb_fragment) goto err; @@ -503,15 +507,18 @@ int batadv_frag_send_packet(struct sk_buff *skb, struct batadv_neigh_node *neigh_node) { struct net_device *net_dev = neigh_node->if_incoming->net_dev; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_frag_packet frag_header; - struct sk_buff *skb_fragment; unsigned int mtu = net_dev->mtu; - unsigned int header_size = sizeof(frag_header); - unsigned int max_fragment_size, num_fragments; + unsigned int max_fragment_size; + struct batadv_priv *bat_priv; + struct sk_buff *skb_fragment; + unsigned int num_fragments; + unsigned int header_size; int ret; + header_size = sizeof(frag_header); + /* To avoid merge and refragmentation at next-hops we never send * fragments larger than BATADV_FRAG_MAX_FRAG_SIZE */ diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index a5ac82eabd25..e6e97c0478cc 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -103,8 +103,8 @@ batadv_gw_get_selected_gw_node(struct batadv_priv *bat_priv) struct batadv_orig_node * batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) { - struct batadv_gw_node *gw_node; struct batadv_orig_node *orig_node = NULL; + struct batadv_gw_node *gw_node; gw_node = batadv_gw_get_selected_gw_node(bat_priv); if (!gw_node) @@ -125,6 +125,14 @@ batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) return orig_node; } +/** + * batadv_gw_select() - select a new currently active gateway + * @bat_priv: the bat priv with all the mesh interface information + * @new_gw_node: gateway node to be set as the current gateway, may be NULL + * + * Atomically replace the currently active gateway with @new_gw_node and drop + * the reference to the previous one. + */ static void batadv_gw_select(struct batadv_priv *bat_priv, struct batadv_gw_node *new_gw_node) { @@ -197,10 +205,10 @@ void batadv_gw_check_client_stop(struct batadv_priv *bat_priv) */ void batadv_gw_election(struct batadv_priv *bat_priv) { + struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_node *router = NULL; struct batadv_gw_node *curr_gw = NULL; struct batadv_gw_node *next_gw = NULL; - struct batadv_neigh_node *router = NULL; - struct batadv_neigh_ifinfo *router_ifinfo = NULL; char gw_addr[18] = { '\0' }; if (READ_ONCE(bat_priv->gw.mode) != BATADV_GW_MODE_CLIENT) @@ -370,7 +378,8 @@ static void batadv_gw_node_add(struct batadv_priv *bat_priv, struct batadv_gw_node *batadv_gw_node_get(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *gw_node_tmp, *gw_node = NULL; + struct batadv_gw_node *gw_node = NULL; + struct batadv_gw_node *gw_node_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node_tmp, &bat_priv->gw.gateway_list, @@ -400,7 +409,8 @@ void batadv_gw_node_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_gateway_data *gateway) { - struct batadv_gw_node *gw_node, *curr_gw = NULL; + struct batadv_gw_node *curr_gw = NULL; + struct batadv_gw_node *gw_node; spin_lock_bh(&bat_priv->gw.list_lock); gw_node = batadv_gw_node_get(bat_priv, orig_node); @@ -545,7 +555,10 @@ int batadv_gw_dump(struct sk_buff *msg, struct netlink_callback *cb) * @chaddr: buffer where the client address will be stored. Valid * only if the function returns BATADV_DHCP_TO_CLIENT * - * This function may re-allocate the data buffer of the skb passed as argument. + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Return: * - BATADV_DHCP_NO if the packet is not a dhcp message or if there was an error @@ -558,11 +571,11 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, u8 *chaddr) { enum batadv_dhcp_recipient ret = BATADV_DHCP_NO; - struct ethhdr *ethhdr; - struct iphdr *iphdr; - struct ipv6hdr *ipv6hdr; - struct udphdr *udphdr; struct vlan_ethhdr *vhdr; + struct ipv6hdr *ipv6hdr; + struct ethhdr *ethhdr; + struct udphdr *udphdr; + struct iphdr *iphdr; int chaddr_offset; __be16 proto; u8 *p; @@ -669,7 +682,11 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, * server. Due to topology changes it may be the case that the GW server * previously selected is not the best one anymore. * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * batadv_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Must be invoked only when the DHCP packet is going TO a DHCP SERVER. * * Return: true if the packet destination is unicast and it is not the best gw, @@ -678,16 +695,17 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, struct sk_buff *skb) { + struct batadv_orig_node *orig_dst_node = NULL; struct batadv_neigh_node *neigh_curr = NULL; struct batadv_neigh_node *neigh_old = NULL; - struct batadv_orig_node *orig_dst_node = NULL; + struct batadv_neigh_ifinfo *curr_ifinfo; + struct batadv_neigh_ifinfo *old_ifinfo; struct batadv_gw_node *gw_node = NULL; struct batadv_gw_node *curr_gw = NULL; - struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo; - struct ethhdr *ethhdr; bool out_of_range = false; - u8 curr_tq_avg; + struct ethhdr *ethhdr; unsigned short vid; + u8 curr_tq_avg; vid = batadv_get_vid(skb, 0); ethhdr = (struct ethhdr *)skb->data; diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c index 675ebf098d4e..65d339205090 100644 --- a/net/batman-adv/gateway_common.c +++ b/net/batman-adv/gateway_common.c @@ -26,7 +26,8 @@ void batadv_gw_tvlv_container_update(struct batadv_priv *bat_priv) { struct batadv_tvlv_gateway_data gw; enum batadv_gw_modes gw_mode; - u32 down, up; + u32 down; + u32 up; gw_mode = READ_ONCE(bat_priv->gw.mode); @@ -59,7 +60,8 @@ static void batadv_gw_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, u8 flags, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_gateway_data gateway, *gateway_ptr; + struct batadv_tvlv_gateway_data *gateway_ptr; + struct batadv_tvlv_gateway_data gateway; /* only fetch the tvlv value if the handler wasn't called via the * CIFNOTFND flag and if there is data to fetch diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 03d01c20a954..e7ad295504e4 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -63,6 +63,7 @@ void batadv_hardif_release(struct kref *ref) struct batadv_hard_iface *hard_iface; hard_iface = container_of(ref, struct batadv_hard_iface, refcount); + netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); netdev_put(hard_iface->net_dev, &hard_iface->dev_tracker); kfree_rcu(hard_iface, rcu); @@ -75,21 +76,21 @@ void batadv_hardif_release(struct kref *ref) * Return: batadv_hard_iface of net_dev (with increased refcnt), NULL on errors */ struct batadv_hard_iface * -batadv_hardif_get_by_netdev(const struct net_device *net_dev) +batadv_hardif_get_by_netdev(struct net_device *net_dev) { struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; - rcu_read_lock(); - list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { - if (hard_iface->net_dev == net_dev && - kref_get_unless_zero(&hard_iface->refcount)) - goto out; - } + ASSERT_RTNL(); - hard_iface = NULL; + mesh_iface = netdev_master_upper_dev_get(net_dev); + if (!mesh_iface || !batadv_meshif_is_valid(mesh_iface)) + return NULL; + + hard_iface = netdev_lower_dev_get_private(mesh_iface, net_dev); + if (!kref_get_unless_zero(&hard_iface->refcount)) + return NULL; -out: - rcu_read_unlock(); return hard_iface; } @@ -196,6 +197,17 @@ static bool batadv_is_on_batman_iface(const struct net_device *net_dev) return ret; } +/** + * batadv_is_valid_iface() - check whether a net_device can be used as a hard + * interface + * @net_dev: the net_device to check + * + * Refuse loopback devices, non-Ethernet devices, devices with a non-Ethernet + * address length and any device that is already part of a batman-adv mesh + * interface stack. + * + * Return: true if @net_dev can be used as a hard interface, false otherwise + */ static bool batadv_is_valid_iface(const struct net_device *net_dev) { if (net_dev->flags & IFF_LOOPBACK) @@ -245,7 +257,7 @@ struct net_device *__batadv_get_real_netdev(struct net_device *netdev) } hard_iface = batadv_hardif_get_by_netdev(netdev); - if (!hard_iface || !hard_iface->mesh_iface) + if (!hard_iface) goto out; net = dev_net(hard_iface->mesh_iface); @@ -338,8 +350,8 @@ static bool batadv_is_cfg80211_netdev(struct net_device *net_device) */ static u32 batadv_wifi_flags_evaluate(struct net_device *net_device) { - u32 wifi_flags = 0; struct net_device *real_netdev; + u32 wifi_flags = 0; if (batadv_is_wext_netdev(net_device)) wifi_flags |= BATADV_HARDIF_WIFI_WEXT_DIRECT; @@ -433,8 +445,8 @@ int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing, u8 *orig_addr, u8 *orig_neigh) { struct batadv_hardif_neigh_node *hardif_neigh; - struct hlist_node *first; int ret = BATADV_HARDIF_BCAST_OK; + struct hlist_node *first; rcu_read_lock(); @@ -466,6 +478,14 @@ int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing, return ret; } +/** + * batadv_hardif_get_active() - retrieve an active hard interface for a mesh + * interface + * @mesh_iface: mesh interface to search + * + * Return: first hard interface in BATADV_IF_ACTIVE state attached to + * @mesh_iface, or NULL if none is active. + */ static struct batadv_hard_iface * batadv_hardif_get_active(struct net_device *mesh_iface) { @@ -486,6 +506,15 @@ batadv_hardif_get_active(struct net_device *mesh_iface) return hard_iface; } +/** + * batadv_primary_if_update_addr() - propagate the new primary interface MAC + * address to all dependent components + * @bat_priv: the bat priv with all the mesh interface information + * @oldif: previously used primary interface, or NULL if none + * + * Inform DAT and BLA that the originator address has changed so they can + * adjust their state accordingly. + */ static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv, struct batadv_hard_iface *oldif) { @@ -501,6 +530,15 @@ static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv, batadv_hardif_put(primary_if); } +/** + * batadv_primary_if_select() - select the new primary interface + * @bat_priv: the bat priv with all the mesh interface information + * @new_hard_iface: new primary interface, may be NULL + * + * Replace the currently selected primary interface with @new_hard_iface, + * invoke the algorithm-specific primary_set hook and update the originator + * MAC address. + */ static void batadv_primary_if_select(struct batadv_priv *bat_priv, struct batadv_hard_iface *new_hard_iface) { @@ -524,6 +562,13 @@ static void batadv_primary_if_select(struct batadv_priv *bat_priv, batadv_hardif_put(curr_hard_iface); } +/** + * batadv_hardif_is_iface_up() - check whether the underlying net_device of a + * hard interface is up + * @hard_iface: the hard interface to check + * + * Return: true if the underlying net_device has the IFF_UP flag set + */ static bool batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) { @@ -533,22 +578,25 @@ batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) return false; } +/** + * batadv_check_known_mac_addr() - warn about duplicate hard interface MAC + * addresses + * @hard_iface: hard interface that was just added or had its MAC changed + * + * Iterate over all hard interfaces of the same mesh interface and emit a + * warning if another in-use interface shares the same MAC address as + * @hard_iface. + */ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_iface) { struct net_device *mesh_iface = hard_iface->mesh_iface; const struct batadv_hard_iface *tmp_hard_iface; struct list_head *iter; - if (!mesh_iface) - return; - netdev_for_each_lower_private(mesh_iface, tmp_hard_iface, iter) { if (tmp_hard_iface == hard_iface) continue; - if (tmp_hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - if (!batadv_compare_eth(tmp_hard_iface->net_dev->dev_addr, hard_iface->net_dev->dev_addr)) continue; @@ -574,9 +622,6 @@ static void batadv_hardif_recalc_extra_skbroom(struct net_device *mesh_iface) rcu_read_lock(); netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - lower_header_len = max_t(unsigned short, lower_header_len, hard_iface->net_dev->hard_header_len); @@ -674,11 +719,20 @@ void batadv_update_min_mtu(struct net_device *mesh_iface) batadv_tt_local_resize_to_mtu(mesh_iface); } +/** + * batadv_hardif_activate_interface() - move a hard interface to the active + * state + * @hard_iface: the interface that has come up + * + * Activate an interface, select it as the primary interface if no + * primary is currently active and invoke the algorithm-specific + * activate hook. + */ static void batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) { - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + struct batadv_priv *bat_priv; if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; @@ -707,6 +761,14 @@ batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_hardif_deactivate_interface() - move a hard interface to the + * inactive state + * @hard_iface: the interface that went down + * + * Demote @hard_iface to BATADV_IF_INACTIVE and recalculate the mesh minimum + * MTU based on the remaining active interfaces. + */ static void batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) { @@ -723,38 +785,63 @@ batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) } /** - * batadv_hardif_enable_interface() - Enslave hard interface to mesh interface - * @hard_iface: hard interface to add to mesh interface + * batadv_hardif_enable_interface() - Enslave interface to mesh interface + * @net_dev: netdev struct of the interface to add to mesh interface * @mesh_iface: netdev struct of the mesh interface * * Return: 0 on success or negative error number in case of failure */ -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface) { - struct batadv_priv *bat_priv; - __be16 ethertype = htons(ETH_P_BATMAN); int max_header_len = batadv_max_header_len(); + __be16 ethertype = htons(ETH_P_BATMAN); + struct batadv_hard_iface *hard_iface; + struct batadv_priv *bat_priv; unsigned int required_mtu; unsigned int hardif_mtu; bool fragmentation; int ret; - hardif_mtu = READ_ONCE(hard_iface->net_dev->mtu); + ASSERT_RTNL(); + + if (!batadv_is_valid_iface(net_dev)) + return -EINVAL; + + hardif_mtu = READ_ONCE(net_dev->mtu); required_mtu = READ_ONCE(mesh_iface->mtu) + max_header_len; if (hardif_mtu < ETH_MIN_MTU + max_header_len) return -EINVAL; - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - goto out; + hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); + if (!hard_iface) + return -ENOMEM; - kref_get(&hard_iface->refcount); + netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); + hard_iface->net_dev = net_dev; + + hard_iface->if_status = BATADV_IF_INACTIVE; + + INIT_HLIST_HEAD(&hard_iface->neigh_list); + + mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); + spin_lock_init(&hard_iface->neigh_list_lock); + kref_init(&hard_iface->refcount); + + hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; + if (batadv_is_wifi_hardif(hard_iface)) + hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; + + WRITE_ONCE(hard_iface->hop_penalty, 0); + + batadv_v_hardif_init(hard_iface); netdev_hold(mesh_iface, &hard_iface->meshif_dev_tracker, GFP_ATOMIC); hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); + bat_priv->hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -764,9 +851,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (ret < 0) goto err_upper; - hard_iface->if_status = BATADV_IF_INACTIVE; - - kref_get(&hard_iface->refcount); hard_iface->batman_adv_ptype.type = ethertype; hard_iface->batman_adv_ptype.func = batadv_batman_skb_recv; hard_iface->batman_adv_ptype.dev = hard_iface->net_dev; @@ -802,14 +886,11 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); -out: return 0; err_upper: netdev_upper_dev_unlink(hard_iface->net_dev, mesh_iface); err_dev: - hard_iface->mesh_iface = NULL; - netdev_put(mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_put(hard_iface); return ret; } @@ -821,17 +902,18 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *primary_if; + + ASSERT_RTNL(); batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) - goto out; + return; batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); - batadv_hardif_put(hard_iface); primary_if = batadv_primary_if_get_selected(bat_priv); if (hard_iface == primary_if) { @@ -842,15 +924,16 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(new_if); } + batadv_hardif_put(primary_if); bat_priv->algo_ops->iface.disable(hard_iface); - hard_iface->if_status = BATADV_IF_NOT_IN_USE; + hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); + bat_priv->hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); @@ -858,67 +941,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (list_empty(&hard_iface->mesh_iface->adj_list.lower)) batadv_gw_check_client_stop(bat_priv); - hard_iface->mesh_iface = NULL; - batadv_hardif_put(hard_iface); - -out: - batadv_hardif_put(primary_if); -} - -static struct batadv_hard_iface * -batadv_hardif_add_interface(struct net_device *net_dev) -{ - struct batadv_hard_iface *hard_iface; - - ASSERT_RTNL(); - - if (!batadv_is_valid_iface(net_dev)) - return NULL; - - hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); - if (!hard_iface) - return NULL; - - netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); - hard_iface->net_dev = net_dev; - - hard_iface->mesh_iface = NULL; - hard_iface->if_status = BATADV_IF_NOT_IN_USE; - - INIT_LIST_HEAD(&hard_iface->list); - INIT_HLIST_HEAD(&hard_iface->neigh_list); - - mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); - spin_lock_init(&hard_iface->neigh_list_lock); - kref_init(&hard_iface->refcount); - - hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; - if (batadv_is_wifi_hardif(hard_iface)) - hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; - - WRITE_ONCE(hard_iface->hop_penalty, 0); - - batadv_v_hardif_init(hard_iface); - - kref_get(&hard_iface->refcount); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - - return hard_iface; -} - -static void batadv_hardif_remove_interface(struct batadv_hard_iface *hard_iface) -{ - ASSERT_RTNL(); - - /* first deactivate interface */ - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - batadv_hardif_disable_interface(hard_iface); - - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - return; - - hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; batadv_hardif_put(hard_iface); } @@ -1068,12 +1090,24 @@ static void batadv_wifi_net_device_event(unsigned long event, } } +/** + * batadv_hard_if_event() - netdevice notifier callback for hard interfaces + * @this: notifier block (unused) + * @event: the NETDEV_* event to handle + * @ptr: notifier info pointing to the affected net_device + * + * Dispatch netdevice events that affect potential or existing hard + * interfaces. Mesh interfaces are handled separately by + * batadv_hard_if_event_meshif(). + * + * Return: NOTIFY_DONE + */ static int batadv_hard_if_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *net_dev = netdev_notifier_info_to_dev(ptr); - struct batadv_hard_iface *hard_iface; struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *hard_iface; struct batadv_priv *bat_priv; if (batadv_meshif_is_valid(net_dev)) @@ -1082,10 +1116,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_wifi_net_device_event(event, net_dev); hard_iface = batadv_hardif_get_by_netdev(net_dev); - if (!hard_iface && (event == NETDEV_REGISTER || - event == NETDEV_POST_TYPE_CHANGE)) - hard_iface = batadv_hardif_add_interface(net_dev); - if (!hard_iface) goto out; @@ -1099,19 +1129,12 @@ static int batadv_hard_if_event(struct notifier_block *this, break; case NETDEV_UNREGISTER: case NETDEV_PRE_TYPE_CHANGE: - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - - batadv_hardif_remove_interface(hard_iface); + batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: - if (hard_iface->mesh_iface) - batadv_update_min_mtu(hard_iface->mesh_iface); + batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - goto hardif_put; - batadv_check_known_mac_addr(hard_iface); bat_priv = netdev_priv(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index af31696c3978..935f47ca9a48 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -21,12 +21,6 @@ * enum batadv_hard_if_state - State of a hard interface */ enum batadv_hard_if_state { - /** - * @BATADV_IF_NOT_IN_USE: interface is not used as slave interface of a - * batman-adv mesh interface - */ - BATADV_IF_NOT_IN_USE, - /** * @BATADV_IF_TO_BE_REMOVED: interface will be removed from mesh * interface @@ -74,8 +68,8 @@ u32 batadv_netdev_get_wifi_flags(struct net_device *net_dev); u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* -batadv_hardif_get_by_netdev(const struct net_device *net_dev); -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +batadv_hardif_get_by_netdev(struct net_device *net_dev); +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); int batadv_hardif_min_mtu(struct net_device *mesh_iface); diff --git a/net/batman-adv/hash.c b/net/batman-adv/hash.c index 759fa29176db..b9a652bd523b 100644 --- a/net/batman-adv/hash.c +++ b/net/batman-adv/hash.c @@ -11,7 +11,10 @@ #include #include -/* clears the hash */ +/** + * batadv_hash_init() - clear all buckets of a hashtable + * @hash: hashtable to clear + */ static void batadv_hash_init(struct batadv_hashtable *hash) { u32 i; diff --git a/net/batman-adv/hash.h b/net/batman-adv/hash.h index 86a2c20000dc..e67afe01304c 100644 --- a/net/batman-adv/hash.h +++ b/net/batman-adv/hash.h @@ -18,21 +18,35 @@ #include #include -/* callback to a compare function. should compare 2 element data for their - * keys +/** + * typedef batadv_hashdata_compare_cb - hash element comparison callback + * @node: hlist node of the element currently stored in the bucket + * @key: opaque payload to compare @node's key against * - * Return: true if same and false if not same + * Compare hash element by its keys. + * + * Return: true if both elements are considered equal, false otherwise. */ -typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *, - const void *); +typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *node, + const void *key); -/* the hashfunction +/** + * typedef batadv_hashdata_choose_cb - hash bucket selection callback + * @key: opaque payload whose key selects the bucket + * @size: number of buckets in the hash table * - * Return: an index based on the key in the data of the first argument and the - * size the second + * Return: bucket index derived from the key in @key and the table @size. */ -typedef u32 (*batadv_hashdata_choose_cb)(const void *, u32); -typedef void (*batadv_hashdata_free_cb)(struct hlist_node *, void *); +typedef u32 (*batadv_hashdata_choose_cb)(const void *key, u32 size); + +/** + * typedef batadv_hashdata_free_cb - hash element free callback + * @node: hlist node of the element being removed + * @arg: opaque caller-supplied argument forwarded from the caller + * + * Release a previously inserted hash element. + */ +typedef void (*batadv_hashdata_free_cb)(struct hlist_node *node, void *arg); /** * struct batadv_hashtable - Wrapper of simple hlist based hashtable @@ -78,11 +92,11 @@ static inline int batadv_hash_add(struct batadv_hashtable *hash, const void *data, struct hlist_node *data_node) { - u32 index; - int ret = -1; + spinlock_t *list_lock; /* spinlock to protect write access */ struct hlist_head *head; struct hlist_node *node; - spinlock_t *list_lock; /* spinlock to protect write access */ + int ret = -1; + u32 index; if (!hash) goto out; @@ -131,10 +145,10 @@ static inline void *batadv_hash_remove(struct batadv_hashtable *hash, batadv_hashdata_choose_cb choose, void *data) { - u32 index; struct hlist_node *node; struct hlist_head *head; void *data_save = NULL; + u32 index; index = choose(data, hash->size); head = &hash->table[index]; diff --git a/net/batman-adv/log.h b/net/batman-adv/log.h index a0d2b0d64b2b..9f3aed826dea 100644 --- a/net/batman-adv/log.h +++ b/net/batman-adv/log.h @@ -117,8 +117,12 @@ static inline void _batadv_dbg(int type __always_unused, */ #define batadv_info(net_dev, fmt, arg...) \ do { \ - struct net_device *_netdev = (net_dev); \ - struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + struct batadv_priv *_batpriv; \ + struct net_device *_netdev; \ + \ + _netdev = (net_dev); \ + _batpriv = netdev_priv(_netdev); \ + \ batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ pr_info("%s: " fmt, _netdev->name, ## arg); \ } while (0) @@ -131,8 +135,12 @@ static inline void _batadv_dbg(int type __always_unused, */ #define batadv_err(net_dev, fmt, arg...) \ do { \ - struct net_device *_netdev = (net_dev); \ - struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + struct batadv_priv *_batpriv; \ + struct net_device *_netdev; \ + \ + _netdev = (net_dev); \ + _batpriv = netdev_priv(_netdev); \ + \ batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ pr_err("%s: " fmt, _netdev->name, ## arg); \ } while (0) diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 67bed3ee77e7..77597171d637 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,11 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -/* List manipulations on hardif_list have to be rtnl_lock()'ed, - * list traversals just rcu-locked - */ -struct list_head batadv_hardif_list; -unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); @@ -87,6 +82,14 @@ static char *batadv_uev_type_str[] = { "bla", }; +/** + * batadv_init() - batman-adv module init function + * + * Initialise the global state used by all mesh interfaces, register the + * netdevice notifier and the netlink/rtnl family. + * + * Return: 0 on success or negative error number in case of failure + */ static int __init batadv_init(void) { int ret; @@ -95,45 +98,73 @@ static int __init batadv_init(void) if (ret < 0) return ret; - INIT_LIST_HEAD(&batadv_hardif_list); batadv_algo_init(); batadv_recv_handler_init(); - batadv_v_init(); - batadv_iv_init(); + ret = batadv_v_init(); + if (ret < 0) + goto err_tt; + + ret = batadv_iv_init(); + if (ret < 0) + goto err_v; + batadv_tp_meter_init(); + ret = batadv_wifi_net_devices_init(); + if (ret < 0) + goto err_iv; + batadv_event_workqueue = create_singlethread_workqueue("bat_events"); if (!batadv_event_workqueue) { ret = -ENOMEM; - goto err_create_wq; + goto err_wifi; } - ret = batadv_wifi_net_devices_init(); + ret = register_netdevice_notifier(&batadv_hard_if_notifier); if (ret < 0) - goto err_init_wifi; + goto err_wq; - register_netdevice_notifier(&batadv_hard_if_notifier); - rtnl_link_register(&batadv_link_ops); - batadv_netlink_register(); + ret = rtnl_link_register(&batadv_link_ops); + if (ret < 0) + goto err_notifier; + + ret = batadv_netlink_register(); + if (ret < 0) + goto err_rtnl; pr_info("B.A.T.M.A.N. advanced %s (compatibility version %i) loaded\n", init_utsname()->release, BATADV_COMPAT_VERSION); return 0; -err_init_wifi: +err_rtnl: + rtnl_link_unregister(&batadv_link_ops); +err_notifier: + unregister_netdevice_notifier(&batadv_hard_if_notifier); +err_wq: destroy_workqueue(batadv_event_workqueue); batadv_event_workqueue = NULL; rcu_barrier(); - -err_create_wq: +err_wifi: + batadv_wifi_net_devices_deinit(); +err_iv: + batadv_iv_deinit(); +err_v: + batadv_v_deinit(); +err_tt: batadv_tt_cache_destroy(); return ret; } +/** + * batadv_exit() - batman-adv module exit function + * + * Unregister the netdevice notifier and tear down all global state allocated + * by batadv_init(). + */ static void __exit batadv_exit(void) { batadv_netlink_unregister(); @@ -360,10 +391,14 @@ int batadv_max_header_len(void) */ void batadv_skb_set_priority(struct sk_buff *skb, int offset) { - struct iphdr ip_hdr_tmp, *ip_hdr; - struct ipv6hdr ip6_hdr_tmp, *ip6_hdr; - struct ethhdr ethhdr_tmp, *ethhdr; - struct vlan_ethhdr *vhdr, vhdr_tmp; + struct vlan_ethhdr vhdr_tmp; + struct ipv6hdr ip6_hdr_tmp; + struct ethhdr ethhdr_tmp; + struct vlan_ethhdr *vhdr; + struct iphdr ip_hdr_tmp; + struct ipv6hdr *ip6_hdr; + struct ethhdr *ethhdr; + struct iphdr *ip_hdr; u32 prio; /* already set, do nothing */ @@ -404,6 +439,17 @@ void batadv_skb_set_priority(struct sk_buff *skb, int offset) skb->priority = prio + 256; } +/** + * batadv_recv_unhandled_packet() - default RX handler for unsupported packet + * types + * @skb: incoming packet + * @recv_if: interface on which the packet was received (unused) + * + * Drop incoming packets whose packet_type has no dedicated RX handler + * registered. + * + * Return: NET_RX_DROP + */ static int batadv_recv_unhandled_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { @@ -412,10 +458,6 @@ static int batadv_recv_unhandled_packet(struct sk_buff *skb, return NET_RX_DROP; } -/* incoming packets with the batman ethertype received on any active hard - * interface - */ - /** * batadv_batman_skb_recv() - Handle incoming message from an hard interface * @skb: the received packet @@ -429,9 +471,9 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype, struct net_device *orig_dev) { - struct batadv_priv *bat_priv; struct batadv_ogm_packet *batadv_ogm_packet; struct batadv_hard_iface *hard_iface; + struct batadv_priv *bat_priv; u8 idx; hard_iface = container_of(ptype, struct batadv_hard_iface, @@ -458,9 +500,6 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, if (unlikely(skb->mac_len != ETH_HLEN || !skb_mac_header(skb))) goto err_free; - if (!hard_iface->mesh_iface) - goto err_free; - bat_priv = netdev_priv(hard_iface->mesh_iface); if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) @@ -501,6 +540,13 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } +/** + * batadv_recv_handler_init() - initialise the RX handler dispatch table + * + * Initialise all entries of the RX handler table as either "unhandled" or with + * protocol indepentend handlers, and perform compile-time size sanity checks on + * all on-wire packet structs. + */ static void batadv_recv_handler_init(void) { int i; @@ -591,6 +637,11 @@ void batadv_recv_handler_unregister(u8 packet_type) * The caller must ensure that at least @header_len + ETH_HLEN bytes are * accessible after skb->data. * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. obtained from skb->data + * or eth_hdr()) before this call must be considered invalid afterwards and has + * to be reacquired. + * * Return: VID with the BATADV_VLAN_HAS_TAG flag when the packet embedded in the * skb is vlan tagged. Otherwise BATADV_NO_FLAGS. */ @@ -659,9 +710,9 @@ bool batadv_vlan_ap_isola_get(struct batadv_priv *bat_priv, unsigned short vid) int batadv_throw_uevent(struct batadv_priv *bat_priv, enum batadv_uev_type type, enum batadv_uev_action action, const char *data) { - int ret = -ENOMEM; - struct kobject *bat_kobj; char *uevent_env[4] = { NULL, NULL, NULL, NULL }; + struct kobject *bat_kobj; + int ret = -ENOMEM; bat_kobj = &bat_priv->mesh_iface->dev.kobj; diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index f68fc8b7239c..e738758ee4a7 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,9 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern struct list_head batadv_hardif_list; -extern unsigned int batadv_hardif_generation; - extern struct workqueue_struct *batadv_event_workqueue; int batadv_mesh_init(struct net_device *mesh_iface); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index fbfd99268de4..8e55b61dd2a6 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -57,6 +57,11 @@ * @skb: packet buffer which should be modified * @len: number of bytes to add * + * Warning: This function may reallocate the skb data buffer via + * skb_cow_head()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: 0 on success or negative error number in case of failure */ int batadv_skb_head_push(struct sk_buff *skb, unsigned int len) @@ -87,7 +92,8 @@ int batadv_skb_head_push(struct sk_buff *skb, unsigned int len) */ static u64 batadv_sum_counter(struct batadv_priv *bat_priv, size_t idx) { - u64 *counters, sum = 0; + u64 *counters; + u64 sum = 0; int cpu; for_each_possible_cpu(cpu) { @@ -98,6 +104,15 @@ static u64 batadv_sum_counter(struct batadv_priv *bat_priv, size_t idx) return sum; } +/** + * batadv_interface_stats() - return netdev stats for a mesh interface + * @dev: the mesh interface to query + * + * Aggregate the per-CPU traffic counters into the standard netdev stats + * structure. + * + * Return: pointer to the populated net_device_stats structure + */ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -111,6 +126,18 @@ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) return stats; } +/** + * batadv_interface_set_mac_addr() - change the MAC address of a mesh + * interface + * @dev: the mesh interface to modify + * @p: pointer to a struct sockaddr holding the new MAC address + * + * Replace the MAC address of the mesh interface. If the mesh is already + * active, also update the local translation table entries for all configured + * VLANs so that the new MAC is announced and the old one is removed. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -140,6 +167,16 @@ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) return 0; } +/** + * batadv_interface_change_mtu() - change the MTU of a mesh interface + * @dev: the mesh interface to modify + * @new_mtu: requested new MTU value + * + * Validate that @new_mtu fits within the range supported by the configured + * hard interfaces and remember it as the user-configured MTU. + * + * Return: 0 on success or -EINVAL if @new_mtu is out of range + */ static int batadv_interface_change_mtu(struct net_device *dev, int new_mtu) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -166,31 +203,39 @@ static void batadv_interface_set_rx_mode(struct net_device *dev) { } +/** + * batadv_interface_tx() - transmit a frame on a mesh interface + * @skb: the frame to send + * @mesh_iface: the mesh interface the frame was queued on + * + * Return: NETDEV_TX_OK on success + */ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct net_device *mesh_iface) { - struct ethhdr *ethhdr; + static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, 0x00, 0x00}; + static const u8 stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00}; struct batadv_priv *bat_priv = netdev_priv(mesh_iface); + enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; + enum batadv_forw_mode forw_mode = BATADV_FORW_BCAST; struct batadv_hard_iface *primary_if = NULL; struct batadv_bcast_packet *bcast_packet; - static const u8 stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, - 0x00, 0x00}; - static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, - 0x00, 0x00}; - enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; - u8 *dst_hint = NULL, chaddr[ETH_ALEN]; - struct vlan_ethhdr *vhdr; - unsigned int header_len = 0; - int data_len = skb->len, ret; - unsigned long brd_delay = 0; - bool do_bcast = false, client_added; - unsigned short vid; - u32 seqno; - int gw_mode; - enum batadv_forw_mode forw_mode = BATADV_FORW_BCAST; - int mcast_is_routable = 0; int network_offset = ETH_HLEN; + unsigned int header_len = 0; + unsigned long brd_delay = 0; + int mcast_is_routable = 0; + struct vlan_ethhdr *vhdr; + int data_len = skb->len; + struct ethhdr *ethhdr; + bool do_bcast = false; + u8 *dst_hint = NULL; + u8 chaddr[ETH_ALEN]; + unsigned short vid; + bool client_added; __be16 proto; + int gw_mode; + u32 seqno; + int ret; if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) goto dropped; @@ -408,8 +453,8 @@ void batadv_interface_rx(struct net_device *mesh_iface, struct sk_buff *skb, int hdr_size, struct batadv_orig_node *orig_node) { - struct batadv_bcast_packet *batadv_bcast_packet; struct batadv_priv *bat_priv = netdev_priv(mesh_iface); + struct batadv_bcast_packet *batadv_bcast_packet; struct vlan_ethhdr *vhdr; struct ethhdr *ethhdr; unsigned short vid; @@ -523,7 +568,8 @@ void batadv_meshif_vlan_release(struct kref *ref) struct batadv_meshif_vlan *batadv_meshif_vlan_get(struct batadv_priv *bat_priv, unsigned short vid) { - struct batadv_meshif_vlan *vlan_tmp, *vlan = NULL; + struct batadv_meshif_vlan *vlan = NULL; + struct batadv_meshif_vlan *vlan_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(vlan_tmp, &bat_priv->meshif_vlan_list, list) { @@ -741,10 +787,10 @@ static void batadv_set_lockdep_class(struct net_device *dev) */ static int batadv_meshif_init_late(struct net_device *dev) { + size_t cnt_len = sizeof(u64) * BATADV_CNT_NUM; struct batadv_priv *bat_priv; u32 random_seqno; int ret; - size_t cnt_len = sizeof(u64) * BATADV_CNT_NUM; batadv_set_lockdep_class(dev); @@ -840,18 +886,7 @@ static int batadv_meshif_slave_add(struct net_device *dev, struct net_device *slave_dev, struct netlink_ext_ack *extack) { - struct batadv_hard_iface *hard_iface; - int ret = -EINVAL; - - hard_iface = batadv_hardif_get_by_netdev(slave_dev); - if (!hard_iface || hard_iface->mesh_iface) - goto out; - - ret = batadv_hardif_enable_interface(hard_iface, dev); - -out: - batadv_hardif_put(hard_iface); - return ret; + return batadv_hardif_enable_interface(slave_dev, dev); } /** @@ -894,6 +929,11 @@ static const struct net_device_ops batadv_netdev_ops = { .ndo_del_slave = batadv_meshif_slave_del, }; +/** + * batadv_get_drvinfo() - ethtool driver info handler for mesh interfaces + * @dev: the mesh interface (unused) + * @info: ethtool_drvinfo struct to populate + */ static void batadv_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { @@ -954,6 +994,12 @@ static const struct { #endif }; +/** + * batadv_get_strings() - ethtool string handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to retrieve + * @data: buffer to copy the requested string set into + */ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) { if (stringset == ETH_SS_STATS) @@ -961,6 +1007,12 @@ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) sizeof(batadv_counters_strings)); } +/** + * batadv_get_ethtool_stats() - ethtool stats handler for mesh interfaces + * @dev: the mesh interface to query + * @stats: ethtool_stats struct (unused) + * @data: destination array for the gathered counter values + */ static void batadv_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *stats, u64 *data) { @@ -971,6 +1023,14 @@ static void batadv_get_ethtool_stats(struct net_device *dev, data[i] = batadv_sum_counter(bat_priv, i); } +/** + * batadv_get_sset_count() - ethtool stringset size handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to query + * + * Return: number of entries in @stringset or -EOPNOTSUPP if @stringset is not + * supported + */ static int batadv_get_sset_count(struct net_device *dev, int stringset) { if (stringset == ETH_SS_STATS) diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c index 1c5315e55c04..82fd527b61c0 100644 --- a/net/batman-adv/multicast.c +++ b/net/batman-adv/multicast.c @@ -274,8 +274,9 @@ static struct batadv_mcast_mla_flags batadv_mcast_mla_flags_get(struct batadv_priv *bat_priv) { struct net_device *dev = bat_priv->mesh_iface; - struct batadv_mcast_querier_state *qr4, *qr6; struct batadv_mcast_mla_flags mla_flags; + struct batadv_mcast_querier_state *qr4; + struct batadv_mcast_querier_state *qr6; struct net_device *bridge; bridge = batadv_mcast_get_bridge(dev); @@ -521,7 +522,8 @@ batadv_mcast_mla_meshif_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct net_device *bridge = batadv_mcast_get_bridge(dev); - int ret4, ret6 = 0; + int ret6 = 0; + int ret4; if (bridge) dev = bridge; @@ -585,10 +587,11 @@ static int batadv_mcast_mla_bridge_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list); - struct br_ip_list *br_ip_entry, *tmp; u8 tvlv_flags = flags->tvlv_flags; + struct br_ip_list *br_ip_entry; struct batadv_hw_addr *new; u8 mcast_addr[ETH_ALEN]; + struct br_ip_list *tmp; int ret; /* we don't need to detect these devices/listeners, the IGMP/MLD @@ -935,8 +938,8 @@ static void __batadv_mcast_mla_update(struct batadv_priv *bat_priv) */ static void batadv_mcast_mla_update(struct work_struct *work) { - struct delayed_work *delayed_work; struct batadv_priv_mcast *priv_mcast; + struct delayed_work *delayed_work; struct batadv_priv *bat_priv; delayed_work = to_delayed_work(work); @@ -951,7 +954,10 @@ static void batadv_mcast_mla_update(struct work_struct *work) * batadv_mcast_is_report_ipv4() - check for IGMP reports * @skb: the ethernet frame destined for the mesh * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * ip_mc_check_igmp()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Checks whether the given frame is a valid IGMP report. * @@ -1017,7 +1023,10 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv, * batadv_mcast_is_report_ipv6() - check for MLD reports * @skb: the ethernet frame destined for the mesh * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * ipv6_mc_check_mld()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Checks whether the given frame is a valid MLD report. * @@ -1223,10 +1232,14 @@ enum batadv_forw_mode batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid, int *is_routable) { - int ret, tt_count, ip_count, unsnoop_count, total_count; bool is_unsnoopable = false; struct ethhdr *ethhdr; + int unsnoop_count; int rtr_count = 0; + int total_count; + int tt_count; + int ip_count; + int ret; ret = batadv_mcast_forw_mode_check(bat_priv, skb, &is_unsnoopable, is_routable); @@ -1301,13 +1314,11 @@ static int batadv_mcast_forw_tt(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid) { - int ret = NET_XMIT_SUCCESS; - struct sk_buff *newskb; - struct batadv_tt_orig_list_entry *orig_entry; - struct batadv_tt_global_entry *tt_global; const u8 *addr = eth_hdr(skb)->h_dest; + int ret = NET_XMIT_SUCCESS; + struct sk_buff *newskb; tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global) @@ -1602,8 +1613,8 @@ static void batadv_mcast_want_unsnoop_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_unsnoopables_node; struct hlist_head *head = &bat_priv->mcast.want_all_unsnoopables_list; + struct hlist_node *node = &orig->mcast_want_all_unsnoopables_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1647,8 +1658,8 @@ static void batadv_mcast_want_ipv4_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_ipv4_node; struct hlist_head *head = &bat_priv->mcast.want_all_ipv4_list; + struct hlist_node *node = &orig->mcast_want_all_ipv4_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1692,8 +1703,8 @@ static void batadv_mcast_want_ipv6_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_ipv6_node; struct hlist_head *head = &bat_priv->mcast.want_all_ipv6_list; + struct hlist_node *node = &orig->mcast_want_all_ipv6_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1737,8 +1748,8 @@ static void batadv_mcast_want_rtr4_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_rtr4_node; struct hlist_head *head = &bat_priv->mcast.want_all_rtr4_list; + struct hlist_node *node = &orig->mcast_want_all_rtr4_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1782,8 +1793,8 @@ static void batadv_mcast_want_rtr6_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_rtr6_node; struct hlist_head *head = &bat_priv->mcast.want_all_rtr6_list; + struct hlist_node *node = &orig->mcast_want_all_rtr6_node; lockdep_assert_held(&orig->mcast_handler_lock); diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index 1404a3b7adfb..bae2a8110976 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -195,8 +195,8 @@ static int batadv_mcast_forw_push_dests_list(struct batadv_priv *bat_priv, unsigned short *num_dests, unsigned short *tvlv_len) { - struct hlist_node *node; struct batadv_orig_node *orig_node; + struct hlist_node *node; rcu_read_lock(); __hlist_for_each_rcu(node, head) { @@ -232,12 +232,9 @@ batadv_mcast_forw_push_tt(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short *tvlv_len) { struct batadv_tt_orig_list_entry *orig_entry; - struct batadv_tt_global_entry *tt_global; const u8 *addr = eth_hdr(skb)->h_dest; - - /* ok */ - int ret = true; + int ret = true; /* ok */ tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global) @@ -368,7 +365,8 @@ static void batadv_mcast_forw_scrape(struct sk_buff *skb, unsigned short offset, unsigned short len) { - char *to, *from; + char *from; + char *to; SKB_LINEAR_ASSERT(skb); @@ -411,7 +409,8 @@ static bool batadv_mcast_forw_push_insert_padding(struct sk_buff *skb, unsigned short *tvlv_len) { unsigned short offset = *tvlv_len; - char *to, *from = skb->data; + char *from = skb->data; + char *to; to = batadv_mcast_forw_push_padding(skb, tvlv_len); if (!to) @@ -933,8 +932,9 @@ static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, unsigned int tvlv_len; unsigned long offset; bool xmitted = false; - u8 *dest, *next_dest; + u8 *next_dest; u16 num_dests; + u8 *dest; int ret; /* (at least) TVLV part needs to be linearized */ @@ -1080,6 +1080,11 @@ unsigned int batadv_mcast_forw_packet_hdrlen(unsigned int num_dests) * Tries to expand an skb's headroom so that its head to tail is 1298 * bytes (minimum IPv6 MTU + vlan ethernet header size) large. * + * Warning: This function may reallocate the skb data buffer via + * skb_cow()/skb_linearize()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: -EINVAL if the given skb's length is too large or -ENOMEM on memory * allocation failure. Otherwise, on success, zero is returned. */ @@ -1120,6 +1125,11 @@ static int batadv_mcast_forw_expand_head(struct batadv_priv *bat_priv, * that signaled interest in it, that is either via the translation table or the * according want-all flags, is attached accordingly. * + * Warning: This function may reallocate the skb data buffer via + * batadv_mcast_forw_expand_head()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: true on success, false otherwise. */ bool batadv_mcast_forw_push(struct batadv_priv *bat_priv, struct sk_buff *skb, diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 4cf9e3c54ad3..107647c9bada 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -52,9 +52,15 @@ struct genl_family batadv_netlink_family; -/* multicast groups */ +/** + * enum batadv_netlink_multicast_groups - batman-adv generic netlink multicast + * groups + */ enum batadv_netlink_multicast_groups { + /** @BATADV_NL_MCGRP_CONFIG: configuration change notifications */ BATADV_NL_MCGRP_CONFIG, + + /** @BATADV_NL_MCGRP_TPMETER: throughput meter result notifications */ BATADV_NL_MCGRP_TPMETER, }; @@ -746,8 +752,8 @@ static int batadv_netlink_tp_meter_cancel(struct sk_buff *skb, struct genl_info *info) { struct batadv_priv *bat_priv = info->user_ptr[0]; - u8 *dst; int ret = 0; + u8 *dst; if (!info->attrs[BATADV_ATTR_ORIG_ADDRESS]) return -EINVAL; @@ -953,10 +959,10 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb, static int batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; + struct batadv_priv *bat_priv; int skip = cb->args[0]; struct list_head *iter; int i = 0; @@ -968,7 +974,7 @@ batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) bat_priv = netdev_priv(mesh_iface); rtnl_lock(); - cb->seq = batadv_hardif_generation << 1 | 1; + cb->seq = bat_priv->hardif_generation << 1 | 1; netdev_for_each_lower_private(mesh_iface, hard_iface, iter) { if (i++ < skip) @@ -1211,7 +1217,9 @@ batadv_netlink_get_hardif_from_ifindex(struct batadv_priv *bat_priv, if (!hard_dev) return ERR_PTR(-ENODEV); + rtnl_lock(); hard_iface = batadv_hardif_get_by_netdev(hard_dev); + rtnl_unlock(); if (!hard_iface) goto err_put_harddev; @@ -1548,14 +1556,18 @@ struct genl_family batadv_netlink_family __ro_after_init = { /** * batadv_netlink_register() - register batadv genl netlink family + * + * Return: 0 on success or negative error number in case of failure */ -void __init batadv_netlink_register(void) +int __init batadv_netlink_register(void) { int ret; ret = genl_register_family(&batadv_netlink_family); if (ret) pr_warn("unable to register netlink family\n"); + + return ret; } /** diff --git a/net/batman-adv/netlink.h b/net/batman-adv/netlink.h index 4eae9e5ff135..f92d3ea7d06c 100644 --- a/net/batman-adv/netlink.h +++ b/net/batman-adv/netlink.h @@ -12,7 +12,7 @@ #include #include -void batadv_netlink_register(void); +int batadv_netlink_register(void); void batadv_netlink_unregister(void); struct net_device *batadv_netlink_get_meshif(struct netlink_callback *cb); struct batadv_hard_iface * diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 9b38bd9e8da7..f06583ef9b3f 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -53,8 +53,9 @@ struct batadv_orig_node * batadv_orig_hash_find(struct batadv_priv *bat_priv, const void *data) { struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *orig_node_tmp = NULL; + struct batadv_orig_node *orig_node; struct hlist_head *head; - struct batadv_orig_node *orig_node, *orig_node_tmp = NULL; int index; if (!hash) @@ -108,7 +109,8 @@ struct batadv_orig_node_vlan * batadv_orig_node_vlan_get(struct batadv_orig_node *orig_node, unsigned short vid) { - struct batadv_orig_node_vlan *vlan = NULL, *tmp; + struct batadv_orig_node_vlan *vlan = NULL; + struct batadv_orig_node_vlan *tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tmp, &orig_node->vlan_list, list) { @@ -282,9 +284,9 @@ void batadv_hardif_neigh_release(struct kref *ref) */ void batadv_neigh_node_release(struct kref *ref) { - struct hlist_node *node_tmp; - struct batadv_neigh_node *neigh_node; struct batadv_neigh_ifinfo *neigh_ifinfo; + struct batadv_neigh_node *neigh_node; + struct hlist_node *node_tmp; neigh_node = container_of(ref, struct batadv_neigh_node, refcount); @@ -314,8 +316,8 @@ struct batadv_neigh_node * batadv_orig_router_get(struct batadv_orig_node *orig_node, const struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *router = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; rcu_read_lock(); hlist_for_each_entry_rcu(orig_ifinfo, &orig_node->ifinfo_list, list) { @@ -373,7 +375,8 @@ struct batadv_orig_ifinfo * batadv_orig_ifinfo_get(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *tmp, *orig_ifinfo = NULL; + struct batadv_orig_ifinfo *orig_ifinfo = NULL; + struct batadv_orig_ifinfo *tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tmp, &orig_node->ifinfo_list, @@ -451,8 +454,8 @@ struct batadv_neigh_ifinfo * batadv_neigh_ifinfo_get(struct batadv_neigh_node *neigh, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_ifinfo *neigh_ifinfo = NULL, - *tmp_neigh_ifinfo; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_neigh_ifinfo *tmp_neigh_ifinfo; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_neigh_ifinfo, &neigh->ifinfo_list, @@ -530,7 +533,8 @@ batadv_neigh_node_get(const struct batadv_orig_node *orig_node, const struct batadv_hard_iface *hard_iface, const u8 *addr) { - struct batadv_neigh_node *tmp_neigh_node, *res = NULL; + struct batadv_neigh_node *tmp_neigh_node; + struct batadv_neigh_node *res = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_neigh_node, &orig_node->neigh_list, list) { @@ -634,7 +638,8 @@ struct batadv_hardif_neigh_node * batadv_hardif_neigh_get(const struct batadv_hard_iface *hard_iface, const u8 *neigh_addr) { - struct batadv_hardif_neigh_node *tmp_hardif_neigh, *hardif_neigh = NULL; + struct batadv_hardif_neigh_node *hardif_neigh = NULL; + struct batadv_hardif_neigh_node *tmp_hardif_neigh; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_hardif_neigh, @@ -668,8 +673,8 @@ batadv_neigh_node_create(struct batadv_orig_node *orig_node, struct batadv_hard_iface *hard_iface, const u8 *neigh_addr) { - struct batadv_neigh_node *neigh_node; struct batadv_hardif_neigh_node *hardif_neigh = NULL; + struct batadv_neigh_node *neigh_node; spin_lock_bh(&orig_node->neigh_list_lock); @@ -753,7 +758,8 @@ batadv_neigh_node_get_or_create(struct batadv_orig_node *orig_node, */ int batadv_hardif_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct batadv_hard_iface *primary_if, *hard_iface; + struct batadv_hard_iface *primary_if; + struct batadv_hard_iface *hard_iface; struct net_device *mesh_iface; struct batadv_priv *bat_priv; int ret; @@ -850,12 +856,12 @@ static void batadv_orig_node_free_rcu(struct rcu_head *rcu) */ void batadv_orig_node_release(struct kref *ref) { - struct hlist_node *node_tmp; + struct batadv_orig_ifinfo *last_candidate; + struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *neigh_node; struct batadv_orig_node *orig_node; - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_orig_node_vlan *vlan; - struct batadv_orig_ifinfo *last_candidate; + struct hlist_node *node_tmp; orig_node = container_of(ref, struct batadv_orig_node, refcount); @@ -898,11 +904,11 @@ void batadv_orig_node_release(struct kref *ref) */ void batadv_originator_free(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* spinlock to protect write access */ struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *orig_node; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* spinlock to protect write access */ - struct batadv_orig_node *orig_node; u32 i; if (!hash) @@ -1033,7 +1039,6 @@ batadv_purge_neigh_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1077,7 +1082,6 @@ batadv_purge_orig_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1111,11 +1115,11 @@ static bool batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct hlist_node *node_tmp; + struct batadv_hard_iface *if_incoming; struct batadv_neigh_node *neigh_node; + struct hlist_node *node_tmp; bool neigh_purged = false; unsigned long last_seen; - struct batadv_hard_iface *if_incoming; spin_lock_bh(&orig_node->neigh_list_lock); @@ -1127,10 +1131,8 @@ batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, if (batadv_has_timed_out(last_seen, BATADV_PURGE_TIMEOUT) || if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) { if (if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "neighbor purge: originator %pM, neighbor: %pM, iface: %s\n", @@ -1171,8 +1173,9 @@ batadv_find_best_neighbor(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_node *best = NULL, *neigh; struct batadv_algo_ops *bao = bat_priv->algo_ops; + struct batadv_neigh_node *best = NULL; + struct batadv_neigh_node *neigh; rcu_read_lock(); hlist_for_each_entry_rcu(neigh, &orig_node->neigh_list, list) { @@ -1207,8 +1210,9 @@ static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, { struct batadv_neigh_node *best_neigh_node; struct batadv_hard_iface *hard_iface; - bool changed_ifinfo, changed_neigh; struct list_head *iter; + bool changed_ifinfo; + bool changed_neigh; if (batadv_has_timed_out(orig_node->last_seen, 2 * BATADV_PURGE_TIMEOUT)) { @@ -1260,11 +1264,11 @@ static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, */ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* spinlock to protect write access */ struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *orig_node; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* spinlock to protect write access */ - struct batadv_orig_node *orig_node; u32 i; if (!hash) @@ -1299,6 +1303,13 @@ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) batadv_gw_election(bat_priv); } +/** + * batadv_purge_orig() - periodic worker to purge stale originator entries + * @work: delayed work embedded in the bat_priv + * + * Invoke batadv_purge_orig_ref() to drop stale originators and reschedule the + * next run after BATADV_ORIG_WORK_PERIOD milliseconds. + */ static void batadv_purge_orig(struct work_struct *work) { struct delayed_work *delayed_work; @@ -1322,7 +1333,8 @@ static void batadv_purge_orig(struct work_struct *work) */ int batadv_orig_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct batadv_hard_iface *primary_if, *hard_iface; + struct batadv_hard_iface *primary_if; + struct batadv_hard_iface *hard_iface; struct net_device *mesh_iface; struct batadv_priv *bat_priv; int ret; diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index bbd40fe3a8e5..6442f5d0cc93 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -172,6 +172,11 @@ bool batadv_window_protected(struct batadv_priv *bat_priv, s32 seq_num_diff, * @hard_iface: incoming hard interface * @header_len: minimal header length of packet type * + * Warning: This function may reallocate the skb data buffer via + * skb_cow()/skb_linearize()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: true when management preconditions are met, false otherwise */ bool batadv_check_management_packet(struct sk_buff *skb, @@ -263,8 +268,7 @@ static bool batadv_skb_decrement_ttl(struct sk_buff *skb) * @bat_priv: the bat priv with all the mesh interface information * @skb: icmp packet to process * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, struct sk_buff *skb) @@ -272,7 +276,8 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_header *icmph; - int res, ret = NET_RX_DROP; + int ret = NET_RX_DROP; + int res; icmph = (struct batadv_icmp_header *)skb->data; @@ -328,13 +333,24 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, return ret; } +/** + * batadv_recv_icmp_ttl_exceeded() - handle an ICMP packet that hit TTL 0 + * @bat_priv: the bat priv with all the mesh interface information + * @skb: ICMP packet whose TTL has expired + * + * For traceroute-style ICMP echo requests, send a TTL exceeded reply back to + * the source. Other ICMP types are simply dropped. + * + * Return: NET_RX_SUCCESS if the reply was queued, NET_RX_DROP otherwise + */ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct sk_buff *skb) { struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_packet *icmp_packet; - int res, ret = NET_RX_DROP; + int ret = NET_RX_DROP; + int res; icmp_packet = (struct batadv_icmp_packet *)skb->data; @@ -366,8 +382,8 @@ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, icmp_packet->ttl = BATADV_TTL; res = batadv_send_skb_to_orig(skb, orig_node, NULL); - if (res == NET_RX_SUCCESS) - ret = NET_XMIT_SUCCESS; + if (res == NET_XMIT_SUCCESS) + ret = NET_RX_SUCCESS; /* skb was consumed */ skb = NULL; @@ -392,12 +408,13 @@ int batadv_recv_icmp_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_icmp_header *icmph; - struct batadv_icmp_packet_rr *icmp_packet_rr; - struct ethhdr *ethhdr; - struct batadv_orig_node *orig_node = NULL; int hdr_size = sizeof(struct batadv_icmp_header); - int res, ret = NET_RX_DROP; + struct batadv_icmp_packet_rr *icmp_packet_rr; + struct batadv_orig_node *orig_node = NULL; + struct batadv_icmp_header *icmph; + struct ethhdr *ethhdr; + int ret = NET_RX_DROP; + int res; /* drop packet if it has not necessary minimum size */ if (unlikely(!pskb_may_pull(skb, hdr_size))) @@ -576,15 +593,17 @@ batadv_find_router(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_hard_iface *recv_if) { - struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_neigh_node *first_candidate_router = NULL; struct batadv_neigh_node *next_candidate_router = NULL; - struct batadv_neigh_node *router, *cand_router = NULL; struct batadv_neigh_node *last_cand_router = NULL; - struct batadv_orig_ifinfo *cand, *first_candidate = NULL; + struct batadv_orig_ifinfo *first_candidate = NULL; + struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_orig_ifinfo *next_candidate = NULL; + struct batadv_neigh_node *cand_router = NULL; struct batadv_orig_ifinfo *last_candidate; bool last_candidate_found = false; + struct batadv_neigh_node *router; + struct batadv_orig_ifinfo *cand; if (!orig_node) return NULL; @@ -706,15 +725,29 @@ batadv_find_router(struct batadv_priv *bat_priv, return router; } +/** + * batadv_route_unicast_packet() - forward a unicast packet towards its + * destination originator + * @skb: the received unicast packet + * @recv_if: interface on which the packet was received + * + * Decrement the TTL, look up the originator for the destination address and + * hand the packet over to batadv_send_skb_to_orig() for transmission. Drop + * the packet when the TTL is exhausted or no route exists. + * + * Return: NET_RX_SUCCESS if the packet was forwarded, NET_RX_DROP otherwise + */ static int batadv_route_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_orig_node *orig_node = NULL; struct batadv_unicast_packet *unicast_packet; + struct batadv_orig_node *orig_node = NULL; struct ethhdr *ethhdr = eth_hdr(skb); - int res, hdr_len, ret = NET_RX_DROP; + int ret = NET_RX_DROP; unsigned int len; + int hdr_len; + int res; unicast_packet = (struct batadv_unicast_packet *)skb->data; @@ -797,10 +830,10 @@ batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, struct batadv_unicast_packet *unicast_packet, u8 *dst_addr, unsigned short vid) { - struct batadv_orig_node *orig_node = NULL; struct batadv_hard_iface *primary_if = NULL; - bool ret = false; + struct batadv_orig_node *orig_node = NULL; const u8 *orig_addr; + bool ret = false; u8 orig_ttvn; if (batadv_is_my_client(bat_priv, dst_addr, vid)) { @@ -836,16 +869,31 @@ batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, return ret; } +/** + * batadv_check_unicast_ttvn() - check and adjust the TTVN of a unicast packet + * @bat_priv: the bat priv with all the mesh interface information + * @skb: the unicast packet to check + * @hdr_len: length of the unicast header preceding the payload + * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_get_vid()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * + * Return: true if the packet may be processed further, false if has to be + * dropped by the caller + */ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_len) { struct batadv_unicast_packet *unicast_packet; struct batadv_hard_iface *primary_if; struct batadv_orig_node *orig_node; - u8 curr_ttvn, old_ttvn; struct ethhdr *ethhdr; unsigned short vid; int is_old_ttvn; + u8 curr_ttvn; + u8 old_ttvn; /* check if there is enough data before accessing it */ if (!pskb_may_pull(skb, hdr_len + ETH_HLEN)) @@ -955,15 +1003,15 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, * @skb: unicast tvlv packet to process * @recv_if: pointer to interface this packet was received on * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_unhandled_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { - struct batadv_unicast_packet *unicast_packet; struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - int check, hdr_size = sizeof(*unicast_packet); + struct batadv_unicast_packet *unicast_packet; + int hdr_size = sizeof(*unicast_packet); + int check; check = batadv_check_unicast_packet(bat_priv, skb, hdr_size); if (check < 0) @@ -992,14 +1040,18 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_unicast_packet *unicast_packet; struct batadv_unicast_4addr_packet *unicast_4addr_packet; - u8 *orig_addr, *orig_addr_gw; - struct batadv_orig_node *orig_node = NULL, *orig_node_gw = NULL; - int check, hdr_size = sizeof(*unicast_packet); + struct batadv_unicast_packet *unicast_packet; + struct batadv_orig_node *orig_node_gw = NULL; + struct batadv_orig_node *orig_node = NULL; + int hdr_size = sizeof(*unicast_packet); enum batadv_subtype subtype; int ret = NET_RX_DROP; - bool is4addr, is_gw; + u8 *orig_addr_gw; + u8 *orig_addr; + bool is4addr; + bool is_gw; + int check; unicast_packet = (struct batadv_unicast_packet *)skb->data; is4addr = unicast_packet->packet_type == BATADV_UNICAST_4ADDR; @@ -1089,18 +1141,17 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, * @skb: unicast tvlv packet to process * @recv_if: pointer to interface this packet was received on * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_unicast_tvlv(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_unicast_tvlv_packet *unicast_tvlv_packet; - unsigned char *tvlv_buff; - u16 tvlv_buff_len; int hdr_size = sizeof(*unicast_tvlv_packet); + unsigned char *tvlv_buff; int ret = NET_RX_DROP; + u16 tvlv_buff_len; if (batadv_check_unicast_packet(bat_priv, skb, hdr_size) < 0) goto free_skb; @@ -1146,7 +1197,7 @@ int batadv_recv_unicast_tvlv(struct sk_buff *skb, * the assembled packet will exceed our MTU; 2) Buffer fragment, if we still * lack further fragments; 3) Merge fragments, if we have all needed parts. * - * Return: NET_RX_DROP if the skb is not consumed, NET_RX_SUCCESS otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_frag_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) @@ -1215,8 +1266,8 @@ int batadv_recv_bcast_packet(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_orig_node *orig_node = NULL; struct batadv_bcast_packet *bcast_packet; - struct ethhdr *ethhdr; int hdr_size = sizeof(*bcast_packet); + struct ethhdr *ethhdr; s32 seq_diff; u32 seqno; int ret; @@ -1334,7 +1385,9 @@ int batadv_recv_bcast_packet(struct sk_buff *skb, * contents of its TVLV forwards it and/or decapsulates it to hand it to the * mesh interface. * - * Return: NET_RX_DROP if the skb is not consumed, NET_RX_SUCCESS otherwise. + * Return: NET_RX_SUCCESS if the skb was locally received, NET_RX_DROP otherwise + * or a negative errno code when the multicast tracker TVLV could not be + * processed */ int batadv_recv_mcast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c index 7f449338a490..929b6dd34c10 100644 --- a/net/batman-adv/send.c +++ b/net/batman-adv/send.c @@ -271,8 +271,8 @@ bool batadv_send_skb_prepare_unicast_4addr(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, int packet_subtype) { - struct batadv_hard_iface *primary_if; struct batadv_unicast_4addr_packet *uc_4addr_packet; + struct batadv_hard_iface *primary_if; bool ret = false; primary_if = batadv_primary_if_get_selected(bat_priv); @@ -322,8 +322,9 @@ int batadv_send_skb_unicast(struct batadv_priv *bat_priv, unsigned short vid) { struct batadv_unicast_packet *unicast_packet; - struct ethhdr *ethhdr; int ret = NET_XMIT_DROP; + struct ethhdr *ethhdr; + int res; if (!orig_node) goto out; @@ -360,7 +361,10 @@ int batadv_send_skb_unicast(struct batadv_priv *bat_priv, if (batadv_tt_global_client_is_roaming(bat_priv, ethhdr->h_dest, vid)) unicast_packet->ttvn = unicast_packet->ttvn - 1; - ret = batadv_send_skb_to_orig(skb, orig_node, NULL); + res = batadv_send_skb_to_orig(skb, orig_node, NULL); + if (res == NET_XMIT_SUCCESS) + ret = NET_XMIT_SUCCESS; + /* skb was consumed */ skb = NULL; @@ -394,7 +398,8 @@ int batadv_send_skb_via_tt_generic(struct batadv_priv *bat_priv, { struct ethhdr *ethhdr = (struct ethhdr *)skb->data; struct batadv_orig_node *orig_node; - u8 *src, *dst; + u8 *src; + u8 *dst; int ret; src = ethhdr->h_source; diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index c2eea7dbc488..022eb13e8ec4 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -221,9 +221,9 @@ static void batadv_tp_batctl_notify(enum batadv_tp_meter_reason reason, unsigned long start_time, u64 total_sent, u32 cookie) { + u32 total_bytes; u32 test_time; u8 result; - u32 total_bytes; if (!batadv_tp_is_error(reason)) { result = BATADV_TP_REASON_COMPLETE; @@ -267,7 +267,8 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason, static struct batadv_tp_sender * batadv_tp_list_find_sender(struct batadv_priv *bat_priv, const u8 *dst) { - struct batadv_tp_sender *pos, *tp_vars = NULL; + struct batadv_tp_sender *tp_vars = NULL; + struct batadv_tp_sender *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -332,7 +333,8 @@ static struct batadv_tp_sender * batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_sender *pos, *tp_vars = NULL; + struct batadv_tp_sender *tp_vars = NULL; + struct batadv_tp_sender *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -358,28 +360,16 @@ batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, } /** - * batadv_tp_vars_common_release() - release batadv_tp_vars_common from lists + * batadv_tp_sender_release() - release batadv_tp_sender * and queue for free after rcu grace period - * @ref: kref pointer of the batadv_tp_vars_common + * @ref: kref pointer of the batadv_tp_sender */ -static void batadv_tp_vars_common_release(struct kref *ref) +static void batadv_tp_sender_release(struct kref *ref) { - struct batadv_tp_vars_common *tp_vars; - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_sender *tp_vars; - tp_vars = container_of(ref, struct batadv_tp_vars_common, refcount); - - /* lock should not be needed because this object is now out of any - * context! - */ - spin_lock_bh(&tp_vars->unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { - list_del(&un->list); - kfree(un); - } - spin_unlock_bh(&tp_vars->unacked_lock); - - kfree_rcu(tp_vars, rcu); + tp_vars = container_of(ref, struct batadv_tp_sender, common.refcount); + kfree_rcu(tp_vars, common.rcu); } /** @@ -392,7 +382,7 @@ static void batadv_tp_sender_put(struct batadv_tp_sender *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_sender_release); } /** @@ -590,8 +580,8 @@ static void batadv_tp_sender_timeout(struct timer_list *t) static void batadv_tp_fill_prerandom(struct batadv_tp_sender *tp_vars, u8 *buf, size_t nbytes) { - u32 local_offset; size_t bytes_inbuf; + u32 local_offset; size_t to_copy; size_t pos = 0; @@ -637,9 +627,9 @@ static int batadv_tp_send_msg(struct batadv_tp_sender *tp_vars, const u8 *src, { struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; - int r; - u8 *data; size_t data_len; + u8 *data; + int r; skb = netdev_alloc_skb_ip_align(NULL, len + ETH_HLEN); if (unlikely(!skb)) @@ -876,7 +866,8 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv, static bool batadv_tp_avail(struct batadv_tp_sender *tp_vars, size_t payload_len) { - u32 win_left, win_limit; + u32 win_limit; + u32 win_left; spin_lock_bh(&tp_vars->cc_lock); @@ -922,14 +913,16 @@ static int batadv_tp_wait_available(struct batadv_tp_sender *tp_vars, size_t ple */ static int batadv_tp_send(void *arg) { - struct batadv_tp_sender *tp_vars = arg; - struct batadv_priv *bat_priv = tp_vars->common.bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; - size_t payload_len, packet_len; + struct batadv_tp_sender *tp_vars = arg; + struct batadv_priv *bat_priv; + size_t payload_len; + size_t packet_len; u32 last_sent; int err = 0; + bat_priv = tp_vars->common.bat_priv; orig_node = batadv_orig_hash_find(bat_priv, tp_vars->common.other_end); if (unlikely(!orig_node)) { err = BATADV_TP_REASON_DST_UNREACHABLE; @@ -1017,8 +1010,8 @@ static int batadv_tp_send(void *arg) */ static void batadv_tp_start_kthread(struct batadv_tp_sender *tp_vars) { - struct task_struct *kthread; struct batadv_priv *bat_priv = tp_vars->common.bat_priv; + struct task_struct *kthread; u32 session_cookie; kref_get(&tp_vars->common.refcount); @@ -1054,9 +1047,9 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, u32 test_length, u32 *cookie) { struct batadv_tp_sender *tp_vars; + u32 session_cookie; u8 session_id[2]; u8 icmp_uid; - u32 session_cookie; get_random_bytes(session_id, sizeof(session_id)); get_random_bytes(&icmp_uid, 1); @@ -1145,9 +1138,6 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, init_waitqueue_head(&tp_vars->more_bytes); init_completion(&tp_vars->finished); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - spin_lock_init(&tp_vars->cc_lock); tp_vars->prerandom_offset = 0; @@ -1226,7 +1216,8 @@ static struct batadv_tp_receiver * batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_receiver *pos, *tp_vars = NULL; + struct batadv_tp_receiver *tp_vars = NULL; + struct batadv_tp_receiver *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_receiver_list, common.list) { @@ -1251,6 +1242,33 @@ batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst return tp_vars; } +/** + * batadv_tp_receiver_release() - release batadv_tp_receiver + * and queue for free after rcu grace period + * @ref: kref pointer of the batadv_tp_receiver + */ +static void batadv_tp_receiver_release(struct kref *ref) +{ + struct batadv_tp_receiver *tp_vars; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; + + tp_vars = container_of(ref, struct batadv_tp_receiver, common.refcount); + + /* lock should not be needed because this object is now out of any + * context! + */ + spin_lock_bh(&tp_vars->ack_seqno_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; + } + spin_unlock_bh(&tp_vars->ack_seqno_lock); + + kfree_rcu(tp_vars, common.rcu); +} + /** * batadv_tp_receiver_put() - decrement the batadv_tp_receiver * refcounter and possibly release it @@ -1261,7 +1279,7 @@ static void batadv_tp_receiver_put(struct batadv_tp_receiver *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_receiver_release); } /** @@ -1284,7 +1302,8 @@ static void batadv_tp_reset_receiver_timer(struct batadv_tp_receiver *tp_vars) static void batadv_tp_receiver_shutdown(struct timer_list *t) { struct batadv_tp_receiver *tp_vars = timer_container_of(tp_vars, t, common.timer); - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; struct batadv_priv *bat_priv; bat_priv = tp_vars->common.bat_priv; @@ -1304,13 +1323,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->common.unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + spin_lock_bh(&tp_vars->ack_seqno_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1339,7 +1358,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, struct batadv_orig_node *orig_node; struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; - int r, ret; + int ret; + int r; orig_node = batadv_orig_hash_find(bat_priv, dst); if (unlikely(!orig_node)) { @@ -1403,69 +1423,106 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *new; - bool added = false; + struct list_head *pos = &tp_vars->unacked_list; + struct batadv_tp_unacked *new = NULL; + u32 end_seqno = seqno + payload_len; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; - new = kmalloc_obj(*new, GFP_ATOMIC); - if (unlikely(!new)) - return false; - - new->seqno = seqno; - new->len = payload_len; - - /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->common.unacked_list)) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; - return true; - } - - /* otherwise loop over the list and either drop the packet because this - * is a duplicate or store it at the right position. + /* loop over the list to find either an existing entry which the new + * seqno range can be merged with or the position at which a new entry + * has to be inserted. * * The iteration is done in the reverse way because it is likely that * the last received packet (the one being processed now) has a bigger * seqno than all the others already stored. */ - list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { - /* check for duplicates */ - if (new->seqno == un->seqno) { - if (new->len > un->len) - un->len = new->len; - kfree(new); - added = true; - break; - } - - /* look for the right position */ - if (batadv_seq_before(new->seqno, un->seqno)) + list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { + /* look for the right position - an un which is smaller */ + if (batadv_seq_before(seqno, un->seqno)) continue; - /* as soon as an entry having a bigger seqno is found, the new - * one is attached _after_ it. In this way the list is kept in - * ascending order + /* smaller/equal seqno was found but they might be directly + * after another or overlapping. keep only a single entry + * + * It is already known that: + * + * un->seqno <= seqno + * + * When establishing that: + * + * seqno <= un->seqno + un->len + * + * Then it is not necessary to add a new entry because the + * smaller/equal seqno of un might already contain the new + * received packet or we only add new data directly after + * the end of un. The latter can be identified using: + * + * un->seqno + un->len <= end_seqno */ - list_add(&new->list, &un->list); - added = true; - tp_vars->common.unacked_count++; + if (!batadv_seq_before(un->seqno + un->len, seqno)) { + /* new data directly after un? */ + if (!batadv_seq_before(end_seqno, un->seqno + un->len)) + un->len = end_seqno - un->seqno; + + /* un now represents both old un + new range and has to + * be used to check if the gap to the next seqno range + * was closed + */ + new = un; + } else { + /* as soon as an entry having a smaller seqno is found, + * the new one is attached _after_ it. In this way the + * list is kept in ascending order + */ + pos = &un->list; + } + break; } - /* received packet with smallest seqno out of order; add it to front */ - if (!added) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + /* no entry to merge with was found; insert a new one after the entry + * with the next smaller seqno (or at the front of the list when the + * new seqno is the smallest or the list is empty) + */ + if (!new) { + new = kmalloc_obj(*new, GFP_ATOMIC); + if (unlikely(!new)) + return false; + + new->seqno = seqno; + new->len = payload_len; + + list_add(&new->list, pos); + tp_vars->unacked_count++; + } + + /* check if new filled the gap to the next list entries */ + un = new; + list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { + if (batadv_seq_before(end_seqno, un->seqno)) + break; + + /* next entry is overlapping or adjacent - combine both */ + if (batadv_seq_before(end_seqno, un->seqno + un->len)) { + end_seqno = un->seqno + un->len; + new->len = end_seqno - new->seqno; + } + + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; } /* remove the last (biggest) unacked seqno when list is too large */ - if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { - un = list_last_entry(&tp_vars->common.unacked_list, + if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) { + un = list_last_entry(&tp_vars->unacked_list, struct batadv_tp_unacked, list); list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } return true; @@ -1477,15 +1534,16 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; u32 to_ack; /* go through the unacked packet list and possibly ACK them as * well */ - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { /* the list is ordered, therefore it is possible to stop as soon * there is a gap between the last acked seqno and the seqno of * the packet under inspection @@ -1493,14 +1551,14 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) if (batadv_seq_before(tp_vars->last_recv, un->seqno)) break; - to_ack = un->seqno + un->len - tp_vars->last_recv; + to_ack = un->seqno + un->len; - if (batadv_seq_before(tp_vars->last_recv, un->seqno + un->len)) - tp_vars->last_recv += to_ack; + if (batadv_seq_before(tp_vars->last_recv, to_ack)) + tp_vars->last_recv = to_ack; list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } } @@ -1547,9 +1605,9 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - tp_vars->common.unacked_count = 0; + spin_lock_init(&tp_vars->ack_seqno_lock); + INIT_LIST_HEAD(&tp_vars->unacked_list); + tp_vars->unacked_count = 0; kref_get(&tp_vars->common.refcount); timer_setup(&tp_vars->common.timer, batadv_tp_receiver_shutdown, 0); @@ -1609,7 +1667,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->common.unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1625,7 +1683,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); goto out; } @@ -1641,7 +1699,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index dae5e1d8c038..68f79853032e 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -128,8 +128,10 @@ static struct batadv_tt_common_entry * batadv_tt_hash_find(struct batadv_hashtable *hash, const u8 *addr, unsigned short vid) { + struct batadv_tt_common_entry *tt_tmp = NULL; + struct batadv_tt_common_entry to_search; + struct batadv_tt_common_entry *tt; struct hlist_head *head; - struct batadv_tt_common_entry to_search, *tt, *tt_tmp = NULL; u32 index; if (!hash) @@ -173,8 +175,8 @@ static struct batadv_tt_local_entry * batadv_tt_local_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_local_entry *tt_local_entry = NULL; + struct batadv_tt_common_entry *tt_common_entry; tt_common_entry = batadv_tt_hash_find(bat_priv->tt.local_hash, addr, vid); @@ -198,8 +200,8 @@ struct batadv_tt_global_entry * batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_global_entry *tt_global_entry = NULL; + struct batadv_tt_common_entry *tt_common_entry; tt_common_entry = batadv_tt_hash_find(bat_priv->tt.global_hash, addr, vid); @@ -421,10 +423,13 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, u8 event_flags) { - struct batadv_tt_change_node *tt_change_node, *entry, *safe; struct batadv_tt_common_entry *common = &tt_local_entry->common; + struct batadv_tt_change_node *tt_change_node; u8 flags = common->flags | event_flags; - bool del_op_requested, del_op_entry; + struct batadv_tt_change_node *entry; + struct batadv_tt_change_node *safe; + bool del_op_requested; + bool del_op_entry; size_t changes; tt_change_node = kmem_cache_alloc(batadv_tt_change_cache, GFP_ATOMIC); @@ -514,9 +519,9 @@ static u16 batadv_tt_entries(u16 tt_len) */ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) { - u16 num_vlan = 0; - u16 tt_local_entries = 0; struct batadv_meshif_vlan *vlan; + u16 tt_local_entries = 0; + u16 num_vlan = 0; int hdr_size; rcu_read_lock(); @@ -535,6 +540,12 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) return hdr_size + batadv_tt_len(tt_local_entries); } +/** + * batadv_tt_local_init() - allocate and initialise the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_local_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.local_hash) @@ -551,6 +562,15 @@ static int batadv_tt_local_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_global_free() - drop a global translation table entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global: the global TT entry to remove + * @message: debug message explaining why the entry is being removed + * + * Remove @tt_global from the global TT hash and drop the reference held by + * the hash. + */ static void batadv_tt_global_free(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global, const char *message) @@ -594,18 +614,20 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, unsigned short vid, int ifindex, u32 mark) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); - struct batadv_tt_local_entry *tt_local; struct batadv_tt_global_entry *tt_global = NULL; - struct net *net = dev_net(mesh_iface); - struct batadv_meshif_vlan *vlan; - struct net_device *in_dev = NULL; - struct hlist_head *head; struct batadv_tt_orig_list_entry *orig_entry; - int hash_added, table_size, packet_size_max; - bool ret = false; + struct batadv_tt_local_entry *tt_local; + struct net *net = dev_net(mesh_iface); + struct net_device *in_dev = NULL; + struct batadv_meshif_vlan *vlan; bool roamed_back = false; bool iif_is_wifi = false; + struct hlist_head *head; + int packet_size_max; + bool ret = false; u8 remote_flags; + int hash_added; + int table_size; u32 match_mark; if (ifindex != BATADV_NULL_IFINDEX) @@ -987,14 +1009,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv, */ static void batadv_tt_tvlv_container_update(struct batadv_priv *bat_priv) { - struct batadv_tt_change_node *entry, *safe; - struct batadv_tvlv_tt_data *tt_data; struct batadv_tvlv_tt_change *tt_change; - int tt_diff_len, tt_change_len = 0; - int tt_diff_entries_num = 0; + struct batadv_tt_change_node *entry; + struct batadv_tvlv_tt_data *tt_data; + struct batadv_tt_change_node *safe; int tt_diff_entries_count = 0; + int tt_diff_entries_num = 0; bool drop_changes = false; size_t tt_extra_len = 0; + int tt_change_len = 0; + int tt_diff_len; u16 tvlv_len; tt_diff_entries_num = READ_ONCE(bat_priv->tt.local_changes); @@ -1087,10 +1111,10 @@ batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_priv *bat_priv, struct batadv_tt_common_entry *common) { - void *hdr; - struct batadv_meshif_vlan *vlan; struct batadv_tt_local_entry *local; + struct batadv_meshif_vlan *vlan; unsigned int last_seen_msecs; + void *hdr; u32 crc; local = container_of(common, struct batadv_tt_local_entry, common); @@ -1181,14 +1205,14 @@ batadv_tt_local_dump_bucket(struct sk_buff *msg, u32 portid, */ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + int portid = NETLINK_CB(cb->skb).portid; + struct net_device *mesh_iface; struct batadv_hashtable *hash; - int ret; + struct batadv_priv *bat_priv; int bucket = cb->args[0]; int idx = cb->args[1]; - int portid = NETLINK_CB(cb->skb).portid; + int ret; mesh_iface = batadv_netlink_get_meshif(cb); if (IS_ERR(mesh_iface)) @@ -1224,6 +1248,17 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) return ret; } +/** + * batadv_tt_local_set_pending() - mark a local TT entry as pending removal + * @bat_priv: the bat priv with all the mesh interface information + * @tt_local_entry: local TT entry to mark + * @flags: TT change flags to announce together with the pending removal + * @message: debug message describing the reason for the change + * + * Schedule the TT change announcement and set BATADV_TT_CLIENT_PENDING on the + * entry. The entry is kept in the local table until the next TTVN increment + * so that a consistency-check response can still be answered. + */ static void batadv_tt_local_set_pending(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, @@ -1259,8 +1294,9 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, { struct batadv_tt_local_entry *tt_removed_entry; struct batadv_tt_local_entry *tt_local_entry; - u16 flags, curr_flags = BATADV_NO_FLAGS; struct hlist_node *tt_removed_node; + u16 curr_flags = BATADV_NO_FLAGS; + u16 flags; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) @@ -1319,8 +1355,8 @@ static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv, struct hlist_head *head, int timeout) { - struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_local_entry *tt_local_entry; struct hlist_node *node_tmp; hlist_for_each_entry_safe(tt_common_entry, node_tmp, head, @@ -1352,9 +1388,9 @@ static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv, static void batadv_tt_local_purge(struct batadv_priv *bat_priv, int timeout) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ u32 i; for (i = 0; i < hash->size; i++) { @@ -1367,12 +1403,19 @@ static void batadv_tt_local_purge(struct batadv_priv *bat_priv, } } +/** + * batadv_tt_local_table_free() - release the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the local TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash; spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_local_entry *tt_local; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; u32 i; @@ -1404,6 +1447,13 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) bat_priv->tt.local_hash = NULL; } +/** + * batadv_tt_global_init() - allocate and initialise the global translation + * table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_global_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.global_hash) @@ -1420,9 +1470,16 @@ static int batadv_tt_global_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_changes_list_free() - drop all pending local TT changes + * @bat_priv: the bat priv with all the mesh interface information + * + * Discard every queued local TT change and reset the pending change counter. + */ static void batadv_tt_changes_list_free(struct batadv_priv *bat_priv) { - struct batadv_tt_change_node *entry, *safe; + struct batadv_tt_change_node *entry; + struct batadv_tt_change_node *safe; spin_lock_bh(&bat_priv->tt.changes_list_lock); @@ -1451,7 +1508,8 @@ static struct batadv_tt_orig_list_entry * batadv_tt_global_orig_entry_find(const struct batadv_tt_global_entry *entry, const struct batadv_orig_node *orig_node) { - struct batadv_tt_orig_list_entry *tmp_orig_entry, *orig_entry = NULL; + struct batadv_tt_orig_list_entry *orig_entry = NULL; + struct batadv_tt_orig_list_entry *tmp_orig_entry; const struct hlist_head *head; rcu_read_lock(); @@ -1604,10 +1662,10 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, { struct batadv_tt_global_entry *tt_global_entry; struct batadv_tt_local_entry *tt_local_entry; - bool ret = false; - int hash_added; struct batadv_tt_common_entry *common; + bool ret = false; u16 local_flags; + int hash_added; /* ignore global entries from backbone nodes */ if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig, vid)) @@ -1763,10 +1821,12 @@ static struct batadv_tt_orig_list_entry * batadv_transtable_best_orig(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global_entry) { - struct batadv_neigh_node *router, *best_router = NULL; + struct batadv_tt_orig_list_entry *best_entry = NULL; struct batadv_algo_ops *bao = bat_priv->algo_ops; + struct batadv_neigh_node *best_router = NULL; + struct batadv_tt_orig_list_entry *orig_entry; + struct batadv_neigh_node *router; struct hlist_head *head; - struct batadv_tt_orig_list_entry *orig_entry, *best_entry = NULL; head = &tt_global_entry->orig_list; hlist_for_each_entry_rcu(orig_entry, head, list) { @@ -1812,9 +1872,9 @@ batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, bool best) { u16 flags = (common->flags & (~BATADV_TT_SYNC_MASK)) | orig->flags; - void *hdr; struct batadv_orig_node_vlan *vlan; u8 last_ttvn; + void *hdr; u32 crc; vlan = batadv_orig_node_vlan_get(orig->orig_node, @@ -1873,7 +1933,8 @@ batadv_tt_global_dump_entry(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_priv *bat_priv, struct batadv_tt_common_entry *common, int *sub_s) { - struct batadv_tt_orig_list_entry *orig_entry, *best_entry; + struct batadv_tt_orig_list_entry *orig_entry; + struct batadv_tt_orig_list_entry *best_entry; struct batadv_tt_global_entry *global; struct hlist_head *head; int sub = 0; @@ -1948,16 +2009,16 @@ batadv_tt_global_dump_bucket(struct sk_buff *msg, u32 portid, u32 seq, */ int batadv_tt_global_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + int portid = NETLINK_CB(cb->skb).portid; + struct net_device *mesh_iface; struct batadv_hashtable *hash; - struct hlist_head *head; - int ret; + struct batadv_priv *bat_priv; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; + int ret; mesh_iface = batadv_netlink_get_meshif(cb); if (IS_ERR(mesh_iface)) @@ -2024,13 +2085,17 @@ _batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry, batadv_tt_orig_list_entry_put(orig_entry); } -/* deletes the orig list of a tt_global_entry */ +/** + * batadv_tt_global_del_orig_list() - drop every orig_list_entry of a global + * TT entry + * @tt_global_entry: the global TT entry to clear + */ static void batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry) { + struct batadv_tt_orig_list_entry *orig_entry; struct hlist_head *head; struct hlist_node *safe; - struct batadv_tt_orig_list_entry *orig_entry; spin_lock_bh(&tt_global_entry->list_lock); head = &tt_global_entry->orig_list; @@ -2055,9 +2120,9 @@ batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const char *message) { + struct batadv_tt_orig_list_entry *orig_entry; struct hlist_head *head; struct hlist_node *safe; - struct batadv_tt_orig_list_entry *orig_entry; unsigned short vid; spin_lock_bh(&tt_global_entry->list_lock); @@ -2077,9 +2142,17 @@ batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv, spin_unlock_bh(&tt_global_entry->list_lock); } -/* If the client is to be deleted, we check if it is the last origantor entry - * within tt_global entry. If yes, we set the BATADV_TT_CLIENT_ROAM flag and the - * timer, otherwise we simply remove the originator scheduled for deletion. +/** + * batadv_tt_global_del_roaming() - remove a roaming client from a global TT + * entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global_entry: the global TT entry of the roaming client + * @orig_node: the originator that the client has roamed away from + * @message: debug message describing the reason for the change + * + * If @orig_node was the last announced source for the client, mark the entry + * for roaming so it can be cleaned up after the roaming timer expires. + * Otherwise simply remove the orig_node entry from the announcer list. */ static void batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, @@ -2087,9 +2160,9 @@ batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const char *message) { - bool last_entry = true; - struct hlist_head *head; struct batadv_tt_orig_list_entry *orig_entry; + struct hlist_head *head; + bool last_entry = true; /* no local entry exists, case 1: * Check if this is the last one or if other entries exist. @@ -2133,8 +2206,8 @@ static void batadv_tt_global_del(struct batadv_priv *bat_priv, const unsigned char *addr, unsigned short vid, const char *message, bool roaming) { - struct batadv_tt_global_entry *tt_global_entry; struct batadv_tt_local_entry *local_entry = NULL; + struct batadv_tt_global_entry *tt_global_entry; tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global_entry) @@ -2196,14 +2269,14 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, s32 match_vid, const char *message) { - struct batadv_tt_global_entry *tt_global; - struct batadv_tt_common_entry *tt_common_entry; - u32 i; + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.global_hash; + struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_global_entry *tt_global; struct hlist_node *safe; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ unsigned short vid; + u32 i; if (!hash) return; @@ -2241,12 +2314,21 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, clear_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_global_to_purge() - check whether a global TT entry has to be + * purged + * @tt_global: global TT entry under consideration + * @msg: storage for a pointer to a human readable reason on return + * + * Return: true if the entry should be purged because its roaming or temporary + * timer has elapsed; false otherwise + */ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, char **msg) { - bool purge = false; unsigned long roam_timeout = BATADV_TT_CLIENT_ROAM_TIMEOUT; unsigned long temp_timeout = BATADV_TT_CLIENT_TEMP_TIMEOUT; + bool purge = false; if ((tt_global->common.flags & BATADV_TT_CLIENT_ROAM) && batadv_has_timed_out(tt_global->roam_at, roam_timeout)) { @@ -2263,16 +2345,23 @@ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, return purge; } +/** + * batadv_tt_global_purge() - purge expired global translation table entries + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the global translation table and drop every entry that the + * roaming or temporary timer has expired for. + */ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash = bat_priv->tt.global_hash; - struct hlist_head *head; - struct hlist_node *node_tmp; spinlock_t *list_lock; /* protects write access to the hash lists */ - u32 i; - char *msg = NULL; + struct batadv_hashtable *hash = bat_priv->tt.global_hash; struct batadv_tt_common_entry *tt_common; struct batadv_tt_global_entry *tt_global; + struct hlist_node *node_tmp; + struct hlist_head *head; + char *msg = NULL; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2302,12 +2391,19 @@ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) } } +/** + * batadv_tt_global_table_free() - release the global translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the global TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash; spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_global_entry *tt_global; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; u32 i; @@ -2338,6 +2434,16 @@ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) bat_priv->tt.global_hash = NULL; } +/** + * _batadv_is_ap_isolated() - check whether two clients are AP-isolated from + * each other + * @tt_local_entry: local TT entry of the sending client + * @tt_global_entry: global TT entry of the destination client + * + * Return: true if traffic between the two clients should be dropped because + * either both are WiFi clients or both carry the ISOLATION flag; false + * otherwise + */ static bool _batadv_is_ap_isolated(struct batadv_tt_local_entry *tt_local_entry, struct batadv_tt_global_entry *tt_global_entry) @@ -2373,10 +2479,10 @@ struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_local_entry *tt_local_entry = NULL; struct batadv_tt_global_entry *tt_global_entry = NULL; - struct batadv_orig_node *orig_node = NULL; + struct batadv_tt_local_entry *tt_local_entry = NULL; struct batadv_tt_orig_list_entry *best_entry; + struct batadv_orig_node *orig_node = NULL; if (src && batadv_vlan_ap_isola_get(bat_priv, vid)) { tt_local_entry = batadv_tt_local_hash_find(bat_priv, src, vid); @@ -2445,9 +2551,11 @@ static u32 batadv_tt_global_crc(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common; struct batadv_tt_global_entry *tt_global; struct hlist_head *head; - u32 i, crc_tmp, crc = 0; - u8 flags; __be16 tmp_vid; + u32 crc_tmp; + u32 crc = 0; + u8 flags; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2523,9 +2631,11 @@ static u32 batadv_tt_local_crc(struct batadv_priv *bat_priv, struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common; struct hlist_head *head; - u32 i, crc_tmp, crc = 0; - u8 flags; __be16 tmp_vid; + u32 crc_tmp; + u32 crc = 0; + u8 flags; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2590,6 +2700,10 @@ static void batadv_tt_req_node_put(struct batadv_tt_req_node *tt_req_node) kref_put(&tt_req_node->refcount, batadv_tt_req_node_release); } +/** + * batadv_tt_req_list_free() - drop all pending TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -2605,6 +2719,18 @@ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.req_list_lock); } +/** + * batadv_tt_save_orig_buffer() - cache the latest TT TVLV payload of an + * originator + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator for which the buffer should be created + * @tt_buff: pointer to the TT TVLV payload to cache + * @tt_buff_len: length of @tt_buff in bytes + * + * Replace the previously cached TT payload of @orig_node with a copy of + * @tt_buff. The buffer is left untouched when @tt_buff_len is 0 so that + * empty OGM updates do not discard the previously cached data. + */ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const void *tt_buff, @@ -2626,6 +2752,10 @@ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, spin_unlock_bh(&orig_node->tt_buff_lock); } +/** + * batadv_tt_req_purge() - drop timed-out TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_purge(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -2654,7 +2784,8 @@ static struct batadv_tt_req_node * batadv_tt_req_node_new(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_tt_req_node *tt_req_node_tmp, *tt_req_node = NULL; + struct batadv_tt_req_node *tt_req_node = NULL; + struct batadv_tt_req_node *tt_req_node_tmp; spin_lock_bh(&bat_priv->tt.req_list_lock); hlist_for_each_entry(tt_req_node_tmp, &bat_priv->tt.req_list, list) { @@ -2763,7 +2894,8 @@ static u16 batadv_tt_tvlv_generate(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common_entry; struct batadv_tvlv_tt_change *tt_change; struct hlist_head *head; - u16 tt_tot, tt_num_entries = 0; + u16 tt_num_entries = 0; + u16 tt_tot; u8 flags; bool ret; u32 i; @@ -2817,8 +2949,9 @@ static bool batadv_tt_global_check_crc(struct batadv_orig_node *orig_node, { struct batadv_tvlv_tt_vlan_data *tt_vlan_tmp; struct batadv_orig_node_vlan *vlan; - int i, orig_num_vlan; + int orig_num_vlan; u32 crc; + int i; /* check if each received CRC matches the locally stored one */ for (i = 0; i < num_vlan; i++) { @@ -2924,7 +3057,8 @@ static bool batadv_send_tt_request(struct batadv_priv *bat_priv, struct batadv_tt_req_node *tt_req_node = NULL; struct batadv_hard_iface *primary_if; bool ret = false; - int i, size; + int size; + int i; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -3000,13 +3134,15 @@ static bool batadv_send_other_tt_response(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_data *tt_data, u8 *req_src, u8 *req_dst) { - struct batadv_orig_node *req_dst_orig_node; struct batadv_orig_node *res_dst_orig_node = NULL; - struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tvlv_tt_data = NULL; - bool ret = false, full_table; - u8 orig_ttvn, req_ttvn; + struct batadv_orig_node *req_dst_orig_node; + struct batadv_tvlv_tt_change *tt_change; + bool ret = false; + bool full_table; + u8 orig_ttvn; u16 tvlv_len; + u8 req_ttvn; s32 tt_len; batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -3133,9 +3269,10 @@ static bool batadv_send_my_tt_response(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_tvlv_tt_change *tt_change; struct batadv_orig_node *orig_node; - u8 my_ttvn, req_ttvn; - u16 tvlv_len; bool full_table; + u16 tvlv_len; + u8 req_ttvn; + u8 my_ttvn; s32 tt_len; batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -3253,13 +3390,25 @@ static bool batadv_send_tt_response(struct batadv_priv *bat_priv, req_dst); } +/** + * _batadv_tt_update_changes() - apply a list of TT changes to the global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_change: array of TT change entries to apply + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * + * Walk @tt_change and add/remove the announced clients in the global TT. + * Abort early without marking the @orig_node TT as initialized if adding + * an entry fails, so that the next TT request can re-sync the full table. + */ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_tt_change *tt_change, u16 tt_num_changes, u8 ttvn) { - int i; int roams; + int i; for (i = 0; i < tt_num_changes; i++) { if ((tt_change + i)->flags & BATADV_TT_CLIENT_DEL) { @@ -3286,6 +3435,18 @@ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, set_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_fill_gtable() - replace the cached TT of an originator with a + * full table response + * @bat_priv: the bat priv with all the mesh interface information + * @tt_change: array of TT change entries describing the full table + * @ttvn: TTVN announced together with the full table + * @resp_src: MAC address of the responder + * @num_entries: number of entries in @tt_change + * + * Drop the previously known global TT entries of @resp_src and replace them + * with the entries from a freshly received full TT response. + */ static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_change *tt_change, u8 ttvn, u8 *resp_src, @@ -3316,6 +3477,15 @@ static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv, batadv_orig_node_put(orig_node); } +/** + * batadv_tt_update_changes() - apply an incremental TT changeset to the + * global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * @tt_change: array of TT change entries to apply + */ static void batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, u16 tt_num_changes, u8 ttvn, @@ -3369,11 +3539,11 @@ static void batadv_handle_tt_response(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_data *tt_data, u8 *resp_src, u16 num_entries) { - struct batadv_tt_req_node *node; - struct hlist_node *safe; struct batadv_orig_node *orig_node = NULL; struct batadv_tvlv_tt_change *tt_change; + struct batadv_tt_req_node *node; u8 *tvlv_ptr = (u8 *)tt_data; + struct hlist_node *safe; batadv_dbg(BATADV_DBG_TT, bat_priv, "Received TT_RESPONSE from %pM for ttvn %d t_size: %d [%c]\n", @@ -3416,9 +3586,14 @@ static void batadv_handle_tt_response(struct batadv_priv *bat_priv, batadv_orig_node_put(orig_node); } +/** + * batadv_tt_roam_list_free() - drop all entries from the roaming clients list + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) { - struct batadv_tt_roam_node *node, *safe; + struct batadv_tt_roam_node *node; + struct batadv_tt_roam_node *safe; spin_lock_bh(&bat_priv->tt.roam_list_lock); @@ -3430,9 +3605,14 @@ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.roam_list_lock); } +/** + * batadv_tt_roam_purge() - drop timed-out roaming clients + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv) { - struct batadv_tt_roam_node *node, *safe; + struct batadv_tt_roam_node *node; + struct batadv_tt_roam_node *safe; spin_lock_bh(&bat_priv->tt.roam_list_lock); list_for_each_entry_safe(node, safe, &bat_priv->tt.roam_list, list) { @@ -3522,8 +3702,8 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client, unsigned short vid, struct batadv_orig_node *orig_node) { - struct batadv_hard_iface *primary_if; struct batadv_tvlv_roam_adv tvlv_roam; + struct batadv_hard_iface *primary_if; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -3552,6 +3732,14 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client, batadv_hardif_put(primary_if); } +/** + * batadv_tt_purge() - periodic worker to maintain the translation table + * @work: delayed work embedded in the per-mesh-interface TT state + * + * Purge timed-out entries from the local and global TT, drop stale TT + * requests and roaming clients, and reschedule the next run after + * BATADV_TT_WORK_PERIOD milliseconds. + */ static void batadv_tt_purge(struct work_struct *work) { struct delayed_work *delayed_work; @@ -3638,15 +3826,22 @@ static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, } } -/* Purge out all the tt local entries marked with BATADV_TT_CLIENT_PENDING */ +/** + * batadv_tt_local_purge_pending_clients() - finalise removal of pending local + * clients + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the local TT and physically remove every entry that has been + * marked as BATADV_TT_CLIENT_PENDING. + */ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common; struct batadv_tt_local_entry *tt_local; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ u32 i; if (!hash) @@ -3736,8 +3931,8 @@ void batadv_tt_local_commit_changes(struct batadv_priv *bat_priv) bool batadv_is_ap_isolated(struct batadv_priv *bat_priv, u8 *src, u8 *dst, unsigned short vid) { - struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_global_entry *tt_global_entry; + struct batadv_tt_local_entry *tt_local_entry; struct batadv_meshif_vlan *vlan; bool ret = false; @@ -3947,9 +4142,12 @@ bool batadv_tt_add_temporary_global_entry(struct batadv_priv *bat_priv, void batadv_tt_local_resize_to_mtu(struct net_device *mesh_iface) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); - int packet_size_max = READ_ONCE(bat_priv->packet_size_max); - int table_size, timeout = BATADV_TT_LOCAL_TIMEOUT / 2; + int timeout = BATADV_TT_LOCAL_TIMEOUT / 2; bool reduced = false; + int packet_size_max; + int table_size; + + packet_size_max = READ_ONCE(bat_priv->packet_size_max); spin_lock_bh(&bat_priv->tt.commit_lock); @@ -3992,8 +4190,9 @@ static void batadv_tt_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, { struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tt_data; - u16 num_entries, num_vlan; size_t tt_data_sz; + u16 num_entries; + u16 num_vlan; if (tvlv_value_len < sizeof(*tt_data)) return; @@ -4115,8 +4314,8 @@ static int batadv_roam_tvlv_unicast_handler_v1(struct batadv_priv *bat_priv, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_roam_adv *roaming_adv; struct batadv_orig_node *orig_node = NULL; + struct batadv_tvlv_roam_adv *roaming_adv; /* If this node is not the intended recipient of the * roaming advertisement the packet is forwarded @@ -4219,12 +4418,12 @@ bool batadv_tt_global_is_isolated(struct batadv_priv *bat_priv, */ int __init batadv_tt_cache_init(void) { - size_t tl_size = sizeof(struct batadv_tt_local_entry); - size_t tg_size = sizeof(struct batadv_tt_global_entry); size_t tt_orig_size = sizeof(struct batadv_tt_orig_list_entry); size_t tt_change_size = sizeof(struct batadv_tt_change_node); - size_t tt_req_size = sizeof(struct batadv_tt_req_node); size_t tt_roam_size = sizeof(struct batadv_tt_roam_node); + size_t tg_size = sizeof(struct batadv_tt_global_entry); + size_t tt_req_size = sizeof(struct batadv_tt_req_node); + size_t tl_size = sizeof(struct batadv_tt_local_entry); batadv_tl_cache = kmem_cache_create("batadv_tl_cache", tl_size, 0, SLAB_HWCACHE_ALIGN, NULL); diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 1c9fb21985f6..de907c07fa15 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -72,7 +72,8 @@ static void batadv_tvlv_handler_put(struct batadv_tvlv_handler *tvlv_handler) static struct batadv_tvlv_handler * batadv_tvlv_handler_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_handler *tvlv_handler_tmp, *tvlv_handler = NULL; + struct batadv_tvlv_handler *tvlv_handler = NULL; + struct batadv_tvlv_handler *tvlv_handler_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tvlv_handler_tmp, @@ -134,7 +135,8 @@ static void batadv_tvlv_container_put(struct batadv_tvlv_container *tvlv) static struct batadv_tvlv_container * batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_container *tvlv_tmp, *tvlv = NULL; + struct batadv_tvlv_container *tvlv = NULL; + struct batadv_tvlv_container *tvlv_tmp; lockdep_assert_held(&bat_priv->tvlv.container_list_lock); @@ -236,7 +238,8 @@ void batadv_tvlv_container_register(struct batadv_priv *bat_priv, u8 type, u8 version, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_container *tvlv_old, *tvlv_new; + struct batadv_tvlv_container *tvlv_old; + struct batadv_tvlv_container *tvlv_new; if (!tvlv_value) tvlv_value_len = 0; @@ -383,8 +386,9 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv, * @tvlv_value: tvlv content * @tvlv_value_len: tvlv content length * - * Return: success if the handler was not found or the return value of the - * handler callback. + * Return: NET_RX_SUCCESS if the handler was not found or the return value of + * the handler callback. The latter is NET_RX_SUCCESS or NET_RX_DROP for the + * unicast handler and additionally a negative errno code for the mcast handler. */ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, struct batadv_tvlv_handler *tvlv_handler, @@ -394,7 +398,8 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, u16 tvlv_value_len) { unsigned int tvlv_offset; - u8 *src, *dst; + u8 *src; + u8 *dst; if (!tvlv_handler) return NET_RX_SUCCESS; @@ -442,6 +447,54 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, return NET_RX_SUCCESS; } +/** + * batadv_tvlv_hdr_next() - move a tvlv buffer cursor to the next container + * @tvlv_value: cursor into the tvlv buffer, advanced past the returned + * container's content on success + * @tvlv_value_len: remaining length of the tvlv buffer, reduced by the returned + * container's size on success + * + * Parses a single container header at the current cursor position and, if a + * complete container is available, advances the cursor and remaining length + * past it. The returned header stays valid; its content is located at + * (returned header + 1) and is ntohs(hdr->len) bytes long. + * + * Return: pointer to the next tvlv container header, or NULL if no further + * complete container is present in the buffer. + */ +static struct batadv_tvlv_hdr *batadv_tvlv_hdr_next(void **tvlv_value, u16 *tvlv_value_len) +{ + struct batadv_tvlv_hdr *tvlv_hdr; + u16 tvlv_value_cont_len; + void *tvlv_value_cont; + u16 tvlv_len; + + tvlv_value_cont = *tvlv_value; + tvlv_len = *tvlv_value_len; + + if (tvlv_len < sizeof(*tvlv_hdr)) + return NULL; + + tvlv_hdr = tvlv_value_cont; + tvlv_value_cont_len = ntohs(tvlv_hdr->len); + tvlv_value_cont = tvlv_hdr + 1; + tvlv_len -= sizeof(*tvlv_hdr); + + if (tvlv_value_cont_len > tvlv_len) + return NULL; + + /* the next tvlv header is accessed assuming (at least) 2-byte + * alignment, so it must start at an even offset. + */ + if (tvlv_value_cont_len & 1) + return NULL; + + *tvlv_value = (u8 *)tvlv_value_cont + tvlv_value_cont_len; + *tvlv_value_len = tvlv_len - tvlv_value_cont_len; + + return tvlv_hdr; +} + /** * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container * @tvlv_value: tvlv content @@ -457,28 +510,10 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, u8 version) { struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; - tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { if (tvlv_hdr->type == type && tvlv_hdr->version == version) return true; - - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } return false; @@ -494,8 +529,9 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, * @tvlv_value: tvlv content * @tvlv_value_len: tvlv content length * - * Return: success when processing an OGM or the return value of all called - * handler callbacks. + * Return: NET_RX_SUCCESS when processing an OGM or the combined return value of + * all called handler callbacks. The latter is NET_RX_SUCCESS, NET_RX_DROP or, + * for BATADV_MCAST packets, a negative errno code. */ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u8 packet_type, @@ -503,40 +539,30 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, struct sk_buff *skb, void *tvlv_value, u16 tvlv_value_len) { + u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; u16 tvlv_value_start_len = tvlv_value_len; struct batadv_tvlv_handler *tvlv_handler; void *tvlv_value_start = tvlv_value; struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; + u16 tvlv_value_cont_len; + int res; - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; tvlv_handler = batadv_tvlv_handler_get(bat_priv, tvlv_hdr->type, tvlv_hdr->version); - ret |= batadv_tvlv_call_handler(bat_priv, tvlv_handler, - packet_type, orig_node, skb, - tvlv_value, - tvlv_value_cont_len); + res = batadv_tvlv_call_handler(bat_priv, tvlv_handler, + packet_type, orig_node, skb, + tvlv_hdr + 1, + tvlv_value_cont_len); + if (ret == NET_RX_SUCCESS || res < 0) + ret = res; + batadv_tvlv_handler_put(tvlv_handler); - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } if (packet_type != BATADV_IV_OGM && @@ -580,8 +606,8 @@ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv, struct batadv_ogm_packet *batadv_ogm_packet, struct batadv_orig_node *orig_node) { - void *tvlv_value; u16 tvlv_value_len; + void *tvlv_value; if (!batadv_ogm_packet) return; @@ -701,12 +727,12 @@ void batadv_tvlv_unicast_send(struct batadv_priv *bat_priv, const u8 *src, void *tvlv_value, u16 tvlv_value_len) { struct batadv_unicast_tvlv_packet *unicast_tvlv_packet; - struct batadv_tvlv_hdr *tvlv_hdr; + ssize_t hdr_len = sizeof(*unicast_tvlv_packet); struct batadv_orig_node *orig_node; - struct sk_buff *skb; + struct batadv_tvlv_hdr *tvlv_hdr; unsigned char *tvlv_buff; unsigned int tvlv_len; - ssize_t hdr_len = sizeof(*unicast_tvlv_packet); + struct sk_buff *skb; orig_node = batadv_orig_hash_find(bat_priv, dst); if (!orig_node) diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index b1f9f8964c3f..42b631573512 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -214,9 +214,6 @@ struct batadv_wifi_net_device_state { * struct batadv_hard_iface - network device known to batman-adv */ struct batadv_hard_iface { - /** @list: list node for batadv_hardif_list */ - struct list_head list; - /** @if_status: status of the interface for batman-adv */ char if_status; @@ -1335,9 +1332,9 @@ struct batadv_tp_unacked { u32 seqno; /** @len: length of the packet */ - u16 len; + u32 len; - /** @list: list node for &batadv_tp_vars_common.unacked_list */ + /** @list: list node for &batadv_tp_receiver.unacked_list */ struct list_head list; }; @@ -1360,15 +1357,6 @@ struct batadv_tp_vars_common { /** @session: TP session identifier */ u8 session[2]; - /** @unacked_list: list of unacked packets (meta-info only) */ - struct list_head unacked_list; - - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; - - /** @unacked_count: number of unacked entries */ - size_t unacked_count; - /** @refcount: number of context where the object is used */ struct kref refcount; @@ -1482,6 +1470,15 @@ struct batadv_tp_receiver { /** @last_recv_time: time (jiffies) a msg was received */ unsigned long last_recv_time; + + /** @unacked_list: list of unacked packets (meta-info only) */ + struct list_head unacked_list; + + /** @ack_seqno_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t ack_seqno_lock; + + /** @unacked_count: number of unacked entries */ + size_t unacked_count; }; /** @@ -1679,6 +1676,9 @@ struct batadv_priv { /** @tp_num: number of currently active tp sessions */ atomic_t tp_num; + /** @hardif_generation: generation counter added to netlink hardif dumps */ + unsigned int hardif_generation; + /** @orig_work: work queue callback item for orig node purging */ struct delayed_work orig_work; @@ -1827,10 +1827,10 @@ struct batadv_bla_claim { /** @hash_entry: hlist node for &batadv_priv_bla.claim_hash */ struct hlist_node hash_entry; - /** @refcount: number of contexts the object is used */ + /** @rcu: struct used for freeing in an RCU-safe manner */ struct rcu_head rcu; - /** @rcu: struct used for freeing in an RCU-safe manner */ + /** @refcount: number of contexts the object is used */ struct kref refcount; }; #endif diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index d504a363a30f..30f4afa18bc8 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -1007,6 +1007,8 @@ static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type, return -ENOENT; } + lockdep_assert_held(&hcon->hdev->lock); + *conn = l2cap_conn_hold_unless_zero(hcon->l2cap_data); BT_DBG("conn %p dst %pMR type %u", *conn, &hcon->dst, hcon->dst_type); diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index d250e94e90eb..1cda01614efe 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -76,7 +76,7 @@ config BT_LE_L2CAP_ECRED config BT_6LOWPAN tristate "Bluetooth 6LoWPAN support" - depends on BT_LE && 6LOWPAN + depends on BT && BT_LE && 6LOWPAN help IPv6 compression over Bluetooth Low Energy. diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 41049b280887..ff466ea97436 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -25,3 +25,5 @@ bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index a2290ffdc2c1..411d66f24393 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -209,6 +209,7 @@ bool bt_sock_linked(struct bt_sock_list *l, struct sock *s) EXPORT_SYMBOL(bt_sock_linked); void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh) + __context_unsafe(/* conditional locking */) { const struct cred *old_cred; struct pid *old_pid; @@ -815,7 +816,8 @@ EXPORT_SYMBOL(bt_sock_wait_ready); #ifdef CONFIG_PROC_FS static void *bt_seq_start(struct seq_file *seq, loff_t *pos) - __acquires(seq->private->l->lock) + __acquires_shared(&((struct bt_sock_list *) + pde_data(file_inode(seq->file)))->lock) { struct bt_sock_list *l = pde_data(file_inode(seq->file)); @@ -831,7 +833,8 @@ static void *bt_seq_next(struct seq_file *seq, void *v, loff_t *pos) } static void bt_seq_stop(struct seq_file *seq, void *v) - __releases(seq->private->l->lock) + __releases_shared(&((struct bt_sock_list *) + pde_data(file_inode(seq->file)))->lock) { struct bt_sock_list *l = pde_data(file_inode(seq->file)); diff --git a/net/bluetooth/bnep/Kconfig b/net/bluetooth/bnep/Kconfig index aac02b5b0d17..f8087e2d2c00 100644 --- a/net/bluetooth/bnep/Kconfig +++ b/net/bluetooth/bnep/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_BNEP tristate "BNEP protocol support" - depends on BT_BREDR + depends on BT && BT_BREDR select CRC32 help BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet diff --git a/net/bluetooth/bnep/Makefile b/net/bluetooth/bnep/Makefile index 8af9d56bb012..f42015cc3245 100644 --- a/net/bluetooth/bnep/Makefile +++ b/net/bluetooth/bnep/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_BT_BNEP) += bnep.o bnep-objs := core.o sock.o netdev.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 720cb79adf96..5bee863bd6d2 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -30,10 +30,9 @@ struct hci_devcoredump_skb_pattern { #define DBG_UNEXPECTED_STATE() \ bt_dev_dbg(hdev, \ - "Unexpected packet (%d) for state (%d). ", \ - hci_dmp_cb(skb)->pkt_type, hdev->dump.state) - -#define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ + "Unexpected packet (%d) for state %s.", \ + hci_dmp_cb(skb)->pkt_type, \ + hci_devcd_state_name(hdev->dump.state)) static int hci_devcd_update_hdr_state(char *buf, size_t size, int state) { @@ -50,8 +49,9 @@ static int hci_devcd_update_hdr_state(char *buf, size_t size, int state) /* Call with hci_dev_lock only. */ static int hci_devcd_update_state(struct hci_dev *hdev, int state) { - bt_dev_dbg(hdev, "Updating devcoredump state from %d to %d.", - hdev->dump.state, state); + bt_dev_dbg(hdev, "Updating devcoredump state from %s to %s.", + hci_devcd_state_name(hdev->dump.state), + hci_devcd_state_name(state)); hdev->dump.state = state; @@ -61,7 +61,6 @@ static int hci_devcd_update_state(struct hci_dev *hdev, int state) static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb) { - char dump_start[] = "--- Start dump ---\n"; char hdr[80]; int hdr_len; @@ -72,7 +71,7 @@ static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb) if (hdev->dump.dmp_hdr) hdev->dump.dmp_hdr(hdev, skb); - skb_put_data(skb, dump_start, strlen(dump_start)); + skb_put_data(skb, HCI_DEVCD_HDR_END_MARKER, strlen(HCI_DEVCD_HDR_END_MARKER)); return skb->len; } @@ -152,7 +151,7 @@ static int hci_devcd_prepare(struct hci_dev *hdev, u32 dump_size) int dump_hdr_size; int err = 0; - skb = alloc_skb(MAX_DEVCOREDUMP_HDR_SIZE, GFP_ATOMIC); + skb = alloc_skb(HCI_DEVCD_HDR_SIZE_MAX, GFP_ATOMIC); if (!skb) return -ENOMEM; @@ -245,7 +244,7 @@ static void hci_devcd_dump(struct hci_dev *hdev) struct sk_buff *skb; u32 size; - bt_dev_dbg(hdev, "state %d", hdev->dump.state); + bt_dev_dbg(hdev, "state %s", hci_devcd_state_name(hdev->dump.state)); size = hdev->dump.tail - hdev->dump.head; @@ -368,8 +367,9 @@ void hci_devcd_rx(struct work_struct *work) break; default: - bt_dev_dbg(hdev, "Unknown packet (%d) for state (%d). ", - hci_dmp_cb(skb)->pkt_type, hdev->dump.state); + bt_dev_dbg(hdev, "Unknown packet (%d) for state %s.", + hci_dmp_cb(skb)->pkt_type, + hci_devcd_state_name(hdev->dump.state)); break; } @@ -390,7 +390,6 @@ void hci_devcd_rx(struct work_struct *work) hci_dev_unlock(hdev); } } -EXPORT_SYMBOL(hci_devcd_rx); void hci_devcd_timeout(struct work_struct *work) { @@ -416,7 +415,6 @@ void hci_devcd_timeout(struct work_struct *work) hci_dev_unlock(hdev); } -EXPORT_SYMBOL(hci_devcd_timeout); int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump, dmp_hdr_t dmp_hdr, notify_change_t notify_change) @@ -551,3 +549,31 @@ int hci_devcd_abort(struct hci_dev *hdev) return 0; } EXPORT_SYMBOL(hci_devcd_abort); + +const char *hci_devcd_state_name(enum devcoredump_state state) +{ + const char *state_name = "Unknown"; + + switch (state) { + case HCI_DEVCOREDUMP_IDLE: + state_name = "IDLE"; + break; + case HCI_DEVCOREDUMP_ACTIVE: + state_name = "ACTIVE"; + break; + case HCI_DEVCOREDUMP_DONE: + state_name = "DONE"; + break; + case HCI_DEVCOREDUMP_ABORT: + state_name = "ABORT"; + break; + case HCI_DEVCOREDUMP_TIMEOUT: + state_name = "TIMEOUT"; + break; + default: + break; + } + + return state_name; +} +EXPORT_SYMBOL(hci_devcd_state_name); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index b1f911fd4ad6..19b7629b1cc1 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -283,8 +283,6 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) struct hci_cp_enhanced_setup_sync_conn cp; const struct sco_param *param; - kfree(conn_handle); - if (!hci_conn_valid(hdev, conn)) return -ECANCELED; @@ -453,6 +451,15 @@ static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) return true; } +static void hci_enhanced_setup_sync_destroy(struct hci_dev *hdev, void *data, + int err) +{ + struct conn_handle_t *conn_handle = data; + + hci_conn_put(conn_handle->conn); + kfree(conn_handle); +} + bool hci_setup_sync(struct hci_conn *conn, __u16 handle) { int result; @@ -464,12 +471,15 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle) if (!conn_handle) return false; - conn_handle->conn = conn; + conn_handle->conn = hci_conn_get(conn); conn_handle->handle = handle; result = hci_cmd_sync_queue(conn->hdev, hci_enhanced_setup_sync, - conn_handle, NULL); - if (result < 0) + conn_handle, + hci_enhanced_setup_sync_destroy); + if (result < 0) { + hci_conn_put(conn); kfree(conn_handle); + } return result == 0; } diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5ba9fe8261ec..509c820a693d 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -62,6 +62,7 @@ static DEFINE_IDA(hci_index_ida); /* Get HCI device by index. * Device is held on return. */ static struct hci_dev *__hci_dev_get(int index, int *srcu_index) + __context_unsafe(/* conditional locking */) { struct hci_dev *hdev = NULL, *d; @@ -89,11 +90,13 @@ struct hci_dev *hci_dev_get(int index) } static struct hci_dev *hci_dev_get_srcu(int index, int *srcu_index) + __context_unsafe(/* conditional locking vs return */) { return __hci_dev_get(index, srcu_index); } static void hci_dev_put_srcu(struct hci_dev *hdev, int srcu_index) + __context_unsafe(/* conditional locking vs return */) { srcu_read_unlock(&hdev->srcu, srcu_index); hci_dev_put(hdev); @@ -2849,9 +2852,9 @@ int hci_resume_dev(struct hci_dev *hdev) EXPORT_SYMBOL(hci_resume_dev); /* Reset HCI device */ -int hci_reset_dev(struct hci_dev *hdev) +int __hci_reset_dev(struct hci_dev *hdev, u8 hw_err_code) { - static const u8 hw_err[] = { HCI_EV_HARDWARE_ERROR, 0x01, 0x00 }; + const u8 hw_err[] = { HCI_EV_HARDWARE_ERROR, 0x01, hw_err_code }; struct sk_buff *skb; skb = bt_skb_alloc(3, GFP_ATOMIC); @@ -2866,7 +2869,7 @@ int hci_reset_dev(struct hci_dev *hdev) /* Send Hardware Error to upper stack */ return hci_recv_frame(hdev, skb); } -EXPORT_SYMBOL(hci_reset_dev); +EXPORT_SYMBOL(__hci_reset_dev); static u8 hci_dev_classify_pkt_type(struct hci_dev *hdev, struct sk_buff *skb) { @@ -2901,10 +2904,9 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) if (hci_conn_num(hdev, CIS_LINK) || hci_conn_num(hdev, BIS_LINK) || hci_conn_num(hdev, PA_LINK)) { - __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); __u8 type; - type = hci_conn_lookup_type(hdev, hci_handle(handle)); + type = hci_conn_lookup_type(hdev, hci_acl_handle(skb)); if (type == CIS_LINK || type == BIS_LINK || type == PA_LINK) hci_skb_pkt_type(skb) = HCI_ISODATA_PKT; diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c index 0635e4641db4..aadffaaff20e 100644 --- a/net/bluetooth/hci_debugfs.c +++ b/net/bluetooth/hci_debugfs.c @@ -1161,16 +1161,12 @@ static ssize_t force_no_mitm_write(struct file *file, size_t count, loff_t *ppos) { struct hci_dev *hdev = file->private_data; - char buf[32]; - size_t buf_size = min(count, (sizeof(buf) - 1)); bool enable; + int err; - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - - buf[buf_size] = '\0'; - if (kstrtobool(buf, &enable)) - return -EINVAL; + err = kstrtobool_from_user(user_buf, count, &enable); + if (err) + return err; if (enable == hci_dev_test_flag(hdev, HCI_FORCE_NO_MITM)) return -EALREADY; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 741d658e9630..3eb1eaf6e6a0 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -269,7 +269,10 @@ static u8 hci_cc_reset(struct hci_dev *hdev, void *data, struct sk_buff *skb) { struct hci_ev_status *rp = data; - bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); + if (rp->status) + bt_dev_err(hdev, "status 0x%2.2x", rp->status); + else + bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); clear_bit(HCI_RESET, &hdev->flags); @@ -294,8 +297,10 @@ static u8 hci_cc_reset(struct hci_dev *hdev, void *data, struct sk_buff *skb) hdev->ssp_debug_mode = 0; + hci_dev_lock(hdev); hci_bdaddr_list_clear(&hdev->le_accept_list); hci_bdaddr_list_clear(&hdev->le_resolv_list); + hci_dev_unlock(hdev); return rp->status; } @@ -1238,6 +1243,39 @@ static u8 hci_cc_le_read_local_features(struct hci_dev *hdev, void *data, return rp->status; } +static u8 hci_cc_le_read_conn_interval(struct hci_dev *hdev, void *data, + struct sk_buff *skb) +{ + struct hci_rp_le_read_conn_interval *rp = data; + u16 min_interval = 0; + int i; + + bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); + + if (rp->status) + return rp->status; + + if (skb->len < flex_array_size(rp, grps, rp->num_grps)) { + bt_dev_err(hdev, "Invalid response length for 0x%4.4x", + HCI_OP_LE_READ_CONN_INTERVAL); + return HCI_ERROR_UNSPECIFIED; + } + + /* Store the smallest minimum supported connection interval reported by + * the controller so the default rate parameters can be clamped to it. + */ + for (i = 0; i < rp->num_grps; i++) { + u16 min = le16_to_cpu(rp->grps[i].min); + + if (!min_interval || min < min_interval) + min_interval = min; + } + + hdev->le_min_rate_interval = min_interval; + + return rp->status; +} + static u8 hci_cc_le_read_adv_tx_power(struct hci_dev *hdev, void *data, struct sk_buff *skb) { @@ -3827,8 +3865,10 @@ static u8 hci_cc_le_set_cig_params(struct hci_dev *hdev, void *data, bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_CIG_PARAMS); - if (!rp->status && (!cp || rp->num_handles != cp->num_cis || - rp->cig_id != cp->cig_id)) { + if (!rp->status && + (!cp || rp->num_handles != cp->num_cis || + rp->cig_id != cp->cig_id || + skb->len < array_size(rp->num_handles, sizeof(*rp->handle)))) { bt_dev_err(hdev, "unexpected Set CIG Parameters response data"); status = HCI_ERROR_UNSPECIFIED; } @@ -4150,6 +4190,9 @@ static const struct hci_cc { sizeof(struct hci_rp_le_read_buffer_size)), HCI_CC(HCI_OP_LE_READ_LOCAL_FEATURES, hci_cc_le_read_local_features, sizeof(struct hci_rp_le_read_local_features)), + HCI_CC_VL(HCI_OP_LE_READ_CONN_INTERVAL, hci_cc_le_read_conn_interval, + sizeof(struct hci_rp_le_read_conn_interval), + HCI_MAX_EVENT_SIZE), HCI_CC(HCI_OP_LE_READ_ADV_TX_POWER, hci_cc_le_read_adv_tx_power, sizeof(struct hci_rp_le_read_adv_tx_power)), HCI_CC(HCI_OP_USER_CONFIRM_REPLY, hci_cc_user_confirm_reply, @@ -5864,6 +5907,17 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, } } + /* If we are central and have subrate parameters stored, queue a + * connection rate request to apply them. + */ + if (conn->role == HCI_ROLE_MASTER && le_sci_capable(hdev)) { + struct hci_conn_params *p; + + p = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); + if (p && p->subrate_max) + hci_le_conn_rate_request(hdev, conn); + } + unlock: hci_update_passive_scan(hdev); hci_dev_unlock(hdev); @@ -6604,6 +6658,13 @@ static void hci_le_per_adv_report_evt(struct hci_dev *hdev, void *data, bt_dev_dbg(hdev, "sync_handle 0x%4.4x", le16_to_cpu(ev->sync_handle)); + /* The reassembly in iso_connect_ind() copies ev->length bytes from the + * stored event, so make sure the event actually carries that many data + * bytes before it is consumed. + */ + if (!hci_le_ev_skb_pull(hdev, skb, HCI_EV_LE_PER_ADV_REPORT, ev->length)) + return; + hci_dev_lock(hdev); mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, PA_LINK, &flags); @@ -7360,6 +7421,36 @@ static void hci_le_read_all_remote_features_evt(struct hci_dev *hdev, hci_dev_unlock(hdev); } +static void hci_le_conn_rate_change_evt(struct hci_dev *hdev, void *data, + struct sk_buff *skb) +{ + struct hci_evt_le_conn_rate_change *ev = data; + struct hci_conn *conn; + + bt_dev_dbg(hdev, "status 0x%2.2x", ev->status); + + hci_dev_lock(hdev); + + conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); + if (conn) { + /* Only update the stored rate parameters on success; on + * failure the values in the event are not valid. Userspace is + * notified either way. + */ + if (!ev->status) { + conn->le_rate_interval = le16_to_cpu(ev->interval); + conn->le_subrate = le16_to_cpu(ev->subrate); + conn->le_rate_latency = le16_to_cpu(ev->latency); + conn->le_cont_num = le16_to_cpu(ev->cont_number); + conn->le_rate_supv_timeout = + le16_to_cpu(ev->supv_timeout); + } + mgmt_conn_subrate_notify(hdev, conn, ev->status); + } + + hci_dev_unlock(hdev); +} + #define HCI_LE_EV_VL(_op, _func, _min_len, _max_len) \ [_op] = { \ .func = _func, \ @@ -7471,6 +7562,9 @@ static const struct hci_le_ev { sizeof(struct hci_evt_le_read_all_remote_features_complete), HCI_MAX_EVENT_SIZE), + /* [0x37 = HCI_EVT_LE_CONN_RATE_CHANGE] */ + HCI_LE_EV(HCI_EVT_LE_CONN_RATE_CHANGE, hci_le_conn_rate_change_evt, + sizeof(struct hci_evt_le_conn_rate_change)), }; static void hci_le_meta_evt(struct hci_dev *hdev, void *data, @@ -7517,6 +7611,14 @@ static void hci_le_meta_evt(struct hci_dev *hdev, void *data, subev->func(hdev, data, skb); } +static void hci_vendor_evt(struct hci_dev *hdev, void *data, struct sk_buff *skb) +{ + if (hdev->handle_ev_vendor && hdev->handle_ev_vendor(hdev, skb)) + return; + + msft_vendor_evt(hdev, data, skb); +} + static bool hci_get_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 event, struct sk_buff *skb) { @@ -7633,7 +7735,7 @@ static const struct hci_ev { HCI_EV_STATUS(HCI_EV_INQUIRY_COMPLETE, hci_inquiry_complete_evt), /* [0x02 = HCI_EV_INQUIRY_RESULT] */ HCI_EV_VL(HCI_EV_INQUIRY_RESULT, hci_inquiry_result_evt, - sizeof(struct hci_ev_inquiry_result), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_inquiry_result), HCI_MAX_EVENT_PLEN), /* [0x03 = HCI_EV_CONN_COMPLETE] */ HCI_EV(HCI_EV_CONN_COMPLETE, hci_conn_complete_evt, sizeof(struct hci_ev_conn_complete)), @@ -7661,7 +7763,7 @@ static const struct hci_ev { sizeof(struct hci_ev_remote_features)), /* [0x0e = HCI_EV_CMD_COMPLETE] */ HCI_EV_REQ_VL(HCI_EV_CMD_COMPLETE, hci_cmd_complete_evt, - sizeof(struct hci_ev_cmd_complete), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_cmd_complete), HCI_MAX_EVENT_PLEN), /* [0x0f = HCI_EV_CMD_STATUS] */ HCI_EV_REQ(HCI_EV_CMD_STATUS, hci_cmd_status_evt, sizeof(struct hci_ev_cmd_status)), @@ -7673,7 +7775,7 @@ static const struct hci_ev { sizeof(struct hci_ev_role_change)), /* [0x13 = HCI_EV_NUM_COMP_PKTS] */ HCI_EV_VL(HCI_EV_NUM_COMP_PKTS, hci_num_comp_pkts_evt, - sizeof(struct hci_ev_num_comp_pkts), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_num_comp_pkts), HCI_MAX_EVENT_PLEN), /* [0x14 = HCI_EV_MODE_CHANGE] */ HCI_EV(HCI_EV_MODE_CHANGE, hci_mode_change_evt, sizeof(struct hci_ev_mode_change)), @@ -7699,7 +7801,7 @@ static const struct hci_ev { HCI_EV_VL(HCI_EV_INQUIRY_RESULT_WITH_RSSI, hci_inquiry_result_with_rssi_evt, sizeof(struct hci_ev_inquiry_result_rssi), - HCI_MAX_EVENT_SIZE), + HCI_MAX_EVENT_PLEN), /* [0x23 = HCI_EV_REMOTE_EXT_FEATURES] */ HCI_EV(HCI_EV_REMOTE_EXT_FEATURES, hci_remote_ext_features_evt, sizeof(struct hci_ev_remote_ext_features)), @@ -7709,7 +7811,7 @@ static const struct hci_ev { /* [0x2f = HCI_EV_EXTENDED_INQUIRY_RESULT] */ HCI_EV_VL(HCI_EV_EXTENDED_INQUIRY_RESULT, hci_extended_inquiry_result_evt, - sizeof(struct hci_ev_ext_inquiry_result), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_ext_inquiry_result), HCI_MAX_EVENT_PLEN), /* [0x30 = HCI_EV_KEY_REFRESH_COMPLETE] */ HCI_EV(HCI_EV_KEY_REFRESH_COMPLETE, hci_key_refresh_complete_evt, sizeof(struct hci_ev_key_refresh_complete)), @@ -7742,9 +7844,9 @@ static const struct hci_ev { sizeof(struct hci_ev_remote_host_features)), /* [0x3e = HCI_EV_LE_META] */ HCI_EV_REQ_VL(HCI_EV_LE_META, hci_le_meta_evt, - sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_PLEN), /* [0xff = HCI_EV_VENDOR] */ - HCI_EV_VL(HCI_EV_VENDOR, msft_vendor_evt, 0, HCI_MAX_EVENT_SIZE), + HCI_EV_VL(HCI_EV_VENDOR, hci_vendor_evt, 0, HCI_MAX_EVENT_PLEN), }; static void hci_event_func(struct hci_dev *hdev, u8 event, struct sk_buff *skb, diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c8d14128c363..b5897545d795 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -540,8 +540,6 @@ static int adv_timeout_expire_sync(struct hci_dev *hdev, void *data) { u8 instance = *(u8 *)data; - kfree(data); - hci_clear_adv_instance_sync(hdev, NULL, instance, false); if (list_empty(&hdev->adv_instances)) @@ -550,6 +548,12 @@ static int adv_timeout_expire_sync(struct hci_dev *hdev, void *data) return 0; } +static void adv_timeout_expire_destroy(struct hci_dev *hdev, void *data, + int err) +{ + kfree(data); +} + static void adv_timeout_expire(struct work_struct *work) { u8 *inst_ptr; @@ -570,7 +574,9 @@ static void adv_timeout_expire(struct work_struct *work) goto unlock; *inst_ptr = hdev->cur_adv_instance; - hci_cmd_sync_queue(hdev, adv_timeout_expire_sync, inst_ptr, NULL); + if (hci_cmd_sync_queue(hdev, adv_timeout_expire_sync, inst_ptr, + adv_timeout_expire_destroy) < 0) + kfree(inst_ptr); unlock: hci_dev_unlock(hdev); @@ -860,32 +866,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev, } EXPORT_SYMBOL(hci_cmd_sync_cancel_entry); -/* Dequeue one HCI command entry: - * - * - Lookup and cancel first entry that matches. - */ -bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev, - hci_cmd_sync_work_func_t func, - void *data, hci_cmd_sync_work_destroy_t destroy) -{ - struct hci_cmd_sync_work_entry *entry; - - mutex_lock(&hdev->cmd_sync_work_lock); - - entry = _hci_cmd_sync_lookup_entry(hdev, func, data, destroy); - if (!entry) { - mutex_unlock(&hdev->cmd_sync_work_lock); - return false; - } - - _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED); - - mutex_unlock(&hdev->cmd_sync_work_lock); - - return true; -} -EXPORT_SYMBOL(hci_cmd_sync_dequeue_once); - /* Dequeue HCI command entry: * * - Lookup and cancel any entry that matches by function callback or data or @@ -1170,6 +1150,32 @@ int hci_update_random_address_sync(struct hci_dev *hdev, bool require_privacy, return 0; } +static int hci_disable_ext_adv_legacy_instance_sync(struct hci_dev *hdev) +{ + struct hci_cp_le_set_ext_adv_enable *cp; + struct hci_cp_ext_adv_set *set; + u8 data[sizeof(*cp) + sizeof(*set) * 1]; + u8 size; + + if (!hci_dev_test_flag(hdev, HCI_LE_ADV_0)) + return 0; + + memset(data, 0, sizeof(data)); + + cp = (void *)data; + set = (void *)cp->data; + + cp->num_of_sets = 0x01; + cp->enable = 0x00; + + set->handle = 0x00; + + size = sizeof(*cp) + sizeof(*set) * cp->num_of_sets; + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EXT_ADV_ENABLE, + size, data, HCI_CMD_TIMEOUT); +} + static int hci_disable_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) { struct hci_cp_le_set_ext_adv_enable *cp; @@ -1395,6 +1401,10 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) return -EINVAL; } } else { + err = hci_disable_ext_adv_legacy_instance_sync(hdev); + if (err) + return err; + adv = NULL; } @@ -3780,18 +3790,20 @@ static const struct hci_init_stage hci_init0[] = { int hci_reset_sync(struct hci_dev *hdev) { - int err; - set_bit(HCI_RESET, &hdev->flags); - err = __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, - HCI_CMD_TIMEOUT); - if (err) - return err; - - return 0; + return __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, + HCI_CMD_TIMEOUT); } +/* Send a raw HCI reset for use by vendor drivers */ +int __hci_reset_sync(struct hci_dev *hdev) +{ + return __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, + HCI_INIT_TIMEOUT); +} +EXPORT_SYMBOL(__hci_reset_sync); + static int hci_init0_sync(struct hci_dev *hdev) { int err; @@ -4565,6 +4577,10 @@ static int hci_le_set_event_mask_sync(struct hci_dev *hdev) events[6] |= 0x02; /* LE CS Subevent Result Continue event */ events[6] |= 0x04; /* LE CS Test End Complete event */ } + + if (le_sci_capable(hdev)) + events[6] |= 0x40; /* LE Connection Rate Change event */ + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EVENT_MASK, sizeof(events), events, HCI_CMD_TIMEOUT); } @@ -4733,12 +4749,59 @@ static int hci_le_set_host_feature_sync(struct hci_dev *hdev, u16 bit, u8 value) sizeof(cp), &cp, HCI_CMD_TIMEOUT); } +static int hci_le_read_conn_interval_sync(struct hci_dev *hdev) +{ + if (!le_sci_capable(hdev)) + return 0; + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_CONN_INTERVAL, + 0, NULL, HCI_CMD_TIMEOUT); +} + +static int hci_le_set_def_rate_sync(struct hci_dev *hdev) +{ + struct hci_cp_le_set_def_rate cp; + u16 interval_min = 0x000a; /* 1.25 ms */ + u16 interval_max = 0x0078; /* 15 ms */ + + if (!le_sci_capable(hdev)) + return 0; + + /* Clamp the interval range to the controller's minimum supported + * connection interval (read via HCI_OP_LE_READ_CONN_INTERVAL) so the + * default rate parameters are not rejected. The maximum is raised as + * well if needed to keep interval_min <= interval_max. + */ + if (hdev->le_min_rate_interval > interval_min) { + interval_min = hdev->le_min_rate_interval; + if (interval_min > interval_max) + interval_max = interval_min; + } + + memset(&cp, 0, sizeof(cp)); + + /* Use the HIDS 1.2 recommended Full Range mode values as the default + * rate parameters (see HOGP v1.2 spec). Connection intervals are in + * units of 0.125 ms and the supervision timeout is in units of 10 ms. + */ + cp.interval_min = cpu_to_le16(interval_min); + cp.interval_max = cpu_to_le16(interval_max); + cp.subrate_min = cpu_to_le16(0x0001); + cp.subrate_max = cpu_to_le16(0x0004); + cp.max_latency = cpu_to_le16(0x0000); + cp.cont_num = cpu_to_le16(0x0001); + cp.supv_timeout = cpu_to_le16(0x000c); /* 120 ms */ + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DEF_RATE, + sizeof(cp), &cp, HCI_CMD_TIMEOUT); +} + /* Set Host Features, each feature needs to be sent separately since * HCI_OP_LE_SET_HOST_FEATURE doesn't support setting all of them at once. */ static int hci_le_set_host_features_sync(struct hci_dev *hdev) { - int err; + int err = 0; if (cis_capable(hdev)) { /* Connected Isochronous Channels (Host Support) */ @@ -4749,9 +4812,16 @@ static int hci_le_set_host_features_sync(struct hci_dev *hdev) return err; } - if (le_cs_capable(hdev)) + if (le_cs_capable(hdev)) { /* Channel Sounding (Host Support) */ err = hci_le_set_host_feature_sync(hdev, 47, 0x01); + if (err) + return err; + } + + if (le_sci_capable(hdev)) + /* Shorter Connection Intervals (Host Support) */ + err = hci_le_set_host_feature_sync(hdev, 73, 0x01); return err; } @@ -4784,6 +4854,10 @@ static const struct hci_init_stage le_init3[] = { HCI_INIT(hci_set_le_support_sync), /* HCI_OP_LE_SET_HOST_FEATURE */ HCI_INIT(hci_le_set_host_features_sync), + /* HCI_OP_LE_READ_CONN_INTERVAL */ + HCI_INIT(hci_le_read_conn_interval_sync), + /* HCI_OP_LE_SET_DEF_RATE */ + HCI_INIT(hci_le_set_def_rate_sync), {} }; @@ -6313,6 +6387,8 @@ static int hci_pause_discovery_sync(struct hci_dev *hdev) static int hci_update_event_filter_sync(struct hci_dev *hdev) { struct bdaddr_list_with_flags *b; + bdaddr_t *accept_list; + size_t i, num_entries = 0; u8 scan = SCAN_DISABLED; bool scanning = test_bit(HCI_PSCAN, &hdev->flags); int err; @@ -6329,23 +6405,49 @@ static int hci_update_event_filter_sync(struct hci_dev *hdev) /* Always clear event filter when starting */ hci_clear_event_filter_sync(hdev); - list_for_each_entry(b, &hdev->accept_list, list) { - if (!(b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP)) - continue; + hci_dev_lock(hdev); - bt_dev_dbg(hdev, "Adding event filters for %pMR", &b->bdaddr); + list_for_each_entry(b, &hdev->accept_list, list) + if (b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP) + num_entries++; - err = hci_set_event_filter_sync(hdev, HCI_FLT_CONN_SETUP, - HCI_CONN_SETUP_ALLOW_BDADDR, - &b->bdaddr, - HCI_CONN_SETUP_AUTO_ON); + if (!num_entries) { + hci_dev_unlock(hdev); + goto update_scan; + } + + accept_list = kmalloc_array(num_entries, sizeof(*accept_list), + GFP_KERNEL); + if (!accept_list) { + hci_dev_unlock(hdev); + return -ENOMEM; + } + + i = 0; + list_for_each_entry(b, &hdev->accept_list, list) + if (b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP) + bacpy(&accept_list[i++], &b->bdaddr); + + hci_dev_unlock(hdev); + + for (i = 0; i < num_entries; i++) { + bt_dev_dbg(hdev, "Adding event filters for %pMR", + &accept_list[i]); + + err = hci_set_event_filter_sync(hdev, HCI_FLT_CONN_SETUP, + HCI_CONN_SETUP_ALLOW_BDADDR, + &accept_list[i], + HCI_CONN_SETUP_AUTO_ON); if (err) bt_dev_err(hdev, "Failed to set event filter for %pMR", - &b->bdaddr); + &accept_list[i]); else scan = SCAN_PAGE; } + kfree(accept_list); + +update_scan: if (scan && !scanning) hci_write_scan_enable_sync(hdev, scan); else if (!scan && scanning) @@ -7317,6 +7419,75 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn, sizeof(cp), &cp, HCI_CMD_TIMEOUT); } +static int hci_le_conn_rate_request_sync(struct hci_dev *hdev, void *data) +{ + struct hci_conn *conn = data; + struct hci_conn_params *params; + struct hci_cp_le_conn_rate cp; + + hci_dev_lock(hdev); + + /* The request was queued asynchronously so re-validate the connection + * and its parameters under hdev->lock. The connection may have been + * torn down, or may not have a valid handle yet (still connecting), + * and the parameters may have been removed in the meantime (e.g. by + * Load Connection Parameters). Snapshot the rate values so the + * blocking command below can run without holding hdev->lock. + */ + if (!hci_conn_valid(hdev, conn) || + HCI_CONN_HANDLE_UNSET(conn->handle)) { + hci_dev_unlock(hdev); + return -ECANCELED; + } + + params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); + if (!params) { + hci_dev_unlock(hdev); + return -ECANCELED; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = cpu_to_le16(conn->handle); + cp.interval_min = cpu_to_le16(params->rate_min_interval); + cp.interval_max = cpu_to_le16(params->rate_max_interval); + cp.subrate_min = cpu_to_le16(params->subrate_min); + cp.subrate_max = cpu_to_le16(params->subrate_max); + cp.max_latency = cpu_to_le16(params->max_latency); + cp.cont_num = cpu_to_le16(params->cont_num); + cp.supv_timeout = cpu_to_le16(params->rate_supv_timeout); + cp.min_ce_len = cpu_to_le16(0x0000); + cp.max_ce_len = cpu_to_le16(0x0000); + + hci_dev_unlock(hdev); + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_CONN_RATE, + sizeof(cp), &cp, HCI_CMD_TIMEOUT); +} + +static void hci_le_conn_rate_request_destroy(struct hci_dev *hdev, void *data, + int err) +{ + struct hci_conn *conn = data; + + hci_conn_put(conn); +} + +int hci_le_conn_rate_request(struct hci_dev *hdev, struct hci_conn *conn) +{ + int err; + + /* Hold a reference to the connection so it cannot be freed while the + * request is pending or running on the cmd_sync worker. + */ + err = hci_cmd_sync_queue(hdev, hci_le_conn_rate_request_sync, + hci_conn_get(conn), + hci_le_conn_rate_request_destroy); + if (err < 0) + hci_conn_put(conn); + + return err; +} + static void create_pa_complete(struct hci_dev *hdev, void *data, int err) { struct hci_conn *conn = data; diff --git a/net/bluetooth/hidp/Kconfig b/net/bluetooth/hidp/Kconfig index e08aae35351a..ba52c7296f18 100644 --- a/net/bluetooth/hidp/Kconfig +++ b/net/bluetooth/hidp/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_HIDP tristate "HIDP protocol support" - depends on BT_BREDR && HID + depends on BT && BT_BREDR && HID help HIDP (Human Interface Device Protocol) is a transport layer for HID reports. HIDP is required for the Bluetooth Human diff --git a/net/bluetooth/hidp/Makefile b/net/bluetooth/hidp/Makefile index f41b0aa02b23..53e139e41bdc 100644 --- a/net/bluetooth/hidp/Makefile +++ b/net/bluetooth/hidp/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_BT_HIDP) += hidp.o hidp-objs := core.o sock.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index a461c8a4efed..aa2ce78f56a2 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1536,6 +1536,7 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, lock_sock(sk); + memset(sa, 0, sizeof(struct sockaddr_iso)); addr->sa_family = AF_BLUETOOTH; if (peer) { @@ -1546,6 +1547,7 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, sa->iso_bdaddr_type = iso_pi(sk)->dst_type; if (hcon && (hcon->type == BIS_LINK || hcon->type == PA_LINK)) { + memset(sa->iso_bc, 0, sizeof(struct sockaddr_iso_bc)); sa->iso_bc->bc_sid = iso_pi(sk)->bc_sid; sa->iso_bc->bc_num_bis = iso_pi(sk)->bc_num_bis; memcpy(sa->iso_bc->bc_bis, iso_pi(sk)->bc_bis, @@ -1658,9 +1660,9 @@ static void iso_conn_defer_accept(struct hci_conn *conn) hci_send_cmd(hdev, HCI_OP_LE_ACCEPT_CIS, sizeof(cp), &cp); } -static void iso_conn_big_sync(struct sock *sk) +static int iso_conn_big_sync(struct sock *sk) { - int err; + int err = 0; struct hci_dev *hdev; struct iso_conn *conn; bdaddr_t src, dst; @@ -1675,7 +1677,7 @@ static void iso_conn_big_sync(struct sock *sk) hdev = hci_get_route(&dst, &src, src_type); if (!hdev) - return; + return -EHOSTUNREACH; /* hci_le_big_create_sync requires hdev lock to be held, since * it enqueues the HCI LE BIG Create Sync command via @@ -1691,8 +1693,10 @@ static void iso_conn_big_sync(struct sock *sk) * both before dereferencing conn->hcon. */ conn = iso_pi(sk)->conn; - if (!conn || !conn->hcon) + if (!conn || !conn->hcon) { + err = -ENOTCONN; goto unlock; + } if (!test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) { err = hci_conn_big_create_sync(hdev, conn->hcon, @@ -1708,6 +1712,8 @@ static void iso_conn_big_sync(struct sock *sk) release_sock(sk); hci_dev_unlock(hdev); hci_dev_put(hdev); + + return err; } static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, @@ -1732,10 +1738,19 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { release_sock(sk); - iso_conn_big_sync(sk); + err = iso_conn_big_sync(sk); lock_sock(sk); - sk->sk_state = BT_LISTEN; + /* The socket lock was dropped, so the + * connection may have been torn down + * meanwhile and iso_chan_del() may have + * already moved the socket to BT_CLOSED. + * Only move on to BT_LISTEN if the BIG sync + * was actually started and nothing else has + * changed the state. + */ + if (!err && sk->sk_state == BT_CONNECT2) + sk->sk_state = BT_LISTEN; } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; @@ -1746,10 +1761,11 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { release_sock(sk); - iso_conn_big_sync(sk); + err = iso_conn_big_sync(sk); lock_sock(sk); - sk->sk_state = BT_LISTEN; + if (!err && sk->sk_state == BT_CONNECTED) + sk->sk_state = BT_LISTEN; early_ret = true; } diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1156aba4e83c..ee459dd411f5 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1791,6 +1791,7 @@ static void l2cap_unregister_all_users(struct l2cap_conn *conn) } static void l2cap_conn_del(struct hci_conn *hcon, int err) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct l2cap_chan *chan, *l; @@ -1833,7 +1834,10 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err) hci_chan_del(conn->hchan); conn->hchan = NULL; + spin_lock(&hcon->proto_lock); hcon->l2cap_data = NULL; + spin_unlock(&hcon->proto_lock); + mutex_unlock(&conn->lock); l2cap_conn_put(conn); } @@ -7150,6 +7154,7 @@ static void process_pending_rx(struct work_struct *work) } static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct hci_chan *hchan; @@ -7168,8 +7173,6 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) } kref_init(&conn->ref); - hcon->l2cap_data = conn; - conn->hcon = hci_conn_get(hcon); conn->hchan = hchan; BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan); @@ -7198,6 +7201,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; + spin_lock(&hcon->proto_lock); + conn->hcon = hci_conn_get(hcon); + hcon->l2cap_data = conn; + spin_unlock(&hcon->proto_lock); + return conn; } @@ -7352,6 +7360,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, goto done; } + lockdep_assert_held(&hcon->hdev->lock); + conn = l2cap_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -7522,6 +7532,7 @@ static struct l2cap_chan *l2cap_global_fixed_chan(struct l2cap_chan *c, } static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) + __must_hold(&hcon->hdev->lock) { struct hci_dev *hdev = hcon->hdev; struct l2cap_conn *conn; @@ -7582,16 +7593,22 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) int l2cap_disconn_ind(struct hci_conn *hcon) { - struct l2cap_conn *conn = hcon->l2cap_data; + struct l2cap_conn *conn; + int ret = HCI_ERROR_REMOTE_USER_TERM; BT_DBG("hcon %p", hcon); - if (!conn) - return HCI_ERROR_REMOTE_USER_TERM; - return conn->disc_reason; + spin_lock(&hcon->proto_lock); + conn = hcon->l2cap_data; + if (conn) + ret = conn->disc_reason; + spin_unlock(&hcon->proto_lock); + + return ret; } static void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) + __must_hold(&hcon->hdev->lock) { if (hcon->type != ACL_LINK && hcon->type != LE_LINK) return; @@ -7619,6 +7636,7 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt) } static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct l2cap_chan *chan; @@ -7803,6 +7821,8 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, return -ENOENT; } + lockdep_assert_held(&hcon->hdev->lock); + hci_conn_enter_active_mode(hcon, BT_POWER_FORCE_ACTIVE_OFF); conn = hcon->l2cap_data; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 4058ff50cc27..735167f73f31 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1365,6 +1365,7 @@ static int __l2cap_wait_ack(struct sock *sk, struct l2cap_chan *chan) } static int l2cap_sock_shutdown(struct socket *sock, int how) + __context_unsafe(/* complex chan->conn locking */) { struct sock *sk = sock->sk; struct l2cap_chan *chan; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 167d75e34526..860c086011b7 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -861,6 +861,9 @@ static u32 get_supported_settings(struct hci_dev *hdev) if (past_receiver_capable(hdev)) settings |= MGMT_SETTING_PAST_RECEIVER; + if (le_sci_capable(hdev)) + settings |= MGMT_SETTING_SCI; + settings |= MGMT_SETTING_PHY_CONFIGURATION; return settings; @@ -952,6 +955,9 @@ static u32 get_current_settings(struct hci_dev *hdev) if (past_receiver_enabled(hdev)) settings |= MGMT_SETTING_PAST_RECEIVER; + if (le_sci_enabled(hdev)) + settings |= MGMT_SETTING_SCI; + return settings; } @@ -2431,11 +2437,15 @@ static int send_cancel(struct hci_dev *hdev, void *data) mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, 0, NULL, 0); - mgmt_pending_free(cmd); return 0; } +static void send_cancel_destroy(struct hci_dev *hdev, void *data, int err) +{ + mgmt_pending_free(data); +} + static int mesh_send_cancel(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -2456,7 +2466,8 @@ static int mesh_send_cancel(struct sock *sk, struct hci_dev *hdev, if (!cmd) err = -ENOMEM; else - err = hci_cmd_sync_queue(hdev, send_cancel, cmd, NULL); + err = hci_cmd_sync_queue(hdev, send_cancel, cmd, + send_cancel_destroy); if (err < 0) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, @@ -2642,7 +2653,7 @@ static int send_hci_cmd_sync(struct hci_dev *hdev, void *data) if (IS_ERR(skb)) { mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, mgmt_status(PTR_ERR(skb))); - goto done; + return 0; } mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, @@ -2650,12 +2661,14 @@ static int send_hci_cmd_sync(struct hci_dev *hdev, void *data) kfree_skb(skb); -done: - mgmt_pending_free(cmd); - return 0; } +static void send_hci_cmd_sync_destroy(struct hci_dev *hdev, void *data, int err) +{ + mgmt_pending_free(data); +} + static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -2668,12 +2681,21 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, return mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, MGMT_STATUS_INVALID_PARAMS); + /* The HCI command header carries the parameter length in a u8, a + * larger value would be truncated there while the parameters are + * still appended to the frame in full. + */ + if (le16_to_cpu(cp->params_len) > U8_MAX) + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, + MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); cmd = mgmt_pending_new(sk, MGMT_OP_HCI_CMD_SYNC, hdev, data, len); if (!cmd) err = -ENOMEM; else - err = hci_cmd_sync_queue(hdev, send_hci_cmd_sync, cmd, NULL); + err = hci_cmd_sync_queue(hdev, send_hci_cmd_sync, cmd, + send_hci_cmd_sync_destroy); if (err < 0) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, @@ -3870,6 +3892,8 @@ static int user_pairing_resp(struct sock *sk, struct hci_dev *hdev, } if (addr->type == BDADDR_LE_PUBLIC || addr->type == BDADDR_LE_RANDOM) { + lockdep_assert_held(&conn->hdev->lock); + err = smp_user_confirm_reply(conn, mgmt_op, passkey); if (!err) err = mgmt_cmd_complete(sk, hdev->id, mgmt_op, @@ -8170,6 +8194,118 @@ static int load_conn_param(struct sock *sk, struct hci_dev *hdev, void *data, NULL, 0); } +static int load_conn_subrate(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_load_conn_subrate *cp = data; + const u16 max_param_count = ((U16_MAX - sizeof(*cp)) / + sizeof(struct mgmt_conn_subrate)); + u16 param_count, expected_len; + int i; + + if (!lmp_le_capable(hdev) || !le_sci_capable(hdev)) + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_NOT_SUPPORTED); + + param_count = __le16_to_cpu(cp->param_count); + if (param_count > max_param_count) { + bt_dev_err(hdev, "too big param_count value %u", param_count); + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_INVALID_PARAMS); + } + + expected_len = struct_size(cp, params, param_count); + if (expected_len != len) { + bt_dev_err(hdev, "expected %u bytes, got %u bytes", + expected_len, len); + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_INVALID_PARAMS); + } + + bt_dev_dbg(hdev, "param_count %u", param_count); + + hci_dev_lock(hdev); + + for (i = 0; i < param_count; i++) { + struct mgmt_conn_subrate *param = &cp->params[i]; + struct hci_conn_params *hci_param; + u16 min, max, subrate_min, subrate_max; + u16 max_latency, cont_num, supv_timeout; + u8 addr_type; + + bt_dev_dbg(hdev, "Adding subrate %pMR (type %u)", + ¶m->addr.bdaddr, param->addr.type); + + if (param->addr.type == BDADDR_LE_PUBLIC) { + addr_type = ADDR_LE_DEV_PUBLIC; + } else if (param->addr.type == BDADDR_LE_RANDOM) { + addr_type = ADDR_LE_DEV_RANDOM; + } else { + bt_dev_err(hdev, "ignoring invalid connection subrate parameters"); + continue; + } + + min = le16_to_cpu(param->min_interval); + max = le16_to_cpu(param->max_interval); + subrate_min = le16_to_cpu(param->subrate_min); + subrate_max = le16_to_cpu(param->subrate_max); + max_latency = le16_to_cpu(param->max_latency); + cont_num = le16_to_cpu(param->cont_num); + supv_timeout = le16_to_cpu(param->supv_timeout); + + /* Validate the parameters before storing them. Reject + * logically inconsistent values instead of forwarding them to + * the controller. + */ + if (min > max || subrate_min > subrate_max || + subrate_min < 1 || supv_timeout < 1) { + bt_dev_err(hdev, "ignoring invalid connection subrate parameters"); + continue; + } + + hci_param = hci_conn_params_add(hdev, ¶m->addr.bdaddr, + addr_type); + if (!hci_param) { + bt_dev_err(hdev, "failed to add connection parameters"); + continue; + } + + hci_param->rate_min_interval = min; + hci_param->rate_max_interval = max; + hci_param->subrate_min = subrate_min; + hci_param->subrate_max = subrate_max; + hci_param->max_latency = max_latency; + hci_param->cont_num = cont_num; + hci_param->rate_supv_timeout = supv_timeout; + + /* If the device is connected as central check if the + * connection rate parameters need to be updated. + */ + if (!i && param_count == 1) { + struct hci_conn *conn; + + conn = hci_conn_hash_lookup_le(hdev, + &hci_param->addr, + addr_type); + if (conn && conn->state == BT_CONNECTED && + conn->role == HCI_ROLE_MASTER && + (conn->le_rate_interval < min || + conn->le_rate_interval > max || + conn->le_subrate < subrate_min || + conn->le_subrate > subrate_max || + conn->le_rate_latency != max_latency || + conn->le_cont_num != cont_num || + conn->le_rate_supv_timeout != supv_timeout)) + hci_le_conn_rate_request(hdev, conn); + } + } + + hci_dev_unlock(hdev); + + return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, 0, + NULL, 0); +} + static int set_external_config(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -9587,6 +9723,8 @@ static const struct hci_mgmt_handler mgmt_handlers[] = { HCI_MGMT_VAR_LEN }, { mesh_send_cancel, MGMT_MESH_SEND_CANCEL_SIZE }, { mgmt_hci_cmd_sync, MGMT_HCI_CMD_SYNC_SIZE, HCI_MGMT_VAR_LEN }, + { load_conn_subrate, MGMT_LOAD_CONN_SUBRATE_SIZE, + HCI_MGMT_VAR_LEN }, }; void mgmt_index_added(struct hci_dev *hdev) @@ -10735,6 +10873,23 @@ int mgmt_init(void) return hci_mgmt_chan_register(&chan); } +void mgmt_conn_subrate_notify(struct hci_dev *hdev, struct hci_conn *conn, + u8 status) +{ + struct mgmt_ev_conn_subrate ev; + + bacpy(&ev.addr.bdaddr, &conn->dst); + ev.addr.type = link_to_bdaddr(conn->type, conn->dst_type); + ev.status = mgmt_status(status); + ev.interval = cpu_to_le16(conn->le_rate_interval); + ev.subrate = cpu_to_le16(conn->le_subrate); + ev.latency = cpu_to_le16(conn->le_rate_latency); + ev.cont_num = cpu_to_le16(conn->le_cont_num); + ev.supv_timeout = cpu_to_le16(conn->le_rate_supv_timeout); + + mgmt_event(MGMT_EV_CONN_SUBRATE, hdev, &ev, sizeof(ev), NULL); +} + void mgmt_exit(void) { hci_mgmt_chan_unregister(&chan); diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index d7badce8746c..ded68568e6c9 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -165,6 +165,11 @@ static bool read_supported_features(struct hci_dev *hdev, if (rp->sub_opcode != MSFT_OP_READ_SUPPORTED_FEATURES) goto failed; + if (skb->len < sizeof(*rp) + rp->evt_prefix_len) { + bt_dev_err(hdev, "MSFT event prefix length mismatch"); + goto failed; + } + if (rp->evt_prefix_len > 0) { msft->evt_prefix = kmemdup(rp->evt_prefix, rp->evt_prefix_len, GFP_KERNEL); diff --git a/net/bluetooth/rfcomm/Kconfig b/net/bluetooth/rfcomm/Kconfig index 9b9953ebf4c0..e7af2d565cea 100644 --- a/net/bluetooth/rfcomm/Kconfig +++ b/net/bluetooth/rfcomm/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_RFCOMM tristate "RFCOMM protocol support" - depends on BT_BREDR + depends on BT && BT_BREDR help RFCOMM provides connection oriented stream transport. RFCOMM support is required for Dialup Networking, OBEX and other Bluetooth diff --git a/net/bluetooth/rfcomm/Makefile b/net/bluetooth/rfcomm/Makefile index 593e5c48c131..15f909f40f25 100644 --- a/net/bluetooth/rfcomm/Makefile +++ b/net/bluetooth/rfcomm/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_BT_RFCOMM) += rfcomm.o rfcomm-y := core.o sock.o rfcomm-$(CONFIG_BT_RFCOMM_TTY) += tty.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 2e8c080b4d9e..9cdfea666a2c 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1331,7 +1331,10 @@ static struct rfcomm_session *rfcomm_recv_disc(struct rfcomm_session *s, return s; } -void rfcomm_dlc_accept(struct rfcomm_dlc *d) +/* Must be called with rfcomm_mutex held, so that the session cannot be + * unlinked from under us. + */ +static void __rfcomm_dlc_accept(struct rfcomm_dlc *d) { struct sock *sk = d->session->sock->sk; struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; @@ -1353,6 +1356,21 @@ void rfcomm_dlc_accept(struct rfcomm_dlc *d) rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); } +void rfcomm_dlc_accept(struct rfcomm_dlc *d) +{ + rfcomm_lock(); + + /* rfcomm_recv_disc() sets the dlc state to BT_CLOSED before calling + * __rfcomm_dlc_close(), so the RFCOMM_DEFER_SETUP handshake there is + * skipped and the session can already be unlinked by the time the + * deferred accept runs from rfcomm_sock_recvmsg(). + */ + if (d->session) + __rfcomm_dlc_accept(d); + + rfcomm_unlock(); +} + static void rfcomm_check_accept(struct rfcomm_dlc *d) { if (rfcomm_check_security(d)) { @@ -1365,7 +1383,7 @@ static void rfcomm_check_accept(struct rfcomm_dlc *d) d->state_change(d, 0); rfcomm_dlc_unlock(d); } else - rfcomm_dlc_accept(d); + __rfcomm_dlc_accept(d); } else { set_bit(RFCOMM_AUTH_PENDING, &d->flags); rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT); @@ -1958,7 +1976,7 @@ static void rfcomm_process_dlcs(struct rfcomm_session *s) d->state_change(d, 0); rfcomm_dlc_unlock(d); } else - rfcomm_dlc_accept(d); + __rfcomm_dlc_accept(d); } continue; } else if (test_and_clear_bit(RFCOMM_AUTH_REJECT, &d->flags)) { diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index feb302a491fa..958081adb9b5 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -60,6 +60,7 @@ static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) } static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) + __must_hold(&d->lock) { struct sock *sk = d->owner, *parent; diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 4a32e4f80b48..6091c47cb002 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -2328,12 +2328,15 @@ static void smp_send_security_req(struct smp_chan *smp, __u8 auth) int smp_conn_security(struct hci_conn *hcon, __u8 sec_level) { - struct l2cap_conn *conn = hcon->l2cap_data; + struct l2cap_conn *conn; struct l2cap_chan *chan; struct smp_chan *smp; __u8 authreq; int ret; + /* Caller shall ensure there can be no race with l2cap_conn_del() */ + conn = context_unsafe(hcon->l2cap_data); + bt_dev_dbg(hcon->hdev, "conn %p hcon %p level 0x%2.2x", conn, hcon, sec_level); @@ -2422,6 +2425,8 @@ int smp_cancel_and_remove_pairing(struct hci_dev *hdev, bdaddr_t *bdaddr, if (!hcon) goto done; + lockdep_assert_held(&hcon->hdev->lock); + conn = hcon->l2cap_data; if (!conn) goto done; diff --git a/net/bluetooth/smp.h b/net/bluetooth/smp.h index eac27bd541bb..c86c46389007 100644 --- a/net/bluetooth/smp.h +++ b/net/bluetooth/smp.h @@ -180,11 +180,13 @@ enum smp_key_pref { /* SMP Commands */ int smp_cancel_and_remove_pairing(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 addr_type); + u8 addr_type) + __must_hold(&hdev->lock); bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level, enum smp_key_pref key_pref); int smp_conn_security(struct hci_conn *hcon, __u8 sec_level); -int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey); +int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey) + __must_hold(&conn->hdev->lock); bool smp_irk_matches(struct hci_dev *hdev, const u8 irk[16], const bdaddr_t *bdaddr); diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index 23eb6931a2b4..b6e5a86b6a92 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -19,6 +19,7 @@ #include #if IS_ENABLED(CONFIG_IPV6) #include +#include #endif #include "br_private.h" @@ -234,21 +235,18 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, #endif #if IS_ENABLED(CONFIG_IPV6) -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb, struct nd_msg *msg) +/* Validate skb as an NS/NA and linearize it for br_nd_send()'s ND + * option parsing; returns the nd_msg, or NULL on failure. + */ +struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb) { - struct nd_msg *m; - - m = skb_header_pointer(skb, skb_network_offset(skb) + - sizeof(struct ipv6hdr), sizeof(*msg), msg); - if (!m) + if (ndisc_check_ns_na(skb)) return NULL; - if (m->icmph.icmp6_code != 0 || - (m->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION && - m->icmph.icmp6_type != NDISC_NEIGHBOUR_ADVERTISEMENT)) + if (skb_linearize(skb)) return NULL; - return m; + return (struct nd_msg *)skb_transport_header(skb); } static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, @@ -257,17 +255,18 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, { struct net_device *dev = request->dev; struct net_bridge_vlan_group *vg; + struct ndisc_options ndopts; struct nd_msg *na, *ns; struct sk_buff *reply; struct ipv6hdr *pip6; int na_olen = 8; /* opt hdr + ETH_ALEN for target */ int ns_olen; - int i, len; u8 *daddr; bool dad; u16 pvid; + int len; - if (!dev || skb_linearize(request)) + if (!dev) return; len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + @@ -284,23 +283,23 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, skb_set_mac_header(reply, 0); daddr = eth_hdr(request)->h_source; - ns = (struct nd_msg *)(skb_network_header(request) + - sizeof(struct ipv6hdr)); + ns = (struct nd_msg *)skb_transport_header(request); - /* Do we need option processing ? */ - ns_olen = request->len - (skb_network_offset(request) + - sizeof(struct ipv6hdr)) - sizeof(*ns); - for (i = 0; i < ns_olen - 1; i += (ns->opt[i + 1] << 3)) { - if (!ns->opt[i + 1] || i + (ns->opt[i + 1] << 3) > ns_olen) { - kfree_skb(reply); - return; - } - if (ns->opt[i] == ND_OPT_SOURCE_LL_ADDR) { - if ((ns->opt[i + 1] << 3) >= - sizeof(struct nd_opt_hdr) + ETH_ALEN) - daddr = ns->opt + i + sizeof(struct nd_opt_hdr); - break; - } + /* Derive the option length from the IPv6 payload length so that any + * trailing L2 padding in the skb is not parsed as ND options. + */ + ns_olen = ntohs(ipv6_hdr(request)->payload_len) - sizeof(*ns); + if (!ndisc_parse_options(dev, ns->opt, ns_olen, &ndopts)) { + kfree_skb(reply); + return; + } + + if (ndopts.nd_opts_src_lladdr) { + u8 *lladdr; + + lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev); + if (lladdr) + daddr = lladdr; } dad = ipv6_addr_any(&ipv6_hdr(request)->saddr); diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index e7f343ab22d3..ff55dab73632 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -80,9 +80,9 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev) pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct nd_msg)) && ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) { - struct nd_msg *msg, _msg; + struct nd_msg *msg; - msg = br_is_nd_neigh_msg(skb, &_msg); + msg = br_is_nd_neigh_msg(skb); if (msg) br_do_suppress_nd(skb, br, vid, NULL, msg); } diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index ddb8f002a40e..d87a5f9fa92b 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -176,9 +176,9 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct nd_msg)) && ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) { - struct nd_msg *msg, _msg; + struct nd_msg *msg; - msg = br_is_nd_neigh_msg(skb, &_msg); + msg = br_is_nd_neigh_msg(skb); if (msg) br_do_suppress_nd(skb, br, vid, p, msg); } diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 00aa9b2879d6..75e1e2a8fc83 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -926,7 +926,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm struct net_bridge_port_group *pg, __be32 ip_dst, __be32 group, bool with_srcs, bool over_lmqt, - u8 sflag, u8 *igmp_type, + u8 sflag, bool *need_rexmit) { struct net_bridge_port *p = pg ? pg->key.port : NULL; @@ -1006,7 +1006,6 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm skb_set_transport_header(skb, skb->len); mrt = group ? brmctx->multicast_last_member_interval : brmctx->multicast_query_response_interval; - *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY; switch (brmctx->multicast_igmp_version) { case 2: @@ -1072,7 +1071,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm const struct in6_addr *ip6_dst, const struct in6_addr *group, bool with_srcs, bool over_llqt, - u8 sflag, u8 *igmp_type, + u8 sflag, bool *need_rexmit) { struct net_bridge_port *p = pg ? pg->key.port : NULL; @@ -1166,7 +1165,6 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm interval = ipv6_addr_any(group) ? brmctx->multicast_query_response_interval : brmctx->multicast_last_member_interval; - *igmp_type = ICMPV6_MGM_QUERY; switch (brmctx->multicast_mld_version) { case 1: mldq = (struct mld_msg *)icmp6_hdr(skb); @@ -1237,8 +1235,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, struct br_ip *ip_dst, struct br_ip *group, bool with_srcs, bool over_lmqt, - u8 sflag, u8 *igmp_type, - bool *need_rexmit) + u8 sflag, bool *need_rexmit) { __be32 ip4_dst; @@ -1248,8 +1245,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, return br_ip4_multicast_alloc_query(brmctx, pmctx, pg, ip4_dst, group->dst.ip4, with_srcs, over_lmqt, - sflag, igmp_type, - need_rexmit); + sflag, need_rexmit); #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): { struct in6_addr ip6_dst; @@ -1263,8 +1259,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, return br_ip6_multicast_alloc_query(brmctx, pmctx, pg, &ip6_dst, &group->dst.ip6, with_srcs, over_lmqt, - sflag, igmp_type, - need_rexmit); + sflag, need_rexmit); } #endif } @@ -1774,6 +1769,64 @@ static void br_multicast_select_own_querier(struct net_bridge_mcast *brmctx, #endif } +static u8 br_multicast_query_type(const struct sk_buff *skb) +{ + return skb->protocol == htons(ETH_P_IP) ? IGMP_HOST_MEMBERSHIP_QUERY : + ICMPV6_MGM_QUERY; +} + +static void br_multicast_port_query_queue_work(struct work_struct *work) +{ + struct net_bridge_mcast_port *pmctx; + struct sk_buff_head list; + struct sk_buff *skb; + + pmctx = container_of(work, struct net_bridge_mcast_port, + query_queue_work); + + __skb_queue_head_init(&list); + spin_lock_bh(&pmctx->query_queue.lock); + skb_queue_splice_tail_init(&pmctx->query_queue, &list); + spin_unlock_bh(&pmctx->query_queue.lock); + + while ((skb = __skb_dequeue(&list))) { + u8 query_type = br_multicast_query_type(skb); + + local_bh_disable(); + br_multicast_count(pmctx->port->br, pmctx->port, skb, + query_type, BR_MCAST_DIR_TX); + NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, dev_net(skb->dev), + NULL, skb, NULL, skb->dev, br_dev_queue_push_xmit); + local_bh_enable(); + } +} + +static void br_multicast_query_queue_work(struct work_struct *work) +{ + struct net_bridge_mcast *brmctx; + struct sk_buff_head list; + struct sk_buff *skb; + + brmctx = container_of(work, struct net_bridge_mcast, query_queue_work); + + __skb_queue_head_init(&list); + spin_lock_bh(&brmctx->query_queue.lock); + skb_queue_splice_tail_init(&brmctx->query_queue, &list); + spin_unlock_bh(&brmctx->query_queue.lock); + + while ((skb = __skb_dequeue(&list))) { + u8 query_type = br_multicast_query_type(skb); + + local_bh_disable(); + br_multicast_count(brmctx->br, NULL, skb, query_type, + BR_MCAST_DIR_RX); + netif_rx(skb); + local_bh_enable(); + } +} + +#define BR_MULTICAST_QUERY_QUEUE_LEN_MAX 1000 + static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, struct net_bridge_mcast_port *pmctx, struct net_bridge_port_group *pg, @@ -1783,28 +1836,30 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, u8 sflag, bool *need_rexmit) { + struct sk_buff_head *queue; bool over_lmqt = !!sflag; struct sk_buff *skb; - u8 igmp_type; if (!br_multicast_ctx_should_use(brmctx, pmctx) || !br_multicast_ctx_matches_vlan_snooping(brmctx)) return; + queue = pmctx ? &pmctx->query_queue : &brmctx->query_queue; + again_under_lmqt: + if (skb_queue_len_lockless(queue) >= BR_MULTICAST_QUERY_QUEUE_LEN_MAX) + return; + skb = br_multicast_alloc_query(brmctx, pmctx, pg, ip_dst, group, - with_srcs, over_lmqt, sflag, &igmp_type, + with_srcs, over_lmqt, sflag, need_rexmit); if (!skb) return; if (pmctx) { skb->dev = pmctx->port->dev; - br_multicast_count(brmctx->br, pmctx->port, skb, igmp_type, - BR_MCAST_DIR_TX); - NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, - dev_net(pmctx->port->dev), NULL, skb, NULL, skb->dev, - br_dev_queue_push_xmit); + skb_queue_tail(queue, skb); + queue_work(system_highpri_wq, &pmctx->query_queue_work); if (over_lmqt && with_srcs && sflag) { over_lmqt = false; @@ -1812,9 +1867,8 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, } } else { br_multicast_select_own_querier(brmctx, group, skb); - br_multicast_count(brmctx->br, NULL, skb, igmp_type, - BR_MCAST_DIR_RX); - netif_rx(skb); + skb_queue_tail(queue, skb); + queue_work(system_highpri_wq, &brmctx->query_queue_work); } } @@ -1997,6 +2051,10 @@ void br_multicast_port_ctx_init(struct net_bridge_port *port, pmctx->port = port; pmctx->vlan = vlan; pmctx->multicast_router = MDB_RTR_TYPE_TEMP_QUERY; + + skb_queue_head_init(&pmctx->query_queue); + INIT_WORK(&pmctx->query_queue_work, br_multicast_port_query_queue_work); + timer_setup(&pmctx->ip4_mc_router_timer, br_ip4_multicast_router_expired, 0); timer_setup(&pmctx->ip4_own_query.timer, @@ -2038,6 +2096,8 @@ void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx) del |= br_ip4_multicast_rport_del(pmctx); br_multicast_rport_del_notify(pmctx, del); spin_unlock_bh(&br->multicast_lock); + cancel_work_sync(&pmctx->query_queue_work); + __skb_queue_purge(&pmctx->query_queue); } int br_multicast_add_port(struct net_bridge_port *port) @@ -4113,6 +4173,9 @@ void br_multicast_ctx_init(struct net_bridge *br, seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock); #endif + skb_queue_head_init(&brmctx->query_queue); + INIT_WORK(&brmctx->query_queue_work, br_multicast_query_queue_work); + timer_setup(&brmctx->ip4_mc_router_timer, br_ip4_multicast_local_router_expired, 0); timer_setup(&brmctx->ip4_other_query.timer, @@ -4136,6 +4199,8 @@ void br_multicast_ctx_init(struct net_bridge *br, void br_multicast_ctx_deinit(struct net_bridge_mcast *brmctx) { __br_multicast_stop(brmctx); + cancel_work_sync(&brmctx->query_queue_work); + __skb_queue_purge(&brmctx->query_queue); } void br_multicast_init(struct net_bridge *br) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index d3880f31edc4..d337b1cfb980 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -131,6 +131,8 @@ struct net_bridge_mcast_port { unsigned char multicast_router; u32 mdb_n_entries; u32 mdb_max_entries; + struct sk_buff_head query_queue; + struct work_struct query_queue_work; #endif /* CONFIG_BRIDGE_IGMP_SNOOPING */ }; @@ -167,6 +169,8 @@ struct net_bridge_mcast { struct bridge_mcast_own_query ip6_own_query; struct bridge_mcast_querier ip6_querier; #endif /* IS_ENABLED(CONFIG_IPV6) */ + struct sk_buff_head query_queue; + struct work_struct query_queue_work; #endif /* CONFIG_BRIDGE_IGMP_SNOOPING */ }; @@ -2363,7 +2367,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p); void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p, struct nd_msg *msg); -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb, struct nd_msg *m); +struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb); bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid); bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid); #endif diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 31c1b2cf75d9..1e0e436629ec 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -1136,7 +1136,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, if (err) goto out; - if (br_vlan_delete(br, old_pvid)) + if (!br_vlan_delete(br, old_pvid)) br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN); __set_bit(0, changed); @@ -1158,7 +1158,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, &vlchange, extack); if (err) goto err_port; - if (nbp_vlan_delete(p, old_pvid)) + if (!nbp_vlan_delete(p, old_pvid)) br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN); __set_bit(p->port_no, changed); diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index 96c9a8f57c87..0bf541936f60 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -39,6 +39,8 @@ #define COUNTER_OFFSET(n) (SMP_ALIGN(n * sizeof(struct ebt_counter))) #define COUNTER_BASE(c, n, cpu) ((struct ebt_counter *)(((char *)c) + \ COUNTER_OFFSET(n) * cpu)) +#define MAX_EBT_ENTRIES (((INT_MAX - sizeof(struct ebt_table_info)) / \ + NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) struct ebt_pernet { struct list_head tables; @@ -1126,10 +1128,9 @@ static int do_replace(struct net *net, sockptr_t arg, unsigned int len) return -EINVAL; /* overflow check */ - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; tmp.name[sizeof(tmp.name) - 1] = 0; @@ -2269,10 +2270,9 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl, if (tmp.entries_size == 0) return -EINVAL; - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry)); diff --git a/net/compat.c b/net/compat.c index d68cf9c3aad5..6bdf4a2c9077 100644 --- a/net/compat.c +++ b/net/compat.c @@ -286,7 +286,7 @@ static int scm_max_fds_compat(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int); } -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc) { struct compat_cmsghdr __user *cm = (struct compat_cmsghdr __user *)msg->msg_control_user; @@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) int err = 0, i; for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc); if (err < 0) break; } diff --git a/net/core/dev.c b/net/core/dev.c index ece6700536d9..38336858c168 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -742,51 +742,73 @@ EXPORT_SYMBOL_GPL(dev_fill_metadata_dst); static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack) { - int k = stack->num_paths++; - - if (k >= NET_DEVICE_PATH_STACK_MAX) + if (stack->num_paths + 1 > NET_DEVICE_PATH_STACK_MAX) return NULL; - return &stack->path[k]; + return &stack->path[stack->num_paths]; } -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +void dev_fill_forward_path_release(struct net_device_path_stack *stack) +{ + struct net_device_path *path; + int k; + + if (stack->num_paths == 0) + return; + + for (k = stack->num_paths - 1; k >= 0; k--) { + path = &stack->path[k]; + switch (path->type) { + case DEV_PATH_TUN: + dst_release(path->tun.dst); + break; + default: + break; + } + } +} +EXPORT_SYMBOL_GPL(dev_fill_forward_path_release); + +int dev_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path_stack *stack) { const struct net_device *last_dev; - struct net_device_path_ctx ctx = { - .dev = dev, - }; struct net_device_path *path; int ret = 0; - memcpy(ctx.daddr, daddr, sizeof(ctx.daddr)); stack->num_paths = 0; - while (ctx.dev && ctx.dev->netdev_ops->ndo_fill_forward_path) { - last_dev = ctx.dev; + while (ctx->dev && ctx->dev->netdev_ops->ndo_fill_forward_path) { + last_dev = ctx->dev; path = dev_fwd_path(stack); if (!path) - return -1; + goto err_out; memset(path, 0, sizeof(struct net_device_path)); - ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path); + ret = ctx->dev->netdev_ops->ndo_fill_forward_path(ctx, path); if (ret < 0) - return -1; + goto err_out; - if (WARN_ON_ONCE(last_dev == ctx.dev)) - return -1; + stack->num_paths++; + if (WARN_ON_ONCE(last_dev == ctx->dev)) + goto err_out; } - if (!ctx.dev) + if (!ctx->dev) return ret; path = dev_fwd_path(stack); if (!path) - return -1; - path->type = DEV_PATH_ETHERNET; - path->dev = ctx.dev; + goto err_out; - return ret; + path->type = DEV_PATH_ETHERNET; + path->dev = ctx->dev; + stack->num_paths++; + + return 0; +err_out: + dev_fill_forward_path_release(stack); + + return -1; } EXPORT_SYMBOL_GPL(dev_fill_forward_path); @@ -1802,6 +1824,7 @@ void netif_close_many(struct list_head *head, bool unlink) __dev_close_many(head); list_for_each_entry_safe(dev, tmp, head, close_list) { + netdev_assert_locked_ops_compat(dev); rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL); call_netdevice_notifiers(NETDEV_DOWN, dev); if (unlink) @@ -1912,9 +1935,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb, struct net_device *dev) { if (dev->flags & IFF_UP) { + netdev_lock_ops(dev); call_netdevice_notifier(nb, NETDEV_GOING_DOWN, dev); call_netdevice_notifier(nb, NETDEV_DOWN, dev); + netdev_unlock_ops(dev); } call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev); } @@ -2627,13 +2652,15 @@ EXPORT_SYMBOL_GPL(dev_queue_xmit_nit); */ static void netif_setup_tc(struct net_device *dev, unsigned int txq) { + struct netdev_tc_txq res; int i; - struct netdev_tc_txq *tc = &dev->tc_to_txq[0]; + + res.combined = READ_ONCE(dev->tc_to_txq[0].combined); /* If TC0 is invalidated disable TC mapping */ - if (tc->offset + tc->count > txq) { + if (res.offset + res.count > txq) { netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n"); - dev->num_tc = 0; + WRITE_ONCE(dev->num_tc, 0); return; } @@ -2641,8 +2668,8 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq) for (i = 1; i < TC_BITMASK + 1; i++) { int q = netdev_get_prio_tc_map(dev, i); - tc = &dev->tc_to_txq[q]; - if (tc->offset + tc->count > txq) { + res.combined = READ_ONCE(dev->tc_to_txq[q].combined); + if (res.offset + res.count > txq) { netdev_warn(dev, "Number of in use tx queues changed. Priority %i to tc mapping %i is no longer valid. Setting map to 0\n", i, q); netdev_set_prio_tc_map(dev, i, 0); @@ -2652,13 +2679,16 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq) int netdev_txq_to_tc(struct net_device *dev, unsigned int txq) { - if (dev->num_tc) { + if (READ_ONCE(dev->num_tc)) { struct netdev_tc_txq *tc = &dev->tc_to_txq[0]; int i; /* walk through the TCs and see if it falls into any of them */ for (i = 0; i < TC_MAX_QUEUE; i++, tc++) { - if ((txq - tc->offset) < tc->count) + struct netdev_tc_txq res; + + res.combined = READ_ONCE(tc->combined); + if ((txq - res.offset) < res.count) return i; } @@ -2851,18 +2881,19 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, u16 index, enum xps_map_type type) { struct xps_dev_maps *dev_maps, *new_dev_maps = NULL, *old_dev_maps = NULL; + int maps_sz, num_tc = 1, tc = 0, dev_num_tc; const unsigned long *online_mask = NULL; bool active = false, copy = false; int i, j, tci, numa_node_id = -2; - int maps_sz, num_tc = 1, tc = 0; struct xps_map *map, *new_map; unsigned int nr_ids; WARN_ON_ONCE(index >= dev->num_tx_queues); - if (dev->num_tc) { + dev_num_tc = READ_ONCE(dev->num_tc); + if (dev_num_tc) { /* Do not allow XPS on subordinate device directly */ - num_tc = dev->num_tc; + num_tc = dev_num_tc; if (num_tc < 0) return -EINVAL; @@ -3078,28 +3109,36 @@ static void netdev_unbind_all_sb_channels(struct net_device *dev) void netdev_reset_tc(struct net_device *dev) { + int i; + #ifdef CONFIG_XPS netif_reset_xps_queues_gt(dev, 0); #endif netdev_unbind_all_sb_channels(dev); /* Reset TC configuration of device */ - dev->num_tc = 0; - memset(dev->tc_to_txq, 0, sizeof(dev->tc_to_txq)); - memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map)); + WRITE_ONCE(dev->num_tc, 0); + for (i = 0; i < TC_MAX_QUEUE; i++) + WRITE_ONCE(dev->tc_to_txq[i].combined, 0); + for (i = 0; i <= TC_BITMASK; i++) + WRITE_ONCE(dev->prio_tc_map[i], 0); } EXPORT_SYMBOL(netdev_reset_tc); int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset) { - if (tc >= dev->num_tc) + struct netdev_tc_txq res = { + .count = count, + .offset = offset, + }; + + if (tc >= READ_ONCE(dev->num_tc)) return -EINVAL; #ifdef CONFIG_XPS netif_reset_xps_queues(dev, offset, count); #endif - dev->tc_to_txq[tc].count = count; - dev->tc_to_txq[tc].offset = offset; + WRITE_ONCE(dev->tc_to_txq[tc].combined, res.combined); return 0; } EXPORT_SYMBOL(netdev_set_tc_queue); @@ -3114,7 +3153,7 @@ int netdev_set_num_tc(struct net_device *dev, u8 num_tc) #endif netdev_unbind_all_sb_channels(dev); - dev->num_tc = num_tc; + WRITE_ONCE(dev->num_tc, num_tc); return 0; } EXPORT_SYMBOL(netdev_set_num_tc); @@ -3123,12 +3162,15 @@ void netdev_unbind_sb_channel(struct net_device *dev, struct net_device *sb_dev) { struct netdev_queue *txq = &dev->_tx[dev->num_tx_queues]; + int i; #ifdef CONFIG_XPS netif_reset_xps_queues_gt(sb_dev, 0); #endif - memset(sb_dev->tc_to_txq, 0, sizeof(sb_dev->tc_to_txq)); - memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map)); + for (i = 0; i < TC_MAX_QUEUE; i++) + WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0); + for (i = 0; i <= TC_BITMASK; i++) + WRITE_ONCE(sb_dev->prio_tc_map[i], 0); while (txq-- != &dev->_tx[0]) { if (txq->sb_dev == sb_dev) @@ -3142,7 +3184,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, u8 tc, u16 count, u16 offset) { /* Make certain the sb_dev and dev are already configured */ - if (sb_dev->num_tc >= 0 || tc >= dev->num_tc) + if (READ_ONCE(sb_dev->num_tc) >= 0 || tc >= READ_ONCE(dev->num_tc)) return -EINVAL; /* We cannot hand out queues we don't have */ @@ -3150,8 +3192,12 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, return -EINVAL; /* Record the mapping */ - sb_dev->tc_to_txq[tc].count = count; - sb_dev->tc_to_txq[tc].offset = offset; + struct netdev_tc_txq res = { + .count = count, + .offset = offset, + }; + + WRITE_ONCE(sb_dev->tc_to_txq[tc].combined, res.combined); /* Provide a way for Tx queue to find the tc_to_txq map or * XPS map for itself. @@ -3177,7 +3223,7 @@ int netdev_set_sb_channel(struct net_device *dev, u16 channel) if (channel > S16_MAX) return -EINVAL; - dev->num_tc = -channel; + WRITE_ONCE(dev->num_tc, -channel); return 0; } @@ -3206,7 +3252,7 @@ int netif_set_real_num_tx_queues(struct net_device *dev, unsigned int txq) if (rc) return rc; - if (dev->num_tc) + if (READ_ONCE(dev->num_tc)) netif_setup_tc(dev, txq); net_shaper_set_real_num_tx_queues(dev, txq); @@ -3515,11 +3561,13 @@ static u16 skb_tx_hash(const struct net_device *dev, u16 qoffset = 0; u16 qcount = dev->real_num_tx_queues; - if (dev->num_tc) { + if (READ_ONCE(dev->num_tc)) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); + struct netdev_tc_txq res; - qoffset = sb_dev->tc_to_txq[tc].offset; - qcount = sb_dev->tc_to_txq[tc].count; + res.combined = READ_ONCE(sb_dev->tc_to_txq[tc].combined); + qoffset = res.offset; + qcount = res.count; if (unlikely(!qcount)) { net_warn_ratelimited("%s: invalid qcount, qoffset %u for tc %u\n", sb_dev->name, qoffset, tc); @@ -9795,6 +9843,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, { unsigned int changes = dev->flags ^ old_flags; + netdev_assert_locked_ops_compat(dev); + if (gchanges) rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh); @@ -10333,6 +10383,37 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode, netdev_assert_locked_ops_compat(dev); + if (prog) { + enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB + ? XDP_MODE_DRV : XDP_MODE_SKB; + bool offload = mode == XDP_MODE_HW; + + if (!offload && dev_xdp_prog(dev, other_mode)) { + NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); + return -EEXIST; + } + if (!offload && bpf_prog_is_offloaded(prog->aux)) { + NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && !bpf_offload_dev_match(prog, dev)) { + NL_SET_ERR_MSG(extack, "Program bound to different device"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && mode == XDP_MODE_SKB) { + NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_DEVMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_CPUMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); + return -EINVAL; + } + } + if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED && prog && !prog->aux->xdp_has_frags) { NL_SET_ERR_MSG(extack, "unable to install XDP to device using tcp-data-split"); @@ -10472,38 +10553,10 @@ static int dev_xdp_attach(struct net_device *dev, struct netlink_ext_ack *extack new_prog = link->link.prog; if (new_prog) { - bool offload = mode == XDP_MODE_HW; - enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB - ? XDP_MODE_DRV : XDP_MODE_SKB; - if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) && cur_prog) { NL_SET_ERR_MSG(extack, "XDP program already attached"); return -EBUSY; } - if (!offload && dev_xdp_prog(dev, other_mode)) { - NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); - return -EEXIST; - } - if (!offload && bpf_prog_is_offloaded(new_prog->aux)) { - NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && !bpf_offload_dev_match(new_prog, dev)) { - NL_SET_ERR_MSG(extack, "Program bound to different device"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && mode == XDP_MODE_SKB) { - NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_DEVMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_CPUMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); - return -EINVAL; - } } /* don't call drivers if the effective program didn't change */ @@ -11619,8 +11672,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list) rtnl_lock(); /* Rebroadcast unregister notification */ - list_for_each_entry(dev, list, todo_list) + list_for_each_entry(dev, list, todo_list) { + struct net *net = dev_net(dev); + + __rtnl_net_lock(net); call_netdevice_notifiers(NETDEV_UNREGISTER, dev); + __rtnl_net_unlock(net); + } __rtnl_unlock(); rcu_barrier(); @@ -12098,6 +12156,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, INIT_LIST_HEAD(&dev->napi_list); INIT_LIST_HEAD(&dev->unreg_list); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + INIT_LIST_HEAD(&dev->unreg_list_net); +#endif INIT_LIST_HEAD(&dev->close_list); INIT_LIST_HEAD(&dev->link_watch_list); INIT_LIST_HEAD(&dev->adj_list.upper); @@ -12164,6 +12225,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, free_percpu(dev->pcpu_refcnt); free_dev: #endif + ref_tracker_dir_exit(&dev->refcnt_tracker); kvfree(dev); return NULL; } @@ -12315,6 +12377,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head) { ASSERT_RTNL(); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net)); +#endif + if (head) { list_move_tail(&dev->unreg_list, head); } else { @@ -12492,6 +12558,16 @@ void unregister_netdevice_many_notify(struct list_head *head, synchronize_net(); list_for_each_entry(dev, head, unreg_list) { +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + struct net *net = dev_net(dev); + + /* spin_lock() can be moved outside of the loop + * once the per-netns RTNL conversion completes. + */ + spin_lock(&net->dev_unreg_lock); + list_del(&dev->unreg_list_net); + spin_unlock(&net->dev_unreg_lock); +#endif netdev_put(dev, &dev->dev_registered_tracker); net_set_todo(dev); cnt++; @@ -12514,6 +12590,96 @@ void unregister_netdevice_many(struct list_head *head) } EXPORT_SYMBOL(unregister_netdevice_many); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL +void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, + struct list_head *head) +{ + netdev_lock(dev); + + if (net_eq(dev_net(dev), net)) { + netdev_unlock(dev); + unregister_netdevice_queue(dev, head); + return; + } + + net = dev_net(dev); + + spin_lock(&net->dev_unreg_lock); + + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list)); + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net)); + + list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head); + rtnl_net_queue_work(net); + + spin_unlock(&net->dev_unreg_lock); + + netdev_unlock(dev); +} +EXPORT_SYMBOL(unregister_netdevice_queue_net); + +void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head) +{ + struct net_device *dev, *tmp; + + spin_lock(&net->dev_unreg_lock); + list_for_each_entry_safe(dev, tmp, head, unreg_list) { + /* Once all cross-netns unregister_netdevice_queue() is + * converted to _net() (or for debugging), remove this check. + */ + if (!net_eq(dev_net(dev), net)) + continue; + + DEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net), + "%s was unregistered from a different netns.\n", + dev->name); + + list_del_init(&dev->unreg_list); + list_move_tail(&dev->unreg_list_net, &net->dev_unreg_head); + } + spin_unlock(&net->dev_unreg_lock); +} + +static void unregister_netdevice_move_net(struct net *net_old, + struct net *net, + struct net_device *dev) +{ + if (net_old > net) { + spin_lock(&net->dev_unreg_lock); + spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING); + } else { + spin_lock(&net_old->dev_unreg_lock); + spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING); + } + + if (!list_empty(&dev->unreg_list_net)) { + list_del(&dev->unreg_list_net); + list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head); + } + + spin_unlock(&net_old->dev_unreg_lock); + spin_unlock(&net->dev_unreg_lock); +} + +void unregister_netdevice_many_net(struct net *net) +{ + struct net_device *dev, *tmp; + LIST_HEAD(unreg_head_net); + LIST_HEAD(unreg_head); + + spin_lock(&net->dev_unreg_lock); + list_splice_init(&net->dev_unreg_head, &unreg_head_net); + spin_unlock(&net->dev_unreg_lock); + + list_for_each_entry_safe(dev, tmp, &unreg_head_net, unreg_list_net) { + list_del_init(&dev->unreg_list_net); + list_add_tail(&dev->unreg_list, &unreg_head); + } + + unregister_netdevice_many(&unreg_head); +} +#endif + /** * unregister_netdev - remove device from the kernel * @dev: device @@ -12670,6 +12836,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, netdev_unlock(dev); dev->ifindex = new_ifindex; +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + unregister_netdevice_move_net(net_old, net, dev); +#endif + if (new_name[0]) { /* Rename the netdev to prepared name */ write_seqlock_bh(&netdev_rename_lock); @@ -13046,7 +13216,7 @@ static void __net_exit default_device_exit_net(struct net *net) * Push all migratable network devices back to the * initial network namespace */ - ASSERT_RTNL(); + for_each_netdev_safe(net, dev, aux) { int err; char fb_name[IFNAMSIZ]; @@ -13089,21 +13259,36 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list) LIST_HEAD(dev_kill_list); rtnl_lock(); + + __rtnl_net_lock(&init_net); + list_for_each_entry(net, net_list, exit_list) { + __rtnl_net_lock(net); default_device_exit_net(net); + __rtnl_net_unlock(net); + cond_resched(); } + __rtnl_net_unlock(&init_net); + list_for_each_entry(net, net_list, exit_list) { + __rtnl_net_lock(net); + for_each_netdev_reverse(net, dev) { if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink) dev->rtnl_link_ops->dellink(dev, &dev_kill_list); else unregister_netdevice_queue(dev, &dev_kill_list); } + + unregister_netdevice_queue_many_net(net, &dev_kill_list); + __rtnl_net_unlock(net); } unregister_netdevice_many(&dev_kill_list); rtnl_unlock(); + + rtnl_net_flush_workqueue(); } static struct pernet_operations __net_initdata default_device_ops = { diff --git a/net/core/devmem.c b/net/core/devmem.c index 957d6b96216b..f4d60654ce7f 100644 --- a/net/core/devmem.c +++ b/net/core/devmem.c @@ -46,7 +46,7 @@ static dma_addr_t net_devmem_get_dma_addr(const struct net_iov *niov) owner = net_devmem_iov_to_chunk_owner(niov); return owner->base_dma_addr + - ((dma_addr_t)net_iov_idx(niov) << PAGE_SHIFT); + ((dma_addr_t)net_iov_idx(niov) << owner->binding->niov_shift); } static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref) @@ -93,13 +93,14 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding) ssize_t offset; ssize_t index; - dma_addr = gen_pool_alloc_owner(binding->chunk_pool, PAGE_SIZE, + dma_addr = gen_pool_alloc_owner(binding->chunk_pool, + 1UL << binding->niov_shift, (void **)&owner); if (!dma_addr) return NULL; offset = dma_addr - owner->base_dma_addr; - index = offset / PAGE_SIZE; + index = offset >> binding->niov_shift; niov = &owner->area.niovs[index]; niov->desc.pp_magic = 0; @@ -113,12 +114,13 @@ void net_devmem_free_dmabuf(struct net_iov *niov) { struct net_devmem_dmabuf_binding *binding = net_devmem_iov_binding(niov); unsigned long dma_addr = net_devmem_get_dma_addr(niov); + size_t niov_size = 1UL << binding->niov_shift; if (WARN_ON(!gen_pool_has_addr(binding->chunk_pool, dma_addr, - PAGE_SIZE))) + niov_size))) return; - gen_pool_free(binding->chunk_pool, dma_addr, PAGE_SIZE); + gen_pool_free(binding->chunk_pool, dma_addr, niov_size); } void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding) @@ -163,6 +165,9 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx, u32 xa_idx; int err; + if (binding->niov_shift != PAGE_SHIFT) + mp_params.rx_page_size = 1U << binding->niov_shift; + err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack); if (err) return err; @@ -184,10 +189,12 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { struct net_devmem_dmabuf_binding *binding; + size_t niov_size = 1UL << niov_shift; static u32 id_alloc_next; struct scatterlist *sg; struct dma_buf *dmabuf; @@ -213,6 +220,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, binding->dev = dev; binding->vdev = vdev; + binding->niov_shift = niov_shift; xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC); err = percpu_ref_init(&binding->ref, @@ -255,11 +263,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, } } - /* For simplicity we expect to make PAGE_SIZE allocations, but the - * binding can be much more flexible than that. We may be able to - * allocate MTU sized chunks here. Leave that for future work... - */ - binding->chunk_pool = gen_pool_create(PAGE_SHIFT, + binding->chunk_pool = gen_pool_create(niov_shift, dev_to_node(&dev->dev)); if (!binding->chunk_pool) { err = -ENOMEM; @@ -273,9 +277,12 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, size_t len = sg_dma_len(sg); struct net_iov *niov; - if (!IS_ALIGNED(len, PAGE_SIZE)) { + if (!IS_ALIGNED(dma_addr, niov_size) || + !IS_ALIGNED(len, niov_size)) { err = -EINVAL; - NL_SET_ERR_MSG(extack, "dma-buf SG length must be PAGE_SIZE aligned"); + NL_SET_ERR_MSG_FMT(extack, + "dmabuf sg entry (addr=%pad, len=%zu) not aligned to niov size %zu", + &dma_addr, len, niov_size); goto err_free_chunks; } @@ -288,7 +295,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, owner->area.base_virtual = virtual; owner->base_dma_addr = dma_addr; - owner->area.num_niovs = len / PAGE_SIZE; + owner->area.num_niovs = len >> niov_shift; owner->binding = binding; err = gen_pool_add_owner(binding->chunk_pool, dma_addr, @@ -454,7 +461,7 @@ int mp_dmabuf_devmem_init(struct page_pool *pool) pool->dma_sync = false; pool->dma_sync_for_cpu = false; - if (pool->p.order != 0) + if (pool->p.order != binding->niov_shift - PAGE_SHIFT) return -E2BIG; net_devmem_dmabuf_binding_get(binding); diff --git a/net/core/devmem.h b/net/core/devmem.h index 3852a56036cb..4a293a7d1149 100644 --- a/net/core/devmem.h +++ b/net/core/devmem.h @@ -71,6 +71,8 @@ struct net_devmem_dmabuf_binding { */ struct net_iov **tx_vec; + unsigned int niov_shift; + struct work_struct unbind_w; }; @@ -93,7 +95,8 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack); struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id); void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding); @@ -122,10 +125,11 @@ static inline u32 net_devmem_iov_binding_id(const struct net_iov *niov) static inline unsigned long net_iov_virtual_addr(const struct net_iov *niov) { - struct net_iov_area *owner = net_iov_owner(niov); + struct dmabuf_genpool_chunk_owner *co = + net_devmem_iov_to_chunk_owner(niov); - return owner->base_virtual + - ((unsigned long)net_iov_idx(niov) << PAGE_SHIFT); + return net_iov_owner(niov)->base_virtual + + ((unsigned long)net_iov_idx(niov) << co->binding->niov_shift); } static inline bool @@ -175,6 +179,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, unsigned int dmabuf_fd, + unsigned int niov_shift, struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { diff --git a/net/core/failover.c b/net/core/failover.c index e43c59cd6868..4c3894a02cb7 100644 --- a/net/core/failover.c +++ b/net/core/failover.c @@ -302,9 +302,7 @@ EXPORT_SYMBOL_GPL(failover_unregister); static __init int failover_init(void) { - register_netdevice_notifier(&failover_notifier); - - return 0; + return register_netdevice_notifier(&failover_notifier); } module_init(failover_init); diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index cf374c208732..7df216c17c67 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -51,7 +51,6 @@ bool fib_rule_matchall(const struct fib_rule *rule) return false; return true; } -EXPORT_SYMBOL_GPL(fib_rule_matchall); int fib_default_rule_add(struct fib_rules_ops *ops, u32 pref, u32 table) @@ -78,7 +77,6 @@ int fib_default_rule_add(struct fib_rules_ops *ops, list_add_tail(&r->list, &ops->rules_list); return 0; } -EXPORT_SYMBOL(fib_default_rule_add); static u32 fib_default_rule_pref(struct fib_rules_ops *ops) { @@ -172,6 +170,7 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&ops->rules_list); + mutex_init(&ops->lock); ops->fro_net = net; err = __fib_rules_register(ops); @@ -182,7 +181,6 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ops; } -EXPORT_SYMBOL_GPL(fib_rules_register); static void fib_rules_cleanup_ops(struct fib_rules_ops *ops) { @@ -205,9 +203,9 @@ void fib_rules_unregister(struct fib_rules_ops *ops) spin_unlock(&net->rules_mod_lock); fib_rules_cleanup_ops(ops); + mutex_destroy(&ops->lock); kfree_rcu(ops, rcu); } -EXPORT_SYMBOL_GPL(fib_rules_unregister); static int uid_range_set(struct fib_kuid_range *range) { @@ -363,7 +361,6 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, return err; } -EXPORT_SYMBOL_GPL(fib_rules_lookup); static int call_fib_rule_notifier(struct notifier_block *nb, enum fib_event_type event_type, @@ -391,7 +388,7 @@ static int call_fib_rule_notifiers(struct net *net, .rule = rule, }; - ASSERT_RTNL_NET(net); + lockdep_assert_held(&ops->lock); /* Paired with READ_ONCE() in fib_rules_seq() */ WRITE_ONCE(ops->fib_rules_seq, ops->fib_rules_seq + 1); @@ -423,7 +420,6 @@ int fib_rules_dump(struct net *net, struct notifier_block *nb, int family, return err; } -EXPORT_SYMBOL_GPL(fib_rules_dump); unsigned int fib_rules_seq_read(const struct net *net, int family) { @@ -439,7 +435,6 @@ unsigned int fib_rules_seq_read(const struct net *net, int family) return fib_rules_seq; } -EXPORT_SYMBOL_GPL(fib_rules_seq_read); static struct fib_rule *rule_find(struct fib_rules_ops *ops, struct fib_rule_hdr *frh, @@ -740,10 +735,10 @@ static int fib_nl2rule(struct net *net, struct nlmsghdr *nlh, return err; } -static int fib_nl2rule_rtnl(struct fib_rule *nlrule, - struct fib_rules_ops *ops, - struct nlattr *tb[], - struct netlink_ext_ack *extack) +static int fib_nl2rule_locked(struct fib_rule *nlrule, + struct fib_rules_ops *ops, + struct nlattr *tb[], + struct netlink_ext_ack *extack) { if (!tb[FRA_PRIORITY]) nlrule->pref = fib_default_rule_pref(ops); @@ -754,12 +749,14 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, return -EINVAL; } + rcu_read_lock(); + if (tb[FRA_IIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->iifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->iifname); if (dev) { - nlrule->iifindex = dev->ifindex; + nlrule->iifindex = READ_ONCE(dev->ifindex); nlrule->iif_is_l3_master = netif_is_l3_master(dev); } } @@ -767,13 +764,15 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, if (tb[FRA_OIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->oifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->oifname); if (dev) { - nlrule->oifindex = dev->ifindex; + nlrule->oifindex = READ_ONCE(dev->ifindex); nlrule->oif_is_l3_master = netif_is_l3_master(dev); } } + rcu_read_unlock(); + return 0; } @@ -883,6 +882,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr *tb[FRA_MAX + 1]; bool user_priority = false; struct fib_rule_hdr *frh; + bool unlock_rtnl = false; frh = nlmsg_payload(nlh, sizeof(*frh)); if (!frh) { @@ -908,10 +908,13 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) + if (!rtnl_held && ops->need_rtnl && ops->need_rtnl(net)) { + unlock_rtnl = true; rtnl_net_lock(net); + } + mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(rule, ops, tb, extack); + err = fib_nl2rule_locked(rule, ops, tb, extack); if (err) goto errout_free; @@ -959,7 +962,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, list_for_each_entry(r, &ops->rules_list, list) { if (r->action == FR_ACT_GOTO && r->target == rule->pref && - rtnl_dereference(r->ctarget) == NULL) { + !rcu_access_pointer(r->ctarget)) { rcu_assign_pointer(r->ctarget, rule); if (--ops->unresolved_rules == 0) break; @@ -978,7 +981,8 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, fib_rule_get(rule); - if (!rtnl_held) + mutex_unlock(&ops->lock); + if (unlock_rtnl) rtnl_net_unlock(net); notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid); @@ -988,7 +992,8 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: - if (!rtnl_held) + mutex_unlock(&ops->lock); + if (unlock_rtnl) rtnl_net_unlock(net); kfree(rule); errout: @@ -1037,10 +1042,9 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) - rtnl_net_lock(net); + mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(nlrule, ops, tb, extack); + err = fib_nl2rule_locked(nlrule, ops, tb, extack); if (err) goto errout_free; @@ -1055,11 +1059,8 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, goto errout_free; } - if (ops->delete) { - err = ops->delete(rule); - if (err) - goto errout_free; - } + if (ops->delete) + ops->delete(rule); if (rule->tun_id) ip_tunnel_unneed_metadata(); @@ -1068,7 +1069,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (rule->action == FR_ACT_GOTO) { ops->nr_goto_rules--; - if (rtnl_dereference(rule->ctarget) == NULL) + if (!rcu_access_pointer(rule->ctarget)) ops->unresolved_rules--; } @@ -1086,7 +1087,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (&n->list == &ops->rules_list || n->pref != rule->pref) n = NULL; list_for_each_entry(r, &ops->rules_list, list) { - if (rtnl_dereference(r->ctarget) != rule) + if (rcu_access_pointer(r->ctarget) != rule) continue; rcu_assign_pointer(r->ctarget, n); if (!n) @@ -1096,8 +1097,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, call_fib_rule_notifiers(net, FIB_EVENT_RULE_DEL, rule, ops, NULL); - if (!rtnl_held) - rtnl_net_unlock(net); + mutex_unlock(&ops->lock); notify_rule_change(RTM_DELRULE, rule, ops, nlh, NETLINK_CB(skb).portid); fib_rule_put(rule); @@ -1107,8 +1107,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: - if (!rtnl_held) - rtnl_net_unlock(net); + mutex_unlock(&ops->lock); kfree(nlrule); errout: rules_ops_put(ops); @@ -1402,24 +1401,30 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event, struct net *net = dev_net(dev); struct fib_rules_ops *ops; - ASSERT_RTNL(); - switch (event) { case NETDEV_REGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; case NETDEV_CHANGENAME: list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); } break; case NETDEV_UNREGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; } diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c index 8a81c5430705..abc4c00728b1 100644 --- a/net/core/lock_debug.c +++ b/net/core/lock_debug.c @@ -24,15 +24,15 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, case NETDEV_CHANGE: case NETDEV_REGISTER: case NETDEV_UP: + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: netdev_assert_locked_ops_compat(dev); fallthrough; - case NETDEV_DOWN: case NETDEV_REBOOT: case NETDEV_UNREGISTER: case NETDEV_CHANGEMTU: case NETDEV_CHANGEADDR: case NETDEV_PRE_CHANGEADDR: - case NETDEV_GOING_DOWN: case NETDEV_FEAT_CHANGE: case NETDEV_BONDING_FAILOVER: case NETDEV_PRE_UP: diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index 25546deacec8..352173df7578 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -1432,7 +1432,7 @@ static ssize_t traffic_class_show(struct kobject *kobj, struct attribute *attr, /* If queue belongs to subordinate dev use its TC mapping */ dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev; - num_tc = dev->num_tc; + num_tc = READ_ONCE(dev->num_tc); tc = netdev_txq_to_tc(dev, index); rtnl_unlock(); diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index d9dafe24f57e..a91d2b58aadd 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -181,6 +181,7 @@ static void ops_exit_rtnl_list(const struct list_head *ops_list, ops->exit_rtnl(net, &dev_kill_list); } + unregister_netdevice_queue_many_net(net, &dev_kill_list); __rtnl_net_unlock(net); } @@ -422,6 +423,9 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n #ifdef CONFIG_DEBUG_NET_SMALL_RTNL mutex_init(&net->rtnl_mutex); lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL); + INIT_WORK(&net->rtnl_work, rtnl_net_work_func); + INIT_LIST_HEAD(&net->dev_unreg_head); + spin_lock_init(&net->dev_unreg_lock); #endif INIT_LIST_HEAD(&net->ptype_all); diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index d18c89b5a6c7..f83790341eae 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -11,6 +11,7 @@ #include #include +#include /* Integer value ranges */ static const struct netlink_range_validation netdev_a_page_pool_id_range = { @@ -27,6 +28,11 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range .max = S32_MAX, }; +static const struct netlink_range_validation netdev_a_dmabuf_rx_page_size_range = { + .min = PAGE_SIZE, + .max = U32_MAX, +}; + /* Common nested types */ const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = { [NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), @@ -106,10 +112,11 @@ static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE }; /* NETDEV_CMD_BIND_RX - do */ -static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1] = { +static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_RX_PAGE_SIZE + 1] = { [NETDEV_A_DMABUF_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, [NETDEV_A_DMABUF_QUEUES] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy), + [NETDEV_A_DMABUF_RX_PAGE_SIZE] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_dmabuf_rx_page_size_range), }; /* NETDEV_CMD_NAPI_SET - do */ @@ -219,7 +226,7 @@ static const struct genl_split_ops netdev_nl_ops[] = { .cmd = NETDEV_CMD_BIND_RX, .doit = netdev_nl_bind_rx_doit, .policy = netdev_bind_rx_nl_policy, - .maxattr = NETDEV_A_DMABUF_FD, + .maxattr = NETDEV_A_DMABUF_RX_PAGE_SIZE, .flags = GENL_UNS_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index d71b435d72c1..3fae88e8f5c5 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -12,6 +12,7 @@ #include #include +#include /* Common nested types */ extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1]; diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index c15d8d4ca1f8..cb18db681640 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1013,6 +1013,7 @@ netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap, int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; + unsigned int niov_shift = PAGE_SHIFT; u32 ifindex, dmabuf_fd, rxq_idx; struct netdev_nl_sock *priv; struct net_device *netdev; @@ -1030,6 +1031,18 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) ifindex = nla_get_u32(info->attrs[NETDEV_A_DEV_IFINDEX]); dmabuf_fd = nla_get_u32(info->attrs[NETDEV_A_DMABUF_FD]); + if (info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]) { + u32 rx_page_size = nla_get_u32(info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]); + + if (!is_power_of_2(rx_page_size)) { + NL_SET_ERR_MSG_ATTR(info->extack, + info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE], + "rx-page-size must be a power of 2"); + return -EINVAL; + } + niov_shift = ilog2(rx_page_size); + } + priv = genl_sk_priv_get(&netdev_nl_family, NETLINK_CB(skb).sk); if (IS_ERR(priv)) return PTR_ERR(priv); @@ -1080,7 +1093,8 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) } binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE, - dmabuf_fd, priv, info->extack); + dmabuf_fd, niov_shift, priv, + info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_rxq_bitmap; @@ -1093,7 +1107,9 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) goto err_unbind; } - nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id)); + genlmsg_end(rsp, hdr); err = genlmsg_reply(rsp, info); @@ -1221,13 +1237,15 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) binding = net_devmem_bind_dmabuf(bind_dev, bind_dev != netdev ? netdev : NULL, dma_dev, DMA_TO_DEVICE, dmabuf_fd, - priv, info->extack); + PAGE_SHIFT, priv, info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_unlock_bind_dev; } - nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id)); + genlmsg_end(rsp, hdr); if (bind_dev != netdev) @@ -1394,7 +1412,9 @@ int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info) netdev_rx_queue_lease(rxq, rxq_lease); - nla_put_u32(rsp, NETDEV_A_QUEUE_ID, queue_id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_QUEUE_ID, queue_id)); + genlmsg_end(rsp, hdr); netdev_unlock(dev_lease); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 96d5945e6a30..fe1e0cda5d6b 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -36,17 +36,22 @@ #include #include -/* - * We maintain a small pool of fully-sized skbs, to make sure the - * message gets out even in extreme OOM situations. - */ - -#define MAX_SKBS 32 #define USEC_PER_POLL 50 +/* + * carrier_timeout is netconsole-specific and only kept here to preserve the + * netpoll.carrier_timeout module-parameter ABI. Its value is exposed to + * netconsole through netpoll_get_carrier_timeout(). + */ static unsigned int carrier_timeout = 4; module_param(carrier_timeout, uint, 0644); +unsigned int netpoll_get_carrier_timeout(void) +{ + return carrier_timeout; +} +EXPORT_SYMBOL_GPL(netpoll_get_carrier_timeout); + static netdev_tx_t netpoll_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) @@ -213,22 +218,6 @@ void netpoll_poll_enable(struct net_device *dev) up(&ni->dev_lock); } -static void refill_skbs(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - struct sk_buff *skb; - - skb_pool = &np->skb_pool; - - while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { - skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC); - if (!skb) - break; - - skb_queue_tail(skb_pool, skb); - } -} - void netpoll_zap_completion_queue(void) { unsigned long flags; @@ -351,32 +340,12 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -static void skb_pool_flush(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - - cancel_work_sync(&np->refill_wq); - skb_pool = &np->skb_pool; - skb_queue_purge_reason(skb_pool, SKB_CONSUMED); -} - -static void refill_skbs_work_handler(struct work_struct *work) -{ - struct netpoll *np = - container_of(work, struct netpoll, refill_wq); - - refill_skbs(np); -} - int __netpoll_setup(struct netpoll *np, struct net_device *ndev) { struct netpoll_info *npinfo; const struct net_device_ops *ops; int err; - skb_queue_head_init(&np->skb_pool); - INIT_WORK(&np->refill_wq, refill_skbs_work_handler); - if (ndev->priv_flags & IFF_DISABLE_NETPOLL) { np_err(np, "%s doesn't support polling, aborting\n", ndev->name); @@ -411,9 +380,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) np->dev = ndev; strscpy(np->dev_name, ndev->name, IFNAMSIZ); - /* fill up the skb queue */ - refill_skbs(np); - /* last thing to do is link it to the net device structure */ rcu_assign_pointer(ndev->npinfo, npinfo); @@ -426,207 +392,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(__netpoll_setup); -/* - * Returns a pointer to a string representation of the identifier used - * to select the egress interface for the given netpoll instance. buf - * is used to format np->dev_mac when np->dev_name is empty; bufsz must - * be at least MAC_ADDR_STR_LEN + 1 to fit the formatted MAC address - * and its NUL terminator. - */ -static char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) -{ - if (np->dev_name[0]) - return np->dev_name; - - snprintf(buf, bufsz, "%pM", np->dev_mac); - return buf; -} - -static void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev, - unsigned int timeout) -{ - unsigned long atmost; - - atmost = jiffies + timeout * HZ; - while (!netif_carrier_ok(ndev)) { - if (time_after(jiffies, atmost)) { - np_notice(np, "timeout waiting for carrier\n"); - break; - } - msleep(1); - } -} - -/* - * Take the IPv6 from ndev and populate local_ip structure in netpoll - */ -static int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) -{ - char buf[MAC_ADDR_STR_LEN + 1]; - int err = -EDESTADDRREQ; - struct inet6_dev *idev; - - if (!IS_ENABLED(CONFIG_IPV6)) { - np_err(np, "IPv6 is not supported %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EINVAL; - } - - idev = __in6_dev_get(ndev); - if (idev) { - struct inet6_ifaddr *ifp; - - read_lock_bh(&idev->lock); - list_for_each_entry(ifp, &idev->addr_list, if_list) { - if (!!(ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) != - !!(ipv6_addr_type(&np->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) - continue; - /* Got the IP, let's return */ - np->local_ip.in6 = ifp->addr; - err = 0; - break; - } - read_unlock_bh(&idev->lock); - } - if (err) { - np_err(np, "no IPv6 address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return err; - } - - np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); - return 0; -} - -/* - * Take the IPv4 from ndev and populate local_ip structure in netpoll - */ -static int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) -{ - char buf[MAC_ADDR_STR_LEN + 1]; - const struct in_ifaddr *ifa; - struct in_device *in_dev; - - in_dev = __in_dev_get_rtnl(ndev); - if (!in_dev) { - np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EDESTADDRREQ; - } - - ifa = rtnl_dereference(in_dev->ifa_list); - if (!ifa) { - np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EDESTADDRREQ; - } - - np->local_ip.ip = ifa->ifa_local; - np_info(np, "local IP %pI4\n", &np->local_ip.ip); - - return 0; -} - -/* - * Test whether the caller left np->local_ip unset, so that - * netpoll_setup() should auto-populate it from the egress device. - * - * np->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), - * so an IPv6 address whose first 4 bytes are zero (e.g. ::1, ::2, - * IPv4-mapped ::ffff:a.b.c.d) must not be tested via the IPv4 arm — - * doing so would misclassify a caller-supplied address as unset and - * silently overwrite it with whatever address the device exposes. - */ -static bool netpoll_local_ip_unset(const struct netpoll *np) -{ - if (np->ipv6) - return ipv6_addr_any(&np->local_ip.in6); - return !np->local_ip.ip; -} - -int netpoll_setup(struct netpoll *np) -{ - struct net *net = current->nsproxy->net_ns; - char buf[MAC_ADDR_STR_LEN + 1]; - struct net_device *ndev = NULL; - bool ip_overwritten = false; - int err; - - rtnl_lock(); - if (np->dev_name[0]) - ndev = __dev_get_by_name(net, np->dev_name); - else if (is_valid_ether_addr(np->dev_mac)) - ndev = dev_getbyhwaddr(net, ARPHRD_ETHER, np->dev_mac); - - if (!ndev) { - np_err(np, "%s doesn't exist, aborting\n", - egress_dev(np, buf, sizeof(buf))); - err = -ENODEV; - goto unlock; - } - netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL); - - if (netdev_master_upper_dev_get(ndev)) { - np_err(np, "%s is a slave device, aborting\n", - egress_dev(np, buf, sizeof(buf))); - err = -EBUSY; - goto put; - } - - if (!netif_running(ndev)) { - np_info(np, "device %s not up yet, forcing it\n", - egress_dev(np, buf, sizeof(buf))); - - err = dev_open(ndev, NULL); - if (err) { - np_err(np, "failed to open %s\n", ndev->name); - goto put; - } - - rtnl_unlock(); - netpoll_wait_carrier(np, ndev, carrier_timeout); - rtnl_lock(); - } - - if (netpoll_local_ip_unset(np)) { - if (!np->ipv6) { - err = netpoll_take_ipv4(np, ndev); - if (err) - goto put; - } else { - err = netpoll_take_ipv6(np, ndev); - if (err) - goto put; - } - ip_overwritten = true; - } - - err = __netpoll_setup(np, ndev); - if (err) - goto flush; - rtnl_unlock(); - - /* Make sure all NAPI polls which started before dev->npinfo - * was visible have exited before we start calling NAPI poll. - * NAPI skips locking if dev->npinfo is NULL. - */ - synchronize_rcu(); - - return 0; - -flush: - skb_pool_flush(np); -put: - DEBUG_NET_WARN_ON_ONCE(np->dev); - if (ip_overwritten) - memset(&np->local_ip, 0, sizeof(np->local_ip)); - netdev_put(ndev, &np->dev_tracker); -unlock: - rtnl_unlock(); - return err; -} -EXPORT_SYMBOL(netpoll_setup); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = @@ -659,8 +424,6 @@ static void __netpoll_cleanup(struct netpoll *np) disable_delayed_work_sync(&npinfo->tx_work); call_rcu(&npinfo->rcu, rcu_cleanup_netpoll_info); } - - skb_pool_flush(np); } void __netpoll_free(struct netpoll *np) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 21dc4a9c8714..50ee550fef73 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -500,29 +500,40 @@ static int page_pool_register_dma_index(struct page_pool *pool, return err; } -static int page_pool_release_dma_index(struct page_pool *pool, - netmem_ref netmem) +static void __page_pool_unmap_netmem_dma(struct page_pool *pool, + netmem_ref netmem) { struct page *old, *page = netmem_to_page(netmem); unsigned long id; + dma_addr_t dma; - if (unlikely(!PP_DMA_INDEX_BITS)) - return 0; + if (!pool->dma_map) + return; - id = netmem_get_dma_index(netmem); - if (!id) - return -1; + /* Cache dma_addr before xa_cmpxchg. The scrub path holds no page ref; + * the unref path calls put_page() regardless of cmpxchg outcome, so + * after the cmpxchg we cannot safely touch netmem fields. + */ + dma = page_pool_get_dma_addr_netmem(netmem); - if (in_softirq()) - old = xa_cmpxchg(&pool->dma_mapped, id, page, NULL, 0); - else - old = xa_cmpxchg_bh(&pool->dma_mapped, id, page, NULL, 0); - if (old != page) - return -1; + if (likely(PP_DMA_INDEX_BITS)) { + id = netmem_get_dma_index(netmem); + if (!id) + return; - netmem_set_dma_index(netmem, 0); + if (in_softirq()) + old = xa_cmpxchg(&pool->dma_mapped, + id, page, NULL, 0); + else + old = xa_cmpxchg_bh(&pool->dma_mapped, + id, page, NULL, 0); + if (old != page) + return; + } - return 0; + dma_unmap_page_attrs(pool->p.dev, dma, + PAGE_SIZE << pool->p.order, pool->p.dma_dir, + DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); } static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t gfp) @@ -728,24 +739,16 @@ void page_pool_clear_pp_info(netmem_ref netmem) static __always_inline void __page_pool_release_netmem_dma(struct page_pool *pool, netmem_ref netmem) { - dma_addr_t dma; - + /* Caller must hold a page ref: __page_pool_unmap_netmem_dma() is + * safe without a ref, but the field clears below require it. + */ if (!pool->dma_map) - /* Always account for inflight pages, even if we didn't - * map them - */ return; - if (page_pool_release_dma_index(pool, netmem)) - return; - - dma = page_pool_get_dma_addr_netmem(netmem); - - /* When page is unmapped, it cannot be returned to our pool */ - dma_unmap_page_attrs(pool->p.dev, dma, - PAGE_SIZE << pool->p.order, pool->p.dma_dir, - DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); + __page_pool_unmap_netmem_dma(pool, netmem); page_pool_set_dma_addr_netmem(netmem, 0); + if (likely(PP_DMA_INDEX_BITS)) + netmem_set_dma_index(netmem, 0); } /* Disconnects a page (from a page_pool). API users can have a need @@ -1171,8 +1174,9 @@ static void page_pool_scrub(struct page_pool *pool) synchronize_net(); } + /* No page ref, dma-unmap only. */ xa_for_each(&pool->dma_mapped, id, ptr) - __page_pool_release_netmem_dma(pool, page_to_netmem((struct page *)ptr)); + __page_pool_unmap_netmem_dma(pool, page_to_netmem((struct page *)ptr)); } /* No more consumers should exist, but producers could still diff --git a/net/core/pktgen.c b/net/core/pktgen.c index ee64f3012321..7f81aed46672 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -566,6 +566,7 @@ static const struct proc_ops pktgen_proc_ops = { static int pktgen_if_show(struct seq_file *seq, void *v) { const struct pktgen_dev *pkt_dev = seq->private; + unsigned int cflows = READ_ONCE(pkt_dev->cflows); ktime_t stopped; unsigned int i; u64 idle; @@ -590,7 +591,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) pkt_dev->nfrags, (unsigned long long) pkt_dev->delay, pkt_dev->clone_skb, pkt_dev->odevname); - seq_printf(seq, " flows: %u flowlen: %u\n", pkt_dev->cflows, + seq_printf(seq, " flows: %u flowlen: %u\n", cflows, pkt_dev->lflow); seq_printf(seq, @@ -675,7 +676,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) for (i = 0; i < NR_PKT_FLAGS; i++) { if (i == FLOW_SEQ_SHIFT) - if (!pkt_dev->cflows) + if (!cflows) continue; if (pkt_dev->flags & (1 << i)) { @@ -1632,8 +1633,8 @@ static ssize_t pktgen_if_write(struct file *file, if (value > MAX_CFLOWS) value = MAX_CFLOWS; - pkt_dev->cflows = value; - sprintf(pg_result, "OK: flows=%u", pkt_dev->cflows); + WRITE_ONCE(pkt_dev->cflows, value); + sprintf(pg_result, "OK: flows=%u", (unsigned int)value); return count; } #ifdef CONFIG_XFRM @@ -2373,7 +2374,7 @@ static inline int f_seen(const struct pktgen_dev *pkt_dev, int flow) return !!(pkt_dev->flows[flow].flags & F_INIT); } -static inline int f_pick(struct pktgen_dev *pkt_dev) +static inline int f_pick(struct pktgen_dev *pkt_dev, unsigned int cflows) { int flow = pkt_dev->curfl; @@ -2383,11 +2384,11 @@ static inline int f_pick(struct pktgen_dev *pkt_dev) pkt_dev->flows[flow].count = 0; pkt_dev->flows[flow].flags = 0; pkt_dev->curfl += 1; - if (pkt_dev->curfl >= pkt_dev->cflows) + if (pkt_dev->curfl >= cflows) pkt_dev->curfl = 0; /*reset */ } } else { - flow = get_random_u32_below(pkt_dev->cflows); + flow = get_random_u32_below(cflows); pkt_dev->curfl = flow; if (pkt_dev->flows[flow].count > pkt_dev->lflow) { @@ -2461,12 +2462,14 @@ static void set_cur_queue_map(struct pktgen_dev *pkt_dev) */ static void mod_cur_headers(struct pktgen_dev *pkt_dev) { + unsigned int cflows; __u32 imn; __u32 imx; int flow = 0; - if (pkt_dev->cflows) - flow = f_pick(pkt_dev); + cflows = READ_ONCE(pkt_dev->cflows); + if (cflows) + flow = f_pick(pkt_dev, cflows); /* Deal with source MAC */ if (pkt_dev->src_mac_count > 1) { @@ -2582,7 +2585,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_saddr = htonl(t); } - if (pkt_dev->cflows && f_seen(pkt_dev, flow)) { + if (cflows && f_seen(pkt_dev, flow)) { pkt_dev->cur_daddr = pkt_dev->flows[flow].cur_daddr; } else { imn = ntohl(pkt_dev->daddr_min); @@ -2611,7 +2614,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_daddr = htonl(t); } } - if (pkt_dev->cflows) { + if (cflows) { pkt_dev->flows[flow].flags |= F_INIT; pkt_dev->flows[flow].cur_daddr = pkt_dev->cur_daddr; @@ -3005,7 +3008,7 @@ static struct sk_buff *fill_packet_ipv4(struct net_device *odev, udph->source = htons(pkt_dev->cur_udp_src); udph->dest = htons(pkt_dev->cur_udp_dst); - udph->len = htons(datalen + 8); /* DATA + udphdr */ + udp_set_len_short(udph, datalen + 8); /* DATA + udphdr */ udph->check = 0; iph->ihl = 5; @@ -3138,7 +3141,7 @@ static struct sk_buff *fill_packet_ipv6(struct net_device *odev, udplen = datalen + sizeof(struct udphdr); udph->source = htons(pkt_dev->cur_udp_src); udph->dest = htons(pkt_dev->cur_udp_dst); - udph->len = htons(udplen); + udp_set_len_short(udph, udplen); udph->check = 0; *(__be32 *) iph = htonl(0x60000000); /* Version + flow */ diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 12aa3aa1688b..81c5a6104dea 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -197,6 +197,7 @@ void __rtnl_net_unlock(struct net *net) { ASSERT_RTNL(); + unregister_netdevice_many_net(net); mutex_unlock(&net->rtnl_mutex); } EXPORT_SYMBOL(__rtnl_net_unlock); @@ -273,6 +274,29 @@ bool lockdep_rtnl_net_is_held(struct net *net) return lockdep_rtnl_is_held() && lockdep_is_held(&net->rtnl_mutex); } EXPORT_SYMBOL(lockdep_rtnl_net_is_held); + +static struct workqueue_struct *rtnl_net_wq; + +void rtnl_net_queue_work(struct net *net) +{ + queue_work(rtnl_net_wq, &net->rtnl_work); +} + +void rtnl_net_flush_workqueue(void) +{ + flush_workqueue(rtnl_net_wq); +} + +void rtnl_net_work_func(struct work_struct *work) +{ + struct net *net = container_of(work, struct net, rtnl_work); + + if (list_empty(&net->dev_unreg_head)) + return; + + rtnl_net_lock(net); + rtnl_net_unlock(net); +} #else static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b) { @@ -282,10 +306,11 @@ static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b) #endif struct rtnl_nets { - /* ->newlink() needs to freeze 3 netns at most; - * 2 for the new device, 1 for its peer. + /* ->newlink() needs to freeze 4 netns at most; + * 2 for the new device, 1 for its peer, 1 for + * an existing device (do_setlink() path). */ - struct net *net[3]; + struct net *net[4]; unsigned char len; }; @@ -636,16 +661,15 @@ int rtnl_link_register(struct rtnl_link_ops *ops) } EXPORT_SYMBOL_GPL(rtnl_link_register); -static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops) +static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops, + struct list_head *dev_kill_list) { struct net_device *dev; - LIST_HEAD(list_kill); for_each_netdev(net, dev) { if (dev->rtnl_link_ops == ops) - ops->dellink(dev, &list_kill); + ops->dellink(dev, dev_kill_list); } - unregister_netdevice_many(&list_kill); } /* Return with the rtnl_lock held when there are no network @@ -676,6 +700,7 @@ static void rtnl_lock_unregistering_all(void) */ void rtnl_link_unregister(struct rtnl_link_ops *ops) { + LIST_HEAD(dev_kill_list); struct net *net; mutex_lock(&link_ops_mutex); @@ -689,8 +714,14 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops) down_write(&pernet_ops_rwsem); rtnl_lock_unregistering_all(); - for_each_net(net) - __rtnl_kill_links(net, ops); + for_each_net(net) { + __rtnl_net_lock(net); + __rtnl_kill_links(net, ops, &dev_kill_list); + unregister_netdevice_queue_many_net(net, &dev_kill_list); + __rtnl_net_unlock(net); + } + + unregister_netdevice_many(&dev_kill_list); rtnl_unlock(); up_write(&pernet_ops_rwsem); @@ -1143,12 +1174,27 @@ static void copy_rtnl_link_stats(struct rtnl_link_stats *a, a->rx_nohandler = b->rx_nohandler; } +/* Cap the number of VFs that IFLA_VFINFO_LIST describes. The nest is one + * netlink attribute, so everything inside it has to fit in the u16 nla_len. + * The cap is a fixed number rather than whatever happens to fit, so that the + * limit is a property of the interface instead of one of the requested + * attribute set and the host's alignment requirements. The largest per-VF + * encoding is 368 bytes with statistics and GUIDs and 236 bytes without + * statistics, so both values keep the nest well inside U16_MAX. + */ +static int rtnl_vfinfo_cap(int num_vfs, u32 ext_filter_mask) +{ + return min(num_vfs, + ext_filter_mask & RTEXT_FILTER_SKIP_STATS ? 256 : 128); +} + /* All VF info */ static inline int rtnl_vfinfo_size(const struct net_device *dev, u32 ext_filter_mask) { if (dev->dev.parent && (ext_filter_mask & RTEXT_FILTER_VF)) { - int num_vfs = dev_num_vf(dev->dev.parent); + int num_vfs = rtnl_vfinfo_cap(dev_num_vf(dev->dev.parent), + ext_filter_mask); size_t size = nla_total_size(0); size += num_vfs * (nla_total_size(0) + @@ -1686,6 +1732,11 @@ static noinline_for_stack int rtnl_fill_vf(struct sk_buff *skb, if (!vfinfo) return -EMSGSIZE; + /* IFLA_NUM_VF above stays the device's VF count; the list itself is + * capped so that its length cannot overflow nla_len. + */ + num_vfs = rtnl_vfinfo_cap(num_vfs, ext_filter_mask); + for (i = 0; i < num_vfs; i++) { if (rtnl_fill_vfinfo(skb, dev, i, ext_filter_mask)) { nla_nest_cancel(skb, vfinfo); @@ -3660,14 +3711,16 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, u32 portid, const struct nlmsghdr *nlh) { unsigned int old_flags, changed; - int err; + int err = 0; + + netdev_lock_ops(dev); old_flags = dev->flags; if (ifm && (ifm->ifi_flags || ifm->ifi_change)) { err = __dev_change_flags(dev, rtnl_dev_combine_flags(dev, ifm), NULL); if (err < 0) - return err; + goto out; } changed = old_flags ^ dev->flags; @@ -3677,7 +3730,10 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, } __dev_notify_flags(dev, old_flags, changed, portid, nlh); - return 0; + +out: + netdev_unlock_ops(dev); + return err; } EXPORT_SYMBOL(rtnl_configure_link); @@ -3918,22 +3974,20 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm, goto out; } - netdev_lock_ops(dev); - err = rtnl_configure_link(dev, ifm, portid, nlh); if (err < 0) goto out_unregister; if (tb[IFLA_MASTER]) { + netdev_lock_ops(dev); err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack); + netdev_unlock_ops(dev); if (err) goto out_unregister; } - netdev_unlock_ops(dev); out: return err; out_unregister: - netdev_unlock_ops(dev); if (ops->newlink) { LIST_HEAD(list_kill); @@ -4155,6 +4209,8 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh, } } + rtnl_nets_add(&rtnl_nets, get_net(sock_net(skb->sk))); + rtnl_nets_lock(&rtnl_nets); ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack); rtnl_nets_unlock(&rtnl_nets); @@ -7221,4 +7277,10 @@ void __init rtnetlink_init(void) register_netdevice_notifier(&rtnetlink_dev_notifier); rtnl_register_many(rtnetlink_rtnl_msg_handlers); + +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + rtnl_net_wq = create_workqueue("rtnl_net"); + if (!rtnl_net_wq) + panic("Could not create rtnl_net workq"); +#endif } diff --git a/net/core/scm.c b/net/core/scm.c index eec13f50ecaf..f0d44ecdb11f 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -351,7 +351,31 @@ static int scm_max_fds(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int); } -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags, + bool notrunc) +{ + int error; + + if (!ufd) + return -EFAULT; + + error = security_file_receive(f); + if (error) + return notrunc ? put_user(error, ufd) : error; + + FD_PREPARE(fdf, flags, get_file(f)); + if (fdf.err) + return fdf.err; + + error = put_user(fd_prepare_fd(fdf), ufd); + if (error) + return error; + + __receive_sock(fd_prepare_file(fdf)); + return fd_publish(fdf); +} + +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc) { struct cmsghdr __user *cm = (__force struct cmsghdr __user *)msg->msg_control_user; @@ -365,12 +389,12 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) return; if (msg->msg_flags & MSG_CMSG_COMPAT) { - scm_detach_fds_compat(msg, scm); + scm_detach_fds_compat(msg, scm, notrunc); return; } for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc); if (err < 0) break; } @@ -523,9 +547,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg, scm_passec(sk, msg, scm); - if (scm->fp) - scm_detach_fds(msg, scm); - return true; } @@ -545,6 +566,13 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, if (!__scm_recv_common(sock->sk, msg, scm, flags)) return; + if (scm->fp) { + struct unix_sock *u; + + u = unix_sk(sock->sk); + scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc)); + } + if (sock->sk->sk_scm_pidfd) scm_pidfd_recv(msg, scm); diff --git a/net/core/selftests.c b/net/core/selftests.c index 0a203d3fb9dc..36b949ae520b 100644 --- a/net/core/selftests.c +++ b/net/core/selftests.c @@ -72,9 +72,9 @@ struct sk_buff *net_test_get_skb(struct net_device *ndev, u8 id, } else { uhdr->source = htons(attr->sport); uhdr->dest = htons(attr->dport); - uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); + udp_set_len_short(uhdr, sizeof(*shdr) + sizeof(*uhdr) + attr->size); if (attr->max_size) - uhdr->len = htons(attr->max_size - + udp_set_len_short(uhdr, attr->max_size - (sizeof(*ihdr) + sizeof(*ehdr))); uhdr->check = 0; } diff --git a/net/core/skbuff.c b/net/core/skbuff.c index ba3dbac80fb4..c82a1472a5ea 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2332,7 +2332,7 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, if (skb_orphan_frags(skb, gfp_mask)) goto nofrags; if (skb_zcopy(skb)) - refcount_inc(&skb_uarg(skb)->refcnt); + net_zcopy_get(skb_uarg(skb)); for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) skb_frag_ref(skb, i); @@ -4781,6 +4781,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, struct sk_buff *tail = NULL; struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list; unsigned int mss = skb_shinfo(head_skb)->gso_size; + bool gso_by_frags = mss == GSO_BY_FRAGS; unsigned int doffset = head_skb->data - skb_mac_header(head_skb); unsigned int offset = doffset; unsigned int tnl_hlen = skb_tnl_header_len(head_skb); @@ -4796,7 +4797,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, int nfrags, pos; if ((skb_shinfo(head_skb)->gso_type & SKB_GSO_DODGY) && - mss != GSO_BY_FRAGS && mss != skb_headlen(head_skb)) { + !gso_by_frags && mss != skb_headlen(head_skb)) { struct sk_buff *check_skb; for (check_skb = list_skb; check_skb; check_skb = check_skb->next) { @@ -4824,7 +4825,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, sg = !!(features & NETIF_F_SG); csum = !!can_checksum_protocol(features, proto); - if (sg && csum && (mss != GSO_BY_FRAGS)) { + if (sg && csum && !gso_by_frags) { if (!(features & NETIF_F_GSO_PARTIAL)) { struct sk_buff *iter; unsigned int frag_len; @@ -4858,9 +4859,8 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, /* GSO partial only requires that we trim off any excess that * doesn't fit into an MSS sized block, so take care of that * now. - * Cap len to not accidentally hit GSO_BY_FRAGS. */ - partial_segs = min(len, GSO_BY_FRAGS - 1) / mss; + partial_segs = len / mss; if (partial_segs > 1) mss *= partial_segs; else @@ -4884,7 +4884,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, int hsize; int size; - if (unlikely(mss == GSO_BY_FRAGS)) { + if (unlikely(gso_by_frags)) { len = list_skb->len; } else { len = head_skb->len - offset; @@ -6848,7 +6848,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, return -ENOMEM; } if (skb_zcopy(skb)) - net_zcopy_get(skb_zcopy(skb)); + net_zcopy_get(skb_uarg(skb)); for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) skb_frag_ref(skb, i); if (skb_has_frag_list(skb)) @@ -6998,7 +6998,7 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off, return -ENOMEM; } if (skb_zcopy(skb)) - net_zcopy_get(skb_zcopy(skb)); + net_zcopy_get(skb_uarg(skb)); skb_release_data(skb, SKB_CONSUMED); skb->head = data; diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c index b508618bfc12..eb35da3556f4 100644 --- a/net/core/sysctl_net_core.c +++ b/net/core/sysctl_net_core.c @@ -678,7 +678,7 @@ static struct ctl_table net_core_table[] = { }, }; -static struct ctl_table netns_core_table[] = { +static const struct ctl_table netns_core_table[] = { #if IS_ENABLED(CONFIG_RPS) { .procname = "rps_default_mask", @@ -787,26 +787,38 @@ static int __init fb_tunnels_only_for_init_net_sysctl_setup(char *str) } __setup("fb_tunnels=", fb_tunnels_only_for_init_net_sysctl_setup); -static __net_init int sysctl_core_net_init(struct net *net) +static const struct ctl_table *netns_core_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(netns_core_table); struct ctl_table *tbl; + int i; + + tbl = kmemdup(netns_core_table, sizeof(netns_core_table), GFP_KERNEL); + if (!tbl) + return NULL; + + for (i = 0; i < table_size; ++i) { + if (tbl[i].data == &sysctl_wmem_max) + break; + + tbl[i].data += (char *)net - (char *)&init_net; + } + for (; i < table_size; ++i) + tbl[i].mode &= ~0222; + + return tbl; +} + +static __net_init int sysctl_core_net_init(struct net *net) +{ + size_t table_size = ARRAY_SIZE(netns_core_table); + const struct ctl_table *tbl; tbl = netns_core_table; if (!net_eq(net, &init_net)) { - int i; - tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL); + tbl = netns_core_table_dup(net); if (tbl == NULL) goto err_dup; - - for (i = 0; i < table_size; ++i) { - if (tbl[i].data == &sysctl_wmem_max) - break; - - tbl[i].data += (char *)net - (char *)&init_net; - } - for (; i < table_size; ++i) - tbl[i].mode &= ~0222; } net->core.sysctl_hdr = register_net_sysctl_sz(net, "net/core", tbl, table_size); diff --git a/net/core/tso.c b/net/core/tso.c index 347b3856ddb9..d2934bcfa795 100644 --- a/net/core/tso.c +++ b/net/core/tso.c @@ -39,7 +39,8 @@ void tso_build_hdr(const struct sk_buff *skb, char *hdr, struct tso_t *tso, } else { struct udphdr *uh = (struct udphdr *)hdr; - uh->len = htons(sizeof(*uh) + size); + /* size is after segmentation. */ + udp_set_len_short(uh, sizeof(*uh) + size); } } EXPORT_SYMBOL(tso_build_hdr); diff --git a/net/devlink/core.c b/net/devlink/core.c index fe9f6a0a67d5..c53a42e17a58 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -67,6 +67,22 @@ static void __devlink_rel_put(struct devlink_rel *rel) devlink_rel_free(rel); } +struct devlink *__must_check devlink_nested_in_get_lock(struct devlink *devlink) +{ + devl_assert_locked(devlink); + if (!devlink->rel) + return NULL; + devlink = devlinks_xa_get(devlink->rel->nested_in.devlink_index); + if (!devlink) + return NULL; + devl_lock(devlink); + if (devl_is_registered(devlink)) + return devlink; + devl_unlock(devlink); + devlink_put(devlink); + return NULL; +} + static void devlink_rel_nested_in_notify_work(struct work_struct *work) { struct devlink_rel *rel = container_of(work, struct devlink_rel, @@ -518,6 +534,9 @@ void devlink_free(struct devlink *devlink) { ASSERT_DEVLINK_NOT_REGISTERED(devlink); + devl_lock(devlink); + WARN_ON(devlink_rates_check(devlink, NULL, NULL)); + devl_unlock(devlink); devlink_rel_put(devlink); WARN_ON(!list_empty(&devlink->trap_policer_list)); @@ -528,7 +547,6 @@ void devlink_free(struct devlink *devlink) WARN_ON(!list_empty(&devlink->resource_list)); WARN_ON(!list_empty(&devlink->dpipe_table_list)); WARN_ON(!list_empty(&devlink->sb_list)); - WARN_ON(devlink_rates_check(devlink, NULL, NULL)); WARN_ON(!list_empty(&devlink->linecard_list)); WARN_ON(!xa_empty(&devlink->ports)); diff --git a/net/devlink/dev.c b/net/devlink/dev.c index fd5633fa88ec..55959b0ff5ab 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -222,7 +222,7 @@ static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -519,7 +519,7 @@ devlink_nl_reload_actions_performed_snd(struct devlink *devlink, u32 actions_per int devlink_nl_reload_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_reload_action action; enum devlink_reload_limit limit; struct net *dest_net = NULL; @@ -684,7 +684,7 @@ static int devlink_nl_eswitch_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -705,7 +705,7 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; enum devlink_eswitch_encap_mode encap_mode; u8 inline_mode; @@ -907,7 +907,7 @@ devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_info_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1135,7 +1135,7 @@ int devlink_nl_flash_update_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *nla_overwrite_mask, *nla_file_name; struct devlink_flash_update_params params = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *file_name; u32 supported_params; int ret; @@ -1303,7 +1303,7 @@ devlink_nl_selftests_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_selftests_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1373,7 +1373,7 @@ static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ int devlink_nl_selftests_run_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *tb[DEVLINK_ATTR_SELFTEST_ID_MAX + 1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *attrs, *selftests; struct sk_buff *msg; void *hdr; diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index e4e48ee2da5a..cdf894ba5a9d 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -136,6 +136,10 @@ typedef void devlink_rel_notify_cb_t(struct devlink *devlink, u32 obj_index); typedef void devlink_rel_cleanup_cb_t(struct devlink *devlink, u32 obj_index, u32 rel_index); +/* Returns the locked+referenced nested-in instance or NULL. */ +struct devlink *__must_check +devlink_nested_in_get_lock(struct devlink *devlink); + void devlink_rel_nested_in_clear(u32 rel_index); int devlink_rel_nested_in_add(u32 *rel_index, u32 devlink_index, u32 obj_index, devlink_rel_notify_cb_t *notify_cb, @@ -147,6 +151,20 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, bool *msg_updated); /* Netlink */ +struct devlink_nl_ctx { + struct devlink *devlink; + struct devlink_port *devlink_port; + struct devlink *parent_devlink; +}; + +static inline struct devlink_nl_ctx * +devlink_nl_ctx(struct genl_info *info) +{ + BUILD_BUG_ON(sizeof(struct devlink_nl_ctx) > + sizeof_field(struct genl_info, ctx)); + return (struct devlink_nl_ctx *)info->ctx; +} + enum devlink_multicast_groups { DEVLINK_MCGRP_CONFIG, }; @@ -180,6 +198,8 @@ typedef int devlink_nl_dump_one_func_t(struct sk_buff *msg, struct devlink * devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, bool dev_lock); +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs); int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb, devlink_nl_dump_one_func_t *dump_one); diff --git a/net/devlink/dpipe.c b/net/devlink/dpipe.c index c8d4a4374ae1..08c7b66fc3e8 100644 --- a/net/devlink/dpipe.c +++ b/net/devlink/dpipe.c @@ -213,7 +213,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, struct list_head *dpipe_tables, const char *table_name) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; struct nlattr *tables_attr; struct sk_buff *skb = NULL; @@ -290,7 +290,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, int devlink_nl_dpipe_table_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name = NULL; if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) @@ -478,7 +478,7 @@ int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) if (!dump_ctx->hdr) goto nla_put_failure; - devlink = dump_ctx->info->user_ptr[0]; + devlink = devlink_nl_ctx(dump_ctx->info)->devlink; if (devlink_nl_put_handle(dump_ctx->skb, devlink)) goto nla_put_failure; dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, @@ -563,7 +563,7 @@ static int devlink_dpipe_entries_fill(struct genl_info *info, int devlink_nl_dpipe_entries_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; const char *table_name; @@ -650,7 +650,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, struct devlink_dpipe_headers * dpipe_headers) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *headers_attr; struct sk_buff *skb = NULL; struct nlmsghdr *nlh; @@ -713,7 +713,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, int devlink_nl_dpipe_headers_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink->dpipe_headers) return -EOPNOTSUPP; @@ -747,7 +747,7 @@ static int devlink_dpipe_table_counters_set(struct devlink *devlink, int devlink_nl_dpipe_table_counters_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name; bool counters_enable; diff --git a/net/devlink/health.c b/net/devlink/health.c index ea7a334e939b..8ce6cd399cb7 100644 --- a/net/devlink/health.c +++ b/net/devlink/health.c @@ -358,7 +358,7 @@ devlink_health_reporter_get_from_info(struct devlink *devlink, int devlink_nl_health_reporter_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct sk_buff *msg; int err; @@ -456,7 +456,7 @@ int devlink_nl_health_reporter_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); int devlink_nl_health_reporter_recover_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1157,7 +1157,7 @@ static int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb, int devlink_nl_health_reporter_diagnose_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct devlink_fmsg *fmsg; int err; @@ -1252,7 +1252,7 @@ int devlink_nl_health_reporter_dump_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1269,7 +1269,7 @@ int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, int devlink_nl_health_reporter_test_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); diff --git a/net/devlink/linecard.c b/net/devlink/linecard.c index 8315d35cb91d..fd18f2759770 100644 --- a/net/devlink/linecard.c +++ b/net/devlink/linecard.c @@ -171,7 +171,7 @@ void devlink_linecards_notify_unregister(struct devlink *devlink) int devlink_nl_linecard_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; struct sk_buff *msg; int err; @@ -371,7 +371,7 @@ static int devlink_linecard_type_unset(struct devlink_linecard *linecard, int devlink_nl_linecard_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; int err; diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index ae4afc739678..300580c1a217 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -12,6 +12,7 @@ #define DEVLINK_NL_FLAG_NEED_PORT BIT(0) #define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) #define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2) +#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3) static const struct genl_multicast_group devlink_nl_mcgrps[] = { [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, @@ -239,37 +240,82 @@ devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, return ERR_PTR(-ENODEV); } +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) +{ + unsigned int maxtype = ARRAY_SIZE(devlink_dl_parent_dev_nl_policy) - 1; + struct devlink *devlink; + struct nlattr **tb; + int err; + + if (!attrs[DEVLINK_ATTR_PARENT_DEV]) + return ERR_PTR(-EINVAL); + + tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL); + if (!tb) + return ERR_PTR(-ENOMEM); + + err = nla_parse_nested(tb, maxtype, attrs[DEVLINK_ATTR_PARENT_DEV], + devlink_dl_parent_dev_nl_policy, NULL); + if (err) + goto out; + + devlink = devlink_get_from_attrs_lock(net, tb, false); + kfree(tb); + return devlink; +out: + kfree(tb); + return ERR_PTR(err); +} + static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { + bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV; bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; + struct devlink *devlink, *parent_devlink = NULL; + struct net *net = genl_info_net(info); + struct nlattr **attrs = info->attrs; struct devlink_port *devlink_port; - struct devlink *devlink; int err; - devlink = devlink_get_from_attrs_lock(genl_info_net(info), info->attrs, - dev_lock); - if (IS_ERR(devlink)) - return PTR_ERR(devlink); + if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) { + parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs); + if (IS_ERR(parent_devlink)) + return PTR_ERR(parent_devlink); + devlink_nl_ctx(info)->parent_devlink = parent_devlink; + /* Drop the parent devlink lock but don't release the reference. + * This will keep it alive until the end of the request. + */ + devl_unlock(parent_devlink); + } - info->user_ptr[0] = devlink; + devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock); + if (IS_ERR(devlink)) { + err = PTR_ERR(devlink); + goto parent_put; + } + devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (IS_ERR(devlink_port)) { err = PTR_ERR(devlink_port); goto unlock; } - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } else if (flags & DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (!IS_ERR(devlink_port)) - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } return 0; unlock: devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); +parent_put: + if (parent_dev && parent_devlink) + devlink_put(parent_devlink); return err; } @@ -298,15 +344,25 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT); } +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + return __devlink_nl_pre_doit(skb, info, + DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; struct devlink *devlink; - devlink = info->user_ptr[0]; + devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); + if (devlink_nl_ctx(info)->parent_devlink) + devlink_put(devlink_nl_ctx(info)->parent_devlink); } void devlink_nl_post_doit(const struct genl_split_ops *ops, @@ -322,6 +378,14 @@ devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK); } +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static int devlink_nl_inst_single_dumpit(struct sk_buff *msg, struct netlink_callback *cb, int flags, devlink_nl_dump_one_func_t *dump_one, diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index f52b0c2b19ed..dec00133178d 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -46,6 +46,12 @@ devlink_attr_param_type_validate(const struct nlattr *attr, } /* Common nested types */ +const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), +}; + const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1] = { [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY, }, [DEVLINK_PORT_FN_ATTR_STATE] = NLA_POLICY_MAX(NLA_U8, 1), @@ -608,7 +614,7 @@ static const struct nla_policy devlink_rate_get_dump_nl_policy[DEVLINK_ATTR_INDE }; /* DEVLINK_CMD_RATE_SET - do */ -static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -619,10 +625,11 @@ static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_NEW - do */ -static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -633,6 +640,7 @@ static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_DEL - do */ @@ -1290,21 +1298,21 @@ const struct genl_split_ops devlink_nl_ops[75] = { { .cmd = DEVLINK_CMD_RATE_SET, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_set_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_set_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { .cmd = DEVLINK_CMD_RATE_NEW, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_new_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_new_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 20034b0929a8..a70e0e4769aa 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,6 +13,7 @@ #include /* Common nested types */ +extern const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1]; extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; @@ -29,12 +30,19 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, struct genl_info *info); int devlink_nl_pre_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); void devlink_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); void devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info); int devlink_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); diff --git a/net/devlink/param.c b/net/devlink/param.c index 3e9d2e5750c2..8ca0f3ed646c 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -117,6 +117,11 @@ static const struct devlink_param devlink_param_generic[] = { .name = DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_NAME, .type = DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_TYPE, }, + { + .id = DEVLINK_PARAM_GENERIC_ID_MAX_SFS, + .name = DEVLINK_PARAM_GENERIC_MAX_SFS_NAME, + .type = DEVLINK_PARAM_GENERIC_MAX_SFS_TYPE, + }, }; static int devlink_param_generic_verify(const struct devlink_param *param) @@ -627,7 +632,7 @@ devlink_param_get_from_info(struct xarray *params, struct genl_info *info) int devlink_nl_param_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_param_item *param_item; struct sk_buff *msg; int err; @@ -728,7 +733,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, int devlink_nl_param_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->params, info, DEVLINK_CMD_PARAM_NEW); diff --git a/net/devlink/port.c b/net/devlink/port.c index 485029d43428..1528f2d148df 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -267,6 +267,8 @@ static int devlink_nl_port_attrs_put(struct sk_buff *msg, nla_put_u32(msg, DEVLINK_ATTR_PORT_PCI_SF_NUMBER, attrs->pci_sf.sf)) return -EMSGSIZE; + if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_sf.external)) + return -EMSGSIZE; break; case DEVLINK_PORT_FLAVOUR_PHYSICAL: case DEVLINK_PORT_FLAVOUR_CPU: @@ -594,7 +596,7 @@ void devlink_ports_notify_unregister(struct devlink *devlink) int devlink_nl_port_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct sk_buff *msg; int err; @@ -830,7 +832,7 @@ static int devlink_port_function_set(struct devlink_port *port, int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; int err; if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { @@ -856,8 +858,8 @@ int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; u32 count; if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) @@ -887,8 +889,8 @@ int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_unsplit_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_unsplit) return -EOPNOTSUPP; @@ -899,7 +901,7 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; struct devlink_port_new_attrs new_attrs = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *devlink_port; struct sk_buff *msg; int err; @@ -961,9 +963,9 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_del) return -EOPNOTSUPP; @@ -1529,7 +1531,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, WARN_ON(1); return -EINVAL; case DEVLINK_PORT_FLAVOUR_PCI_PF: - if (attrs->pci_pf.external) { + if (attrs->pci_pf.external || attrs->pci_pf.controller) { n = snprintf(name, len, "c%u", attrs->pci_pf.controller); if (n >= len) return -EINVAL; @@ -1539,7 +1541,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, n = snprintf(name, len, "pf%u", attrs->pci_pf.pf); break; case DEVLINK_PORT_FLAVOUR_PCI_VF: - if (attrs->pci_vf.external) { + if (attrs->pci_vf.external || attrs->pci_vf.controller) { n = snprintf(name, len, "c%u", attrs->pci_vf.controller); if (n >= len) return -EINVAL; @@ -1550,7 +1552,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, attrs->pci_vf.pf, attrs->pci_vf.vf); break; case DEVLINK_PORT_FLAVOUR_PCI_SF: - if (attrs->pci_sf.external) { + if (attrs->pci_sf.external || attrs->pci_sf.controller) { n = snprintf(name, len, "c%u", attrs->pci_sf.controller); if (n >= len) return -EINVAL; diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 533d21b028a7..e727c8b8b33e 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -30,13 +30,53 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) return devlink_rate ?: ERR_PTR(-ENODEV); } +/* Repeatedly walks the nested devlink chain while cross device rate nodes are + * supported and finds the topmost instance where rates should be stored. + * That instance is locked, referenced and returned. + * When cross device rate nodes aren't supported the original devlink instance + * is returned. + */ +static struct devlink *devl_rate_lock(struct devlink *devlink) +{ + struct devlink *rate_devlink = devlink, *parent; + + devl_assert_locked(devlink); + + while (rate_devlink->ops && + rate_devlink->ops->supported_cross_device_rate_nodes) { + parent = devlink_nested_in_get_lock(rate_devlink); + if (!parent) + break; + if (rate_devlink != devlink) { + /* Unlock intermediate instances. */ + devl_unlock(rate_devlink); + devlink_put(rate_devlink); + } + rate_devlink = parent; + } + return rate_devlink; +} + +/* Unlocks and puts 'rate devlink' if different than 'devlink'. */ +static void devl_rate_unlock(struct devlink *devlink, + struct devlink *rate_devlink) +{ + if (devlink == rate_devlink) + return; + + devl_unlock(rate_devlink); + devlink_put(rate_devlink); +} + static struct devlink_rate * -devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) +devlink_rate_node_get_by_name(struct devlink *rate_devlink, + struct devlink *devlink, const char *node_name) { struct devlink_rate *devlink_rate; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate) && + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate) && !strcmp(node_name, devlink_rate->name)) return devlink_rate; } @@ -44,7 +84,8 @@ devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) } static struct devlink_rate * -devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +devlink_rate_node_get_from_attrs(struct devlink *rate_devlink, + struct devlink *devlink, struct nlattr **attrs) { const char *rate_node_name; size_t len; @@ -57,24 +98,30 @@ devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) if (!len || strspn(rate_node_name, "0123456789") == len) return ERR_PTR(-EINVAL); - return devlink_rate_node_get_by_name(devlink, rate_node_name); + return devlink_rate_node_get_by_name(rate_devlink, devlink, + rate_node_name); } static struct devlink_rate * -devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_node_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, + struct genl_info *info) { - return devlink_rate_node_get_from_attrs(devlink, info->attrs); + return devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); } static struct devlink_rate * -devlink_rate_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, struct genl_info *info) { struct nlattr **attrs = info->attrs; if (attrs[DEVLINK_ATTR_PORT_INDEX]) return devlink_rate_leaf_get_from_info(devlink, info); else if (attrs[DEVLINK_ATTR_RATE_NODE_NAME]) - return devlink_rate_node_get_from_info(devlink, info); + return devlink_rate_node_get_from_info(rate_devlink, devlink, + info); else return ERR_PTR(-EINVAL); } @@ -102,6 +149,25 @@ static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw) return -EMSGSIZE; } +static int devlink_nl_rate_parent_fill(struct sk_buff *msg, + struct devlink_rate *devlink_rate) +{ + struct devlink_rate *parent = devlink_rate->parent; + struct devlink *devlink = parent->devlink; + + if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, + parent->name)) + return -EMSGSIZE; + + if (devlink != devlink_rate->devlink && + devlink_nl_put_nested_handle(msg, + devlink_net(devlink_rate->devlink), + devlink, DEVLINK_ATTR_PARENT_DEV)) + return -EMSGSIZE; + + return 0; +} + static int devlink_nl_rate_fill(struct sk_buff *msg, struct devlink_rate *devlink_rate, enum devlink_command cmd, u32 portid, u32 seq, @@ -146,10 +212,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg, devlink_rate->tx_weight)) goto nla_put_failure; - if (devlink_rate->parent) - if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, - devlink_rate->parent->name)) - goto nla_put_failure; + if (devlink_rate->parent && + devlink_nl_rate_parent_fill(msg, devlink_rate)) + goto nla_put_failure; if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw)) goto nla_put_failure; @@ -190,17 +255,25 @@ static void devlink_rate_notify(struct devlink_rate *devlink_rate, void devlink_rates_notify_register(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); } void devlink_rates_notify_unregister(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry_reverse(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + devl_rate_unlock(devlink, rate_devlink); } static int @@ -209,17 +282,20 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, { struct devlink_nl_dump_state *state = devlink_dump_state(cb); struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; int idx = 0; int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; u32 id = NETLINK_CB(cb->skb).portid; - if (idx < state->idx) { + if (idx < state->idx || devlink_rate->devlink != devlink) { idx++; continue; } + err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, cb->nlh->nlmsg_seq, flags, NULL); if (err) { @@ -228,6 +304,7 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, } idx++; } + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -239,28 +316,38 @@ int devlink_nl_rate_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) int devlink_nl_rate_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; struct sk_buff *msg; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; + if (!msg) { + err = -ENOMEM; + goto unlock; + } err = devlink_nl_rate_fill(msg, devlink_rate, DEVLINK_CMD_RATE_NEW, info->snd_portid, info->snd_seq, 0, info->extack); - if (err) { - nlmsg_free(msg); - return err; - } + if (err) + goto err_fill; + devl_rate_unlock(devlink, rate_devlink); return genlmsg_reply(msg, info); + +err_fill: + nlmsg_free(msg); +unlock: + devl_rate_unlock(devlink, rate_devlink); + return err; } static bool @@ -277,16 +364,18 @@ devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, static int devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, struct genl_info *info, struct nlattr *nla_parent) { - struct devlink *devlink = devlink_rate->devlink; + struct devlink *devlink = devlink_rate->devlink, *parent_devlink; const char *parent_name = nla_data(nla_parent); const struct devlink_ops *ops = devlink->ops; size_t len = strlen(parent_name); struct devlink_rate *parent; int err = -EOPNOTSUPP; + parent_devlink = devlink_nl_ctx(info)->parent_devlink ? : devlink; parent = devlink_rate->parent; if (parent && !len) { @@ -304,7 +393,14 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, refcount_dec(&parent->refcnt); devlink_rate->parent = NULL; } else if (len) { - parent = devlink_rate_node_get_by_name(devlink, parent_name); + /* parent_devlink (when different than devlink) isn't locked, + * but the rate node devlink instance is, so nobody from the + * same group of devices sharing rates could change the used + * fields or unregister the parent. + */ + parent = devlink_rate_node_get_by_name(rate_devlink, + parent_devlink, + parent_name); if (IS_ERR(parent)) return -ENODEV; @@ -423,6 +519,7 @@ static int devlink_nl_rate_tc_bw_set(struct devlink_rate *devlink_rate, } static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, const struct devlink_ops *ops, struct genl_info *info) { @@ -497,7 +594,8 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, */ nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; if (nla_parent) { - err = devlink_nl_rate_parent_node_set(devlink_rate, info, + err = devlink_nl_rate_parent_node_set(devlink_rate, + rate_devlink, info, nla_parent); if (err) return err; @@ -588,31 +686,51 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } ops = devlink->ops; - if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) - return -EOPNOTSUPP; + if (!ops || + !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) { + err = -EOPNOTSUPP; + goto unlock; + } - err = devlink_nl_rate_set(devlink_rate, ops, info); + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + err = -EOPNOTSUPP; + goto unlock; + } + + err = devlink_nl_rate_set(devlink_rate, rate_devlink, ops, info); if (!err) devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; ops = devlink->ops; @@ -624,15 +742,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE)) return -EOPNOTSUPP; - rate_node = devlink_rate_node_get_from_attrs(devlink, info->attrs); - if (!IS_ERR(rate_node)) - return -EEXIST; - else if (rate_node == ERR_PTR(-EINVAL)) - return -EINVAL; + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + return -EOPNOTSUPP; + } + + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); + if (!IS_ERR(rate_node)) { + err = -EEXIST; + goto unlock; + } else if (rate_node == ERR_PTR(-EINVAL)) { + err = -EINVAL; + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return -ENOMEM; + if (!rate_node) { + err = -ENOMEM; + goto unlock; + } rate_node->devlink = devlink; rate_node->type = DEVLINK_RATE_TYPE_NODE; @@ -646,13 +778,14 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (err) goto err_node_new; - err = devlink_nl_rate_set(rate_node, ops, info); + err = devlink_nl_rate_set(rate_node, rate_devlink, ops, info); if (err) goto err_rate_set; refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; err_rate_set: @@ -661,22 +794,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) kfree(rate_node->name); err_strdup: kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; int err; - rate_node = devlink_rate_node_get_from_info(devlink, info); - if (IS_ERR(rate_node)) - return PTR_ERR(rate_node); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_info(rate_devlink, devlink, + info); + if (IS_ERR(rate_node)) { + err = PTR_ERR(rate_node); + goto unlock; + } if (refcount_read(&rate_node->refcnt) > 1) { NL_SET_ERR_MSG(info->extack, "Node has children. Cannot delete node."); - return -EBUSY; + err = -EBUSY; + goto unlock; } devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); @@ -687,6 +827,8 @@ int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) list_del(&rate_node->list); kfree(rate_node->name); kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -695,14 +837,20 @@ int devlink_rates_check(struct devlink *devlink, struct netlink_ext_ack *extack) { struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; + int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) - if (!rate_filter || rate_filter(devlink_rate)) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) + if (devlink_rate->devlink == devlink && + (!rate_filter || rate_filter(devlink_rate))) { if (extack) NL_SET_ERR_MSG(extack, "Rate node(s) exists."); - return -EBUSY; + err = -EBUSY; + break; } - return 0; + devl_rate_unlock(devlink, rate_devlink); + return err; } /** @@ -719,14 +867,21 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, struct devlink_rate *parent) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - rate_node = devlink_rate_node_get_by_name(devlink, node_name); - if (!IS_ERR(rate_node)) - return ERR_PTR(-EEXIST); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_by_name(rate_devlink, devlink, + node_name); + if (!IS_ERR(rate_node)) { + rate_node = ERR_PTR(-EEXIST); + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return ERR_PTR(-ENOMEM); + if (!rate_node) { + rate_node = ERR_PTR(-ENOMEM); + goto unlock; + } rate_node->type = DEVLINK_RATE_TYPE_NODE; rate_node->devlink = devlink; @@ -735,7 +890,8 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, rate_node->name = kstrdup(node_name, GFP_KERNEL); if (!rate_node->name) { kfree(rate_node); - return ERR_PTR(-ENOMEM); + rate_node = ERR_PTR(-ENOMEM); + goto unlock; } if (parent) { @@ -744,8 +900,10 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, } refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return rate_node; } EXPORT_SYMBOL_GPL(devl_rate_node_create); @@ -761,10 +919,10 @@ EXPORT_SYMBOL_GPL(devl_rate_node_create); int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, struct devlink_rate *parent) { - struct devlink *devlink = devlink_port->devlink; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; struct devlink_rate *devlink_rate; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (WARN_ON(devlink_port->devlink_rate)) return -EBUSY; @@ -773,6 +931,7 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, if (!devlink_rate) return -ENOMEM; + rate_devlink = devl_rate_lock(devlink); if (parent) { devlink_rate->parent = parent; refcount_inc(&devlink_rate->parent->refcnt); @@ -782,9 +941,10 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, devlink_rate->devlink = devlink; devlink_rate->devlink_port = devlink_port; devlink_rate->priv = priv; - list_add_tail(&devlink_rate->list, &devlink->rate_list); + list_add_tail(&devlink_rate->list, &rate_devlink->rate_list); devlink_port->devlink_rate = devlink_rate; devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; } @@ -800,16 +960,19 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_create); void devl_rate_leaf_destroy(struct devlink_port *devlink_port) { struct devlink_rate *devlink_rate = devlink_port->devlink_rate; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (!devlink_rate) return; + rate_devlink = devl_rate_lock(devlink); devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_DEL); if (devlink_rate->parent) refcount_dec(&devlink_rate->parent->refcnt); list_del(&devlink_rate->list); devlink_port->devlink_rate = NULL; + devl_rate_unlock(devlink, rate_devlink); kfree(devlink_rate); } EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); @@ -818,20 +981,30 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); * devl_rate_nodes_destroy - destroy all devlink rate nodes on device * @devlink: devlink instance * - * Unset parent for all rate objects and destroy all rate nodes - * on specified device. + * Unset parent for all rate objects involving this device and destroy all rate + * nodes on it. */ void devl_rate_nodes_destroy(struct devlink *devlink) { - const struct devlink_ops *ops = devlink->ops; struct devlink_rate *devlink_rate, *tmp; + const struct devlink_ops *ops; + struct devlink *rate_devlink; devl_assert_locked(devlink); + rate_devlink = devl_rate_lock(devlink); - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (!devlink_rate->parent) + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (!devlink_rate->parent || + (devlink_rate->devlink != devlink && + devlink_rate->parent->devlink != devlink)) continue; + /* This could destroy rate objects on other devlinks in the + * same hierarchy under 'rate_devlink'. This is safe because + * the shared common ancestor is locked so there can be no + * other concurrent rate operations on devlink_rate->devlink. + */ + ops = devlink_rate->devlink->ops; if (devlink_rate_is_leaf(devlink_rate)) ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, NULL, NULL); @@ -842,13 +1015,17 @@ void devl_rate_nodes_destroy(struct devlink *devlink) refcount_dec(&devlink_rate->parent->refcnt); devlink_rate->parent = NULL; } - list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate)) { + ops = devlink->ops; + list_for_each_entry_safe(devlink_rate, tmp, &rate_devlink->rate_list, + list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate)) { ops->rate_node_del(devlink_rate, devlink_rate->priv, NULL); list_del(&devlink_rate->list); kfree(devlink_rate->name); kfree(devlink_rate); } } + devl_rate_unlock(devlink, rate_devlink); } EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); diff --git a/net/devlink/region.c b/net/devlink/region.c index 5588e3d560b9..537779bbff07 100644 --- a/net/devlink/region.c +++ b/net/devlink/region.c @@ -469,7 +469,7 @@ static void devlink_region_snapshot_del(struct devlink_region *region, int devlink_nl_region_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *port = NULL; struct devlink_region *region; const char *region_name; @@ -588,7 +588,7 @@ int devlink_nl_region_get_dumpit(struct sk_buff *skb, int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct devlink_region *region; @@ -633,7 +633,7 @@ int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct nlattr *snapshot_id_attr; diff --git a/net/devlink/resource.c b/net/devlink/resource.c index 574108ccfe5d..c3cfda7ea070 100644 --- a/net/devlink/resource.c +++ b/net/devlink/resource.c @@ -117,7 +117,7 @@ devlink_resource_validate_size(struct devlink_resource *resource, u64 size, int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_resource *resource; u64 resource_id; u64 size; @@ -251,8 +251,9 @@ static int devlink_resource_list_fill(struct sk_buff *skb, static int devlink_resource_fill(struct genl_info *info, enum devlink_command cmd, int flags) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct devlink_resource *resource; struct list_head *resource_list; struct nlattr *resources_attr; @@ -263,6 +264,7 @@ static int devlink_resource_fill(struct genl_info *info, int i; int err; + devlink_port = ctx->devlink_port; resource_list = devlink_port ? &devlink_port->resource_list : &devlink->resource_list; resource = list_first_entry(resource_list, @@ -326,10 +328,12 @@ static int devlink_resource_fill(struct genl_info *info, int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct list_head *resource_list; + devlink_port = ctx->devlink_port; if (info->attrs[DEVLINK_ATTR_PORT_INDEX] && !devlink_port) return -ENODEV; diff --git a/net/devlink/sb.c b/net/devlink/sb.c index 49fcbfe08f15..129bd016e302 100644 --- a/net/devlink/sb.c +++ b/net/devlink/sb.c @@ -204,7 +204,7 @@ static int devlink_nl_sb_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; int err; @@ -306,7 +306,7 @@ static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; u16 pool_index; @@ -415,7 +415,7 @@ static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, int devlink_nl_sb_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_threshold_type threshold_type; struct devlink_sb *devlink_sb; u16 pool_index; @@ -506,7 +506,7 @@ static int devlink_nl_sb_port_pool_fill(struct sk_buff *msg, int devlink_nl_sb_port_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -624,8 +624,8 @@ static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, int devlink_nl_sb_port_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; u16 pool_index; u32 threshold; @@ -716,7 +716,7 @@ devlink_nl_sb_tc_pool_bind_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_tc_pool_bind_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -864,8 +864,8 @@ static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_pool_type pool_type; struct devlink_sb *devlink_sb; u16 tc_index; @@ -902,7 +902,7 @@ int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; @@ -918,7 +918,7 @@ int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_sb_occ_max_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; diff --git a/net/devlink/trap.c b/net/devlink/trap.c index 8edb31654a68..793ffc66dc11 100644 --- a/net/devlink/trap.c +++ b/net/devlink/trap.c @@ -302,7 +302,7 @@ static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; struct sk_buff *msg; int err; @@ -412,7 +412,7 @@ static int devlink_trap_action_set(struct devlink *devlink, int devlink_nl_trap_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; if (list_empty(&devlink->trap_list)) @@ -511,7 +511,7 @@ devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_group_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; struct sk_buff *msg; int err; @@ -682,7 +682,7 @@ static int devlink_trap_group_set(struct devlink *devlink, int devlink_nl_trap_group_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; bool modified = false; int err; @@ -804,7 +804,7 @@ int devlink_nl_trap_policer_get_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -924,7 +924,7 @@ int devlink_nl_trap_policer_set_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (list_empty(&devlink->trap_policer_list)) return -EOPNOTSUPP; diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c index c3c8c3240ef9..451247864a63 100644 --- a/net/dns_resolver/dns_key.c +++ b/net/dns_resolver/dns_key.c @@ -203,7 +203,7 @@ dns_resolver_preparse(struct key_preparsed_payload *prep) kdebug("store result"); prep->quotalen = result_len; - upayload = kmalloc_flex(*upayload, data, result_len + 1); + upayload = kmalloc_flex(*upayload, data, result_len); if (!upayload) { kleave(" = -ENOMEM"); return -ENOMEM; @@ -211,7 +211,6 @@ dns_resolver_preparse(struct key_preparsed_payload *prep) upayload->datalen = result_len; memcpy(upayload->data, data, result_len); - upayload->data[result_len] = '\0'; prep->payload.data[dns_key_data] = upayload; kleave(" = 0"); diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c index 14bee83cbe22..b8fb9a1245bf 100644 --- a/net/dns_resolver/dns_query.c +++ b/net/dns_resolver/dns_query.c @@ -72,7 +72,7 @@ int dns_query(struct net *net, kenter("%s,%*.*s,%zu,%s", type, (int)namelen, (int)namelen, name, namelen, options); - if (!name || namelen < 3 || namelen > 255) + if (!name || namelen < 1 || namelen > 255) return -EINVAL; if (type && *type == '\0') return -EINVAL; diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index d5e725b90d78..23b4b74004ed 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA Say Y or M if you want to enable support for tagging frames for the Marvell switches which use EtherType DSA headers. +config NET_DSA_TAG_MT7628 + tristate "Tag driver for the MT7628 embedded switch" + help + Say Y or M if you want to enable support for tagging frames for the + switch embedded in the MT7628 SoC. + config NET_DSA_TAG_MTK tristate "Tag driver for Mediatek switches" help diff --git a/net/dsa/Makefile b/net/dsa/Makefile index b8c2667cd14a..d15bcf5c68f0 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o +obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 9cb732f6b1e3..2771c34e8400 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "conduit.h" @@ -1382,7 +1383,7 @@ static int dsa_switch_parse_of(struct dsa_switch *ds, struct device_node *dn) static int dev_is_class(struct device *dev, const void *class) { - if (dev->class != NULL && !strcmp(dev->class->name, class)) + if (dev->class && !strcmp(dev->class->name, class)) return 1; return 0; @@ -1620,10 +1621,23 @@ void dsa_switch_shutdown(struct dsa_switch *ds) rtnl_lock(); - dsa_switch_for_each_cpu_port(dp, ds) - list_add(&dp->conduit->close_list, &close_list); + dsa_switch_for_each_cpu_port(dp, ds) { + if (!(dp->conduit->flags & IFF_UP)) + continue; + list_add_tail(&dp->conduit->close_list, &close_list); + netdev_lock_ops(dp->conduit); + } - netif_close_many(&close_list, true); + netif_close_many(&close_list, false); + + while (!list_empty(&close_list)) { + struct net_device *conduit; + + conduit = list_first_entry(&close_list, struct net_device, + close_list); + netdev_unlock_ops(conduit); + list_del_init(&conduit->close_list); + } dsa_switch_for_each_user_port(dp, ds) { conduit = dsa_port_to_conduit(dp); diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index 67fa89f102e0..b4d70ba930dc 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -12,6 +12,7 @@ #include "tag.h" +#define KSZ8463_NAME "ksz8463" #define KSZ8795_NAME "ksz8795" #define KSZ9477_NAME "ksz9477" #define KSZ9893_NAME "ksz9893" @@ -103,72 +104,6 @@ static struct sk_buff *ksz_common_rcv(struct sk_buff *skb, return skb; } -/* - * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) - * - * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag0 : zero-based value represents port - * (eg, 0x0=port1, 0x2=port3, 0x3=port4) - */ - -#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) -#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) -#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) - -static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ethhdr *hdr; - u8 *tag; - - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ8795_TAIL_TAG_OVERRIDE; - - return skb; -} - -static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) -{ - u8 *tag; - - if (skb_linearize(skb)) { - kfree_skb(skb); - return NULL; - } - - tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; - - return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, - KSZ_EGRESS_TAG_LEN); -} - -static const struct dsa_device_ops ksz8795_netdev_ops = { - .name = KSZ8795_NAME, - .proto = DSA_TAG_PROTO_KSZ8795, - .xmit = ksz8795_xmit, - .rcv = ksz8795_rcv, - .needed_tailroom = KSZ_INGRESS_TAG_LEN, -}; - -DSA_TAG_DRIVER(ksz8795_netdev_ops); -MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); - /* * For Ingress (Host -> KSZ9477), 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- @@ -284,6 +219,37 @@ static struct sk_buff *ksz_defer_xmit(struct dsa_port *dp, struct sk_buff *skb) return NULL; } +static struct sk_buff *ksz_common_xmit(struct sk_buff *skb, + struct net_device *dev, + bool do_tstamp, + u8 prio, + u8 override_mask) +{ + struct dsa_port *dp = dsa_user_to_port(dev); + struct ethhdr *hdr; + u8 *tag; + + if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { + kfree_skb(skb); + return NULL; + } + + /* Tag encoding */ + if (do_tstamp) + ksz_xmit_timestamp(dp, skb); + + tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); + hdr = skb_eth_hdr(skb); + + *tag = dsa_xmit_port_mask(skb, dev); + *tag |= prio; + + if (is_link_local_ether_addr(hdr->h_dest)) + *tag |= override_mask; + + return ksz_defer_xmit(dp, skb); +} + static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, struct net_device *dev) { @@ -353,6 +319,56 @@ static const struct dsa_device_ops ksz9477_netdev_ops = { DSA_TAG_DRIVER(ksz9477_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9477, KSZ9477_NAME); +/* + * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) + * + * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag0 : zero-based value represents port + * (eg, 0x0=port1, 0x2=port3, 0x3=port4) + */ + +#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) +#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) +#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) + +static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) +{ + return ksz_common_xmit(skb, dev, false, 0, KSZ8795_TAIL_TAG_OVERRIDE); +} + +static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) +{ + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + + return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, + KSZ_EGRESS_TAG_LEN); +} + +static const struct dsa_device_ops ksz8795_netdev_ops = { + .name = KSZ8795_NAME, + .proto = DSA_TAG_PROTO_KSZ8795, + .xmit = ksz8795_xmit, + .rcv = ksz8795_rcv, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8795_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); + #define KSZ9893_TAIL_TAG_PRIO GENMASK(4, 3) #define KSZ9893_TAIL_TAG_OVERRIDE BIT(5) #define KSZ9893_TAIL_TAG_LOOKUP BIT(6) @@ -362,28 +378,10 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb, { u16 queue_mapping = skb_get_queue_mapping(skb); u8 prio = netdev_txq_to_tc(dev, queue_mapping); - struct dsa_port *dp = dsa_user_to_port(dev); - struct ethhdr *hdr; - u8 *tag; - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - ksz_xmit_timestamp(dp, skb); - - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - *tag |= FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio); - - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ9893_TAIL_TAG_OVERRIDE; - - return ksz_defer_xmit(dp, skb); + return ksz_common_xmit(skb, dev, true, + FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio), + KSZ9893_TAIL_TAG_OVERRIDE); } static const struct dsa_device_ops ksz9893_netdev_ops = { @@ -399,6 +397,70 @@ static const struct dsa_device_ops ksz9893_netdev_ops = { DSA_TAG_DRIVER(ksz9893_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME); +#define KSZ8463_TAIL_TAG_PRIO GENMASK(4, 3) +#define KSZ8463_TAIL_TAG_EG_PORT_M GENMASK(2, 0) + +static struct sk_buff *ksz8463_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + u16 queue_mapping = skb_get_queue_mapping(skb); + u8 prio = netdev_txq_to_tc(dev, queue_mapping); + + return ksz_common_xmit(skb, dev, false, + FIELD_PREP(KSZ8463_TAIL_TAG_PRIO, prio), + 0); +} + +static struct sk_buff *ksz8463_rcv(struct sk_buff *skb, struct net_device *dev) +{ + unsigned int len = KSZ_EGRESS_TAG_LEN; + struct ptp_header *ptp_hdr; + unsigned int ptp_class; + unsigned int port; + ktime_t ts; + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + KSZ_SKB_CB(skb)->tstamp = 0; + + /* Tag decoding */ + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + port = tag[0] & KSZ8463_TAIL_TAG_EG_PORT_M; + + __skb_push(skb, ETH_HLEN); + ptp_class = ptp_classify_raw(skb); + __skb_pull(skb, ETH_HLEN); + if (ptp_class == PTP_CLASS_NONE) + goto common_rcv; + + ptp_hdr = ptp_parse_header(skb, ptp_class); + if (ptp_hdr) { + ts = ksz_decode_tstamp(get_unaligned_be32(&ptp_hdr->reserved2)); + KSZ_SKB_CB(skb)->tstamp = ts; + ptp_hdr->reserved2 = 0; + } + +common_rcv: + return ksz_common_rcv(skb, dev, port, len); +} + +static const struct dsa_device_ops ksz8463_netdev_ops = { + .name = KSZ8463_NAME, + .proto = DSA_TAG_PROTO_KSZ8463, + .xmit = ksz8463_xmit, + .rcv = ksz8463_rcv, + .connect = ksz_connect, + .disconnect = ksz_disconnect, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8463_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8463, KSZ8463_NAME); + /* For xmit, 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|tag1(1byte)| @@ -471,6 +533,7 @@ DSA_TAG_DRIVER(lan937x_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_LAN937X, LAN937X_NAME); static struct dsa_tag_driver *dsa_tag_driver_array[] = { + &DSA_TAG_DRIVER_NAME(ksz8463_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz8795_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9477_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9893_netdev_ops), diff --git a/net/dsa/tag_mt7628.c b/net/dsa/tag_mt7628.c new file mode 100644 index 000000000000..80b50ff08e53 --- /dev/null +++ b/net/dsa/tag_mt7628.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026, Joris Vaisvila + * MT7628 switch tag support + */ + +#include +#include +#include + +#include "tag.h" + +/* + * The MT7628 tag is encoded in the VLAN TPID field. + * On TX the lower 6 bits encode the destination port bitmask. + * On RX the lower 3 bits encode the source port number. + * + * The switch hardware will not modify the TPID of an incoming packet if it is + * already VLAN tagged. To work around this the switch is configured to always + * append a tag_8021q standalone VLAN tag for each port. That means we can + * safely strip the outer VLAN tag after parsing it. + * + * A VLAN tag is constructed on egress to target the standalone VLAN and + * destination port. + */ + +#define MT7628_TAG_NAME "mt7628" + +#define MT7628_TAG_TX_PORT GENMASK(5, 0) +#define MT7628_TAG_RX_PORT GENMASK(2, 0) +#define MT7628_TAG_LEN 4 + +static struct sk_buff *mt7628_tag_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct dsa_port *dp; + u16 xmit_vlan; + __be16 *tag; + + dp = dsa_user_to_port(dev); + xmit_vlan = dsa_tag_8021q_standalone_vid(dp); + + skb_push(skb, MT7628_TAG_LEN); + dsa_alloc_etype_header(skb, MT7628_TAG_LEN); + + tag = dsa_etype_header_pos_tx(skb); + + tag[0] = htons(ETH_P_8021Q | + FIELD_PREP(MT7628_TAG_TX_PORT, + dsa_xmit_port_mask(skb, dev))); + tag[1] = htons(xmit_vlan); + + return skb; +} + +static struct sk_buff *mt7628_tag_rcv(struct sk_buff *skb, + struct net_device *dev) +{ + __be16 *phdr; + + if (unlikely(!pskb_may_pull(skb, MT7628_TAG_LEN))) { + kfree_skb(skb); + return NULL; + } + + phdr = dsa_etype_header_pos_rx(skb); + skb->dev = + dsa_conduit_find_user(dev, 0, + FIELD_GET(MT7628_TAG_RX_PORT, ntohs(*phdr))); + if (!skb->dev) { + kfree_skb(skb); + return NULL; + } + + skb_pull_rcsum(skb, MT7628_TAG_LEN); + dsa_strip_etype_header(skb, MT7628_TAG_LEN); + dsa_default_offload_fwd_mark(skb); + return skb; +} + +static const struct dsa_device_ops mt7628_tag_ops = { + .name = MT7628_TAG_NAME, + .proto = DSA_TAG_PROTO_MT7628, + .xmit = mt7628_tag_xmit, + .rcv = mt7628_tag_rcv, + .needed_headroom = MT7628_TAG_LEN, +}; + +module_dsa_tag_driver(mt7628_tag_ops); + +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MT7628, MT7628_TAG_NAME); +MODULE_DESCRIPTION("DSA tag driver for MT7628 switch"); +MODULE_LICENSE("GPL"); diff --git a/net/dsa/user.c b/net/dsa/user.c index 072fa76972cc..041f9060c8ef 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -2546,14 +2547,13 @@ static int dsa_user_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path) { struct dsa_port *dp = dsa_user_to_port(ctx->dev); - struct net_device *conduit = dsa_port_to_conduit(dp); struct dsa_port *cpu_dp = dp->cpu_dp; path->dev = ctx->dev; path->type = DEV_PATH_DSA; path->dsa.proto = cpu_dp->tag_ops->proto; path->dsa.port = dp->index; - ctx->dev = conduit; + ctx->dev = NULL; return 0; } @@ -2779,7 +2779,7 @@ int dsa_user_create(struct dsa_port *port) user_dev = alloc_netdev_mqs(sizeof(struct dsa_user_priv), name, assign_type, ether_setup, ds->num_tx_queues, 1); - if (user_dev == NULL) + if (!user_dev) return -ENOMEM; user_dev->rtnl_link_ops = &dsa_link_ops; @@ -3599,10 +3599,24 @@ static int dsa_user_netdevice_event(struct notifier_block *nb, if (dp->cpu_dp != cpu_dp) continue; - list_add(&dp->user->close_list, &close_list); + if (!(dp->user->flags & IFF_UP)) + continue; + + list_add_tail(&dp->user->close_list, &close_list); + netdev_lock_ops(dp->user); } - netif_close_many(&close_list, true); + netif_close_many(&close_list, false); + + while (!list_empty(&close_list)) { + struct net_device *user_dev; + + user_dev = list_first_entry(&close_list, + struct net_device, + close_list); + netdev_unlock_ops(user_dev); + list_del_init(&user_dev->close_list); + } return NOTIFY_OK; } diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c index 24b64862011f..6be3aa5d4bc1 100644 --- a/net/ethtool/tsconfig.c +++ b/net/ethtool/tsconfig.c @@ -359,8 +359,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (ret < 0) goto err_free_hwprov; - /* Select only one tx type at a time */ - if (ffs(req_tx_type) != fls(req_tx_type)) { + /* Select exactly one tx type at a time */ + if (hweight32(req_tx_type) != 1) { + NL_SET_BAD_ATTR(info->extack, + tb[ETHTOOL_A_TSCONFIG_TX_TYPES]); ret = -EINVAL; goto err_free_hwprov; } @@ -380,8 +382,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (ret < 0) goto err_free_hwprov; - /* Select only one rx filter at a time */ - if (ffs(req_rx_filter) != fls(req_rx_filter)) { + /* Select exactly one rx filter at a time */ + if (hweight32(req_rx_filter) != 1) { + NL_SET_BAD_ATTR(info->extack, + tb[ETHTOOL_A_TSCONFIG_RX_FILTERS]); ret = -EINVAL; goto err_free_hwprov; } diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 5555b71ab19b..d14de44e14b7 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -372,10 +372,21 @@ static void send_prp_supervision_frame(struct hsr_port *master, { struct hsr_priv *hsr = master->hsr; struct hsr_sup_payload *hsr_sp; + struct hsr_sup_tlv *hsr_stlv; struct hsr_sup_tag *hsr_stag; struct sk_buff *skb; + bool redbox_proxy; + int extra = 0; - skb = hsr_init_skb(master, 0); + redbox_proxy = hsr->redbox && master->type == HSR_PT_INTERLINK; + + /* A proxy-announce carries a RedBox-MAC TLV and an EOT marker. */ + if (redbox_proxy) + extra = sizeof(struct hsr_sup_tlv) + + sizeof(struct hsr_sup_payload) + + sizeof(struct hsr_sup_tlv); + + skb = hsr_init_skb(master, extra); if (!skb) { netdev_warn_once(master->dev, "PRP: Could not send supervision frame\n"); return; @@ -393,9 +404,25 @@ static void send_prp_supervision_frame(struct hsr_port *master, hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD; hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload); - /* Payload: MacAddressA */ + /* Payload: MacAddressA, the announced node. */ hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload)); - ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr); + ether_addr_copy(hsr_sp->macaddress_A, addr); + + /* Proxy-announce: append the RedBox-MAC TLV (Type 30) and an explicit + * EOT to terminate the TLV chain before zero padding. + */ + if (redbox_proxy) { + hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv)); + hsr_stlv->HSR_TLV_type = PRP_TLV_REDBOX_MAC; + hsr_stlv->HSR_TLV_length = sizeof(struct hsr_sup_payload); + + hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload)); + ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox); + + hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv)); + hsr_stlv->HSR_TLV_type = HSR_TLV_EOT; + hsr_stlv->HSR_TLV_length = 0; + } if (skb_put_padto(skb, ETH_ZLEN)) { spin_unlock_bh(&hsr->seqnr_lock); @@ -768,6 +795,15 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], /* Make sure the 1st call to netif_carrier_on() gets through */ netif_carrier_off(hsr_dev); + /* Publish the RedBox state before any port is attached: the rx + * handlers are live from hsr_add_port() on, and hsr_add_node() + * sizes each node's per-port sequence state from hsr->redbox. + */ + if (interlink) { + hsr->redbox = true; + ether_addr_copy(hsr->macaddress_redbox, interlink->dev_addr); + } + res = hsr_add_port(hsr, hsr_dev, HSR_PT_MASTER, extack); if (res) goto err_add_master; @@ -805,8 +841,6 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], if (res) goto err_unregister; - hsr->redbox = true; - ether_addr_copy(hsr->macaddress_redbox, interlink->dev_addr); mod_timer(&hsr->prune_proxy_timer, jiffies + msecs_to_jiffies(PRUNE_PROXY_PERIOD)); } @@ -820,6 +854,8 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], hsr_del_ports(hsr); err_add_master: hsr_del_self_node(hsr); + hsr_del_nodes(&hsr->node_db); + hsr_del_nodes(&hsr->proxy_node_db); if (unregister) unregister_netdevice(hsr_dev); diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c index 0774981a65c1..7734a521a96c 100644 --- a/net/hsr/hsr_forward.c +++ b/net/hsr/hsr_forward.c @@ -440,12 +440,34 @@ static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port, return dev_queue_xmit(skb); } +static bool prp_is_lan_dup(enum hsr_port_type rx, struct hsr_port *port) +{ + return (rx == HSR_PT_SLAVE_A && port->type == HSR_PT_SLAVE_B) || + (rx == HSR_PT_SLAVE_B && port->type == HSR_PT_SLAVE_A); +} + bool prp_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) { - return ((frame->port_rcv->type == HSR_PT_SLAVE_A && - port->type == HSR_PT_SLAVE_B) || - (frame->port_rcv->type == HSR_PT_SLAVE_B && - port->type == HSR_PT_SLAVE_A)); + enum hsr_port_type rx = frame->port_rcv->type; + + /* Supervision frames are not delivered to a SAN on the interlink. */ + if (frame->is_supervision && port->type == HSR_PT_INTERLINK) + return true; + + if (prp_is_lan_dup(rx, port)) + return true; + + /* LAN to interlink: keep PRP-network unicast off the SAN segment. */ + if ((rx == HSR_PT_SLAVE_A || rx == HSR_PT_SLAVE_B) && + port->type == HSR_PT_INTERLINK) + return frame->dst_in_node_db; + + /* Interlink to LAN: keep SAN-to-SAN unicast local. */ + if ((port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) && + rx == HSR_PT_INTERLINK) + return frame->dst_in_proxy_node_db; + + return false; } bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) @@ -453,7 +475,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) struct sk_buff *skb; if (port->dev->features & NETIF_F_HW_HSR_FWD) - return prp_drop_frame(frame, port); + return prp_is_lan_dup(frame->port_rcv->type, port); /* RedBox specific frames dropping policies * @@ -466,7 +488,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) * are addressed to interlink port (and are in the ProxyNodeTable). */ skb = frame->skb_hsr; - if (skb && prp_drop_frame(frame, port) && + if (skb && prp_is_lan_dup(frame->port_rcv->type, port) && is_unicast_ether_addr(eth_hdr(skb)->h_dest) && hsr_is_node_in_db(&port->hsr->proxy_node_db, eth_hdr(skb)->h_dest)) { @@ -706,6 +728,18 @@ static int fill_frame_info(struct hsr_frame_info *frame, frame->is_vlan = false; proto = ethhdr->h_proto; + /* PRP RedBox only: classify the unicast destination once so the + * per-egress-port decision in prp_drop_frame() stays O(1). HSR RedBox + * does its own classification and must not pay these node-table walks. + */ + if (hsr->prot_version == PRP_V1 && hsr->redbox && + is_unicast_ether_addr(ethhdr->h_dest)) { + frame->dst_in_node_db = + hsr_is_node_in_db(&hsr->node_db, ethhdr->h_dest); + frame->dst_in_proxy_node_db = + hsr_is_node_in_db(&hsr->proxy_node_db, ethhdr->h_dest); + } + if (proto == htons(ETH_P_8021Q)) frame->is_vlan = true; diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c index e44929871274..b3b106be692e 100644 --- a/net/hsr/hsr_framereg.c +++ b/net/hsr/hsr_framereg.c @@ -199,7 +199,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr, spin_lock_init(&new_node->seq_out_lock); if (hsr->prot_version == PRP_V1) - new_node->seq_port_cnt = 1; + new_node->seq_port_cnt = hsr->redbox ? 2 : 1; else new_node->seq_port_cnt = HSR_PT_PORTS - 1; @@ -293,8 +293,17 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db, */ if (ethhdr->h_proto == htons(ETH_P_PRP) || ethhdr->h_proto == htons(ETH_P_HSR)) { - /* Check if skb contains hsr_ethhdr */ - if (skb->mac_len < sizeof(struct hsr_ethhdr)) + bool prp_sup; + + /* A PRP supervision frame is an untagged ETH_P_PRP frame + * (mac_len == ETH_HLEN); its RCT is appended only on egress. + * HSR (ETH_P_HSR) supervision is front-tagged and still must + * contain a struct hsr_ethhdr. + */ + prp_sup = hsr->prot_version == PRP_V1 && + ethhdr->h_proto == htons(ETH_P_PRP) && is_sup; + + if (!prp_sup && skb->mac_len < sizeof(struct hsr_ethhdr)) return NULL; } else { rct = skb_get_PRP_rct(skb); @@ -381,6 +390,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) struct ethhdr *ethhdr; unsigned int total_pull_size = 0; unsigned int pull_size = 0; + unsigned int seq_port_cnt; unsigned long idx; int i; @@ -474,6 +484,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) } } + seq_port_cnt = min(node_real->seq_port_cnt, node_curr->seq_port_cnt); xa_for_each(&node_curr->seq_blocks, idx, src_blk) { if (hsr_seq_block_is_old(src_blk)) continue; @@ -482,7 +493,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) if (!merge_blk) continue; merge_blk->time = min(merge_blk->time, src_blk->time); - for (i = 0; i < node_real->seq_port_cnt; i++) { + for (i = 0; i < seq_port_cnt; i++) { bitmap_or(merge_blk->seq_nrs[i], merge_blk->seq_nrs[i], src_blk->seq_nrs[i], HSR_SEQ_BLOCK_SIZE); } @@ -649,9 +660,13 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame) if (frame->port_rcv->type == HSR_PT_MASTER) return 0; - /* for PRP we should only forward frames from the slave ports - * to the master port + /* RedBox: forward LAN frames out the interlink to a SAN, deduping the + * two LAN copies on a dedicated slot. */ + if (port->type == HSR_PT_INTERLINK) + return hsr_check_duplicate(frame, 1); + + /* For PRP only slave-to-master frames are forwarded. */ if (port->type != HSR_PT_MASTER) return 1; diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h index c65ecb925734..127a3fb64d5f 100644 --- a/net/hsr/hsr_framereg.h +++ b/net/hsr/hsr_framereg.h @@ -27,6 +27,8 @@ struct hsr_frame_info { bool is_local_dest; bool is_local_exclusive; bool is_from_san; + bool dst_in_node_db; + bool dst_in_proxy_node_db; }; void hsr_del_self_node(struct hsr_priv *hsr); diff --git a/net/hsr/hsr_main.h b/net/hsr/hsr_main.h index 134e4f3fff60..53e95bae0ee2 100644 --- a/net/hsr/hsr_main.h +++ b/net/hsr/hsr_main.h @@ -211,7 +211,7 @@ struct hsr_priv { */ bool fwd_offloaded; /* Forwarding offloaded to HW */ bool redbox; /* Device supports HSR RedBox */ - unsigned char macaddress_redbox[ETH_ALEN]; + unsigned char macaddress_redbox[ETH_ALEN] __aligned(sizeof(u16)); unsigned char sup_multicast_addr[ETH_ALEN] __aligned(sizeof(u16)); /* Align to u16 boundary to avoid unaligned access * in ether_addr_equal diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c index 8099f2069a74..88940e8014b2 100644 --- a/net/hsr/hsr_netlink.c +++ b/net/hsr/hsr_netlink.c @@ -121,14 +121,8 @@ static int hsr_newlink(struct net_device *dev, } } - if (proto == HSR_PROTOCOL_PRP) { + if (proto == HSR_PROTOCOL_PRP) proto_version = PRP_V1; - if (interlink) { - NL_SET_ERR_MSG_MOD(extack, - "Interlink only works with HSR"); - return -EINVAL; - } - } return hsr_dev_finalize(dev, link, interlink, multicast_spec, proto_version, extack); diff --git a/net/ieee802154/socket.c b/net/ieee802154/socket.c index 85dce296d751..5a36e87893f6 100644 --- a/net/ieee802154/socket.c +++ b/net/ieee802154/socket.c @@ -831,20 +831,12 @@ static int ieee802154_dgram_deliver(struct net_device *dev, struct sk_buff *skb) return ret; } -static int dgram_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_dgram_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct dgram_sock *ro = dgram_sk(sk); - int val, len; - if (level != SOL_IEEE802154) - return -EOPNOTSUPP; - - if (get_user(len, optlen)) - return -EFAULT; - - len = min_t(unsigned int, len, sizeof(int)); + len = umin(sizeof(int), opt->optlen); switch (optname) { case WPAN_WANTACK: @@ -871,10 +863,32 @@ static int dgram_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - if (put_user(len, optlen)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; - if (copy_to_user(optval, &val, len)) + return 0; +} + +static int dgram_getsockopt(struct sock *sk, int level, int optname, + char __user *optval, int __user *optlen) +{ + sockopt_t opt; + int err; + + if (level != SOL_IEEE802154) + return -EOPNOTSUPP; + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_dgram_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) return -EFAULT; + return 0; } diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index a35b72662e43..a90be57c63be 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -322,6 +322,8 @@ static void inetdev_destroy(struct in_device *in_dev) in_dev->dead = 1; + RCU_INIT_POINTER(dev->ip_ptr, NULL); + ip_mc_destroy_dev(in_dev); while ((ifa = rtnl_dereference(in_dev->ifa_list)) != NULL) { @@ -329,8 +331,6 @@ static void inetdev_destroy(struct in_device *in_dev) inet_free_ifa(ifa); } - RCU_INIT_POINTER(dev->ip_ptr, NULL); - devinet_sysctl_unregister(in_dev); neigh_parms_release(&arp_tbl, in_dev->arp_parms); arp_ifdown(dev); @@ -2607,10 +2607,9 @@ static int devinet_conf_proc(const struct ctl_table *ctl, int write, static int devinet_sysctl_forward(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct net *net = ctl->extra2; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct net *net = ctl->extra2; int ret; if (write && !ns_capable(net->user_ns, CAP_NET_ADMIN)) @@ -2623,7 +2622,6 @@ static int devinet_sysctl_forward(const struct ctl_table *ctl, int write, if (!rtnl_net_trylock(net)) { /* Restore the original values before restarting */ *valp = val; - *ppos = pos; return restart_syscall(); } if (valp == &IPV4_DEVCONF_ALL(net, FORWARDING)) { @@ -2798,7 +2796,7 @@ static void devinet_sysctl_unregister(struct in_device *idev) neigh_sysctl_unregister(idev->arp_parms); } -static struct ctl_table ctl_forward_entry[] = { +static const struct ctl_table ctl_forward_entry[] = { { .procname = "ip_forward", .data = &ipv4_devconf.data[ diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae..a6c18aea7498 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -323,7 +323,7 @@ static struct ip_esp_hdr *esp_output_udp_encap(struct sk_buff *skb, uh = (struct udphdr *)esp->esph; uh->source = sport; uh->dest = dport; - uh->len = htons(len); + udp_set_len_short(uh, len); uh->check = 0; /* For IPv4 ESP with UDP encapsulation, if xo is not null, the skb is in the crypto offload diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 42212970d735..8a3dc04e8cac 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -76,7 +76,7 @@ static int __net_init fib4_rules_init(struct net *net) struct fib_table *fib_new_table(struct net *net, u32 id) { - struct fib_table *tb, *alias = NULL; + struct fib_table *tb, *new_tb, *alias = NULL; unsigned int h; if (id == 0) @@ -85,14 +85,27 @@ struct fib_table *fib_new_table(struct net *net, u32 id) if (tb) return tb; + if (!check_net(net)) + return NULL; + if (id == RT_TABLE_LOCAL && !net->ipv4.fib_has_custom_rules) alias = fib_new_table(net, RT_TABLE_MAIN); - if (check_net(net)) - tb = fib_trie_table(id, alias); - if (!tb) + new_tb = fib_trie_table(id, alias); + if (!new_tb) return NULL; + spin_lock(&net->ipv4.fib_table_hash_lock); + + tb = fib_get_table(net, id); + if (tb) { + spin_unlock(&net->ipv4.fib_table_hash_lock); + fib_free_table(new_tb); + return tb; + } + + tb = new_tb; + switch (id) { case RT_TABLE_MAIN: rcu_assign_pointer(net->ipv4.fib_main, tb); @@ -106,28 +119,35 @@ struct fib_table *fib_new_table(struct net *net, u32 id) h = id & (FIB_TABLE_HASHSZ - 1); hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]); + + spin_unlock(&net->ipv4.fib_table_hash_lock); + return tb; } EXPORT_SYMBOL_GPL(fib_new_table); -/* caller must hold either rtnl or rcu read lock */ struct fib_table *fib_get_table(struct net *net, u32 id) { - struct fib_table *tb; + struct fib_table *tb = NULL; struct hlist_head *head; unsigned int h; if (id == 0) id = RT_TABLE_MAIN; h = id & (FIB_TABLE_HASHSZ - 1); - head = &net->ipv4.fib_table_hash[h]; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) { + + /* fib_table is not destroyed until ip_fib_net_exit() + * except for the merged main/local table. + * fib_unmerge() is called under RTNL, so other readers + * under RTNL (e.g. fib_flush(), fib_info_notify_update()) + * can safely traverse the list with rcu_dereference_raw(). + */ + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) if (tb->tb_id == id) - return tb; - } - return NULL; + break; + + return tb; } #endif /* CONFIG_IP_MULTIPLE_TABLES */ @@ -190,10 +210,9 @@ void fib_flush(struct net *net) for (h = 0; h < FIB_TABLE_HASHSZ; h++) { struct hlist_head *head = &net->ipv4.fib_table_hash[h]; - struct hlist_node *tmp; struct fib_table *tb; - hlist_for_each_entry_safe(tb, tmp, head, tb_hlist) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) flushed += fib_table_flush(net, tb, false); } @@ -1019,10 +1038,11 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) .dump_routes = true, .dump_exceptions = true, }; - unsigned int e = 0, s_e, h, s_h; struct hlist_head *head; int dumped = 0, err = 0; struct fib_table *tb; + unsigned int h, s_h; + u32 s_id; rcu_read_lock(); if (cb->strict_check) { @@ -1054,29 +1074,28 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) } s_h = cb->args[0]; - s_e = cb->args[1]; + s_id = cb->args[1]; err = 0; - for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_e = 0) { - e = 0; + for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_id = 0) { head = &net->ipv4.fib_table_hash[h]; hlist_for_each_entry_rcu(tb, head, tb_hlist) { - if (e < s_e) - goto next; + if (s_id && tb->tb_id != s_id) + continue; + + s_id = 0; if (dumped) memset(&cb->args[2], 0, sizeof(cb->args) - 2 * sizeof(cb->args[0])); + cb->args[1] = tb->tb_id; err = fib_table_dump(tb, skb, cb, &filter); if (err < 0) goto out; dumped = 1; -next: - e++; } } out: - cb->args[1] = e; cb->args[0] = h; unlock: @@ -1566,6 +1585,10 @@ static int __net_init ip_fib_net_init(struct net *net) FIB_MULTIPATH_HASH_FIELD_DEFAULT_MASK; #endif +#ifdef CONFIG_IP_MULTIPLE_TABLES + spin_lock_init(&net->ipv4.fib_table_hash_lock); +#endif + /* Avoid false sharing : Use at least a full cache line */ size = max_t(size_t, size, L1_CACHE_BYTES); diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index e068a5bace73..4edb0dca7be8 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -301,10 +301,12 @@ static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb, fib4_nl2rule_dscp_mask(tb[FRA_DSCP_MASK], rule4, extack) < 0) goto errout; - /* split local/main if they are not already split */ - err = fib_unmerge(net); - if (err) - goto errout; + if (!net->ipv4.fib_has_custom_rules) { + /* split local/main if they are not already split */ + err = fib_unmerge(net); + if (err) + goto errout; + } if (rule->table == RT_TABLE_UNSPEC && !rule->l3mdev) { if (rule->action == FR_ACT_TO_TBL) { @@ -349,7 +351,7 @@ static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb, return err; } -static int fib4_rule_delete(struct fib_rule *rule) +static void fib4_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; @@ -361,8 +363,6 @@ static int fib4_rule_delete(struct fib_rule *rule) if (net->ipv4.fib_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv4.fib_rules_require_fldissect--; - - return 0; } static int fib4_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, @@ -460,6 +460,11 @@ static void fib4_rule_flush_cache(struct fib_rules_ops *ops) rt_cache_flush(ops->fro_net); } +static bool fib4_rule_need_rtnl(struct net *net) +{ + return !net->ipv4.fib_has_custom_rules; +} + static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .family = AF_INET, .rule_size = sizeof(struct fib4_rule), @@ -473,6 +478,7 @@ static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .fill = fib4_rule_fill, .nlmsg_payload = fib4_rule_nlmsg_payload, .flush_cache = fib4_rule_flush_cache, + .need_rtnl = fib4_rule_need_rtnl, .nlgroup = RTNLGRP_IPV4_RULE, .owner = THIS_MODULE, }; diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index 6badad29593b..acb1e4385914 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -2137,8 +2137,7 @@ void fib_info_notify_update(struct net *net, struct nl_info *info) struct hlist_head *head = &net->ipv4.fib_table_hash[h]; struct fib_table *tb; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) __fib_info_notify_update(net, tb, info); } } diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index ab09dfcdecbd..5e867f1b5c1d 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -1040,7 +1040,7 @@ static void fou_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - uh->len = htons(skb->len); + udp_set_len(uh, skb->len); udp_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM), skb, fl4->saddr, fl4->daddr, skb->len); diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index bb2d4441a492..b80b8a92f46e 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1487,6 +1487,7 @@ int inet_fill_ifmcaddr(struct sk_buff *skb, struct net_device *dev, ci.ifa_valid = INFINITY_LIFE_TIME; if (nla_put_in_addr(skb, IFA_MULTICAST, im->multiaddr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(im->users)) < 0 || nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci) < 0) { nlmsg_cancel(skb, nlh); return -EMSGSIZE; @@ -1508,6 +1509,7 @@ static void inet_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(__be32)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 0ba1e94e9012..82309efd417e 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -475,12 +475,9 @@ static int gre_rcv(struct sk_buff *skb) static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, - __be16 proto) + __be16 proto, const unsigned long *flags) { struct ip_tunnel *tunnel = netdev_priv(dev); - IP_TUNNEL_DECLARE_FLAGS(flags); - - ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); /* Push GRE header. */ gre_build_header(skb, tunnel->tun_hlen, @@ -653,6 +650,7 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); const struct iphdr *tnl_params; if (!pskb_inet_may_pull(skb)) @@ -688,11 +686,12 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, tnl_params = &tunnel->parms.iph; } - if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, - tunnel->parms.o_flags))) + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + + if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) goto free_skb; - __gre_xmit(skb, dev, tnl_params, skb->protocol); + __gre_xmit(skb, dev, tnl_params, skb->protocol, flags); return NETDEV_TX_OK; free_skb: @@ -705,6 +704,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); bool truncate = false; __be16 proto; @@ -728,10 +728,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, truncate = true; } + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + /* Push ERSPAN header */ if (tunnel->erspan_ver == 0) { proto = htons(ETH_P_ERSPAN); - __clear_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.o_flags); + __clear_bit(IP_TUNNEL_SEQ_BIT, flags); } else if (tunnel->erspan_ver == 1) { erspan_build_header(skb, ntohl(tunnel->parms.o_key), tunnel->index, @@ -746,8 +748,8 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, goto free_skb; } - __clear_bit(IP_TUNNEL_KEY_BIT, tunnel->parms.o_flags); - __gre_xmit(skb, dev, &tunnel->parms.iph, proto); + __clear_bit(IP_TUNNEL_KEY_BIT, flags); + __gre_xmit(skb, dev, &tunnel->parms.iph, proto, flags); return NETDEV_TX_OK; free_skb: @@ -760,6 +762,7 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); if (!pskb_inet_may_pull(skb)) goto free_skb; @@ -769,14 +772,15 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, return NETDEV_TX_OK; } - if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, - tunnel->parms.o_flags))) + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + + if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) goto free_skb; if (skb_cow_head(skb, dev->needed_headroom)) goto free_skb; - __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB)); + __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB), flags); return NETDEV_TX_OK; free_skb: @@ -1560,12 +1564,15 @@ static size_t ipgre_get_size(const struct net_device *dev) static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); - struct ip_tunnel_parm_kern *p = &t->parms; + const struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel_parm_kern *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags); + if (t->erspan_ver != 0 && !t->collect_md) + __set_bit(IP_TUNNEL_KEY_BIT, o_flags); + if (nla_put_u32(skb, IFLA_GRE_LINK, p->link) || nla_put_be16(skb, IFLA_GRE_IFLAGS, gre_tnl_flags_to_gre_flags(p->i_flags)) || @@ -1608,12 +1615,9 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) static int erspan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel *t = netdev_priv(dev); if (t->erspan_ver <= 2) { - if (t->erspan_ver != 0 && !t->collect_md) - __set_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); - if (nla_put_u8(skb, IFLA_GRE_ERSPAN_VER, t->erspan_ver)) goto nla_put_failure; @@ -1716,53 +1720,6 @@ static struct rtnl_link_ops erspan_link_ops __read_mostly = { .get_link_net = ip_tunnel_get_link_net, }; -struct net_device *gretap_fb_dev_create(struct net *net, const char *name, - u8 name_assign_type) -{ - struct rtnl_newlink_params params = { .src_net = net }; - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - LIST_HEAD(list_kill); - struct ip_tunnel *t; - int err; - - memset(&tb, 0, sizeof(tb)); - params.tb = tb; - - dev = rtnl_create_link(net, name, name_assign_type, - &ipgre_tap_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - /* Configure flow based GRE device. */ - t = netdev_priv(dev); - t->collect_md = true; - - err = ipgre_newlink(dev, ¶ms, NULL); - if (err < 0) { - free_netdev(dev); - return ERR_PTR(err); - } - - /* openvswitch users expect packet sizes to be unrestricted, - * so set the largest MTU we can. - */ - err = __ip_tunnel_change_mtu(dev, IP_MAX_MTU, false); - if (err) - goto out; - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) - goto out; - - return dev; -out: - ip_tunnel_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(gretap_fb_dev_create); - static int __net_init ipgre_tap_init_net(struct net *net) { return ip_tunnel_init_net(net, gre_tap_net_id, &ipgre_tap_ops, "gretap0"); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index e6dd1e5b8c32..74e095b6b7ca 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -790,6 +790,10 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, */ hlen = iph->ihl * 4; + if (mtu < hlen + 8) { + err = -EMSGSIZE; + goto fail; + } mtu = mtu - hlen; /* Size of data space */ IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE; ll_rs = LL_RESERVED_SPACE(rt->dst.dev); diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..e6bcf01411d0 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -317,7 +317,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev) mtu = min(tdev->mtu, IP_MAX_MTU); } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0); if (mtu < IPV4_MIN_MTU) @@ -1054,7 +1054,7 @@ int ip_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr, } EXPORT_SYMBOL_GPL(ip_tunnel_siocdevprivate); -int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict) +int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu) { struct ip_tunnel *tunnel = netdev_priv(dev); int t_hlen = tunnel->hlen + sizeof(struct iphdr); @@ -1063,25 +1063,12 @@ int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict) if (dev->type == ARPHRD_ETHER) max_mtu -= dev->hard_header_len; - if (new_mtu < ETH_MIN_MTU) + if (new_mtu < ETH_MIN_MTU || new_mtu > max_mtu) return -EINVAL; - if (new_mtu > max_mtu) { - if (strict) - return -EINVAL; - - new_mtu = max_mtu; - } - WRITE_ONCE(dev->mtu, new_mtu); return 0; } -EXPORT_SYMBOL_GPL(__ip_tunnel_change_mtu); - -int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu) -{ - return __ip_tunnel_change_mtu(dev, new_mtu, true); -} EXPORT_SYMBOL_GPL(ip_tunnel_change_mtu); static void ip_tunnel_dev_free(struct net_device *dev) diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c index a35ffedacc7c..155db067eaec 100644 --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c @@ -847,7 +847,7 @@ static void __init ic_bootp_send_if(struct ic_device *d, unsigned long jiffies_d /* Construct UDP header */ b->udph.source = htons(68); b->udph.dest = htons(67); - b->udph.len = htons(sizeof(struct bootp_pkt) - sizeof(struct iphdr)); + udp_set_len_short(&b->udph, sizeof(struct bootp_pkt) - sizeof(struct iphdr)); /* UDP checksum not calculated -- explicitly allowed in BOOTP RFC */ /* Construct DHCP/BOOTP header */ @@ -1025,10 +1025,10 @@ static int __init ic_bootp_recv(struct sk_buff *skb, struct net_device *dev, str if (b->udph.source != htons(67) || b->udph.dest != htons(68)) goto drop; - if (ntohs(h->tot_len) < ntohs(b->udph.len) + sizeof(struct iphdr)) + if (ntohs(h->tot_len) < udp_get_len_short(&b->udph) + sizeof(struct iphdr)) goto drop; - len = ntohs(b->udph.len) - sizeof(struct udphdr); + len = udp_get_len_short(&b->udph) - sizeof(struct udphdr); ext_len = len - (sizeof(*b) - sizeof(struct iphdr) - sizeof(struct udphdr) - diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index b643194f57d2..1630325c77d3 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -360,19 +360,29 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, const struct iphdr *tiph = &tunnel->parms.iph; struct rtable *rt; - rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, 0, 0, 0, - RT_SCOPE_UNIVERSE); + if (ctx->ether_type != cpu_to_be16(ETH_P_IP)) + return -EOPNOTSUPP; + + if (tunnel->collect_md) + return -EOPNOTSUPP; + + if (tunnel->parms.iph.tos & 0x1) + return -EOPNOTSUPP; + + rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, tiph->saddr, + inet_dsfield_to_dscp(tiph->tos), + tunnel->parms.link, RT_SCOPE_UNIVERSE); if (IS_ERR(rt)) return PTR_ERR(rt); path->type = DEV_PATH_TUN; path->tun.src_v4.s_addr = tiph->saddr; path->tun.dst_v4.s_addr = tiph->daddr; - path->tun.l3_proto = IPPROTO_IPIP; + path->tun.inner_proto = IPPROTO_IPIP; + path->tun.dst = &rt->dst; path->dev = ctx->dev; ctx->dev = rt->dst.dev; - ip_rt_put(rt); return 0; } diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 1d9a4ac14fce..e5f2b1c6150d 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2213,6 +2213,9 @@ int ip_mr_input(struct sk_buff *skb) if (IPCB(skb)->flags & IPSKB_FORWARDED) goto dont_forward; + if (!local) + skb_orphan(skb); + mrt = ipmr_rt_fib_lookup(net, skb); if (IS_ERR(mrt)) { kfree_skb(skb); diff --git a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c index 0ede138dfd29..4492bc548a66 100644 --- a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c +++ b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c @@ -127,7 +127,7 @@ static int snmp_translate(struct nf_conn *ct, int dir, struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); struct udphdr *udph = (struct udphdr *)((__be32 *)iph + iph->ihl); - u16 datalen = ntohs(udph->len) - sizeof(struct udphdr); + u16 datalen = udp_get_len_short(udph) - sizeof(struct udphdr); char *data = (unsigned char *)udph + sizeof(struct udphdr); struct snmp_ctx ctx; int ret; @@ -181,7 +181,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, * enough room for a UDP header. Just verify the UDP length field so we * can mess around with the payload. */ - if (ntohs(udph->len) != skb->len - (iph->ihl << 2)) { + if (udp_get_len_short(udph) != skb->len - (iph->ihl << 2)) { nf_ct_helper_log(skb, ct, "dropping malformed packet\n"); return NF_DROP; } @@ -213,7 +213,7 @@ static int __init nf_nat_snmp_basic_init(void) RCU_INIT_POINTER(nf_nat_snmp_hook, help); nf_ct_helper_init(&snmp_trap_helper, AF_INET, IPPROTO_UDP, - "snmp_trap", SNMP_TRAP_PORT, SNMP_TRAP_PORT, SNMP_TRAP_PORT, + "snmp_trap", &snmp_exp_policy, 0, help, NULL, THIS_MODULE); err = nf_conntrack_helper_register(&snmp_trap_helper, &snmp_trap_helper_ptr); diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index 4626dc46808f..59ec465a9df9 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -263,6 +264,7 @@ static int nf_reject_fill_skb_dst(struct sk_buff *skb_in) if (!dst) return -1; + skb_dst_drop(skb_in); skb_dst_set(skb_in, dst); return 0; } @@ -279,7 +281,7 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb, if (!oth) return; - if (!skb_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0) + if (!skb_valid_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0) return; if (skb_rtable(oldskb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) @@ -352,7 +354,7 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook) if (iph->frag_off & htons(IP_OFFSET)) return; - if (!skb_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0) + if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0) return; if (skb_csum_unnecessary(skb_in) || diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index 0f1e21a5c812..a7c2b8dced4e 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -39,6 +39,7 @@ static const struct nla_policy rtm_nh_policy_new[] = { [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, [NHA_ENCAP] = { .type = NLA_NESTED }, [NHA_FDB] = { .type = NLA_FLAG }, + [NHA_DST_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), [NHA_RES_GROUP] = { .type = NLA_NESTED }, [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true), }; @@ -956,6 +957,9 @@ static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, } else if (nhi->fdb_nh) { if (nla_put_flag(skb, NHA_FDB)) goto nla_put_failure; + if (nhi->dst_port && + nla_put_be16(skb, NHA_DST_PORT, nhi->dst_port)) + goto nla_put_failure; } else { const struct net_device *dev; @@ -1055,6 +1059,9 @@ static size_t nh_nlmsg_size_single(struct nexthop *nh) break; } + if (nhi->dst_port) + sz += nla_total_size(2); /* NHA_DST_PORT */ + if (nhi->fib_nhc.nhc_lwtstate) { sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ @@ -2965,8 +2972,10 @@ static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, nhi->family = cfg->nh_family; nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; - if (cfg->nh_fdb) + if (cfg->nh_fdb) { nhi->fdb_nh = 1; + nhi->dst_port = cfg->nh_dst_port; + } if (cfg->nh_blackhole) { nhi->reject_nh = 1; @@ -3156,6 +3165,15 @@ static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); } + if (tb[NHA_DST_PORT]) { + if (!tb[NHA_FDB] || !tb[NHA_GATEWAY]) { + NL_SET_ERR_MSG(extack, + "Destination port can only be set on fdb nexthops that have a gateway"); + goto out; + } + cfg->nh_dst_port = nla_get_be16(tb[NHA_DST_PORT]); + } + if (tb[NHA_GROUP]) { if (nhm->nh_family != AF_UNSPEC) { NL_SET_ERR_MSG(extack, "Invalid family for group"); @@ -4201,12 +4219,22 @@ static const struct rtnl_msg_handler nexthop_rtnl_msg_handlers[] __initconst = { static int __init nexthop_init(void) { - register_pernet_subsys(&nexthop_net_ops); + int err; - register_netdevice_notifier(&nh_netdev_notifier); + err = register_pernet_subsys(&nexthop_net_ops); + if (err) + return err; + + err = register_netdevice_notifier(&nh_netdev_notifier); + if (err) + goto err_unregister_pernet; rtnl_register_many(nexthop_rtnl_msg_handlers); return 0; + +err_unregister_pernet: + unregister_pernet_subsys(&nexthop_net_ops); + return err; } subsys_initcall(nexthop_init); diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index 7f74d8b95a37..d756e5e31b90 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -818,23 +818,18 @@ static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen) return 0; } -static int raw_geticmpfilter(struct sock *sk, char __user *optval, int __user *optlen) +static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt) { - int len, ret = -EFAULT; + int len = opt->optlen; - if (get_user(len, optlen)) - goto out; - ret = -EINVAL; if (len < 0) - goto out; + return -EINVAL; if (len > sizeof(struct icmp_filter)) len = sizeof(struct icmp_filter); - ret = -EFAULT; - if (put_user(len, optlen) || - copy_to_user(optval, &raw_sk(sk)->filter, len)) - goto out; - ret = 0; -out: return ret; + opt->optlen = len; + if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len) + return -EFAULT; + return 0; } static int do_raw_setsockopt(struct sock *sk, int optname, @@ -857,14 +852,13 @@ static int raw_setsockopt(struct sock *sk, int level, int optname, return do_raw_setsockopt(sk, optname, optval, optlen); } -static int do_raw_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { if (optname == ICMP_FILTER) { if (inet_sk(sk)->inet_num != IPPROTO_ICMP) return -EOPNOTSUPP; else - return raw_geticmpfilter(sk, optval, optlen); + return raw_geticmpfilter(sk, opt); } return -ENOPROTOOPT; } @@ -872,9 +866,24 @@ static int do_raw_getsockopt(struct sock *sk, int optname, static int raw_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { + sockopt_t opt; + int err; + if (level != SOL_RAW) return ip_getsockopt(sk, level, optname, optval, optlen); - return do_raw_getsockopt(sk, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_raw_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int raw_ioctl(struct sock *sk, int cmd, int *karg) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index b82401a6baed..604cc51dfd9b 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -903,21 +903,23 @@ void ip_rt_send_redirect(struct sk_buff *skb) { struct rtable *rt = skb_rtable(skb); struct in_device *in_dev; + struct net_device *dev; struct inet_peer *peer; - struct net *net; int log_martians; + struct net *net; int vif; rcu_read_lock(); - in_dev = __in_dev_get_rcu(rt->dst.dev); + dev = dst_dev_rcu(&rt->dst); + in_dev = __in_dev_get_rcu(dev); if (!in_dev || !IN_DEV_TX_REDIRECTS(in_dev)) { rcu_read_unlock(); return; } log_martians = IN_DEV_LOG_MARTIANS(in_dev); - vif = l3mdev_master_ifindex_rcu(rt->dst.dev); + vif = l3mdev_master_ifindex_rcu(dev); - net = dev_net(rt->dst.dev); + net = dev_net_rcu(dev); peer = inet_getpeer_v4(net->ipv4.peers, ip_hdr(skb)->saddr, vif); if (!peer) { rcu_read_unlock(); @@ -1314,29 +1316,32 @@ void ip_rt_get_source(u8 *addr, struct sk_buff *skb, struct rtable *rt) { __be32 src; - if (rt_is_output_route(rt)) + rcu_read_lock(); + if (rt_is_output_route(rt)) { src = ip_hdr(skb)->saddr; - else { - struct fib_result res; + } else { + struct net_device *dev = dst_dev_rcu(&rt->dst); + struct net *net = dev_net_rcu(dev); struct iphdr *iph = ip_hdr(skb); + struct fib_result res; struct flowi4 fl4 = { .daddr = iph->daddr, .saddr = iph->saddr, .flowi4_dscp = ip4h_dscp(iph), - .flowi4_oif = rt->dst.dev->ifindex, + .flowi4_oif = dev->ifindex, .flowi4_iif = skb->dev->ifindex, .flowi4_mark = skb->mark, }; - rcu_read_lock(); - if (fib_lookup(dev_net(rt->dst.dev), &fl4, &res, 0) == 0) - src = fib_result_prefsrc(dev_net(rt->dst.dev), &res); + if (fib_lookup(net, &fl4, &res, 0) == 0) + src = fib_result_prefsrc(net, &res); else - src = inet_select_addr(rt->dst.dev, + src = inet_select_addr(dev, rt_nexthop(rt, iph->daddr), RT_SCOPE_UNIVERSE); - rcu_read_unlock(); } + rcu_read_unlock(); + memcpy(addr, &src, 4); } @@ -1592,7 +1597,7 @@ void rt_flush_dev(struct net_device *dev) list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { if (rt->dst.dev != dev) continue; - rt->dst.dev = blackhole_netdev; + rcu_assign_pointer(rt->dst.dev_rcu, blackhole_netdev); netdev_ref_replace(dev, blackhole_netdev, &rt->dst.dev_tracker, GFP_ATOMIC); list_del_init(&rt->dst.rt_uncached); @@ -3214,7 +3219,7 @@ static struct sk_buff *inet_rtm_getroute_build_skb(__be32 src, __be32 dst, udph = skb_put_zero(skb, sizeof(struct udphdr)); udph->source = sport; udph->dest = dport; - udph->len = htons(sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct udphdr)); udph->check = 0; break; } diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index ca1180dba1de..2f0363bca2a8 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -624,7 +624,7 @@ static struct ctl_table ipv4_table[] = { }, }; -static struct ctl_table ipv4_net_table[] = { +static const struct ctl_table ipv4_net_table[] = { { .procname = "tcp_max_tw_buckets", .data = &init_net.ipv4.tcp_death_row.sysctl_max_tw_buckets, @@ -1654,35 +1654,45 @@ static struct ctl_table ipv4_net_table[] = { }, }; -static __net_init int ipv4_sysctl_init_net(struct net *net) +static const struct ctl_table *ipv4_net_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(ipv4_net_table); struct ctl_table *table; + int i; + + table = kmemdup(ipv4_net_table, sizeof(ipv4_net_table), GFP_KERNEL); + if (!table) + return NULL; + + for (i = 0; i < table_size; i++) { + if (table[i].data) { + /* Update the variables to point into + * the current struct net + */ + table[i].data += (void *)net - (void *)&init_net; + } else { + /* Entries without data pointer are global; + * Make them read-only in non-init_net ns + */ + table[i].mode &= ~0222; + } + if (table[i].extra2 >= (void *)&init_net.ipv4 && + table[i].extra2 < (void *)(&init_net.ipv4 + 1)) + table[i].extra2 += (void *)net - (void *)&init_net; + } + return table; +} + +static __net_init int ipv4_sysctl_init_net(struct net *net) +{ + size_t table_size = ARRAY_SIZE(ipv4_net_table); + const struct ctl_table *table; table = ipv4_net_table; if (!net_eq(net, &init_net)) { - int i; - - table = kmemdup(table, sizeof(ipv4_net_table), GFP_KERNEL); + table = ipv4_net_table_dup(net); if (!table) goto err_alloc; - - for (i = 0; i < table_size; i++) { - if (table[i].data) { - /* Update the variables to point into - * the current struct net - */ - table[i].data += (void *)net - (void *)&init_net; - } else { - /* Entries without data pointer are global; - * Make them read-only in non-init_net ns - */ - table[i].mode &= ~0222; - } - if (table[i].extra2 >= (void *)&init_net.ipv4 && - table[i].extra2 < (void *)(&init_net.ipv4 + 1)) - table[i].extra2 += (void *)net - (void *)&init_net; - } } net->ipv4.ipv4_hdr = register_net_sysctl_sz(net, "net/ipv4", table, diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index 6ab3e3a0b431..12254e6eb2f3 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -453,8 +453,8 @@ void tcp_openreq_init_rwin(struct request_sock *req, rcv_wnd = tcp_rwnd_init_bpf((struct sock *)req); if (rcv_wnd == 0) rcv_wnd = dst_metric(dst, RTAX_INITRWND); - else if (full_space < rcv_wnd * mss) - full_space = rcv_wnd * mss; + else if (full_space < (u64)rcv_wnd * mss) + full_space = min_t(u64, (u64)rcv_wnd * mss, INT_MAX); /* tcp_full_space because it is guaranteed to be the first packet */ tcp_select_initial_window(sk_listener, full_space, diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d7c1444b5e30..fcaa04e65189 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -251,7 +251,7 @@ void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, (*rcv_wnd) = space; if (init_rcv_wnd) - *rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss); + *rcv_wnd = min_t(u64, *rcv_wnd, (u64)init_rcv_wnd * mss); *rcv_wscale = 0; if (wscale_ok) { @@ -4103,6 +4103,7 @@ static void tcp_connect_init(struct sock *sk) const struct dst_entry *dst = __sk_dst_get(sk); struct tcp_sock *tp = tcp_sk(sk); __u8 rcv_wscale; + int full_space; u16 user_mss; u32 rcv_wnd; @@ -4137,10 +4138,13 @@ static void tcp_connect_init(struct sock *sk) WRITE_ONCE(tp->window_clamp, tcp_full_space(sk)); rcv_wnd = tcp_rwnd_init_bpf(sk); + full_space = tcp_full_space(sk); if (rcv_wnd == 0) rcv_wnd = dst_metric(dst, RTAX_INITRWND); + else if (full_space < (u64)rcv_wnd * tp->advmss) + full_space = min_t(u64, (u64)rcv_wnd * tp->advmss, INT_MAX); - tcp_select_initial_window(sk, tcp_full_space(sk), + tcp_select_initial_window(sk, full_space, tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0), &tp->rcv_wnd, &tp->window_clamp, diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index 4df1c9745c5d..1038e7ba9c2e 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -750,7 +750,7 @@ static void tcp_write_timer(struct timer_list *t) sock_put(sk); } -void tcp_syn_ack_timeout(const struct request_sock *req) +noinline_for_tracing void tcp_syn_ack_timeout(const struct request_sock *req) { struct net *net = read_pnet(&inet_rsk(req)->ireq_net); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 70f6cbd4ef73..af9603217444 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -76,6 +76,7 @@ #include #include +#include #include #include #include @@ -1107,7 +1108,8 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4, uh = udp_hdr(skb); uh->source = inet_sk(sk)->inet_sport; uh->dest = fl4->fl4_dport; - uh->len = htons(len); + /* Datagram length checked in udp_sendmsg. */ + udp_set_len_short(uh, len); uh->check = 0; if (cork->gso_size) { @@ -2475,6 +2477,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, struct udp_hslot *hslot; struct sk_buff *nskb; bool use_hash2; + int ret; hash2_any = 0; hash2 = 0; @@ -2519,8 +2522,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } if (first) { - if (udp_queue_rcv_skb(first, skb) > 0) - consume_skb(skb); + ret = udp_queue_rcv_skb(first, skb); + if (ret > 0) + return -ret; } else { kfree_skb(skb); __UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI); @@ -2590,8 +2594,8 @@ int udp_rcv(struct sk_buff *skb) struct rtable *rt = skb_rtable(skb); struct net *net = dev_net(skb->dev); struct sock *sk = NULL; - unsigned short ulen; __be32 saddr, daddr; + unsigned int ulen; struct udphdr *uh; bool refcounted; int drop_reason; @@ -2605,7 +2609,7 @@ int udp_rcv(struct sk_buff *skb) goto drop; /* No space for header. */ uh = udp_hdr(skb); - ulen = ntohs(uh->len); + ulen = udp_get_len(skb, uh, 0); saddr = ip_hdr(skb)->saddr; daddr = ip_hdr(skb)->daddr; @@ -2995,14 +2999,13 @@ static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t opt } int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) + sockopt_t *opt) { struct udp_sock *up = udp_sk(sk); int val, len; - if (get_user(len, optlen)) - return -EFAULT; - + len = opt->optlen; + /* keep the check so direct sockopt_t callers stay covered. */ if (len < 0) return -EINVAL; @@ -3037,9 +3040,8 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; } @@ -3047,9 +3049,29 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, static int udp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ip_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + /* + * keep the old __user pointers, until ip_getsockopt() moves + * to sockopt_t + */ + if (level != SOL_UDP) + return ip_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + + /* optval was written by copy_to_iter() in udp_lib_getsockopt() */ + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } /** diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index abcd3cc2d9a3..cf07c3c6611a 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -281,11 +281,11 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb, * segment instead of the entire frame. */ if (gso_partial && skb_is_gso(skb)) { - uh->len = htons(skb_shinfo(skb)->gso_size + - SKB_GSO_CB(skb)->data_offset + - skb->head - (unsigned char *)uh); + udp_set_len_short(uh, skb_shinfo(skb)->gso_size + + SKB_GSO_CB(skb)->data_offset + + skb->head - (unsigned char *)uh); } else { - uh->len = htons(len); + udp_set_len_short(uh, len); } if (!need_csum) @@ -470,7 +470,7 @@ static struct sk_buff *__udp_gso_segment_list(struct sk_buff *skb, if (IS_ERR(skb)) return skb; - udp_hdr(skb)->len = htons(sizeof(struct udphdr) + mss); + udp_set_len_short(udp_hdr(skb), sizeof(struct udphdr) + mss); if (is_ipv6) return __udpv6_gso_segment_list_csum(skb); @@ -488,8 +488,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, unsigned int mss; bool copy_dtor; __sum16 check; - __be16 newlen; int ret = 0; + u16 newlen; mss = skb_shinfo(gso_skb)->gso_size; if (gso_skb->len <= sizeof(*uh) + mss) @@ -566,8 +566,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, (skb_shinfo(gso_skb)->tx_flags & SKBTX_ANY_TSTAMP); /* compute checksum adjustment based on old length versus new */ - newlen = htons(sizeof(*uh) + mss); - check = csum16_add(csum16_sub(uh->check, uh->len), newlen); + newlen = sizeof(*uh) + mss; + check = csum16_add(csum16_sub(uh->check, uh->len), htons(newlen)); for (;;) { if (copy_dtor) { @@ -579,7 +579,7 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, if (!seg->next) break; - uh->len = newlen; + udp_set_len_short(uh, newlen); uh->check = check; if (seg->ip_summed == CHECKSUM_PARTIAL) @@ -596,11 +596,10 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, * segment may not be full MSS, account for that in the checksum */ if (!skb_is_gso(seg)) - newlen = htons(skb_tail_pointer(seg) - - skb_transport_header(seg) + seg->data_len); - check = csum16_add(csum16_sub(uh->check, uh->len), newlen); + newlen = skb_tail_pointer(seg) - skb_transport_header(seg) + seg->data_len; + check = csum16_add(csum16_sub(uh->check, uh->len), htons(newlen)); - uh->len = newlen; + udp_set_len_short(uh, newlen); uh->check = check; if (seg->ip_summed == CHECKSUM_PARTIAL) @@ -710,12 +709,8 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, return NULL; } - /* Do not deal with padded or malicious packets, sorry ! */ - ulen = ntohs(uh->len); - if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) { - NAPI_GRO_CB(skb)->flush = 1; - return NULL; - } + ulen = udp_get_len_short(uh); + /* pull encapsulating udp header */ skb_gro_pull(skb, sizeof(struct udphdr)); @@ -744,7 +739,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, * On len mismatch merge the first packet shorter than gso_size, * otherwise complete the GRO packet. */ - if (ulen > ntohs(uh2->len) || flush) { + if (ulen > udp_get_len_short(uh2) || flush) { pp = p; } else { if (NAPI_GRO_CB(skb)->is_flist) { @@ -767,7 +762,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, } } - if (ret || ulen != ntohs(uh2->len) || + if (ret || ulen != udp_get_len_short(uh2) || NAPI_GRO_CB(p)->count >= UDP_GRO_CNT_MAX) pp = p; @@ -785,8 +780,14 @@ struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb, struct sk_buff *p; struct udphdr *uh2; unsigned int off = skb_gro_offset(skb); + unsigned int ulen; int flush = 1; + /* Do not deal with padded or malicious packets, sorry! */ + ulen = udp_get_len_short(uh); + if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) + goto out; + /* We can do L4 aggregation only if the packet can't land in a tunnel * otherwise we could corrupt the inner stream. Detecting such packets * cannot be foolproof and the aggregation might still happen in some @@ -917,12 +918,12 @@ static int udp_gro_complete_segment(struct sk_buff *skb) int udp_gro_complete(struct sk_buff *skb, int nhoff, udp_lookup_t lookup) { - __be16 newlen = htons(skb->len - nhoff); struct udphdr *uh = (struct udphdr *)(skb->data + nhoff); + unsigned int newlen = skb->len - nhoff; struct sock *sk; int err; - uh->len = newlen; + udp_set_len(uh, newlen); sk = INDIRECT_CALL_INET(lookup, udp6_lib_lookup_skb, udp4_lib_lookup_skb, skb, uh->source, uh->dest); @@ -959,7 +960,7 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - uh->len = htons(skb->len - nhoff); + udp_set_len(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 9ab3728f9630..a128fe85620d 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -178,7 +178,7 @@ void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb uh->dest = dst_port; uh->source = src_port; - uh->len = htons(skb->len); + udp_set_len(uh, skb->len); memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c index 58faf1ddd2b1..ab7a01029d49 100644 --- a/net/ipv4/xfrm4_policy.c +++ b/net/ipv4/xfrm4_policy.c @@ -141,7 +141,7 @@ static const struct xfrm_policy_afinfo xfrm4_policy_afinfo = { }; #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm4_policy_table[] = { +static const struct ctl_table xfrm4_policy_table[] = { { .procname = "xfrm4_gc_thresh", .data = &init_net.xfrm.xfrm4_dst_ops.gc_thresh, @@ -151,18 +151,30 @@ static struct ctl_table xfrm4_policy_table[] = { }, }; -static __net_init int xfrm4_net_sysctl_init(struct net *net) +static const struct ctl_table *xfrm4_policy_table_dup(struct net *net) { struct ctl_table *table; + + table = kmemdup(xfrm4_policy_table, sizeof(xfrm4_policy_table), + GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh; + + return table; +} + +static __net_init int xfrm4_net_sysctl_init(struct net *net) +{ + const struct ctl_table *table; struct ctl_table_header *hdr; table = xfrm4_policy_table; if (!net_eq(net, &init_net)) { - table = kmemdup(table, sizeof(xfrm4_policy_table), GFP_KERNEL); + table = xfrm4_policy_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh; } hdr = register_net_sysctl_sz(net, "net/ipv4", table, diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 5b0cd6488021..cf5e01f83ce3 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -51,7 +51,7 @@ obj-$(subst m,y,$(CONFIG_IPV6)) += inet6_hashtables.o ifneq ($(CONFIG_IPV6),) obj-$(CONFIG_NET_UDP_TUNNEL) += ip6_udp_tunnel.o -obj-y += mcast_snoop.o +obj-y += mcast_snoop.o ndisc_snoop.o obj-$(CONFIG_TCP_AO) += tcp_ao.o endif diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index cbe681de3818..9d89be7e0544 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -2874,7 +2874,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) if (rt) { /* Autoconf prefix route */ if (valid_lft == 0) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, + RT_DEL_REASON_RA_WITHDRAWN); rt = NULL; } else { table = rt->fib6_table; @@ -5264,6 +5265,7 @@ int inet6_fill_ifmcaddr(struct sk_buff *skb, put_ifaddrmsg(nlh, 128, IFA_F_PERMANENT, scope, ifindex); if (nla_put_in6_addr(skb, IFA_MULTICAST, &ifmca->mca_addr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(ifmca->mca_users)) < 0 || put_cacheinfo(skb, ifmca->mca_cstamp, READ_ONCE(ifmca->mca_tstamp), INFINITY_LIFE_TIME, INFINITY_LIFE_TIME) < 0) { nlmsg_cancel(skb, nlh); @@ -6362,10 +6364,9 @@ static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp) static int addrconf_sysctl_forward(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* @@ -6381,8 +6382,6 @@ static int addrconf_sysctl_forward(const struct ctl_table *ctl, int write, if (write) ret = addrconf_fixup_forwarding(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6461,10 +6460,9 @@ static int addrconf_disable_ipv6(const struct ctl_table *table, int *p, int newf static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* @@ -6480,8 +6478,6 @@ static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, if (write) ret = addrconf_disable_ipv6(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6666,10 +6662,9 @@ int addrconf_sysctl_ignore_routes_with_linkdown(const struct ctl_table *ctl, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* ctl->data points to idev->cnf.ignore_routes_when_linkdown @@ -6684,8 +6679,6 @@ int addrconf_sysctl_ignore_routes_with_linkdown(const struct ctl_table *ctl, if (write) ret = addrconf_fixup_linkdown(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6766,10 +6759,9 @@ int addrconf_disable_policy(const struct ctl_table *ctl, int *valp, int val) static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; lctl = *ctl; @@ -6781,9 +6773,6 @@ static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write if (write && (*valp != val)) ret = addrconf_disable_policy(ctl, valp, val); - if (ret) - *ppos = pos; - return ret; } @@ -6815,7 +6804,6 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri int *valp = ctl->data; int new_val = *valp; int old_val = *valp; - loff_t pos = *ppos; int ret; tmp_ctl.extra1 = SYSCTL_ZERO; @@ -6851,8 +6839,6 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri rtnl_net_unlock(net); } - if (ret) - *ppos = pos; return ret; } diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb..72ec0d7d1120 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -230,7 +230,8 @@ static void esp_output_encap_csum(struct sk_buff *skb) if (*skb_mac_header(skb) == IPPROTO_UDP) { struct udphdr *uh = udp_hdr(skb); struct ipv6hdr *ip6h = ipv6_hdr(skb); - int len = ntohs(uh->len); + /* esp6_output_udp_encap limits len to U16_MAX. */ + int len = udp_get_len_short(uh); unsigned int offset = skb_transport_offset(skb); __wsum csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -358,7 +359,7 @@ static struct ip_esp_hdr *esp6_output_udp_encap(struct sk_buff *skb, uh = (struct udphdr *)esp->esph; uh->source = sport; uh->dest = dport; - uh->len = htons(len); + udp_set_len_short(uh, len); uh->check = 0; *skb_mac_header(skb) = IPPROTO_UDP; diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index 89ee3c969ca7..0ffd7b47a046 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -481,15 +481,13 @@ static int fib6_rule_configure(struct fib_rule *rule, struct sk_buff *skb, return err; } -static int fib6_rule_delete(struct fib_rule *rule) +static void fib6_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; if (net->ipv6.fib6_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv6.fib6_rules_require_fldissect--; - - return 0; } static int fib6_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, @@ -638,21 +636,14 @@ static int __net_init fib6_rules_net_init(struct net *net) goto out; } -static void __net_exit fib6_rules_net_exit_batch(struct list_head *net_list) +static void __net_exit fib6_rules_net_exit(struct net *net) { - struct net *net; - - rtnl_lock(); - list_for_each_entry(net, net_list, exit_list) { - fib_rules_unregister(net->ipv6.fib6_rules_ops); - cond_resched(); - } - rtnl_unlock(); + fib_rules_unregister(net->ipv6.fib6_rules_ops); } static struct pernet_operations fib6_rules_net_ops = { .init = fib6_rules_net_init, - .exit_batch = fib6_rules_net_exit_batch, + .exit = fib6_rules_net_exit, }; int __init fib6_rules_init(void) diff --git a/net/ipv6/fou6.c b/net/ipv6/fou6.c index 157765259e2f..4b659ca60ba9 100644 --- a/net/ipv6/fou6.c +++ b/net/ipv6/fou6.c @@ -30,7 +30,7 @@ static void fou6_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - uh->len = htons(skb->len); + udp_set_len(uh, skb->len); udp6_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM6), skb, &fl6->saddr, &fl6->daddr, skb->len); diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index efb23807a026..a95b0351824f 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -1374,7 +1374,7 @@ EXPORT_SYMBOL(icmpv6_err_convert); static u32 icmpv6_errors_extension_mask_all = GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0); -static struct ctl_table ipv6_icmp_table_template[] = { +static const struct ctl_table ipv6_icmp_table_template[] = { { .procname = "ratelimit", .data = &init_net.ipv6.sysctl.icmpv6_time, diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index e9fc692d4f3b..3e382ba1573e 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1967,7 +1967,8 @@ static struct fib6_node *fib6_repair_tree(struct net *net, } static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, - struct fib6_info __rcu **rtp, struct nl_info *info) + struct fib6_info __rcu **rtp, struct nl_info *info, + enum rt_del_reason del_reason) { struct fib6_info *leaf, *replace_rt = NULL; struct fib6_walker *w; @@ -2056,13 +2057,14 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, call_fib6_entry_notifiers_replace(net, replace_rt); } if (!info->skip_notify) - inet6_rt_notify(RTM_DELROUTE, rt, info, 0); + inet6_rt_del_notify(rt, info, del_reason); fib6_info_release(rt); } /* Need to own table->tb6_lock */ -int fib6_del(struct fib6_info *rt, struct nl_info *info) +int fib6_del(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct fib6_info __rcu **rtp; @@ -2091,7 +2093,7 @@ int fib6_del(struct fib6_info *rt, struct nl_info *info) if (rt == cur) { if (fib6_requires_src(cur)) fib6_routes_require_src_dec(info->nl_net); - fib6_del_route(table, fn, rtp, info); + fib6_del_route(table, fn, rtp, info, del_reason); return 0; } rtp_next = &cur->fib6_next; @@ -2253,7 +2255,7 @@ static int fib6_clean_node(struct fib6_walker *w) res = c->func(rt, c->arg); if (res == -1) { w->leaf = rt; - res = fib6_del(rt, &info); + res = fib6_del(rt, &info, RT_DEL_REASON_UNSPEC); if (res) { #if RT6_DEBUG >= 2 pr_debug("%s: del failed: rt=%p@%p err=%d\n", @@ -2401,7 +2403,7 @@ static void fib6_gc_table(struct net *net, hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link) if (fib6_age(rt, gc_args) == -1) - fib6_del(rt, &info); + fib6_del(rt, &info, RT_DEL_REASON_EXPIRED); } static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..69c51f1a5bf0 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -964,7 +964,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen)) goto tx_err; - __clear_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); IPCB(skb)->flags = 0; /* For collect_md mode, derive fl6 from the tunnel key, @@ -1137,13 +1136,11 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - unsigned short dst_len = rt->dst.dev->hard_header_len + - t_hlen; + unsigned int headroom; - if (t->dev->header_ops) - dev->hard_header_len = dst_len; - else - dev->needed_headroom = dst_len; + headroom = rt->dst.dev->hard_header_len + t_hlen; + headroom = ip_tunnel_limit_headroom(headroom); + dev->needed_headroom = headroom; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; @@ -1171,8 +1168,8 @@ static int ip6gre_calc_hlen(struct ip6_tnl *tunnel) t_hlen = tunnel->hlen + sizeof(struct ipv6hdr); - if (tunnel->dev->header_ops) - tunnel->dev->hard_header_len = LL_MAX_HEADER + t_hlen; + if (tunnel->dev->header_ops && tunnel->dev->type == ARPHRD_IP6GRE) + tunnel->dev->hard_header_len = t_hlen; else tunnel->dev->needed_headroom = LL_MAX_HEADER + t_hlen; @@ -2115,8 +2112,8 @@ static size_t ip6gre_get_size(const struct net_device *dev) static int ip6gre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip6_tnl *t = netdev_priv(dev); - struct __ip6_tnl_parm *p = &t->parms; + const struct ip6_tnl *t = netdev_priv(dev); + const struct __ip6_tnl_parm *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags); diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c index 8972863c93ee..d332ec60f915 100644 --- a/net/ipv6/ip6_input.c +++ b/net/ipv6/ip6_input.c @@ -622,6 +622,7 @@ int ip6_mc_input(struct sk_buff *skb) if (deliver) { skb2 = skb_clone(skb, GFP_ATOMIC); } else { + skb_orphan(skb); skb2 = skb; skb = NULL; } diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 2c44e5ed6171..8fc4766c8da9 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -116,6 +116,8 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff * if (res != LWTUNNEL_XMIT_CONTINUE) return res; + hdr = ipv6_hdr(skb); + daddr = &hdr->daddr; } IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len); diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index ebf83f090376..d5ff50a2ac01 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1236,19 +1236,8 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield, */ max_headroom += LL_RESERVED_SPACE(tdev); - if (skb_headroom(skb) < max_headroom || skb_shared(skb) || - (skb_cloned(skb) && !skb_clone_writable(skb, 0))) { - struct sk_buff *new_skb; - - new_skb = skb_realloc_headroom(skb, max_headroom); - if (!new_skb) - goto tx_err_dst_release; - - if (skb->sk) - skb_set_owner_w(new_skb, skb->sk); - consume_skb(skb); - skb = new_skb; - } + if (skb_cow_head(skb, max_headroom)) + goto tx_err_dst_release; if (t->parms.collect_md) { if (t->encap.type != TUNNEL_ENCAP_NONE) @@ -1525,8 +1514,11 @@ static void ip6_tnl_link_config(struct ip6_tnl *t) tdev = __dev_get_by_index(t->net, p->link); if (tdev) { - dev->needed_headroom = tdev->hard_header_len + - tdev->needed_headroom + t_hlen; + unsigned int headroom; + + headroom = tdev->hard_header_len + tdev->needed_headroom; + headroom += t_hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(headroom); mtu = min_t(unsigned int, tdev->mtu, IP6_MAX_MTU); mtu = mtu - t_hlen; @@ -1848,31 +1840,42 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path) { struct ip6_tnl *t = netdev_priv(ctx->dev); - struct flowi6 fl6 = { - .daddr = t->parms.raddr, - }; struct dst_entry *dst; + struct flowi6 fl6; int err; - if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) { - /* encaplimit option is currently not supported is - * sw-acceleration path. - */ + if (ctx->ether_type != cpu_to_be16(ETH_P_IPV6)) return -EOPNOTSUPP; - } + + if (t->parms.flags & (IP6_TNL_F_USE_ORIG_TCLASS | + IP6_TNL_F_USE_ORIG_FLOWLABEL | + IP6_TNL_F_USE_ORIG_FWMARK)) + return -EOPNOTSUPP; + + if (t->parms.collect_md) + return -EOPNOTSUPP; + + if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) + return -EOPNOTSUPP; + + memcpy(&fl6, &t->fl.u.ip6, sizeof(fl6)); + fl6.flowi6_mark = t->parms.fwmark; + fl6.flowi6_proto = 0; dst = ip6_route_output(dev_net(ctx->dev), NULL, &fl6); if (!dst->error) { path->type = DEV_PATH_TUN; - path->tun.src_v6 = t->parms.laddr; - path->tun.dst_v6 = t->parms.raddr; - path->tun.l3_proto = IPPROTO_IPV6; + path->tun.src_v6 = fl6.saddr; + path->tun.dst_v6 = fl6.daddr; + path->tun.inner_proto = IPPROTO_IPV6; + path->tun.dst = dst; path->dev = ctx->dev; ctx->dev = dst->dev; } err = dst->error; - dst_release(dst); + if (err) + dst_release(dst); return err; } diff --git a/net/ipv6/ip6_udp_tunnel.c b/net/ipv6/ip6_udp_tunnel.c index 9adb5775487f..32525a051a6f 100644 --- a/net/ipv6/ip6_udp_tunnel.c +++ b/net/ipv6/ip6_udp_tunnel.c @@ -93,7 +93,7 @@ void udp_tunnel6_xmit_skb(struct dst_entry *dst, struct sock *sk, uh->dest = dst_port; uh->source = src_port; - uh->len = htons(skb->len); + udp_set_len(uh, skb->len); skb_dst_set(skb, dst); diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 4d2b9377ba2d..aaba4c2aae23 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -908,6 +908,7 @@ static void inet6_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index fe36b3f51285..75515fd99383 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -283,6 +283,7 @@ struct ndisc_options *ndisc_parse_options(const struct net_device *dev, } return ndopts; } +EXPORT_SYMBOL_GPL(ndisc_parse_options); int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir) { @@ -1361,7 +1362,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb) defrtr_usr_metric = in6_dev->cnf.ra_defrtr_metric; /* delete the route if lifetime is 0 or if metric needs change */ if (rt && (lifetime == 0 || rt->fib6_metric != defrtr_usr_metric)) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, + lifetime == 0 ? RT_DEL_REASON_RA_WITHDRAWN : + RT_DEL_REASON_UNSPEC); rt = NULL; } diff --git a/net/ipv6/ndisc_snoop.c b/net/ipv6/ndisc_snoop.c new file mode 100644 index 000000000000..fa86528d5cfe --- /dev/null +++ b/net/ipv6/ndisc_snoop.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +static int ndisc_check_ip6hdr(struct sk_buff *skb) +{ + const struct ipv6hdr *ip6h; + unsigned int offset, len; + + offset = skb_network_offset(skb) + sizeof(*ip6h); + if (!pskb_may_pull(skb, offset)) + return -EINVAL; + + ip6h = ipv6_hdr(skb); + + if (ip6h->version != 6) + return -EINVAL; + + if (ip6h->nexthdr != IPPROTO_ICMPV6) + return -ENOMSG; + + /* RFC 4861 7.1.1 / 7.1.2: must not have been forwarded by a router */ + if (ip6h->hop_limit != 255) + return -EINVAL; + + len = offset + ntohs(ip6h->payload_len); + if (skb->len < len || len <= offset) + return -EINVAL; + + skb_set_transport_header(skb, offset); + + return 0; +} + +static __sum16 ndisc_validate_checksum(struct sk_buff *skb) +{ + return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); +} + +static int ndisc_check_icmpv6(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); + unsigned int transport_len = ipv6_transport_len(skb); + struct sk_buff *skb_chk; + struct icmp6hdr *hdr; + + if (!pskb_may_pull(skb, len)) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: the ICMPv6 checksum must be valid */ + skb_chk = skb_checksum_trimmed(skb, transport_len, + ndisc_validate_checksum); + if (!skb_chk) + return -EINVAL; + + if (skb_chk != skb) + kfree_skb(skb_chk); + + /* RFC 4861 7.1.1 / 7.1.2: Code must be 0 */ + hdr = (struct icmp6hdr *)skb_transport_header(skb); + if (hdr->icmp6_code != 0) + return -EINVAL; + + return 0; +} + +static int ndisc_check_options(struct sk_buff *skb, unsigned int opts_len, + bool reject_slla) +{ + unsigned int offset = skb_transport_offset(skb) + sizeof(struct nd_msg); + struct nd_opt_hdr *opt, _opt; + + while (opts_len > 0) { + if (opts_len < sizeof(*opt)) + return -EINVAL; + + opt = skb_header_pointer(skb, offset, sizeof(_opt), &_opt); + if (!opt) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: all option lengths must be > 0 */ + if (!opt->nd_opt_len) + return -EINVAL; + + /* RFC 4861 7.1.1: DAD NS must not contain a source link-layer + * address option + */ + if (reject_slla && opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR) + return -EINVAL; + + if (opt->nd_opt_len * 8 > opts_len) + return -EINVAL; + + offset += opt->nd_opt_len * 8; + opts_len -= opt->nd_opt_len * 8; + } + + return 0; +} + +static int ndisc_check_nd_msg(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb) + sizeof(struct nd_msg); + unsigned int transport_len = ipv6_transport_len(skb); + bool reject_slla = false; + const struct nd_msg *msg; + + if (!pskb_may_pull(skb, len)) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: ICMP length is at least sizeof(nd_msg) */ + if (transport_len < sizeof(struct nd_msg)) + return -EINVAL; + + msg = (struct nd_msg *)skb_transport_header(skb); + + /* RFC 4861 7.1.1 / 7.1.2: Target Address must not be a + * multicast address + */ + if (ipv6_addr_is_multicast(&msg->target)) + return -EINVAL; + + switch (msg->icmph.icmp6_type) { + case NDISC_NEIGHBOUR_SOLICITATION: + if (ipv6_addr_any(&ipv6_hdr(skb)->saddr)) { + /* RFC 4861 7.1.1: DAD NS destination must be a + * solicited-node multicast address + */ + if (!ipv6_addr_is_solict_mult(&ipv6_hdr(skb)->daddr)) + return -EINVAL; + /* RFC 4861 7.1.1: DAD NS must not contain a source + * link-layer address option + */ + reject_slla = true; + } + break; + case NDISC_NEIGHBOUR_ADVERTISEMENT: + /* RFC 4861 7.1.2: Solicited flag must be 0 for + * multicast destinations + */ + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) && + msg->icmph.icmp6_solicited) + return -EINVAL; + break; + default: + return -ENODATA; + } + + return ndisc_check_options(skb, transport_len - sizeof(struct nd_msg), + reject_slla); +} + +/** + * ndisc_check_ns_na - validate an NS/NA packet and set its transport header + * @skb: the skb to validate + * + * Validates an IPv6 packet for compliance with RFC 4861 sections 7.1.1 + * (Neighbor Solicitation) and 7.1.2 (Neighbor Advertisement). If valid, + * sets the skb transport header. + * + * Caller needs to set the skb network header. + * + * Return: + * * 0 - valid NS/NA; the skb transport header has been set. + * * -EINVAL - a broken packet was detected, i.e. it violates some + * internet standard. + * * -ENOMSG - IP header validation succeeded but it is not an ICMPv6 + * packet. + * * -ENODATA - IP+ICMPv6 header validation succeeded but it is not a + * Neighbor Solicitation or Neighbor Advertisement. + */ +int ndisc_check_ns_na(struct sk_buff *skb) +{ + int ret; + + ret = ndisc_check_ip6hdr(skb); + if (ret < 0) + return ret; + + ret = ndisc_check_icmpv6(skb); + if (ret < 0) + return ret; + + return ndisc_check_nd_msg(skb); +} +EXPORT_SYMBOL_GPL(ndisc_check_ns_na); diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c index ef5b7e85cffa..07cdaa10da0d 100644 --- a/net/ipv6/netfilter/nf_reject_ipv6.c +++ b/net/ipv6/netfilter/nf_reject_ipv6.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -304,6 +305,7 @@ static int nf_reject6_fill_skb_dst(struct sk_buff *skb_in) if (!dst) return -1; + skb_dst_drop(skb_in); skb_dst_set(skb_in, dst); return 0; } @@ -336,10 +338,12 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb, fl6.fl6_sport = otcph->dest; fl6.fl6_dport = otcph->source; - if (!skb_dst(oldskb)) { + if (!skb_valid_dst(oldskb)) { nf_ip6_route(net, &dst, flowi6_to_flowi(&fl6), false); if (!dst) return; + + skb_dst_drop(oldskb); skb_dst_set(oldskb, dst); } @@ -440,7 +444,7 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) skb_in->dev = net->loopback_dev; - if (!skb_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0) + if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0) return; icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index b88d364e78aa..b965258cf9e5 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -1051,14 +1051,12 @@ static int rawv6_setsockopt(struct sock *sk, int level, int optname, return do_rawv6_setsockopt(sk, level, optname, optval, optlen); } -static int do_rawv6_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_rawv6_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct raw6_sock *rp = raw6_sk(sk); int val, len; - if (get_user(len, optlen)) - return -EFAULT; + len = opt->optlen; switch (optname) { case IPV6_HDRINCL: @@ -1080,11 +1078,10 @@ static int do_rawv6_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - len = min_t(unsigned int, sizeof(int), len); + len = umin(sizeof(int), len); - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; } @@ -1092,6 +1089,9 @@ static int do_rawv6_getsockopt(struct sock *sk, int level, int optname, static int rawv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { + sockopt_t opt; + int err; + switch (level) { case SOL_RAW: break; @@ -1109,7 +1109,18 @@ static int rawv6_getsockopt(struct sock *sk, int level, int optname, return ipv6_getsockopt(sk, level, optname, optval, optlen); } - return do_rawv6_getsockopt(sk, level, optname, optval, optlen); + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_rawv6_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int rawv6_ioctl(struct sock *sk, int cmd, int *karg) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 5968ce5ad150..16dfac54a259 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -111,7 +111,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, struct fib6_info *rt, struct dst_entry *dst, struct in6_addr *dest, struct in6_addr *src, int iif, int type, u32 portid, u32 seq, - unsigned int flags); + unsigned int flags, enum rt_del_reason del_reason); static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res, const struct in6_addr *daddr, const struct in6_addr *saddr); @@ -1020,7 +1020,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, gwaddr, dev); if (rt && !lifetime) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, RT_DEL_REASON_RA_WITHDRAWN); rt = NULL; } @@ -3973,7 +3973,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, return err; } -static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info) +static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct fib6_table *table; @@ -3986,7 +3987,7 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info) table = rt->fib6_table; spin_lock_bh(&table->tb6_lock); - err = fib6_del(rt, info); + err = fib6_del(rt, info, del_reason); spin_unlock_bh(&table->tb6_lock); out: @@ -4001,7 +4002,15 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify) .skip_notify = skip_notify }; - return __ip6_del_rt(rt, &info); + return __ip6_del_rt(rt, &info, RT_DEL_REASON_UNSPEC); +} + +int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, + enum rt_del_reason del_reason) +{ + struct nl_info info = { .nl_net = net }; + + return __ip6_del_rt(rt, &info, del_reason); } static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) @@ -4028,7 +4037,8 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) if (rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, RTM_DELROUTE, - info->portid, seq, 0) < 0) { + info->portid, seq, 0, + RT_DEL_REASON_UNSPEC) < 0) { kfree_skb(skb); skb = NULL; } else @@ -4064,13 +4074,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) list_for_each_entry_safe(sibling, next_sibling, &rt->fib6_siblings, fib6_siblings) { - err = fib6_del(sibling, info); + err = fib6_del(sibling, info, RT_DEL_REASON_UNSPEC); if (err) goto out_unlock; } } - err = fib6_del(rt, info); + err = fib6_del(rt, info, RT_DEL_REASON_UNSPEC); out_unlock: spin_unlock_bh(&table->tb6_lock); out_put: @@ -4196,7 +4206,8 @@ static int ip6_route_del(struct fib6_config *cfg, if (!fib6_info_hold_safe(rt)) continue; - err = __ip6_del_rt(rt, &cfg->fc_nlinfo); + err = __ip6_del_rt(rt, &cfg->fc_nlinfo, + RT_DEL_REASON_UNSPEC); break; } if (cfg->fc_nh_id) @@ -4215,7 +4226,8 @@ static int ip6_route_del(struct fib6_config *cfg, /* if gateway was specified only delete the one hop */ if (cfg->fc_flags & RTF_GATEWAY) - err = __ip6_del_rt(rt, &cfg->fc_nlinfo); + err = __ip6_del_rt(rt, &cfg->fc_nlinfo, + RT_DEL_REASON_UNSPEC); else err = __ip6_del_rt_siblings(rt, cfg); break; @@ -5739,6 +5751,7 @@ static size_t rt6_nlmsg_size(struct fib6_info *f6i) + nla_total_size(sizeof(struct rta_cacheinfo)) + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */ + nla_total_size(1) /* RTA_PREF */ + + nla_total_size(4) /* RTA_DEL_REASON */ + nexthop_len; } @@ -5775,7 +5788,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, struct fib6_info *rt, struct dst_entry *dst, struct in6_addr *dest, struct in6_addr *src, int iif, int type, u32 portid, u32 seq, - unsigned int flags) + unsigned int flags, enum rt_del_reason del_reason) { struct rt6_info *rt6 = dst_rt6_info(dst); struct rt6key *rt6_dst, *rt6_src; @@ -5954,6 +5967,10 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, if (rtnl_put_cacheinfo(skb, dst, 0, expires, dst ? dst->error : 0) < 0) goto nla_put_failure; + if (del_reason != RT_DEL_REASON_UNSPEC && + nla_put_u32(skb, RTA_DEL_REASON, del_reason)) + goto nla_put_failure; + if (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt6_flags))) goto nla_put_failure; @@ -6055,7 +6072,8 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg) &rt6_ex->rt6i->dst, NULL, NULL, 0, RTM_NEWROUTE, NETLINK_CB(dump->cb->skb).portid, - dump->cb->nlh->nlmsg_seq, w->flags); + dump->cb->nlh->nlmsg_seq, w->flags, + RT_DEL_REASON_UNSPEC); if (err) return err; @@ -6103,7 +6121,8 @@ int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip) if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL, 0, RTM_NEWROUTE, NETLINK_CB(arg->cb->skb).portid, - arg->cb->nlh->nlmsg_seq, flags)) { + arg->cb->nlh->nlmsg_seq, flags, + RT_DEL_REASON_UNSPEC)) { return 0; } count++; @@ -6336,12 +6355,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, err = rt6_fill_node(net, skb, from, NULL, NULL, NULL, iif, RTM_NEWROUTE, NETLINK_CB(in_skb).portid, - nlh->nlmsg_seq, 0); + nlh->nlmsg_seq, 0, + RT_DEL_REASON_UNSPEC); else err = rt6_fill_node(net, skb, from, dst, &fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE, NETLINK_CB(in_skb).portid, - nlh->nlmsg_seq, 0); + nlh->nlmsg_seq, 0, + RT_DEL_REASON_UNSPEC); } else { err = -ENETUNREACH; } @@ -6357,8 +6378,9 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, return err; } -void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, - unsigned int nlm_flags) +static void __inet6_rt_notify(int event, struct fib6_info *rt, + struct nl_info *info, unsigned int nlm_flags, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct sk_buff *skb; @@ -6377,7 +6399,7 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, goto errout; err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, - event, info->portid, seq, nlm_flags); + event, info->portid, seq, nlm_flags, del_reason); if (err < 0) { kfree_skb(skb); /* -EMSGSIZE implies needed space grew under us. */ @@ -6398,6 +6420,18 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err); } +void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, + unsigned int nlm_flags) +{ + __inet6_rt_notify(event, rt, info, nlm_flags, RT_DEL_REASON_UNSPEC); +} + +void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) +{ + __inet6_rt_notify(RTM_DELROUTE, rt, info, 0, del_reason); +} + void fib6_rt_update(struct net *net, struct fib6_info *rt, struct nl_info *info) { @@ -6410,7 +6444,8 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt, goto errout; err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, - RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE); + RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE, + RT_DEL_REASON_UNSPEC); if (err < 0) { /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); @@ -6463,7 +6498,7 @@ 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); + 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); @@ -6555,7 +6590,7 @@ static int ipv6_sysctl_rtcache_flush(const struct ctl_table *ctl, int write, return 0; } -static struct ctl_table ipv6_route_table_template[] = { +static const struct ctl_table ipv6_route_table_template[] = { { .procname = "max_size", .data = &init_net.ipv6.sysctl.ip6_rt_max_size, diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 4c45c0a77d75..61c6a27bf202 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -51,6 +51,7 @@ struct seg6_lwt { struct dst_cache cache_input; struct dst_cache cache_output; struct in6_addr tunsrc; + u32 table; struct seg6_iptunnel_encap tuninfo[]; }; @@ -68,6 +69,7 @@ seg6_encap_lwtunnel(struct lwtunnel_state *lwt) static const struct nla_policy seg6_iptunnel_policy[SEG6_IPTUNNEL_MAX + 1] = { [SEG6_IPTUNNEL_SRH] = { .type = NLA_BINARY }, [SEG6_IPTUNNEL_SRC] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [SEG6_IPTUNNEL_TABLE] = { .type = NLA_U32 }, }; static int nla_put_srh(struct sk_buff *skb, int attrtype, @@ -479,6 +481,73 @@ int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh) } EXPORT_SYMBOL_GPL(seg6_do_srh_inline); +/* look up a route in a specific FIB table. + * Returns a refcounted dst, or NULL if the table does not exist. + */ +static struct dst_entry *seg6_table_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, u32 tbl_id) +{ + struct fib6_table *table; + struct rt6_info *rt; + + table = fib6_get_table(net, tbl_id); + if (!table) + return NULL; + + rt = ip6_pol_route(net, table, 0, fl6, skb, RT6_LOOKUP_F_HAS_SADDR); + return &rt->dst; +} + +static void seg6_init_flowi6(struct sk_buff *skb, struct ipv6hdr *hdr, + struct flowi6 *fl6) +{ + memset(fl6, 0, sizeof(*fl6)); + + fl6->daddr = hdr->daddr; + fl6->saddr = hdr->saddr; + fl6->flowlabel = ip6_flowinfo(hdr); + fl6->flowi6_mark = skb->mark; + fl6->flowi6_proto = hdr->nexthdr; +} + +/* look up the route for the first SID on the input path and set it on the skb. + * Returns the refcounted dst, or NULL if a reference could not be safely taken. + */ +static struct dst_entry *seg6_input_route(struct net *net, + struct sk_buff *skb, + struct seg6_lwt *slwt) +{ + u32 table = slwt->table; + + if (table) { + struct ipv6hdr *hdr = ipv6_hdr(skb); + struct dst_entry *dst; + struct flowi6 fl6; + + seg6_init_flowi6(skb, hdr, &fl6); + fl6.flowi6_iif = skb->dev->ifindex; + + dst = seg6_table_lookup(net, skb, &fl6, table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + + skb_dst_drop(skb); + skb_dst_set(skb, dst); + } else { + ip6_route_input(skb); + + /* ip6_route_input() sets a NOREF dst; force a refcount on it + * before caching or further use. + */ + skb_dst_force(skb); + } + + return skb_dst(skb); +} + static int seg6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -513,15 +582,9 @@ static int seg6_input_core(struct net *net, struct sock *sk, goto drop; } - if (!dst) { - ip6_route_input(skb); - - /* ip6_route_input() sets a NOREF dst; force a refcount on it - * before caching or further use. - */ - skb_dst_force(skb); - dst = skb_dst(skb); - if (unlikely(!dst)) { + if (unlikely(!dst)) { + dst = seg6_input_route(net, skb, slwt); + if (!dst) { err = -ENETUNREACH; goto drop; } @@ -578,6 +641,29 @@ static int seg6_input(struct sk_buff *skb) return seg6_input_core(dev_net(skb->dev), NULL, skb); } +/* look up the route for the first SID on the output path. Always returns a + * refcounted dst. + */ +static struct dst_entry *seg6_output_dst_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, + struct seg6_lwt *slwt) +{ + struct dst_entry *dst; + + if (slwt->table) { + dst = seg6_table_lookup(net, skb, fl6, slwt->table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + } else { + dst = ip6_route_output(net, NULL, fl6); + } + + return dst; +} + static int seg6_output_core(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -600,14 +686,9 @@ static int seg6_output_core(struct net *net, struct sock *sk, struct ipv6hdr *hdr = ipv6_hdr(skb); struct flowi6 fl6; - memset(&fl6, 0, sizeof(fl6)); - fl6.daddr = hdr->daddr; - fl6.saddr = hdr->saddr; - fl6.flowlabel = ip6_flowinfo(hdr); - fl6.flowi6_mark = skb->mark; - fl6.flowi6_proto = hdr->nexthdr; + seg6_init_flowi6(skb, hdr, &fl6); - dst = ip6_route_output(net, NULL, &fl6); + dst = seg6_output_dst_lookup(net, skb, &fl6, slwt); if (dst->error) { err = dst->error; goto drop; @@ -752,6 +833,15 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, } } + if (tb[SEG6_IPTUNNEL_TABLE]) { + slwt->table = nla_get_u32(tb[SEG6_IPTUNNEL_TABLE]); + if (!slwt->table) { + NL_SET_ERR_MSG(extack, "invalid lookup table"); + err = -EINVAL; + goto err_destroy_output; + } + } + newts->type = LWTUNNEL_ENCAP_SEG6; newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT; @@ -795,6 +885,10 @@ static int seg6_fill_encap_info(struct sk_buff *skb, nla_put_in6_addr(skb, SEG6_IPTUNNEL_SRC, &slwt->tunsrc)) return -EMSGSIZE; + if (slwt->table && + nla_put_u32(skb, SEG6_IPTUNNEL_TABLE, slwt->table)) + return -EMSGSIZE; + return 0; } @@ -809,6 +903,9 @@ static int seg6_encap_nlsize(struct lwtunnel_state *lwtstate) if (!ipv6_addr_any(&slwt->tunsrc)) nlsize += nla_total_size(sizeof(slwt->tunsrc)); + if (slwt->table) + nlsize += nla_total_size(sizeof(u32)); + return nlsize; } @@ -826,6 +923,9 @@ static int seg6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) if (!ipv6_addr_equal(&a_slwt->tunsrc, &b_slwt->tunsrc)) return 1; + if (a_slwt->table != b_slwt->table) + return 1; + return memcmp(a_hdr, b_hdr, len); } diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..19b7fa8d1a2a 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1131,7 +1131,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev) WRITE_ONCE(dev->mtu, mtu); hlen = tdev->hard_header_len + tdev->needed_headroom; } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); } static void ipip6_tunnel_update(struct ip_tunnel *t, diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c index d2cd33e2698d..1a0a36dcdabc 100644 --- a/net/ipv6/sysctl_net_ipv6.c +++ b/net/ipv6/sysctl_net_ipv6.c @@ -61,7 +61,7 @@ proc_rt6_multipath_hash_fields(const struct ctl_table *table, int write, void *b return ret; } -static struct ctl_table ipv6_table_template[] = { +static const struct ctl_table ipv6_table_template[] = { { .procname = "bindv6only", .data = &init_net.ipv6.sysctl.bindv6only, diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 15e032194ecc..fd875908ac0c 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, struct udp_hslot *hslot; struct sk_buff *nskb; bool use_hash2; + int ret; hash2_any = 0; hash2 = 0; @@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } if (first) { - if (udpv6_queue_rcv_skb(first, skb) > 0) - consume_skb(skb); + ret = udpv6_queue_rcv_skb(first, skb); + if (ret > 0) + return ret; } else { kfree_skb(skb); __UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI); @@ -1081,12 +1083,12 @@ INDIRECT_CALLABLE_SCOPE int udpv6_rcv(struct sk_buff *skb) daddr = &ipv6_hdr(skb)->daddr; uh = udp_hdr(skb); - ulen = ntohs(uh->len); + ulen = udp_get_len(skb, uh, 0); if (ulen > skb->len) goto short_packet; /* Check for jumbo payload */ - if (ulen == 0) + if (ulen == 0 && inet6_is_jumbogram(skb)) ulen = skb->len; if (ulen < sizeof(*uh)) @@ -1369,7 +1371,8 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6, uh = udp_hdr(skb); uh->source = fl6->fl6_sport; uh->dest = fl6->fl6_dport; - uh->len = htons(len); + /* Datagram length checked in udpv6_sendmsg. */ + udp_set_len_short(uh, len); uh->check = 0; if (cork->gso_size) { @@ -1826,9 +1829,22 @@ static int udpv6_setsockopt(struct sock *sk, int level, int optname, static int udpv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ipv6_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + if (level != SOL_UDP) + return ipv6_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + if (put_user(opt.optlen, optlen)) + return -EFAULT; + return 0; } diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c index 778afc7453ce..7370bcb80332 100644 --- a/net/ipv6/udp_offload.c +++ b/net/ipv6/udp_offload.c @@ -171,7 +171,7 @@ int udp6_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - uh->len = htons(skb->len - nhoff); + udp_set_len(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c index 3b749475f6ed..5ec063cb4aa4 100644 --- a/net/ipv6/xfrm6_policy.c +++ b/net/ipv6/xfrm6_policy.c @@ -187,7 +187,7 @@ static void xfrm6_policy_fini(void) } #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm6_policy_table[] = { +static const struct ctl_table xfrm6_policy_table[] = { { .procname = "xfrm6_gc_thresh", .data = &init_net.xfrm.xfrm6_dst_ops.gc_thresh, @@ -197,18 +197,30 @@ static struct ctl_table xfrm6_policy_table[] = { }, }; -static int __net_init xfrm6_net_sysctl_init(struct net *net) +static const struct ctl_table *xfrm6_policy_table_dup(struct net *net) { struct ctl_table *table; + + table = kmemdup(xfrm6_policy_table, sizeof(xfrm6_policy_table), + GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh; + + return table; +} + +static int __net_init xfrm6_net_sysctl_init(struct net *net) +{ + const struct ctl_table *table; struct ctl_table_header *hdr; table = xfrm6_policy_table; if (!net_eq(net, &init_net)) { - table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL); + table = xfrm6_policy_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh; } hdr = register_net_sysctl_sz(net, "net/ipv6", table, diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index d469abcd989b..71af69d442f2 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -5,6 +5,7 @@ * Copyright (c) 2016 Tom Herbert */ +#include #include #include #include @@ -391,7 +392,9 @@ static int kcm_parse_func_strparser(struct strparser *strp, struct sk_buff *skb) struct bpf_prog *prog = psock->bpf_prog; int res; + rcu_read_lock(); res = bpf_prog_run_pin_on_cpu(prog, skb); + rcu_read_unlock(); return res; } diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index f940914959b1..4712cc41881a 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1296,7 +1296,7 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, uns ret = NET_XMIT_DROP; goto out_unlock; } - uh->len = htons(udp_len); + udp_set_len_short(uh, udp_len); /* Calculate UDP checksum if configured to do so */ #if IS_ENABLED(CONFIG_IPV6) diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c index 59457c0c14aa..c0c4d1ebc7a3 100644 --- a/net/l2tp/l2tp_netlink.c +++ b/net/l2tp/l2tp_netlink.c @@ -116,7 +116,8 @@ static int l2tp_tunnel_notify(struct genl_family *family, NLM_F_ACK, tunnel, cmd); if (ret >= 0) { - ret = genlmsg_multicast_allns(family, msg, 0, 0); + ret = genlmsg_multicast_netns(family, tunnel->l2tp_net, msg, + 0, 0, GFP_KERNEL); /* We don't care if no one is listening */ if (ret == -ESRCH) ret = 0; @@ -144,7 +145,9 @@ static int l2tp_session_notify(struct genl_family *family, NLM_F_ACK, session, cmd); if (ret >= 0) { - ret = genlmsg_multicast_allns(family, msg, 0, 0); + ret = genlmsg_multicast_netns(family, + session->tunnel->l2tp_net, msg, + 0, 0, GFP_KERNEL); /* We don't care if no one is listening */ if (ret == -ESRCH) ret = 0; diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index c61df637232a..0c54cdbd753c 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -685,7 +685,7 @@ static int ieee80211_fill_rx_status(struct ieee80211_rx_status *stat, if (ieee80211_fill_rate_info(hw, stat, band, ri)) return 0; - if (!ieee80211_rate_valid(rate)) + if (!rate || !ieee80211_rate_valid(rate)) return -1; if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH) @@ -753,6 +753,53 @@ u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(ieee80211_calc_tx_airtime); +u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, + struct ieee80211_tx_rate *tx_rate, + struct rate_info *ri, + enum nl80211_band band, + bool ampdu, int len) +{ + struct ieee80211_rx_status stat; + u32 duration, overhead; + u8 agg_shift; + + if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len)) + return 0; + + if (stat.encoding == RX_ENC_LEGACY || !ampdu) + return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; + + duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + + /* + * Assume that HT/VHT transmission on any AC except VO will + * use aggregation. Since we don't have reliable reporting + * of aggregation length, assume an average size based on the + * tx rate. + * This will not be very accurate, but much better than simply + * assuming un-aggregated tx in all cases. + */ + if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */ + agg_shift = 1; + else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */ + agg_shift = 2; + else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */ + agg_shift = 3; + else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */ + agg_shift = 4; + else if (stat.encoding != RX_ENC_HE || + duration > 20 * 1024) /* <= HE40 MCS6 2S */ + agg_shift = 5; + else + agg_shift = 6; + + duration *= len; + duration /= AVG_PKT_SIZE; + duration += (overhead * 1024 >> agg_shift); + + return duration; +} + u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *pubsta, @@ -775,45 +822,13 @@ u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, if (pubsta) { struct sta_info *sta = container_of(pubsta, struct sta_info, sta); - struct ieee80211_rx_status stat; struct ieee80211_tx_rate *tx_rate = &sta->deflink.tx_stats.last_rate; struct rate_info *ri = &sta->deflink.tx_stats.last_rate_info; - u32 duration, overhead; - u8 agg_shift; + u32 duration; - if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len)) - return 0; - - if (stat.encoding == RX_ENC_LEGACY || !ampdu) - return ieee80211_calc_rx_airtime(hw, &stat, len); - - duration = ieee80211_get_rate_duration(hw, &stat, &overhead); - /* - * Assume that HT/VHT transmission on any AC except VO will - * use aggregation. Since we don't have reliable reporting - * of aggregation length, assume an average size based on the - * tx rate. - * This will not be very accurate, but much better than simply - * assuming un-aggregated tx in all cases. - */ - if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */ - agg_shift = 1; - else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */ - agg_shift = 2; - else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */ - agg_shift = 3; - else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */ - agg_shift = 4; - else if (stat.encoding != RX_ENC_HE || - duration > 20 * 1024) /* <= HE40 MCS6 2S */ - agg_shift = 5; - else - agg_shift = 6; - - duration *= len; - duration /= AVG_PKT_SIZE; + duration = ieee80211_rate_expected_tx_airtime(hw, tx_rate, ri, + band, true, len); duration /= 1024; - duration += (overhead >> agg_shift); return max_t(u32, duration, 4); } diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 43f142624d33..23f4f9ec86d0 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4831,19 +4831,6 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, return __ieee80211_channel_switch(wiphy, dev, params); } -u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local) -{ - lockdep_assert_wiphy(local->hw.wiphy); - - local->roc_cookie_counter++; - - /* wow, you wrapped 64 bits ... more likely a bug */ - if (WARN_ON(local->roc_cookie_counter == 0)) - local->roc_cookie_counter++; - - return local->roc_cookie_counter; -} - int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u64 *cookie, gfp_t gfp) { @@ -4868,7 +4855,6 @@ int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, IEEE80211_SKB_CB(skb)->status_data_idr = 1; IEEE80211_SKB_CB(skb)->status_data = id; - *cookie = ieee80211_mgmt_tx_cookie(local); IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; return 0; @@ -4953,106 +4939,107 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy, return 0; } -static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie) +static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; struct ieee80211_qos_hdr *nullfunc; struct sk_buff *skb; - int size = sizeof(*nullfunc); __le16 fc; - bool qos; + bool qos, fromds; + struct ieee80211_bss_conf *conf; struct ieee80211_tx_info *info; struct sta_info *sta; struct ieee80211_chanctx_conf *chanctx_conf; - struct ieee80211_bss_conf *conf; enum nl80211_band band; - u8 link_id; + const u8 *dst_addr; + const u8 *src_addr; + int link_id; + int size; int ret; /* the lock is needed to assign the cookie later */ lockdep_assert_wiphy(local->hw.wiphy); - rcu_read_lock(); - sta = sta_info_get_bss(sdata, peer); - if (!sta) { - ret = -ENOLINK; - goto unlock; + switch (ieee80211_vif_type_p2p(&sdata->vif)) { + case NL80211_IFTYPE_AP: + fromds = true; + break; + case NL80211_IFTYPE_STATION: + /* For STA, the peer is always the associated AP/GO */ + peer = sdata->vif.cfg.ap_addr; + fromds = false; + break; + default: + return -EOPNOTSUPP; } + sta = sta_info_get_bss(sdata, peer); + if (!sta) + return -ENOLINK; + qos = sta->sta.wme; + dst_addr = sta->sta.addr; if (ieee80211_vif_is_mld(&sdata->vif)) { - if (sta->sta.mlo) { - link_id = IEEE80211_LINK_UNSPECIFIED; - } else { + if (fromds && !sta->sta.mlo) { /* - * For non-MLO clients connected to an AP MLD, band - * information is not used; instead, sta->deflink is - * used to send packets. + * AP mode, non-MLO client on AP MLD: use the + * per-link address for the client's link. */ link_id = sta->deflink.link_id; - - conf = rcu_dereference(sdata->vif.link_conf[link_id]); - - if (unlikely(!conf)) { - ret = -ENOLINK; - goto unlock; - } + conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.link_conf[link_id]); + if (!conf) + return -ENOLINK; + src_addr = conf->addr; + } else { + /* + * MLO client (AP or STA mode), or STA mode: + * always use LINK_UNSPECIFIED and MLD address. + */ + link_id = IEEE80211_LINK_UNSPECIFIED; + src_addr = sdata->vif.addr; } /* MLD transmissions must not rely on the band */ band = 0; } else { - chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); - if (WARN_ON(!chanctx_conf)) { - ret = -EINVAL; - goto unlock; - } + chanctx_conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.bss_conf.chanctx_conf); + if (WARN_ON(!chanctx_conf)) + return -EINVAL; band = chanctx_conf->def.chan->band; link_id = 0; + src_addr = sdata->vif.addr; } - if (qos) { - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_QOS_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } else { + size = sizeof(*nullfunc); + fc = cpu_to_le16(IEEE80211_FTYPE_DATA | + (qos ? IEEE80211_STYPE_QOS_NULLFUNC + : IEEE80211_STYPE_NULLFUNC) | + (fromds ? IEEE80211_FCTL_FROMDS : IEEE80211_FCTL_TODS)); + if (!qos) size -= 2; - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } skb = dev_alloc_skb(local->hw.extra_tx_headroom + size); - if (!skb) { - ret = -ENOMEM; - goto unlock; - } + if (!skb) + return -ENOMEM; skb->dev = dev; - skb_reserve(skb, local->hw.extra_tx_headroom); - nullfunc = skb_put(skb, size); + nullfunc = skb_put_zero(skb, size); nullfunc->frame_control = fc; - nullfunc->duration_id = 0; - memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); - if (ieee80211_vif_is_mld(&sdata->vif) && !sta->sta.mlo) { - memcpy(nullfunc->addr2, conf->addr, ETH_ALEN); - memcpy(nullfunc->addr3, conf->addr, ETH_ALEN); - } else { - memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN); - memcpy(nullfunc->addr3, sdata->vif.addr, ETH_ALEN); - } - nullfunc->seq_ctrl = 0; + + memcpy(nullfunc->addr1, dst_addr, ETH_ALEN); + memcpy(nullfunc->addr2, src_addr, ETH_ALEN); + memcpy(nullfunc->addr3, fromds ? src_addr : dst_addr, ETH_ALEN); info = IEEE80211_SKB_CB(skb); - info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | IEEE80211_TX_INTFL_NL80211_FRAME_TX; info->band = band; - info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); skb_set_queue_mapping(skb, IEEE80211_AC_VO); @@ -5060,21 +5047,19 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, if (qos) nullfunc->qos_ctrl = cpu_to_le16(7); - ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC); + ret = ieee80211_attach_ack_skb(local, skb, &cookie, GFP_ATOMIC); if (ret) { kfree_skb(skb); - goto unlock; + return ret; } local_bh_disable(); + rcu_read_lock(); ieee80211_xmit(sdata, sta, skb); + rcu_read_unlock(); local_bh_enable(); - ret = 0; -unlock: - rcu_read_unlock(); - - return ret; + return 0; } static int ieee80211_cfg_get_channel(struct wiphy *wiphy, @@ -6064,7 +6049,7 @@ const struct cfg80211_ops mac80211_config_ops = { .tdls_mgmt = ieee80211_tdls_mgmt, .tdls_channel_switch = ieee80211_tdls_channel_switch, .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch, - .probe_client = ieee80211_probe_client, + .probe_peer = ieee80211_probe_peer, .set_noack_map = ieee80211_set_noack_map, #ifdef CONFIG_PM .set_wakeup = ieee80211_set_wakeup, diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 5152b84a3357..75bb204ad743 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -607,10 +607,12 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local, max_bw = max(max_bw, width); } - if (!rsvd_for || - rsvd_for->sdata == rcu_access_pointer(local->monitor_sdata)) + if (!rsvd_for) goto check_monitor; + if (rsvd_for->sdata == rcu_access_pointer(local->monitor_sdata)) + return max(max_bw, ctx->conf.def.width); + /* Consider the link for which this chanctx is reserved/going to be assigned */ width = ieee80211_get_width_of_link(rsvd_for); max_bw = max(max_bw, width); diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index a4d5461f6480..105653a16b68 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -171,23 +171,10 @@ static ssize_t airtime_flags_write(struct file *file, size_t count, loff_t *ppos) { struct ieee80211_local *local = file->private_data; - char buf[16]; + int ret; - if (count >= sizeof(buf)) - return -EINVAL; - - if (copy_from_user(buf, user_buf, count)) - return -EFAULT; - - if (count && buf[count - 1] == '\n') - buf[count - 1] = '\0'; - else - buf[count] = '\0'; - - if (kstrtou16(buf, 0, &local->airtime_flags)) - return -EINVAL; - - return count; + ret = kstrtou16_from_user(user_buf, count, 0, &local->airtime_flags); + return ret ? : count; } static const struct debugfs_short_fops airtime_flags_ops = { @@ -210,11 +197,13 @@ static ssize_t aql_pending_read(struct file *file, "VI %u us\n" "BE %u us\n" "BK %u us\n" + "MC %u us\n" "total %u us\n", atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VO]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VI]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BE]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BK]), + atomic_read(&local->aql_mc_pending_airtime), atomic_read(&local->aql_total_pending_airtime)); return simple_read_from_buffer(user_buf, count, ppos, buf, len); @@ -239,7 +228,8 @@ static ssize_t aql_txq_limit_read(struct file *file, "VO %u %u\n" "VI %u %u\n" "BE %u %u\n" - "BK %u %u\n", + "BK %u %u\n" + "MC %u\n", local->aql_txq_limit_low[IEEE80211_AC_VO], local->aql_txq_limit_high[IEEE80211_AC_VO], local->aql_txq_limit_low[IEEE80211_AC_VI], @@ -247,7 +237,8 @@ static ssize_t aql_txq_limit_read(struct file *file, local->aql_txq_limit_low[IEEE80211_AC_BE], local->aql_txq_limit_high[IEEE80211_AC_BE], local->aql_txq_limit_low[IEEE80211_AC_BK], - local->aql_txq_limit_high[IEEE80211_AC_BK]); + local->aql_txq_limit_high[IEEE80211_AC_BK], + local->aql_txq_limit_mc); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } @@ -273,6 +264,11 @@ static ssize_t aql_txq_limit_write(struct file *file, else buf[count] = '\0'; + if (sscanf(buf, "mcast %u", &q_limit_low) == 1) { + local->aql_txq_limit_mc = q_limit_low; + return count; + } + if (sscanf(buf, "%u %u %u", &ac, &q_limit_low, &q_limit_high) != 3) return -EINVAL; diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index 882f91abbb66..d74b66426349 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c @@ -880,11 +880,13 @@ static void ieee80211_rx_mgmt_deauth_ibss(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) { - u16 reason = le16_to_cpu(mgmt->u.deauth.reason_code); + u16 reason; if (len < IEEE80211_DEAUTH_FRAME_LEN) return; + reason = le16_to_cpu(mgmt->u.deauth.reason_code); + ibss_dbg(sdata, "RX DeAuth SA=%pM DA=%pM\n", mgmt->sa, mgmt->da); ibss_dbg(sdata, "\tBSSID=%pM (reason: %d)\n", mgmt->bssid, reason); sta_info_destroy_addr(sdata, mgmt->sa); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 34a9ea8b6f85..5761e9621491 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1464,10 +1464,12 @@ struct ieee80211_local { spinlock_t handle_wake_tx_queue_lock; u16 airtime_flags; + u32 aql_txq_limit_mc; u32 aql_txq_limit_low[IEEE80211_NUM_ACS]; u32 aql_txq_limit_high[IEEE80211_NUM_ACS]; u32 aql_threshold; atomic_t aql_total_pending_airtime; + atomic_t aql_mc_pending_airtime; atomic_t aql_ac_pending_airtime[IEEE80211_NUM_ACS]; const struct ieee80211_ops *ops; @@ -1708,8 +1710,6 @@ struct ieee80211_local { struct list_head roc_list; struct wiphy_work hw_roc_start, hw_roc_done; unsigned long hw_roc_start_time; - u64 roc_cookie_counter; - struct idr ack_status_frames; spinlock_t ack_status_lock; @@ -1989,7 +1989,6 @@ u64 ieee80211_reset_erp_info(struct ieee80211_sub_if_data *sdata); void ieee80211_handle_queued_frames(struct ieee80211_local *local); -u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local); int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u64 *cookie, gfp_t gfp); @@ -2151,12 +2150,12 @@ void ieee80211_roc_purge(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr); int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie); + struct cfg80211_mgmt_tx_params *params, u64 cookie); int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); @@ -2235,7 +2234,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, u32 ctrl_flags, - u64 *cookie); + u64 cookie); struct sk_buff * ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, u32 info_flags); @@ -2249,7 +2248,7 @@ void ieee80211_clear_fast_xmit(struct sta_info *sta); int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len, const u8 *dest, __be16 proto, bool unencrypted, - int link_id, u64 *cookie); + int link_id, u64 cookie); int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len); void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, @@ -2470,9 +2469,7 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, static inline bool ieee80211_require_encrypted_assoc(__le16 fc, struct sta_info *sta) { - return (sta && sta->sta.epp_peer && - (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc) || - ieee80211_is_assoc_resp(fc) || ieee80211_is_reassoc_resp(fc))); + return sta && sta->sta.epp_peer && ieee80211_is_assoc(fc); } /* sta_out needs to be checked for ERR_PTR() before using */ @@ -2773,8 +2770,8 @@ int ieee80211_put_eht_cap(struct sk_buff *skb, int ieee80211_put_uhr_cap(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata, const struct ieee80211_supported_band *sband); -int ieee80211_put_reg_conn(struct sk_buff *skb, - enum ieee80211_channel_flags flags); +void ieee80211_put_reg_conn(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb); /* channel management */ bool ieee80211_chandef_ht_oper(const struct ieee80211_ht_operation *ht_oper, @@ -2939,6 +2936,11 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, extern const struct ethtool_ops ieee80211_ethtool_ops; +u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, + struct ieee80211_tx_rate *tx_rate, + struct rate_info *ri, + enum nl80211_band band, + bool ampdu, int len); u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *pubsta, diff --git a/net/mac80211/link.c b/net/mac80211/link.c index dc68144dc363..931950a10508 100644 --- a/net/mac80211/link.c +++ b/net/mac80211/link.c @@ -360,7 +360,8 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata, link = links[link_id]; ieee80211_link_init(sdata, link_id, &link->data, &link->conf); ieee80211_link_setup(&link->data); - ieee80211_set_wmm_default(&link->data, true, non_sta); + if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN) + ieee80211_set_wmm_default(&link->data, true, non_sta); } if (new_links == 0) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index eb1eaaf34612..a59837b9f480 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -588,9 +588,7 @@ static int ieee80211_ifa_changed(struct notifier_block *nb, if (sdata->vif.type != NL80211_IFTYPE_STATION) return NOTIFY_DONE; - idev = __in_dev_get_rtnl(sdata->dev); - if (!idev) - return NOTIFY_DONE; + idev = ifa->ifa_dev; ifmgd = &sdata->u.mgd; @@ -988,6 +986,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, spin_lock_init(&local->rx_path_lock); spin_lock_init(&local->queue_stop_reason_lock); + local->aql_txq_limit_mc = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC; for (i = 0; i < IEEE80211_NUM_ACS; i++) { INIT_LIST_HEAD(&local->active_txqs[i]); spin_lock_init(&local->active_txq_lock[i]); diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index fa773f3b0541..f51167f0fc46 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1701,8 +1701,7 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, if (stype != IEEE80211_STYPE_BEACON && chanreq.oper.npca_chan && elems->uhr_operation && ieee80211_uhr_oper_size_ok((const void *)elems->uhr_operation, - elems->uhr_operation_len, - false)) { + elems->uhr_operation_len)) { const struct ieee80211_uhr_npca_info *npca; struct ieee80211_bss_npca_params params = {}; @@ -2307,13 +2306,14 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata, offset = ieee80211_add_before_reg_conn(skb, extra_elems, extra_elems_len, offset); - if (sband->band == NL80211_BAND_6GHZ) { + /* only add this on the assoc link, not in per-STA profiles */ + if (link) { /* * as per Section E.2.7 of IEEE 802.11 REVme D7.0, non-AP STA * capable of operating on the 6 GHz band shall transmit * regulatory connectivity element. */ - ieee80211_put_reg_conn(skb, chan->flags); + ieee80211_put_reg_conn(sdata, skb); } /* @@ -2564,11 +2564,8 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata, sizeof(struct ieee80211_he_mcs_nss_supp) + IEEE80211_HE_PPE_THRES_MAX_LEN; - if (sband->band == NL80211_BAND_6GHZ) { + if (sband->band == NL80211_BAND_6GHZ) size += 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa); - /* reg connection */ - size += 4; - } size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) + sizeof(struct ieee80211_eht_mcs_nss_supp) + @@ -2615,7 +2612,8 @@ static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) 2 + assoc_data->ssid_len + /* SSID */ assoc_data->ie_len + /* extra IEs */ (assoc_data->fils_kek_len ? 16 /* AES-SIV */ : 0) + - 9; /* WMM */ + 9 /* WMM */ + + 4 /* regulatory connectivity, if 6 GHz is supported */; for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { struct cfg80211_bss *cbss = assoc_data->link[link_id].bss; @@ -5259,7 +5257,8 @@ void ieee80211_disconnect(struct ieee80211_vif *vif, bool reconnect) EXPORT_SYMBOL(ieee80211_disconnect); static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata, - bool assoc) + bool assoc, + struct ieee80211_prep_tx_info *info) { struct ieee80211_mgd_auth_data *auth_data = sdata->u.mgd.auth_data; @@ -5267,6 +5266,9 @@ static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata, sdata->u.mgd.auth_data = NULL; + if (info) + drv_mgd_complete_tx(sdata->local, sdata, info); + if (!assoc) { /* * we are not authenticated yet, the only timer that could be @@ -5298,7 +5300,8 @@ enum assoc_status { }; static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata, - enum assoc_status status) + enum assoc_status status, + struct ieee80211_prep_tx_info *info) { struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data; @@ -5306,6 +5309,9 @@ static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata, sdata->u.mgd.assoc_data = NULL; + if (info) + drv_mgd_complete_tx(sdata->local, sdata, info); + if (status != ASSOC_SUCCESS) { /* * we are not associated yet, the only timer that could be @@ -5497,11 +5503,11 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata, sdata_info(sdata, "%pM denied authentication (status %d)\n", mgmt->sa, status_code); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, &info); event.u.mlme.status = MLME_DENIED; event.u.mlme.reason = status_code; drv_event_callback(sdata->local, sdata, &event); - goto notify_driver; + return; } switch (ifmgd->auth_data->algorithm) { @@ -5675,7 +5681,7 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, ifmgd->assoc_data->ap_addr, reason_code, ieee80211_get_reason_code_string(reason_code)); - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, NULL); cfg80211_rx_mlme_mgmt(sdata->dev, (u8 *)mgmt, len); return; @@ -5873,8 +5879,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, const struct cfg80211_bss_ies *bss_ies = NULL; struct ieee80211_supported_band *sband; struct ieee802_11_elems *elems; - const __le16 prof_bss_param_ch_present = - cpu_to_le16(IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT); u16 capab_info; bool ret; @@ -5890,20 +5894,13 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, * successful, so set the status directly to success */ assoc_data->link[link_id].status = WLAN_STATUS_SUCCESS; - if (elems->ml_basic) { - int bss_param_ch_cnt = - ieee80211_mle_get_bss_param_ch_cnt((const void *)elems->ml_basic); - - if (bss_param_ch_cnt < 0) { - ret = false; - goto out; - } - bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; - bss_conf->bss_param_ch_cnt_link_id = link_id; - } - } else if (elems->parse_error & IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC || - !elems->prof || - !(elems->prof->control & prof_bss_param_ch_present)) { + } else if (elems->parse_error & IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC) { + sdata_info(sdata, + "association response had nested multi-link element\n"); + ret = false; + goto out; + } else if (!elems->prof) { + link_info(link, "link missing from association response\n"); ret = false; goto out; } else { @@ -5917,10 +5914,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, */ capab_info = get_unaligned_le16(ptr); assoc_data->link[link_id].status = get_unaligned_le16(ptr + 2); - bss_param_ch_cnt = - ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof); - bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; - bss_conf->bss_param_ch_cnt_link_id = link_id; if (assoc_data->link[link_id].status != WLAN_STATUS_SUCCESS) { link_info(link, "association response status code=%u\n", @@ -5928,6 +5921,66 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, ret = true; goto out; } + + if (!(elems->prof->control & + cpu_to_le16(IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT))) { + link_info(link, + "per-STA profile missing BSS parameter change count\n"); + ret = false; + goto out; + } + bss_param_ch_cnt = + ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof); + bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; + bss_conf->bss_param_ch_cnt_link_id = link_id; + + if (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) { + const struct ieee80211_enh_crit_upd *enh_crit_upd; + + enh_crit_upd = ieee80211_mle_basic_sta_prof_enh_crit_upd(elems->prof); + if (!enh_crit_upd) { + link_info(link, + "per-STA profile missing enhanced critical updates\n"); + ret = false; + goto out; + } + + bss_conf->enh_bss_param_ch_cnt = + u8_get_bits(enh_crit_upd->v, + IEEE80211_ENH_CRIT_UPD_EBPCC); + bss_conf->enh_bss_param_ch_cnt_link_id = link_id; + } + } + + if (link_id == assoc_data->assoc_link_id && elems->ml_basic) { + const void *mle = (const void *)elems->ml_basic; + int bss_param_ch_cnt = ieee80211_mle_get_bss_param_ch_cnt(mle); + + if (bss_param_ch_cnt < 0) { + sdata_info(sdata, + "No BSS parameter change count in assoc response\n"); + ret = false; + goto out; + } + bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; + bss_conf->bss_param_ch_cnt_link_id = link_id; + + if (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) { + const struct ieee80211_enh_crit_upd *enh_crit_upd; + + enh_crit_upd = ieee80211_mle_get_enh_crit_upd_info(mle); + if (!enh_crit_upd) { + link_info(link, + "No enhanced critical updates in assoc response\n"); + ret = false; + goto out; + } + + bss_conf->enh_bss_param_ch_cnt = + u8_get_bits(enh_crit_upd->v, + IEEE80211_ENH_CRIT_UPD_EBPCC); + bss_conf->enh_bss_param_ch_cnt_link_id = link_id; + } } if (!is_s1g && !elems->supp_rates) { @@ -7140,6 +7193,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; + enum assoc_status assoc_status = ASSOC_ABANDON; u16 capab_info, status_code, aid = 0; struct ieee80211_elems_parse_params parse_params = { .bss = NULL, @@ -7147,7 +7201,6 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, .from_ap = true, .type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE, }; - struct ieee802_11_elems *elems; struct sta_info *sta; int ac; const u8 *elem_start; @@ -7213,7 +7266,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, elem_len = len - (elem_start - (u8 *)mgmt); parse_params.start = elem_start; parse_params.len = elem_len; - elems = ieee802_11_parse_elems_full(&parse_params); + struct ieee802_11_elems *elems __free(kfree) = + ieee802_11_parse_elems_full(&parse_params); if (!elems) goto notify_driver; @@ -7222,7 +7276,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, else if (!assoc_data->s1g) aid = le16_to_cpu(mgmt->u.assoc_resp.aid); else if (status_code == WLAN_STATUS_SUCCESS) - goto abandon_assoc; + goto destroy_assoc_data; /* * The 5 MSB of the AID field are reserved for a non-S1G STA. For @@ -7281,7 +7335,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, sdata_info(sdata, "MLO association with %pM but no (basic) multi-link element in response!\n", assoc_data->ap_addr); - goto abandon_assoc; + goto destroy_assoc_data; } common = (void *)elems->ml_basic->variable; @@ -7292,7 +7346,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, "AP MLD MAC address mismatch: got %pM expected %pM\n", common->mld_mac_addr, assoc_data->ap_addr); - goto abandon_assoc; + goto destroy_assoc_data; } sdata->vif.cfg.eml_cap = @@ -7309,8 +7363,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, if (!ieee80211_assoc_success(sdata, mgmt, elems, elem_start, elem_len)) { /* oops -- internal error -- send timeout for now */ - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); - goto notify_driver; + assoc_status = ASSOC_TIMEOUT; + goto destroy_assoc_data; } event.u.mlme.status = MLME_SUCCESS; drv_event_callback(sdata->local, sdata, &event); @@ -7354,23 +7408,19 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, sta = sta_info_get_bss(sdata, sdata->vif.cfg.ap_addr); resp.assoc_encrypted = sta && sta->sta.epp_peer; - ieee80211_destroy_assoc_data(sdata, - status_code == WLAN_STATUS_SUCCESS ? - ASSOC_SUCCESS : - ASSOC_REJECTED); - resp.buf = (u8 *)mgmt; resp.len = len; resp.req_ies = ifmgd->assoc_req_ies; resp.req_ies_len = ifmgd->assoc_req_ies_len; cfg80211_rx_assoc_resp(sdata->dev, &resp); + assoc_status = status_code == WLAN_STATUS_SUCCESS ? ASSOC_SUCCESS : + ASSOC_REJECTED; +destroy_assoc_data: + ieee80211_destroy_assoc_data(sdata, assoc_status, &info); + return; + notify_driver: drv_mgd_complete_tx(sdata->local, sdata, &info); - kfree(elems); - return; -abandon_assoc: - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); - goto notify_driver; } static void ieee80211_rx_bss_info(struct ieee80211_link_data *link, @@ -7977,7 +8027,7 @@ ieee80211_mgd_check_cross_link_csa(struct ieee80211_sub_if_data *sdata, prof = (void *)sta_profiles[link_id]; prof_elems = ieee802_11_parse_elems(prof->variable + (prof->sta_info_len - 1), - len - + len - sizeof(*prof) - (prof->sta_info_len - 1), IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON, @@ -9103,7 +9153,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) * ok ... we waited for assoc or continuation but * userspace didn't do it, so kill the auth data */ - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); } else if (ieee80211_auth(sdata)) { u8 ap_addr[ETH_ALEN]; struct ieee80211_event event = { @@ -9114,7 +9164,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) memcpy(ap_addr, ifmgd->auth_data->ap_addr, ETH_ALEN); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); cfg80211_auth_timeout(sdata->dev, ap_addr); drv_event_callback(sdata->local, sdata, &event); @@ -9133,7 +9183,8 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) .u.mlme.status = MLME_TIMEOUT, }; - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT, + NULL); drv_event_callback(sdata->local, sdata, &event); } } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started) @@ -9346,9 +9397,10 @@ void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata) WLAN_REASON_DEAUTH_LEAVING, false, frame_buf); if (ifmgd->assoc_data) - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, + NULL); if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, IEEE80211_DEAUTH_FRAME_LEN, false); @@ -9965,7 +10017,7 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata, auth_data->peer_confirmed = ifmgd->auth_data->peer_confirmed; } - ieee80211_destroy_auth_data(sdata, cont_auth); + ieee80211_destroy_auth_data(sdata, cont_auth, NULL); } /* prep auth_data so we don't go into idle on disassoc */ @@ -10499,7 +10551,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, /* Cleanup is delayed if auth_data matches */ if (ifmgd->auth_data && !match_auth) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); if (req->ie && req->ie_len) { memcpy(assoc_data->ie, req->ie, req->ie_len); @@ -10649,7 +10701,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, /* We are associating, clean up auth_data */ if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, true); + ieee80211_destroy_auth_data(sdata, true, NULL); return 0; err_clear: @@ -10687,11 +10739,10 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, &info); ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, req->reason_code, false); - drv_mgd_complete_tx(sdata->local, sdata, &info); return 0; } @@ -10708,11 +10759,10 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, &info); ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, req->reason_code, false); - drv_mgd_complete_tx(sdata->local, sdata, &info); return 0; } @@ -10789,9 +10839,9 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata) &ifmgd->uhr_omp.status_work); if (ifmgd->assoc_data) - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT, NULL); if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); spin_lock_bh(&ifmgd->teardown_lock); if (ifmgd->teardown_skb) { kfree_skb(ifmgd->teardown_skb); diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 2bceb73717c6..7acef80d5f1f 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -602,14 +602,12 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, /* * cookie is either the roc cookie (for normal roc) - * or the SKB (for mgmt TX) + * or the mgmt_tx cookie; both are pre-assigned by cfg80211 */ - if (!txskb) { - roc->cookie = ieee80211_mgmt_tx_cookie(local); - *cookie = roc->cookie; - } else { + if (!txskb) + roc->cookie = *cookie; + else roc->mgmt_tx_cookie = *cookie; - } req = wiphy_dereference(local->hw.wiphy, local->scan_req); @@ -704,7 +702,7 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); @@ -713,7 +711,7 @@ int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, lockdep_assert_wiphy(local->hw.wiphy); return ieee80211_start_roc_work(local, sdata, chan, - duration, cookie, NULL, + duration, &cookie, NULL, IEEE80211_ROC_TYPE_NORMAL); } @@ -810,7 +808,7 @@ int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, } int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); struct ieee80211_local *local = sdata->local; @@ -1016,18 +1014,11 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* make a copy to preserve the frame contents * in case of encryption. */ - ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_KERNEL); + ret = ieee80211_attach_ack_skb(local, skb, &cookie, GFP_KERNEL); if (ret) { kfree_skb(skb); goto out_unlock; } - } else { - /* Assign a dummy non-zero cookie, it's not sent to - * userspace in this case but we rely on its value - * internally in the need_offchan case to distinguish - * mgmt-tx from remain-on-channel. - */ - *cookie = 0xffffffff; } if (!need_offchan) { @@ -1044,7 +1035,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* This will handle all kinds of coalescing and immediate TX */ ret = ieee80211_start_roc_work(local, sdata, params->chan, - params->wait, cookie, skb, + params->wait, &cookie, skb, IEEE80211_ROC_TYPE_MGMT_TX); if (ret) ieee80211_free_txskb(&local->hw, skb); diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c index c2f2f78f2b4f..cb2be167cde7 100644 --- a/net/mac80211/parse.c +++ b/net/mac80211/parse.c @@ -209,9 +209,7 @@ ieee80211_parse_extension_element(u32 *crc, if (params->mode < IEEE80211_CONN_MODE_UHR) break; calc_crc = true; - if (ieee80211_uhr_oper_size_ok(data, len, - params->type == (IEEE80211_FTYPE_MGMT | - IEEE80211_STYPE_BEACON))) { + if (ieee80211_uhr_oper_size_ok(data, len)) { elems->uhr_operation = data; elems->uhr_operation_len = len; } diff --git a/net/mac80211/s1g.c b/net/mac80211/s1g.c index abc338e22e59..bd7baf481836 100644 --- a/net/mac80211/s1g.c +++ b/net/mac80211/s1g.c @@ -147,7 +147,7 @@ ieee80211_s1g_tx_twt_setup_fail(struct ieee80211_sub_if_data *sdata, drv_twt_teardown_request(sdata->local, sdata, &sta->sta, flowid); - ieee80211_s1g_send_twt_teardown(sdata, mgmt->sa, sdata->vif.addr, + ieee80211_s1g_send_twt_teardown(sdata, mgmt->da, sdata->vif.addr, flowid); } diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index ec622750e1c9..880f4625775d 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -227,7 +227,7 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, { enum nl80211_band new_band = current_band; int new_freq; - u8 new_chan_no = 0, new_op_class = 0; + int new_chan_no = -1, new_op_class = -1; struct ieee80211_channel *new_chan; struct cfg80211_chan_def new_chandef = {}; const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; @@ -256,7 +256,7 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, new_op_class = ext_chansw_elem->new_operating_class; if (!ieee80211_operating_class_to_band(new_op_class, &new_band)) { - new_op_class = 0; + new_op_class = -1; if (!unprot_action) sdata_info(sdata, "cannot understand ECSA IE operating class, %d, ignoring\n", @@ -268,14 +268,14 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, } } - if (!new_op_class && elems->ch_switch_ie) { + if (new_op_class < 0 && elems->ch_switch_ie) { new_chan_no = elems->ch_switch_ie->new_ch_num; csa_ie->count = elems->ch_switch_ie->count; csa_ie->mode = elems->ch_switch_ie->mode; } /* nothing here we understand */ - if (!new_chan_no) + if (new_chan_no < 0) return 1; /* Mesh Channel Switch Parameters Element */ @@ -349,7 +349,8 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, get_unaligned_le16(bwi->info.optional); } else if (!wide_bw_chansw_ie || !wbcs_elem_to_chandef(wide_bw_chansw_ie, &new_chandef)) { - if (!ieee80211_operating_class_to_chandef(new_op_class, new_chan, + if (new_op_class < 0 || + !ieee80211_operating_class_to_chandef(new_op_class, new_chan, &new_chandef)) new_chandef = csa_ie->chanreq.oper; } diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 22eba0e6e54c..fdf00cbf49d8 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2491,13 +2491,28 @@ EXPORT_SYMBOL(ieee80211_sta_recalc_aggregates); void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, struct sta_info *sta, u8 ac, - u16 tx_airtime, bool tx_completed) + u16 tx_airtime, bool tx_completed, + bool mcast) { int tx_pending; if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) return; + if (mcast) { + if (!tx_completed) { + atomic_add(tx_airtime, &local->aql_mc_pending_airtime); + return; + } + + tx_pending = atomic_sub_return(tx_airtime, + &local->aql_mc_pending_airtime); + if (tx_pending < 0) + atomic_cmpxchg(&local->aql_mc_pending_airtime, + tx_pending, 0); + return; + } + if (!tx_completed) { if (sta) atomic_add(tx_airtime, @@ -2797,6 +2812,28 @@ void sta_set_accumulated_removed_links_sinfo(struct sta_info *sta, } } +static u32 sta_estimate_expected_throughput(struct sta_info *sta, + struct rate_info *ri, + struct ieee80211_bss_conf *bss_conf) +{ + struct ieee80211_hw *hw = &sta->sdata->local->hw; + struct ieee80211_chanctx_conf *conf; + u32 duration; + u8 band; + + conf = sdata_dereference(bss_conf->chanctx_conf, sta->sdata); + if (!conf) + return 0; + band = conf->def.chan->band; + + duration = ieee80211_rate_expected_tx_airtime(hw, NULL, ri, band, true, 1024); + duration += duration >> 4; /* add assumed packet error rate of ~6% */ + if (!duration) + return 0; + + return ((1024 * USEC_PER_SEC) / duration) * 8; +} + static void sta_set_link_sinfo(struct sta_info *sta, struct link_station_info *link_sinfo, struct ieee80211_link_data *link, @@ -3011,6 +3048,10 @@ static void sta_set_link_sinfo(struct sta_info *sta, link_sinfo->bss_param.beacon_interval = link->conf->beacon_int; thr = sta_get_expected_throughput(sta); + if (!thr && (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) + thr = sta_estimate_expected_throughput(sta, + &link_sinfo->txrate, + link->conf); if (thr != 0) { link_sinfo->filled |= @@ -3264,6 +3305,14 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, if (thr != 0) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); sinfo->expected_throughput = thr; + } else if (!sta->sta.valid_links && + (sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) { + thr = sta_estimate_expected_throughput(sta, &sinfo->txrate, + &sdata->vif.bss_conf); + if (thr) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); + sinfo->expected_throughput = thr; + } } if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL)) && @@ -3284,6 +3333,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, if (sta->sta.valid_links) { struct ieee80211_link_data *link; struct link_sta_info *link_sta; + u32 est_thr = 0; int link_id; sinfo->mlo_params_valid = true; @@ -3295,17 +3345,25 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, sinfo->valid_links = sta->sta.valid_links; for_each_valid_link(sinfo, link_id) { + struct link_station_info *link_sinfo = sinfo->links[link_id]; + link_sta = wiphy_dereference(sta->local->hw.wiphy, sta->link[link_id]); link = wiphy_dereference(sdata->local->hw.wiphy, sdata->link[link_id]); - if (!link_sta || !sinfo->links[link_id] || !link) { + if (!link_sta || !link_sinfo || !link) { sinfo->valid_links &= ~BIT(link_id); continue; } - sta_set_link_sinfo(sta, sinfo->links[link_id], - link, tidstats); + sta_set_link_sinfo(sta, link_sinfo, link, tidstats); + if (!thr && + (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT))) + est_thr += link_sinfo->expected_throughput; + } + if (est_thr) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); + sinfo->expected_throughput = est_thr; } } } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 5da3142d8516..ee0d32877c5b 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -147,7 +147,8 @@ struct airtime_info { void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, struct sta_info *sta, u8 ac, - u16 tx_airtime, bool tx_completed); + u16 tx_airtime, bool tx_completed, + bool mcast); struct sta_info; diff --git a/net/mac80211/status.c b/net/mac80211/status.c index dd1dbba06838..3d811f652603 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -655,7 +655,10 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local, GFP_ATOMIC); else if (ieee80211_is_any_nullfunc(hdr->frame_control)) cfg80211_probe_status(sdata->dev, hdr->addr1, - cookie, acked, + cookie, + info->status.link_valid ? + info->status.link_id : -1, + acked, info->status.ack_signal, is_valid_ack_signal, GFP_ATOMIC); @@ -774,7 +777,7 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, ieee80211_sta_update_pending_airtime(local, sta, skb_get_queue_mapping(skb), tx_time_est, - true); + true, info->tx_time_mc); rcu_read_unlock(); } @@ -1186,10 +1189,11 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw, /* Do this here to avoid the expensive lookup of the sta * in ieee80211_report_used_skb(). */ + bool mcast = IEEE80211_SKB_CB(skb)->tx_time_mc; ieee80211_sta_update_pending_airtime(local, sta, skb_get_queue_mapping(skb), tx_time_est, - true); + true, mcast); ieee80211_info_set_tx_time_est(IEEE80211_SKB_CB(skb), 0); } diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index ffd575a8d188..dc2f662fe4c4 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1121,7 +1121,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, /* disable bottom halves when entering the Tx path */ local_bh_disable(); __ieee80211_subif_start_xmit(skb, dev, flags, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); local_bh_enable(); return ret; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 91b14112e24f..3a1e2c9e1565 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1309,10 +1309,17 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local, (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE)) return NULL; + /* + * While (re)association request/response frames are not considered + * bufferable MMPDUs, use the TXQ abstraction for the transmission of + * these frames. This is specifically useful for drivers that might + * associate other resources with the TXQ, e.g., encryption keys etc. + */ if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && unlikely(!ieee80211_is_data_present(hdr->frame_control))) { if ((!ieee80211_is_mgmt(hdr->frame_control) || ieee80211_is_bufferable_mmpdu(skb) || + ieee80211_is_assoc(hdr->frame_control) || vif->type == NL80211_IFTYPE_STATION || vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA) && @@ -2573,7 +2580,7 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u32 *info_flags, - u64 *cookie) + u64 cookie) { struct sk_buff *ack_skb; u16 info_id = 0; @@ -2595,10 +2602,8 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, if (id >= 0) { info_id = id; *info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; - if (cookie) { - *cookie = ieee80211_mgmt_tx_cookie(local); - IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; - } + if (cookie) + IEEE80211_SKB_CB(ack_skb)->ack.cookie = cookie; } else { kfree_skb(ack_skb); } @@ -2643,7 +2648,7 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, u32 info_flags, struct sta_info *sta, u32 ctrl_flags, - u64 *cookie) + u64 cookie) { struct ieee80211_local *local = sdata->local; struct ieee80211_tx_info *info; @@ -4054,20 +4059,20 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw, encap_out: info->control.vif = vif; - if (tx.sta && - wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) { - bool ampdu = txq->ac != IEEE80211_AC_VO; + if (wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) { + bool ampdu = txq->sta && txq->ac != IEEE80211_AC_VO; u32 airtime; airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta, skb->len, ampdu); - if (airtime) { - airtime = ieee80211_info_set_tx_time_est(info, airtime); - ieee80211_sta_update_pending_airtime(local, tx.sta, - txq->ac, - airtime, - false); - } + if (!airtime) + return skb; + + airtime = ieee80211_info_set_tx_time_est(info, airtime); + info->tx_time_mc = !tx.sta; + ieee80211_sta_update_pending_airtime(local, tx.sta, txq->ac, + airtime, false, + info->tx_time_mc); } return skb; @@ -4119,6 +4124,7 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac) struct ieee80211_txq *ret = NULL; struct txq_info *txqi = NULL, *head = NULL; bool found_eligible_txq = false; + bool aql_check; spin_lock_bh(&local->active_txq_lock[ac]); @@ -4142,26 +4148,28 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac) if (!head) head = txqi; + aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq); + if (aql_check) + found_eligible_txq = true; + if (txqi->txq.sta) { struct sta_info *sta = container_of(txqi->txq.sta, struct sta_info, sta); - bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq); - s32 deficit = ieee80211_sta_deficit(sta, txqi->txq.ac); - if (aql_check) - found_eligible_txq = true; - - if (deficit < 0) + if (ieee80211_sta_deficit(sta, txqi->txq.ac) < 0) { sta->airtime[txqi->txq.ac].deficit += sta->airtime_weight; - if (deficit < 0 || !aql_check) { - list_move_tail(&txqi->schedule_order, - &local->active_txqs[txqi->txq.ac]); - goto begin; + aql_check = false; } } + if (!aql_check) { + list_move_tail(&txqi->schedule_order, + &local->active_txqs[txqi->txq.ac]); + goto begin; + } + if (txqi->schedule_round == local->schedule_round[ac]) goto out; @@ -4228,7 +4236,8 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, return true; if (!txq->sta) - return true; + return atomic_read(&local->aql_mc_pending_airtime) < + local->aql_txq_limit_mc; if (unlikely(txq->tid == IEEE80211_NUM_TIDS)) return true; @@ -4248,6 +4257,24 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, } EXPORT_SYMBOL(ieee80211_txq_airtime_check); +u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ieee80211_local *local = hw_to_local(hw); + struct sta_info *sta; + + if (unlikely(txq->tid == IEEE80211_NUM_TIDS)) + return 0; + + if (!txq->sta) + return atomic_read(&local->aql_mc_pending_airtime); + + sta = container_of(txq->sta, struct sta_info, sta); + + return atomic_read(&sta->airtime[txq->ac].aql_tx_pending); +} +EXPORT_SYMBOL(ieee80211_txq_aql_pending); + static bool ieee80211_txq_schedule_airtime_check(struct ieee80211_local *local, u8 ac) { @@ -4277,15 +4304,15 @@ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw, spin_lock_bh(&local->active_txq_lock[ac]); - if (!txqi->txq.sta) - goto out; - if (list_empty(&txqi->schedule_order)) goto out; if (!ieee80211_txq_schedule_airtime_check(local, ac)) goto out; + if (!txqi->txq.sta) + goto out; + list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac], schedule_order) { if (iter == txqi) @@ -4342,7 +4369,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, u32 ctrl_flags, - u64 *cookie) + u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -4537,7 +4564,7 @@ static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata, return; ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, NULL); + __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, 0); } static void ieee80211_mlo_multicast_tx(struct net_device *dev, @@ -4552,8 +4579,7 @@ static void ieee80211_mlo_multicast_tx(struct net_device *dev, ctrl_flags |= u32_encode_bits(__ffs(links), IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags, - NULL); + __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags, 0); return; } @@ -4595,7 +4621,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, while ((skb = __skb_dequeue(&queue))) __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); } else if (ieee80211_vif_is_mld(&sdata->vif) && ((sdata->vif.type == NL80211_IFTYPE_AP && !ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) || @@ -4606,7 +4632,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, normal: __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); } return NETDEV_TX_OK; @@ -4705,7 +4731,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, /* fall back to non-offload slow path */ __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); return; } @@ -4741,7 +4767,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, if (unlikely(sk_requests_wifi_status(skb->sk))) { info->status_data = ieee80211_store_ack_skb(local, skb, - &info->flags, NULL); + &info->flags, 0); if (info->status_data) info->status_data_idr = 1; } @@ -4884,7 +4910,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, } skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); if (IS_ERR(skb)) goto out; @@ -5249,7 +5275,8 @@ static void ieee80211_set_beacon_cntdwn(struct ieee80211_sub_if_data *sdata, if (sdata->vif.type == NL80211_IFTYPE_AP && resp) { u16 *resp_offsets = resp->cntdwn_counter_offsets; - resp->data[resp_offsets[i]] = count; + if (resp_offsets[i]) + resp->data[resp_offsets[i]] = count; } } } @@ -6499,7 +6526,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len, const u8 *dest, __be16 proto, bool unencrypted, - int link_id, u64 *cookie) + int link_id, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -6634,7 +6661,7 @@ int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev, local_bh_disable(); __ieee80211_subif_start_xmit(skb, skb->dev, 0, IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP, - NULL); + 0); local_bh_enable(); return 0; diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 59f73dabe6e0..a96078a6bfa2 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -2706,12 +2706,31 @@ int ieee80211_put_he_cap(struct sk_buff *skb, return 0; } -int ieee80211_put_reg_conn(struct sk_buff *skb, - enum ieee80211_channel_flags flags) +void ieee80211_put_reg_conn(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) { + struct ieee80211_local *local = sdata->local; u8 reg_conn = IEEE80211_REG_CONN_LPI_VALID | IEEE80211_REG_CONN_LPI_VALUE | IEEE80211_REG_CONN_SP_VALID; + struct ieee80211_supported_band *sband; + bool available_channels = false; + u32 flags = 0; + int i; + + sband = local->hw.wiphy->bands[NL80211_BAND_6GHZ]; + if (!sband) + return; + + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & IEEE80211_CHAN_DISABLED) + continue; + flags |= sband->channels[i].flags; + available_channels = true; + } + + if (!available_channels) + return; if (!(flags & IEEE80211_CHAN_NO_6GHZ_AFC_CLIENT)) reg_conn |= IEEE80211_REG_CONN_SP_VALUE; @@ -2720,7 +2739,6 @@ int ieee80211_put_reg_conn(struct sk_buff *skb, skb_put_u8(skb, 1 + sizeof(reg_conn)); skb_put_u8(skb, WLAN_EID_EXT_NON_AP_STA_REG_CON); skb_put_u8(skb, reg_conn); - return 0; } int ieee80211_put_he_6ghz_cap(struct sk_buff *skb, diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 961be5054a03..17b78dcbf8ab 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -221,6 +221,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) { const struct iphdr *v4hdr; + hdr = mpls_hdr(skb) + label_index; v4hdr = (const struct iphdr *)(hdr + 1); if (v4hdr->version == 4) { hash = jhash_3words(ntohl(v4hdr->saddr), @@ -231,6 +232,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) sizeof(struct ipv6hdr))) { const struct ipv6hdr *v6hdr; + hdr = mpls_hdr(skb) + label_index; v6hdr = (const struct ipv6hdr *)(hdr + 1); hash = __ipv6_addr_jhash(&v6hdr->saddr, hash); hash = __ipv6_addr_jhash(&v6hdr->daddr, hash); diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c index f23fda0c55a7..608cb568897c 100644 --- a/net/mptcp/mib.c +++ b/net/mptcp/mib.c @@ -21,14 +21,19 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("MPFallbackTokenInit", MPTCP_MIB_TOKENFALLBACKINIT), SNMP_MIB_ITEM("MPTCPRetrans", MPTCP_MIB_RETRANSSEGS), SNMP_MIB_ITEM("MPJoinNoTokenFound", MPTCP_MIB_JOINNOTOKEN), + SNMP_MIB_ITEM("MPJoinNoIdFound", MPTCP_MIB_MPJOINNOIDFOUND), SNMP_MIB_ITEM("MPJoinSynRx", MPTCP_MIB_JOINSYNRX), SNMP_MIB_ITEM("MPJoinSynBackupRx", MPTCP_MIB_JOINSYNBACKUPRX), SNMP_MIB_ITEM("MPJoinSynAckRx", MPTCP_MIB_JOINSYNACKRX), SNMP_MIB_ITEM("MPJoinSynAckBackupRx", MPTCP_MIB_JOINSYNACKBACKUPRX), SNMP_MIB_ITEM("MPJoinSynAckHMacFailure", MPTCP_MIB_JOINSYNACKMAC), + SNMP_MIB_ITEM("MPJoinSynAckNoMPJoin", MPTCP_MIB_MPJOINSYNACKNOMPJOIN), SNMP_MIB_ITEM("MPJoinAckRx", MPTCP_MIB_JOINACKRX), SNMP_MIB_ITEM("MPJoinAckHMacFailure", MPTCP_MIB_JOINACKMAC), + SNMP_MIB_ITEM("MPJoinAckNoMPJoin", MPTCP_MIB_MPJOINACKNOMPJOIN), + SNMP_MIB_ITEM("MPJoinAckNoCtx", MPTCP_MIB_MPJOINACKNOCTX), SNMP_MIB_ITEM("MPJoinRejected", MPTCP_MIB_JOINREJECTED), + SNMP_MIB_ITEM("MPJoinNotEstablished", MPTCP_MIB_MPJOINNOTESTABLISHED), SNMP_MIB_ITEM("MPJoinSynTx", MPTCP_MIB_JOINSYNTX), SNMP_MIB_ITEM("MPJoinSynTxCreatSkErr", MPTCP_MIB_JOINSYNTXCREATSKERR), SNMP_MIB_ITEM("MPJoinSynTxBindErr", MPTCP_MIB_JOINSYNTXBINDERR), @@ -81,10 +86,15 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("Blackhole", MPTCP_MIB_BLACKHOLE), SNMP_MIB_ITEM("MPCapableDataFallback", MPTCP_MIB_MPCAPABLEDATAFALLBACK), SNMP_MIB_ITEM("MD5SigFallback", MPTCP_MIB_MD5SIGFALLBACK), + SNMP_MIB_ITEM("MD5SigReset", MPTCP_MIB_MD5SIGRESET), SNMP_MIB_ITEM("DssFallback", MPTCP_MIB_DSSFALLBACK), + SNMP_MIB_ITEM("DssReset", MPTCP_MIB_DSSRESET), SNMP_MIB_ITEM("SimultConnectFallback", MPTCP_MIB_SIMULTCONNFALLBACK), SNMP_MIB_ITEM("FallbackFailed", MPTCP_MIB_FALLBACKFAILED), SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE), + SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP), + SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED), + SNMP_MIB_ITEM("OFOPruned", MPTCP_MIB_OFOPRUNED), }; /* mptcp_mib_alloc - allocate percpu mib counters diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h index 812218b5ed2b..1ebdb55e9534 100644 --- a/net/mptcp/mib.h +++ b/net/mptcp/mib.h @@ -16,14 +16,19 @@ enum linux_mptcp_mib_field { MPTCP_MIB_TOKENFALLBACKINIT, /* Could not init/allocate token */ MPTCP_MIB_RETRANSSEGS, /* Segments retransmitted at the MPTCP-level */ MPTCP_MIB_JOINNOTOKEN, /* Received MP_JOIN but the token was not found */ + MPTCP_MIB_MPJOINNOIDFOUND, /* Received MP_JOIN but no local ID was found */ MPTCP_MIB_JOINSYNRX, /* Received a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNBACKUPRX, /* Received a SYN + MP_JOIN + backup flag */ MPTCP_MIB_JOINSYNACKRX, /* Received a SYN/ACK + MP_JOIN */ MPTCP_MIB_JOINSYNACKBACKUPRX, /* Received a SYN/ACK + MP_JOIN + backup flag */ MPTCP_MIB_JOINSYNACKMAC, /* HMAC was wrong on SYN/ACK + MP_JOIN */ + MPTCP_MIB_MPJOINSYNACKNOMPJOIN, /* MP_RST: missing MP_JOIN in SYN/ACK */ MPTCP_MIB_JOINACKRX, /* Received an ACK + MP_JOIN */ MPTCP_MIB_JOINACKMAC, /* HMAC was wrong on ACK + MP_JOIN */ + MPTCP_MIB_MPJOINACKNOMPJOIN, /* MP_RST: missing MP_JOIN in ACK */ + MPTCP_MIB_MPJOINACKNOCTX, /* MP_RST: no subflow context on ACK */ MPTCP_MIB_JOINREJECTED, /* The PM rejected the JOIN request */ + MPTCP_MIB_MPJOINNOTESTABLISHED, /* MP_RST: JOIN on not-fully-established msk */ MPTCP_MIB_JOINSYNTX, /* Sending a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNTXCREATSKERR, /* Not able to create a socket when sending a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNTXBINDERR, /* Not able to bind() the address when sending a SYN + MP_JOIN */ @@ -84,10 +89,15 @@ enum linux_mptcp_mib_field { * established packet */ MPTCP_MIB_MD5SIGFALLBACK, /* Conflicting TCP option enabled */ + MPTCP_MIB_MD5SIGRESET, /* MP_RST: MD5SIG enabled on listener */ MPTCP_MIB_DSSFALLBACK, /* Bad or missing DSS */ + MPTCP_MIB_DSSRESET, /* MP_RST: bad data mapping */ MPTCP_MIB_SIMULTCONNFALLBACK, /* Simultaneous connect */ MPTCP_MIB_FALLBACKFAILED, /* Can't fallback due to msk status */ MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */ + MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */ + MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */ + MPTCP_MIB_OFOPRUNED, /* MPTCP-level OoO queue pruned */ __MPTCP_MIB_MAX }; diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 1057d500577b..b8318e030138 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -1190,8 +1190,34 @@ static bool add_addr_hmac_valid(struct mptcp_sock *msk, return hmac == mp_opt->ahmac; } -/* Return false in case of error (or subflow has been reset), - * else return true. +static bool mptcp_over_limit(struct sock *sk, struct sock *ssk, + const struct sk_buff *skb) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + u32 rcvbuf = READ_ONCE(sk->sk_rcvbuf); + + if (likely((u32)sk_rmem_alloc_get(sk) <= rcvbuf && + READ_ONCE(msk->backlog_len) <= rcvbuf)) + return false; + + /* Avoid silently dropping pure acks, fin, rst or already-acked segm. */ + if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || + TCP_SKB_CB(skb)->tcp_flags & (TCPHDR_FIN | TCPHDR_RST) || + !after(TCP_SKB_CB(skb)->end_seq, tcp_sk(ssk)->rcv_nxt)) + return false; + + /* Dropped due to memory constraints, schedule an ack. */ + inet_csk(ssk)->icsk_ack.pending |= ICSK_ACK_NOMEM | ICSK_ACK_NOW; + inet_csk_schedule_ack(ssk); + + /* Plain TCP (fallback) and skb is dropped before the TCP recv queue. */ + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPRCVQDROP); + + return true; +} + +/* Return false when the caller must drop the packet, i.e. in case of error, + * subflow has been reset, or over memory limits. */ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb) { @@ -1217,7 +1243,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb) __mptcp_data_acked(subflow->conn); mptcp_data_unlock(subflow->conn); - return true; + return !mptcp_over_limit(subflow->conn, sk, skb); } mptcp_get_options(skb, &mp_opt); diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index 64a1236aabee..8b68868255c5 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -670,7 +670,8 @@ void mptcp_pm_subflow_check_next(struct mptcp_sock *msk, if (mptcp_pm_is_userspace(msk)) { if (update_subflows) { spin_lock_bh(&pm->lock); - pm->extra_subflows--; + if (!WARN_ON_ONCE(pm->extra_subflows == 0)) + pm->extra_subflows--; spin_unlock_bh(&pm->lock); } return; @@ -1065,7 +1066,8 @@ bool mptcp_pm_is_backup(struct mptcp_sock *msk, struct sock_common *skc) return mptcp_pm_nl_is_backup(msk, &skc_local); } -static void mptcp_pm_subflows_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) +static void +mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) { struct mptcp_subflow_context *iter, *subflow = mptcp_subflow_ctx(ssk); struct sock *sk = (struct sock *)msk; @@ -1102,22 +1104,34 @@ static void mptcp_pm_subflows_chk_stale(const struct mptcp_sock *msk, struct soc } } -void mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) +void mptcp_pm_chk_stale(const struct mptcp_sock *msk) { - struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(ssk); - u32 rcv_tstamp = READ_ONCE(tcp_sk(ssk)->rcv_tstamp); + struct mptcp_subflow_context *subflow; - /* keep track of rtx periods with no progress */ - if (!subflow->stale_count) { - subflow->stale_rcv_tstamp = rcv_tstamp; - subflow->stale_count++; - } else if (subflow->stale_rcv_tstamp == rcv_tstamp) { - if (subflow->stale_count < U8_MAX) + mptcp_for_each_subflow(msk, subflow) { + struct sock *ssk = mptcp_subflow_tcp_sock(subflow); + u32 rcv_tstamp; + + if (!__mptcp_subflow_active(subflow)) + continue; + + /* No data outstanding at TCP level? not stale */ + if (tcp_rtx_and_write_queues_empty(ssk)) + continue; + + /* keep track of rtx periods with no progress */ + rcv_tstamp = READ_ONCE(tcp_sk(ssk)->rcv_tstamp); + if (!subflow->stale_count) { + subflow->stale_rcv_tstamp = rcv_tstamp; subflow->stale_count++; - mptcp_pm_subflows_chk_stale(msk, ssk); - } else { - subflow->stale_count = 0; - mptcp_subflow_set_active(subflow); + } else if (subflow->stale_rcv_tstamp == rcv_tstamp) { + if (subflow->stale_count < U8_MAX) + subflow->stale_count++; + mptcp_pm_subflow_chk_stale(msk, ssk); + } else { + subflow->stale_count = 0; + mptcp_subflow_set_active(subflow); + } } } diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index 2203cc2d2748..b94fbb483bf9 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -281,8 +281,9 @@ static int mptcp_userspace_pm_remove_id_zero_address(struct mptcp_sock *msk) return err; } -void mptcp_pm_remove_addr_entry(struct mptcp_sock *msk, - struct mptcp_pm_addr_entry *entry) +static void +mptcp_userspace_pm_remove_addr_entry(struct mptcp_sock *msk, + struct mptcp_pm_addr_entry *entry) { struct mptcp_rm_list alist = { .nr = 0 }; int anno_nr = 0; @@ -340,7 +341,7 @@ int mptcp_pm_nl_remove_doit(struct sk_buff *skb, struct genl_info *info) list_del_rcu(&match->list); spin_unlock_bh(&msk->pm.lock); - mptcp_pm_remove_addr_entry(msk, match); + mptcp_userspace_pm_remove_addr_entry(msk, match); release_sock(sk); diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 7c8180d8d5ef..b474d03620a7 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -242,6 +242,65 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) return false; } +/* "Inspired" from the TCP version; main difference: stop as soon as the MPTCP + * socket is under memory limit. + */ +static void mptcp_prune_ofo_queue(struct sock *sk, + const struct sk_buff *in_skb) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + struct rb_node *node, *prev; + bool pruned = false; + u64 mem; + + if (RB_EMPTY_ROOT(&msk->out_of_order_queue)) + return; + + node = &msk->ooo_last_skb->rbnode; + + do { + struct sk_buff *skb = rb_to_skb(node); + + /* Stop pruning if the incoming skb would land in OoO tail. */ + if (after64(MPTCP_SKB_CB(in_skb)->map_seq, + MPTCP_SKB_CB(skb)->map_seq)) + break; + + pruned = true; + prev = rb_prev(node); + rb_erase(node, &msk->out_of_order_queue); + mptcp_drop(sk, skb); + msk->ooo_last_skb = rb_to_skb(prev); + + mem = (unsigned int)sk_rmem_alloc_get(sk); + if (mem <= sk->sk_rcvbuf) + break; + + node = prev; + } while (node); + + if (pruned) + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOPRUNED); +} + +/* The stack can't drop packets for fallback socket at the msk level, or the + * stream will break. + */ +static bool mptcp_can_ingest(const struct sock *sk) +{ + return unlikely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || + __mptcp_check_fallback(mptcp_sk(sk)); +} + +static bool mptcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb) +{ + if (!mptcp_can_ingest(sk)) { + mptcp_prune_ofo_queue(sk, skb); + return mptcp_can_ingest(sk); + } + return true; +} + /* "inspired" by tcp_data_queue_ofo(), main differences: * - use mptcp seqs * - don't cope with sacks @@ -253,6 +312,12 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) u64 seq, end_seq, max_seq; struct sk_buff *skb1; + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return; + } + seq = MPTCP_SKB_CB(skb)->map_seq; end_seq = MPTCP_SKB_CB(skb)->end_seq; max_seq = atomic64_read(&msk->rcv_wnd_sent); @@ -389,6 +454,13 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) { /* in sequence */ +insert: + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return false; + } + msk->bytes_received += copy_len; WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len); tail = skb_peek_tail(&sk->sk_receive_queue); @@ -403,26 +475,20 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) return false; } - /* Completely old data? */ - if (!after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) { - MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA); - mptcp_drop(sk, skb); - return false; + /* Partial packet */ + if (after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) { + copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq; + MPTCP_SKB_CB(skb)->offset += msk->ack_seq - + MPTCP_SKB_CB(skb)->map_seq; + MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq - + MPTCP_SKB_CB(skb)->map_seq; + goto insert; } - /* Partial packet: map_seq < ack_seq < end_seq. - * Skip the already-acked bytes and enqueue the new data. - */ - copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq; - MPTCP_SKB_CB(skb)->offset += msk->ack_seq - MPTCP_SKB_CB(skb)->map_seq; - MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq - - MPTCP_SKB_CB(skb)->map_seq; - msk->bytes_received += copy_len; - WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len); - - skb_set_owner_r(skb, sk); - __skb_queue_tail(&sk->sk_receive_queue, skb); - return true; + /* Completely old data */ + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA); + mptcp_drop(sk, skb); + return false; } static void mptcp_stop_rtx_timer(struct sock *sk) @@ -515,17 +581,23 @@ static bool mptcp_pending_data_fin(struct sock *sk, u64 *seq) static void mptcp_set_datafin_timeout(struct sock *sk) { struct inet_connection_sock *icsk = inet_csk(sk); + u32 rto_min = READ_ONCE(icsk->icsk_rto_min); + u32 rto_max = READ_ONCE(icsk->icsk_rto_max); u32 retransmits; + /* The sysctls are validated independently: rto_min > rto_max is + * possible, guard against ilog2(0). + */ retransmits = min_t(u32, icsk->icsk_retransmits, - ilog2(TCP_RTO_MAX / TCP_RTO_MIN)); + ilog2(max_t(u32, rto_max / rto_min, 1))); - mptcp_sk(sk)->timer_ival = TCP_RTO_MIN << retransmits; + mptcp_sk(sk)->timer_ival = rto_min << retransmits; } static void __mptcp_set_timeout(struct sock *sk, long tout) { - mptcp_sk(sk)->timer_ival = tout > 0 ? tout : TCP_RTO_MIN; + mptcp_sk(sk)->timer_ival = tout > 0 ? tout : + READ_ONCE(inet_csk(sk)->icsk_rto_min); } static long mptcp_timeout_from_subflow(const struct mptcp_subflow_context *subflow) @@ -681,6 +753,7 @@ static void __mptcp_add_backlog(struct sock *sk, struct sk_buff *tail = NULL; struct sock *ssk = skb->sk; bool fragstolen; + u64 limit; int delta; if (unlikely(sk->sk_state == TCP_CLOSE)) { @@ -688,6 +761,16 @@ static void __mptcp_add_backlog(struct sock *sk, return; } + /* Similar additional allowance as plain TCP. */ + limit = READ_ONCE(sk->sk_rcvbuf); + limit += (limit >> 1) + 64 * 1024; + limit = min_t(u64, limit, UINT_MAX); + if (msk->backlog_len > limit && !__mptcp_check_fallback(msk)) { + __MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_BACKLOGDROP); + kfree_skb_reason(skb, SKB_DROP_REASON_SOCKET_BACKLOG); + return; + } + /* Try to coalesce with the last skb in our backlog */ if (!list_empty(&msk->backlog_list)) tail = list_last_entry(&msk->backlog_list, struct sk_buff, list); @@ -759,7 +842,7 @@ static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, mptcp_init_skb(ssk, skb, offset, len); - if (own_msk && sk_rmem_alloc_get(sk) < sk->sk_rcvbuf) { + if (own_msk) { mptcp_subflow_lend_fwdmem(subflow, skb); ret |= __mptcp_move_skb(sk, skb); } else { @@ -1142,13 +1225,6 @@ static void __mptcp_clean_una_wakeup(struct sock *sk) mptcp_write_space(sk); } -static void mptcp_clean_una_wakeup(struct sock *sk) -{ - mptcp_data_lock(sk); - __mptcp_clean_una_wakeup(sk); - mptcp_data_unlock(sk); -} - static void mptcp_enter_memory_pressure(struct sock *sk) { struct mptcp_subflow_context *subflow; @@ -2215,12 +2291,7 @@ static bool __mptcp_move_skbs(struct sock *sk, struct list_head *skbs, u32 *delt struct mptcp_sock *msk = mptcp_sk(sk); bool moved = false; - *delta = 0; while (1) { - /* If the msk recvbuf is full stop, don't drop */ - if (sk_rmem_alloc_get(sk) > sk->sk_rcvbuf) - break; - prefetch(skb->next); list_del(&skb->list); *delta += skb->truesize; @@ -2248,9 +2319,7 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs) DEBUG_NET_WARN_ON_ONCE(msk->backlog_unaccounted && sk->sk_socket && mem_cgroup_from_sk(sk)); - /* Don't spool the backlog if the rcvbuf is full. */ - if (list_empty(&msk->backlog_list) || - sk_rmem_alloc_get(sk) > sk->sk_rcvbuf) + if (list_empty(&msk->backlog_list)) return false; INIT_LIST_HEAD(skbs); @@ -2258,20 +2327,12 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs) return true; } -static void mptcp_backlog_spooled(struct sock *sk, u32 moved, - struct list_head *skbs) -{ - struct mptcp_sock *msk = mptcp_sk(sk); - - WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); - list_splice(skbs, &msk->backlog_list); -} - static bool mptcp_move_skbs(struct sock *sk) { + struct mptcp_sock *msk = mptcp_sk(sk); struct list_head skbs; bool enqueued = false; - u32 moved; + u32 moved = 0; mptcp_data_lock(sk); while (mptcp_can_spool_backlog(sk, &skbs)) { @@ -2279,8 +2340,8 @@ static bool mptcp_move_skbs(struct sock *sk) enqueued |= __mptcp_move_skbs(sk, &skbs, &moved); mptcp_data_lock(sk); - mptcp_backlog_spooled(sk, moved, &skbs); } + WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); mptcp_data_unlock(sk); if (enqueued && mptcp_epollin_ready(sk)) @@ -2469,7 +2530,6 @@ struct sock *mptcp_subflow_get_retrans(struct mptcp_sock *msk) /* still data outstanding at TCP level? skip this */ if (!tcp_rtx_and_write_queues_empty(ssk)) { - mptcp_pm_subflow_chk_stale(msk, ssk); min_stale_count = min_t(int, min_stale_count, subflow->stale_count); continue; } @@ -2791,44 +2851,22 @@ static void mptcp_check_fastclose(struct mptcp_sock *msk) sk_error_report(sk); } -static void __mptcp_retrans(struct sock *sk) +/* + * Retransmit the specified data fragment on all the selected subflows, + * starting from the specified sequence + */ +static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag, + u64 sent_seq) { struct mptcp_sendmsg_info info = { .data_lock_held = true, }; struct mptcp_sock *msk = mptcp_sk(sk); struct mptcp_subflow_context *subflow; - struct mptcp_data_frag *dfrag; struct sock *ssk; - int ret, err; - u16 len = 0; - - mptcp_clean_una_wakeup(sk); - - /* first check ssk: need to kick "stale" logic */ - err = mptcp_sched_get_retrans(msk); - dfrag = mptcp_rtx_head(sk); - if (!dfrag) { - if (mptcp_data_fin_enabled(msk)) { - struct inet_connection_sock *icsk = inet_csk(sk); - - WRITE_ONCE(icsk->icsk_retransmits, - icsk->icsk_retransmits + 1); - mptcp_set_datafin_timeout(sk); - mptcp_send_ack(msk); - - goto reset_timer; - } - - if (!mptcp_send_head(sk)) - goto clear_scheduled; - - goto reset_timer; - } - - if (err) - goto reset_timer; + int ret, len = 0; mptcp_for_each_subflow(msk, subflow) { if (READ_ONCE(subflow->scheduled)) { + u16 offset = sent_seq - dfrag->data_seq; u16 copied = 0; mptcp_subflow_set_scheduled(subflow, false); @@ -2838,7 +2876,7 @@ static void __mptcp_retrans(struct sock *sk) lock_sock(ssk); /* limit retransmission to the bytes already sent on some subflows */ - info.sent = 0; + info.sent = offset; info.limit = READ_ONCE(msk->csum_enabled) ? dfrag->data_len : dfrag->already_sent; @@ -2853,7 +2891,7 @@ static void __mptcp_retrans(struct sock *sk) !msk->allow_subflows) { spin_unlock_bh(&msk->fallback_lock); release_sock(ssk); - goto clear_scheduled; + return -1; } while (info.sent < info.limit) { @@ -2876,13 +2914,99 @@ static void __mptcp_retrans(struct sock *sk) release_sock(ssk); } } + return len; +} - msk->bytes_retrans += len; - dfrag->already_sent = max(dfrag->already_sent, len); +static void __mptcp_retrans(struct sock *sk) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + struct mptcp_subflow_context *subflow; + struct mptcp_data_frag *dfrag; + u64 retrans_seq, sent_seq; + bool need_retrans; + int err, len; - /* With csum enabled retransmission can send new data. */ - if (after64(dfrag->already_sent + dfrag->data_seq, msk->snd_nxt)) - WRITE_ONCE(msk->snd_nxt, dfrag->already_sent + dfrag->data_seq); + mptcp_pm_chk_stale(msk); + + /* Get an updated and consistent rtx queue status. */ + mptcp_data_lock(sk); + __mptcp_clean_una_wakeup(sk); + retrans_seq = msk->snd_una; + dfrag = mptcp_rtx_head(sk); + need_retrans = !!dfrag; + mptcp_data_unlock(sk); + + for (;;) { + bool already_acked; + + err = mptcp_sched_get_retrans(msk); + if (err) + break; + + /* `already_sent` can be 0 for `dfrag` belonging to the RTX + * queue due to __mptcp_retransmit_pending_data(). + */ + if (!dfrag || !dfrag->already_sent) + break; + + /* Can fail only in case of fallback. */ + len = __mptcp_push_retrans(sk, dfrag, retrans_seq); + if (len < 0) + goto clear_scheduled; + + retrans_seq += len; + msk->bytes_retrans += len; + dfrag->already_sent = max_t(u16, dfrag->already_sent, + retrans_seq - dfrag->data_seq); + + /* With csum enabled, retransmission can send new data. */ + sent_seq = dfrag->already_sent + dfrag->data_seq; + if (after64(sent_seq, msk->snd_nxt)) + WRITE_ONCE(msk->snd_nxt, sent_seq); + + /* Attempt the next fragment only if the current one is + * completely retransmitted. + */ + if (before64(retrans_seq, dfrag->data_seq + dfrag->data_len)) + break; + + dfrag = list_is_last(&dfrag->list, &msk->rtx_queue) ? + NULL : list_next_entry(dfrag, list); + if (!dfrag) + break; + + /* Incoming acks can move snd_una after the current dfrag + * across loop iterations, if so start again from RTX head. + */ + mptcp_data_lock(sk); + already_acked = !before64(msk->snd_una, dfrag->data_seq + + dfrag->already_sent); + if (already_acked) { + __mptcp_clean_una_wakeup(sk); + retrans_seq = msk->snd_una; + dfrag = mptcp_rtx_head(sk); + need_retrans = !!dfrag; + } else if (after64(msk->snd_una, retrans_seq)) { + retrans_seq = msk->snd_una; + } + mptcp_data_unlock(sk); + } + + /* Attempt data-fin retransmission only when the RTX queue is empty. */ + if (!need_retrans) { + if (mptcp_data_fin_enabled(msk)) { + struct inet_connection_sock *icsk = inet_csk(sk); + + WRITE_ONCE(icsk->icsk_retransmits, + icsk->icsk_retransmits + 1); + mptcp_set_datafin_timeout(sk); + mptcp_send_ack(msk); + goto reset_timer; + } + + if (!mptcp_send_head(sk)) + goto clear_scheduled; + } reset_timer: mptcp_check_and_set_pending(sk); @@ -3043,7 +3167,9 @@ static void mptcp_worker(struct work_struct *work) static void __mptcp_init_sock(struct sock *sk) { + struct inet_connection_sock *icsk = inet_csk(sk); struct mptcp_sock *msk = mptcp_sk(sk); + struct net *net = sock_net(sk); INIT_LIST_HEAD(&msk->conn_list); INIT_LIST_HEAD(&msk->join_list); @@ -3052,7 +3178,13 @@ static void __mptcp_init_sock(struct sock *sk) INIT_WORK(&msk->work, mptcp_worker); msk->out_of_order_queue = RB_ROOT; msk->first_pending = NULL; - msk->timer_ival = TCP_RTO_MIN; + + /* msk does not go through tcp_init_sock(); seed RTO bounds. */ + icsk->icsk_rto_min = + usecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_min_us)); + icsk->icsk_rto_max = + msecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_max_ms)); + msk->timer_ival = icsk->icsk_rto_min; msk->scaling_ratio = TCP_DEFAULT_SCALING_RATIO; msk->backlog_len = 0; mptcp_init_rtt_est(msk); @@ -3676,12 +3808,12 @@ static void mptcp_release_cb(struct sock *sk) __must_hold(&sk->sk_lock.slock) { struct mptcp_sock *msk = mptcp_sk(sk); + u32 moved = 0; for (;;) { unsigned long flags = (msk->cb_flags & MPTCP_FLAGS_PROCESS_CTX_NEED); struct list_head join_list, skbs; bool spool_bl; - u32 moved; spool_bl = mptcp_can_spool_backlog(sk, &skbs); if (!flags && !spool_bl) @@ -3714,9 +3846,9 @@ static void mptcp_release_cb(struct sock *sk) cond_resched(); spin_lock_bh(&sk->sk_lock.slock); - if (spool_bl) - mptcp_backlog_spooled(sk, moved, &skbs); } + if (moved) + WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); if (__test_and_clear_bit(MPTCP_CLEAN_UNA, &msk->cb_flags)) __mptcp_clean_una_wakeup(sk); @@ -3870,6 +4002,7 @@ bool mptcp_finish_join(struct sock *ssk) /* mptcp socket already closing? */ if (!mptcp_is_fully_established(parent)) { + MPTCP_INC_STATS(sock_net(parent), MPTCP_MIB_MPJOINNOTESTABLISHED); subflow->reset_reason = MPTCP_RST_EMPTCP; return false; } diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 1b80f2d6ec5a..06a107d4e839 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -580,12 +580,11 @@ struct mptcp_subflow_context { remote_key_valid : 1, /* received the peer key from */ disposable : 1, /* ctx can be free at ulp release time */ closing : 1, /* must not pass rx data to msk anymore */ - stale : 1, /* unable to snd/rcv data, do not use for xmit */ valid_csum_seen : 1, /* at least one csum validated */ is_mptfo : 1, /* subflow is doing TFO */ close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ - __unused : 8; + __unused : 9; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ @@ -604,7 +603,11 @@ struct mptcp_subflow_context { u8 reset_seen:1; u8 reset_transient:1; u8 reset_reason:4; - u8 stale_count; + u8 stale_count; /* Protected by the msk socket lock */ + u8 stale; /* Protected by the msk socket lock, + * if set the subflow is unable to snd/rcv + * data, the schedule should skip it + */ u32 subflow_id; @@ -1103,7 +1106,7 @@ int mptcp_pm_parse_entry(struct nlattr *attr, struct genl_info *info, bool mptcp_pm_addr_families_match(const struct sock *sk, const struct mptcp_addr_info *loc, const struct mptcp_addr_info *rem); -void mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk); +void mptcp_pm_chk_stale(const struct mptcp_sock *msk); void mptcp_pm_new_connection(struct mptcp_sock *msk, const struct sock *ssk, int server_side); void mptcp_pm_fully_established(struct mptcp_sock *msk, const struct sock *ssk); bool mptcp_pm_allow_new_subflow(struct mptcp_sock *msk); @@ -1146,8 +1149,6 @@ int mptcp_pm_announce_addr(struct mptcp_sock *msk, const struct mptcp_addr_info *addr, bool echo); int mptcp_pm_remove_addr(struct mptcp_sock *msk, const struct mptcp_rm_list *rm_list); -void mptcp_pm_remove_addr_entry(struct mptcp_sock *msk, - struct mptcp_pm_addr_entry *entry); /* the default path manager, used in mptcp_pm_unregister */ extern struct mptcp_pm_ops mptcp_pm_kernel; @@ -1251,7 +1252,8 @@ u8 mptcp_pm_get_limit_extra_subflows(const struct mptcp_sock *msk); /* called under PM lock */ static inline void __mptcp_pm_close_subflow(struct mptcp_sock *msk) { - if (--msk->pm.extra_subflows < mptcp_pm_get_limit_extra_subflows(msk)) + if (!WARN_ON_ONCE(msk->pm.extra_subflows == 0) && + --msk->pm.extra_subflows < mptcp_pm_get_limit_extra_subflows(msk)) WRITE_ONCE(msk->pm.accept_subflow, true); } diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index e1f20ff8fdb4..af81ad5e699d 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -96,6 +96,7 @@ static struct mptcp_sock *subflow_token_join_request(struct request_sock *req) local_id = mptcp_pm_get_local_id(msk, (struct sock_common *)req); if (local_id < 0) { + SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPJOINNOIDFOUND); sock_put((struct sock *)msk); return NULL; } @@ -160,6 +161,7 @@ static int subflow_check_req(struct request_sock *req, * TCP option space. */ if (rcu_access_pointer(tcp_sk(sk_listener)->md5sig_info)) { + MPTCP_INC_STATS(sock_net(sk_listener), MPTCP_MIB_MD5SIGRESET); subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP); return -EINVAL; } @@ -563,6 +565,7 @@ static void subflow_finish_connect(struct sock *sk, const struct sk_buff *skb) u8 hmac[SHA256_DIGEST_SIZE]; if (!(mp_opt.suboptions & OPTION_MPTCP_MPJ_SYNACK)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPJOINSYNACKNOMPJOIN); subflow->reset_reason = MPTCP_RST_EMPTCP; goto do_reset; } @@ -865,6 +868,12 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk, */ if (!ctx || fallback) { if (fallback_is_fatal) { + if (!ctx) + MPTCP_INC_STATS(sock_net(sk), + MPTCP_MIB_MPJOINACKNOCTX); + else + MPTCP_INC_STATS(sock_net(sk), + MPTCP_MIB_MPJOINACKNOMPJOIN); subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP); goto dispose_child; } @@ -1416,6 +1425,7 @@ static bool subflow_check_data_avail(struct sock *ssk) * subflow_error_report() will introduce the appropriate barriers */ subflow->reset_transient = 0; + MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_DSSRESET); subflow->reset_reason = status == MAPPING_NODSS ? MPTCP_RST_EMIDDLEBOX : MPTCP_RST_EMPTCP; diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index eb806813292a..ba0957798bad 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -176,7 +176,7 @@ void ip_vs_rht_rcu_free(struct rcu_head *head) struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) { - struct ip_vs_rht *t = kzalloc(sizeof(*t), GFP_KERNEL); + struct ip_vs_rht *t = kzalloc_obj(*t); int i; if (!t) @@ -186,7 +186,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) scounts = min(scounts, buckets); scounts = min(scounts, ml); - t->seqc = kvmalloc_array(scounts, sizeof(*t->seqc), GFP_KERNEL); + t->seqc = kvmalloc_objs(*t->seqc, scounts); if (!t->seqc) goto err; for (i = 0; i < scounts; i++) @@ -194,8 +194,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) if (locks) { locks = min(locks, scounts); - t->lock = kvmalloc_array(locks, sizeof(*t->lock), - GFP_KERNEL); + t->lock = kvmalloc_objs(*t->lock, locks); if (!t->lock) goto err; for (i = 0; i < locks; i++) @@ -203,7 +202,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) } } - t->buckets = kvmalloc_array(buckets, sizeof(*t->buckets), GFP_KERNEL); + t->buckets = kvmalloc_objs(*t->buckets, buckets); if (!t->buckets) goto err; for (i = 0; i < buckets; i++) diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 8f9a8e491ad6..4c1c739446b7 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -235,7 +235,7 @@ static void defense_work_handler(struct work_struct *work) update_defense_level(ipvs); if (atomic_read(&ipvs->dropentry)) ip_vs_random_dropentry(ipvs); - queue_delayed_work(system_long_wq, &ipvs->defense_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work, DEFENSE_TIMER_PERIOD); } #endif @@ -290,7 +290,7 @@ static void est_reload_work_handler(struct work_struct *work) atomic_set(&ipvs->est_genid_done, genid); if (repeat) - queue_delayed_work(system_long_wq, &ipvs->est_reload_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work, delay); unlock: @@ -5177,7 +5177,7 @@ static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs) goto err; /* Schedule defense work */ - queue_delayed_work(system_long_wq, &ipvs->defense_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work, DEFENSE_TIMER_PERIOD); return 0; diff --git a/net/netfilter/ipvs/ip_vs_est.c b/net/netfilter/ipvs/ip_vs_est.c index 05a216a47b45..9a3727a1c40b 100644 --- a/net/netfilter/ipvs/ip_vs_est.c +++ b/net/netfilter/ipvs/ip_vs_est.c @@ -246,7 +246,7 @@ void ip_vs_est_reload_start(struct netns_ipvs *ipvs, bool restart) /* Bump the kthread configuration genid if stopping is requested */ if (restart) atomic_inc(&ipvs->est_genid); - queue_delayed_work(system_long_wq, &ipvs->est_reload_work, 0); + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work, 0); } /* Start kthread task with current configuration */ diff --git a/net/netfilter/ipvs/ip_vs_nfct.c b/net/netfilter/ipvs/ip_vs_nfct.c index 81974f69e5bb..347185fd0c8c 100644 --- a/net/netfilter/ipvs/ip_vs_nfct.c +++ b/net/netfilter/ipvs/ip_vs_nfct.c @@ -208,7 +208,7 @@ static void ip_vs_nfct_expect_callback(struct nf_conn *ct, * Use port 0 to expect connection from any port. */ void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs) { struct nf_conntrack_expect *exp; diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 04450a48f01a..ebc6b697ae51 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1098,7 +1098,7 @@ ipvs_gue_encap(struct net *net, struct sk_buff *skb, dport = cp->dest->tun_port; udph->dest = dport; udph->source = sport; - udph->len = htons(skb->len); + udp_set_len(udph, skb->len); udph->check = 0; *next_protocol = IPPROTO_UDP; diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index e9ea6d9466e7..85487f92af50 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -158,6 +158,8 @@ static bool get_ct_or_tuple_from_skb(struct net *net, return true; found_ct = nf_ct_tuplehash_to_ctrack(h); + *tuple = found_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + *zone = nf_ct_zone(found_ct); *refcounted = true; *ct = found_ct; diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index ddafbdfc96dc..14ae660491f3 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -89,7 +89,7 @@ static int amanda_help(struct sk_buff *skb, struct nf_conntrack_tuple *tuple; unsigned int dataoff, start, stop, off, i; char pbuf[sizeof("65535")], *tmp; - u_int16_t len; + u16 len; __be16 port; int ret = NF_ACCEPT; nf_nat_amanda_hook_fn *nf_nat_amanda; @@ -151,7 +151,7 @@ static int amanda_help(struct sk_buff *skb, nf_nat_amanda = rcu_dereference(nf_nat_amanda_hook); if (nf_nat_amanda && ct->status & IPS_NAT_MASK) - ret = nf_nat_amanda(skb, ctinfo, protoff, + ret = nf_nat_amanda(skb, ct, ctinfo, protoff, off - dataoff, len, exp); else if (nf_ct_expect_related(exp, 0) != 0) { nf_ct_helper_log(skb, ct, "cannot add expectation"); @@ -199,10 +199,10 @@ static int __init nf_conntrack_amanda_init(void) } nf_ct_helper_init(&amanda_helper[0], AF_INET, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); nf_ct_helper_init(&amanda_helper[1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); ret = nf_conntrack_helpers_register(amanda_helper, diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c index bf78828c7549..0922e30b6ab0 100644 --- a/net/netfilter/nf_conntrack_broadcast.c +++ b/net/netfilter/nf_conntrack_broadcast.c @@ -14,6 +14,7 @@ #include #include #include +#include int nf_conntrack_broadcast_help(struct sk_buff *skb, struct nf_conn *ct, @@ -27,6 +28,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, struct rtable *rt = skb_rtable(skb); struct in_device *in_dev; struct nf_conn_help *help = nfct_help(ct); + struct nf_conntrack_ecache *ecache; __be32 mask = 0; if (!help) @@ -66,8 +68,6 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple; helper = rcu_dereference(help->helper); - if (helper) - exp->tuple.src.u.udp.port = helper->tuple.src.u.udp.port; exp->mask.src.u3.ip = mask; exp->mask.src.u.udp.port = htons(0xFFFF); @@ -81,6 +81,10 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, #ifdef CONFIG_NF_CONNTRACK_ZONES exp->zone = ct->zone; #endif + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + nf_ct_expect_related(exp, 0); nf_ct_expect_put(exp); diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 784bd1d7a9bf..d0d9e5ea84a0 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -1031,7 +1031,7 @@ static int __nf_ct_resolve_clash(struct sk_buff *skb, nf_conntrack_get(&ct->ct_general); nf_ct_acct_merge(ct, ctinfo, loser_ct); - nf_ct_put(loser_ct); + nf_reset_ct(skb); nf_ct_set(skb, ct, ctinfo); NF_CT_STAT_INC(net, clash_resolve); diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index cc8d8e85169f..fb731ee235d8 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -245,7 +245,6 @@ void nf_ct_expect_event_report(enum ip_conntrack_expect_events event, { struct net *net = nf_ct_exp_net(exp); struct nf_ct_event_notifier *notify; - struct nf_conntrack_ecache *e; lockdep_nfct_expect_lock_held(); @@ -254,11 +253,7 @@ void nf_ct_expect_event_report(enum ip_conntrack_expect_events event, if (!notify) goto out_unlock; - e = nf_ct_ecache_find(exp->master); - if (!e) - goto out_unlock; - - if (e->expmask & (1 << event)) { + if (exp->event_mask & (1 << event)) { struct nf_exp_event item = { .exp = exp, .portid = portid, diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 8a3b9e33e94f..f1f0c582db5d 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -330,6 +330,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, struct nf_conntrack_helper *helper = NULL; struct nf_conn *ct = exp->master; struct net *net = read_pnet(&ct->ct_net); + struct nf_conntrack_ecache *ecache; struct nf_conn_help *help; int len; @@ -342,6 +343,10 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, exp->class = class; exp->expectfn = NULL; + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + help = nfct_help(ct); if (help) helper = rcu_dereference(help->helper); @@ -523,6 +528,12 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, int ret; spin_lock_bh(&nf_conntrack_expect_lock); + if (expect->flags & NF_CT_EXPECT_DEAD) { + DEBUG_NET_WARN_ON_ONCE(1); + ret = -EINVAL; + goto out; + } + master_help = nfct_help(expect->master); if (!master_help) { ret = -ESHUTDOWN; diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index 0847f845613d..f4fe13fd0e70 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -35,11 +35,6 @@ MODULE_ALIAS("ip_conntrack_ftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); static DEFINE_SPINLOCK(nf_ftp_lock); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - static bool loose; module_param(loose, bool, 0600); @@ -520,7 +515,7 @@ static int help(struct sk_buff *skb, * (possibly changed) expectation itself. */ nf_nat_ftp = rcu_dereference(nf_nat_ftp_hook); if (nf_nat_ftp && ct->status & IPS_NAT_MASK) - ret = nf_nat_ftp(skb, ctinfo, search[dir][i].ftptype, + ret = nf_nat_ftp(skb, ct, ctinfo, search[dir][i].ftptype, protoff, matchoff, matchlen, exp); else { /* Can't expect this? Best to drop packet now. */ @@ -560,8 +555,8 @@ static int nf_ct_ftp_from_nlattr(struct nlattr *attr, struct nf_conn *ct) return 0; } -static struct nf_conntrack_helper ftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *ftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper ftp __read_mostly; +static struct nf_conntrack_helper *ftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy ftp_exp_policy = { .max_expected = 1, @@ -570,32 +565,23 @@ static const struct nf_conntrack_expect_policy ftp_exp_policy = { static void __exit nf_conntrack_ftp_fini(void) { - nf_conntrack_helpers_unregister(ftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(ftp_ptr); } static int __init nf_conntrack_ftp_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master)); - if (ports_c == 0) - ports[ports_c++] = FTP_PORT; - /* FIXME should be configurable whether IPv4 and IPv6 FTP connections are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&ftp[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - nf_ct_helper_init(&ftp[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - } + nf_ct_helper_init(&ftp, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &ftp_exp_policy, 0, help, + nf_ct_ftp_from_nlattr, THIS_MODULE); - ret = nf_conntrack_helpers_register(ftp, ports_c * 2, ftp_ptr); + ret = nf_conntrack_helper_register(&ftp, &ftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 24931e379985..4cb1665bba02 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -671,7 +671,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct, static int callforward_do_filter(struct net *net, const union nf_inet_addr *src, const union nf_inet_addr *dst, - u_int8_t family) + u8 family) { int ret = 0; @@ -1713,19 +1713,19 @@ static int __init h323_helper_init(void) int ret; nf_ct_helper_init(&nf_conntrack_helper_ras[0], AF_INET, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_ras[1], AF_INET6, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_h245, AF_UNSPEC, IPPROTO_UDP, - "H.245", 0, 0, 0, + "H.245", &h245_exp_policy, 0, h245_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[0], AF_INET, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[1], AF_INET6, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); ret = nf_conntrack_helper_register(&nf_conntrack_helper_h245, diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 500509b17663..c30ae3f203be 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -40,12 +40,16 @@ static unsigned int nf_ct_helper_count __read_mostly; static DEFINE_MUTEX(nf_ct_nat_helpers_mutex); static struct list_head nf_ct_nat_helpers __read_mostly; -/* Stupid hash, but collision free for the default registrations of the - * helpers currently in the kernel. */ -static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple) +static unsigned int helper_hash(const char *name, u8 protonum) { - return (((tuple->src.l3num << 8) | tuple->dst.protonum) ^ - (__force __u16)tuple->src.u.all) % nf_ct_helper_hsize; + static u32 seed; + u32 initval; + + get_random_once(&seed, sizeof(seed)); + + initval = seed ^ protonum; + + return jhash(name, strlen(name), initval) % nf_ct_helper_hsize; } struct nf_conntrack_helper * @@ -54,18 +58,18 @@ __nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum) struct nf_conntrack_helper *h; unsigned int i; - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { - if (strcmp(h->name, name)) - continue; + if (!nf_ct_helper_hash) + return NULL; - if (h->tuple.src.l3num != NFPROTO_UNSPEC && - h->tuple.src.l3num != l3num) - continue; + i = helper_hash(name, protonum); - if (h->tuple.dst.protonum == protonum) - return h; - } + hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { + if (strcmp(h->name, name)) + continue; + if (h->nfproto != NFPROTO_UNSPEC && h->nfproto != l3num) + continue; + if (h->l4proto == protonum) + return h; } return NULL; } @@ -363,9 +367,8 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_log); int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) { - struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; - unsigned int h = helper_hash(&me->tuple); struct nf_conntrack_helper *cur; + unsigned int h; int ret = 0, i; BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); @@ -382,29 +385,18 @@ int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) return -EINVAL; } + h = helper_hash(me->name, me->l4proto); mutex_lock(&nf_ct_helper_mutex); - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[i], hnode) { - if (!strcmp(cur->name, me->name) && - (cur->tuple.src.l3num == NFPROTO_UNSPEC || - cur->tuple.src.l3num == me->tuple.src.l3num) && - cur->tuple.dst.protonum == me->tuple.dst.protonum) { - ret = -EBUSY; - goto out; - } + hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { + if (!strcmp(cur->name, me->name) && + (cur->nfproto == NFPROTO_UNSPEC || + cur->nfproto == me->nfproto) && + cur->l4proto == me->l4proto) { + ret = -EBUSY; + goto out; } } - /* avoid unpredictable behaviour for auto_assign_helper */ - if (!(me->flags & NF_CT_HELPER_F_USERSPACE)) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { - if (nf_ct_tuple_src_mask_cmp(&cur->tuple, &me->tuple, - &mask)) { - ret = -EBUSY; - goto out; - } - } - } refcount_set(&me->ct_refcnt, 1); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); nf_ct_helper_count++; @@ -456,6 +448,15 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) return this == me; } +void nf_conntrack_helper_release(struct nf_conntrack_helper *me) +{ + nf_ct_expect_iterate_destroy(expect_iter_me, me); + + if (refcount_dec_and_test(&me->ct_refcnt)) + kfree_rcu(me, rcu); +} +EXPORT_SYMBOL_GPL(nf_conntrack_helper_release); + void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) { mutex_lock(&nf_ct_helper_mutex); @@ -471,16 +472,12 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) */ synchronize_rcu(); - nf_ct_expect_iterate_destroy(expect_iter_me, me); - - if (refcount_dec_and_test(&me->ct_refcnt)) - kfree_rcu(me, rcu); + nf_conntrack_helper_release(me); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, + u8 l3num, u16 protonum, const char *name, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, @@ -492,9 +489,8 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, { memset(helper, 0, sizeof(*helper)); - helper->tuple.src.l3num = l3num; - helper->tuple.dst.protonum = protonum; - helper->tuple.src.u.all = htons(spec_port); + helper->nfproto = l3num; + helper->l4proto = protonum; rcu_assign_pointer(helper->help, help); helper->from_nlattr = from_nlattr; @@ -502,10 +498,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, snprintf(helper->nat_mod_name, sizeof(helper->nat_mod_name), NF_NAT_HELPER_PREFIX "%s", name); - if (spec_port == default_port) - snprintf(helper->name, sizeof(helper->name), "%s", name); - else - snprintf(helper->name, sizeof(helper->name), "%s-%u", name, id); + snprintf(helper->name, sizeof(helper->name), "%s", name); if (WARN_ON_ONCE(expect_class_max >= NF_CT_MAX_EXPECT_CLASSES)) return; diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 193ab34db795..92360963757a 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -21,9 +21,6 @@ #include #include -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; static unsigned int max_dcc_channels = 8; static unsigned int dcc_timeout __read_mostly = 300; /* This is slow, but it's simple. --RR */ @@ -42,8 +39,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_irc"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of IRC servers"); module_param(max_dcc_channels, uint, 0400); MODULE_PARM_DESC(max_dcc_channels, "max number of expected DCC channels per " "IRC session"); @@ -236,7 +231,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, nf_nat_irc = rcu_dereference(nf_nat_irc_hook); if (nf_nat_irc && ct->status & IPS_NAT_MASK) - ret = nf_nat_irc(skb, ctinfo, protoff, + ret = nf_nat_irc(skb, ct, ctinfo, protoff, addr_beg_p - ib_ptr, addr_end_p - addr_beg_p, exp); @@ -254,13 +249,13 @@ static int help(struct sk_buff *skb, unsigned int protoff, return ret; } -static struct nf_conntrack_helper irc[MAX_PORTS] __read_mostly; -static struct nf_conntrack_helper *irc_ptr[MAX_PORTS] __read_mostly; +static struct nf_conntrack_helper irc __read_mostly; +static struct nf_conntrack_helper *irc_ptr __read_mostly; static struct nf_conntrack_expect_policy irc_exp_policy; static int __init nf_conntrack_irc_init(void) { - int i, ret; + int ret; nf_conntrack_helper_deprecated(HELPER_NAME); @@ -282,17 +277,11 @@ static int __init nf_conntrack_irc_init(void) if (!irc_buffer) return -ENOMEM; - /* If no port given, default to standard irc port */ - if (ports_c == 0) - ports[ports_c++] = IRC_PORT; + nf_ct_helper_init(&irc, AF_INET, IPPROTO_TCP, HELPER_NAME, + &irc_exp_policy, + 0, help, NULL, THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&irc[i], AF_INET, IPPROTO_TCP, HELPER_NAME, - IRC_PORT, ports[i], i, &irc_exp_policy, - 0, help, NULL, THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(&irc[0], ports_c, irc_ptr); + ret = nf_conntrack_helper_register(&irc, &irc_ptr); if (ret) { pr_err("failed to register helpers\n"); kfree(irc_buffer); @@ -304,7 +293,7 @@ static int __init nf_conntrack_irc_init(void) static void __exit nf_conntrack_irc_fini(void) { - nf_conntrack_helpers_unregister(irc_ptr, ports_c); + nf_conntrack_helper_unregister(irc_ptr); kfree(irc_buffer); } diff --git a/net/netfilter/nf_conntrack_netbios_ns.c b/net/netfilter/nf_conntrack_netbios_ns.c index 89d1cf7d6512..caa2b101fa9e 100644 --- a/net/netfilter/nf_conntrack_netbios_ns.c +++ b/net/netfilter/nf_conntrack_netbios_ns.c @@ -21,7 +21,6 @@ #include #define HELPER_NAME "netbios-ns" -#define NMBD_PORT 137 MODULE_AUTHOR("Patrick McHardy "); MODULE_DESCRIPTION("NetBIOS name service broadcast connection tracking helper"); @@ -54,7 +53,6 @@ static int __init nf_conntrack_netbios_ns_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, HELPER_NAME, - NMBD_PORT, NMBD_PORT, NMBD_PORT, &exp_policy, 0, netbios_ns_help, NULL, THIS_MODULE); return nf_conntrack_helper_register(&helper, &helper_ptr); diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 31cbb1b55b9e..fc3f60099af3 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3524,6 +3524,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, { struct net *net = read_pnet(&ct->ct_net); struct nf_conntrack_helper *helper; + struct nf_conntrack_ecache *ecache; struct nf_conntrack_expect *exp; struct nf_conn_help *help; u32 class = 0; @@ -3575,6 +3576,10 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, exp->mask.src.u3 = mask->src.u3; exp->mask.src.u.all = mask->src.u.all; + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + if (cda[CTA_EXPECT_NAT]) { err = ctnetlink_parse_expect_nat(cda[CTA_EXPECT_NAT], exp, nf_ct_l3num(ct)); diff --git a/net/netfilter/nf_conntrack_ovs.c b/net/netfilter/nf_conntrack_ovs.c index 49d1511e9921..b4085af3ad1c 100644 --- a/net/netfilter/nf_conntrack_ovs.c +++ b/net/netfilter/nf_conntrack_ovs.c @@ -31,8 +31,8 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, if (!helper) return NF_ACCEPT; - if (helper->tuple.src.l3num != NFPROTO_UNSPEC && - helper->tuple.src.l3num != proto) + if (helper->nfproto != NFPROTO_UNSPEC && + helper->nfproto != proto) return NF_ACCEPT; switch (proto) { @@ -60,7 +60,7 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, return NF_DROP; } - if (helper->tuple.dst.protonum != proto) + if (helper->l4proto != proto) return NF_ACCEPT; helper_cb = rcu_dereference(helper->help); diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index 80fc14c87ddc..cbf32a3cb1f6 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -540,7 +540,7 @@ static int __init nf_conntrack_pptp_init(void) NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_pptp_master)); nf_ct_helper_init(&pptp, AF_INET, IPPROTO_TCP, - "pptp", PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, + "pptp", &pptp_exp_policy, 0, conntrack_pptp_help, NULL, THIS_MODULE); pptp.destroy = gre_pptp_destroy_siblings; diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c index 30b970e2ade5..ad6f1986d52a 100644 --- a/net/netfilter/nf_conntrack_proto_tcp.c +++ b/net/netfilter/nf_conntrack_proto_tcp.c @@ -1325,8 +1325,10 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct, if (ct->proto.tcp.retrans >= tn->tcp_max_retrans && timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS]) timeout = timeouts[TCP_CONNTRACK_RETRANS]; - else if (unlikely(index == TCP_RST_SET)) - timeout = timeouts[TCP_CONNTRACK_CLOSE]; + else if (unlikely(index == TCP_RST_SET && + new_state == TCP_CONNTRACK_ESTABLISHED) && + timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK]) + timeout = timeouts[TCP_CONNTRACK_UNACK]; else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) & IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED && timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK]) diff --git a/net/netfilter/nf_conntrack_proto_udp.c b/net/netfilter/nf_conntrack_proto_udp.c index cc9b7e5e1935..a7edfefd6fd1 100644 --- a/net/netfilter/nf_conntrack_proto_udp.c +++ b/net/netfilter/nf_conntrack_proto_udp.c @@ -41,11 +41,22 @@ static void udp_error_log(const struct sk_buff *skb, nf_l4proto_log_invalid(skb, state, IPPROTO_UDP, "%s", msg); } +static bool udp_validate_len(struct sk_buff *skb, + const struct udphdr *hdr, + unsigned int dataoff) +{ + unsigned int udplen = udp_get_len(skb, hdr, dataoff); + unsigned int skblen = skb->len - dataoff; + + if (udplen > skblen || udplen < sizeof(*hdr)) + return false; + return true; +} + static bool udp_error(struct sk_buff *skb, unsigned int dataoff, const struct nf_hook_state *state) { - unsigned int udplen = skb->len - dataoff; const struct udphdr *hdr; struct udphdr _hdr; @@ -57,7 +68,7 @@ static bool udp_error(struct sk_buff *skb, } /* Truncated/malformed packets */ - if (ntohs(hdr->len) > udplen || ntohs(hdr->len) < sizeof(*hdr)) { + if (!udp_validate_len(skb, hdr, dataoff)) { udp_error_log(skb, state, "truncated/malformed packet"); return true; } diff --git a/net/netfilter/nf_conntrack_sane.c b/net/netfilter/nf_conntrack_sane.c index 39085acf7a71..a0658f69d78f 100644 --- a/net/netfilter/nf_conntrack_sane.c +++ b/net/netfilter/nf_conntrack_sane.c @@ -34,11 +34,6 @@ MODULE_AUTHOR("Michal Schmidt "); MODULE_DESCRIPTION("SANE connection tracking helper"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - struct sane_request { __be32 RPC_code; #define SANE_NET_START 7 /* RPC code */ @@ -169,8 +164,8 @@ static int help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper sane[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *sane_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper sane __read_mostly; +static struct nf_conntrack_helper *sane_ptr __read_mostly; static const struct nf_conntrack_expect_policy sane_exp_policy = { .max_expected = 1, @@ -179,32 +174,21 @@ static const struct nf_conntrack_expect_policy sane_exp_policy = { static void __exit nf_conntrack_sane_fini(void) { - nf_conntrack_helpers_unregister(sane_ptr, ports_c * 2); + nf_conntrack_helper_unregister(sane_ptr); } static int __init nf_conntrack_sane_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sane_master)); - if (ports_c == 0) - ports[ports_c++] = SANE_PORT; + nf_ct_helper_init(&sane, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &sane_exp_policy, 0, help, NULL, + THIS_MODULE); - /* FIXME should be configurable whether IPv4 and IPv6 connections - are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sane[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - nf_ct_helper_init(&sane[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(sane, ports_c * 2, sane_ptr); + ret = nf_conntrack_helper_register(&sane, &sane_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index e4a70d1d77b0..3ccf34fc1c53 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -35,12 +35,6 @@ MODULE_DESCRIPTION("SIP connection tracking helper"); MODULE_ALIAS("ip_conntrack_sip"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of SIP servers"); - static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT; module_param(sip_timeout, uint, 0600); MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); @@ -1767,8 +1761,8 @@ static int sip_help_udp(struct sk_buff *skb, unsigned int protoff, return process_sip_msg(skb, ct, protoff, dataoff, &dptr, &datalen); } -static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly; -static struct nf_conntrack_helper *sip_ptr[MAX_PORTS * 4] __read_mostly; +static struct nf_conntrack_helper sip[2] __read_mostly; +static struct nf_conntrack_helper *sip_ptr[2] __read_mostly; static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = { [SIP_EXPECT_SIGNALLING] = { @@ -1795,38 +1789,25 @@ static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1 static void __exit nf_conntrack_sip_fini(void) { - nf_conntrack_helpers_unregister(sip_ptr, ports_c * 4); + nf_conntrack_helpers_unregister(sip_ptr, 2); } static int __init nf_conntrack_sip_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sip_master)); - if (ports_c == 0) - ports[ports_c++] = SIP_PORT; + nf_ct_helper_init(&sip[0], NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, + NULL, THIS_MODULE); + nf_ct_helper_init(&sip[1], NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, + NULL, THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sip[4 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 1], AF_INET, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 2], AF_INET6, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 3], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(sip, ports_c * 4, sip_ptr); + ret = nf_conntrack_helpers_register(sip, 2, sip_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_snmp.c b/net/netfilter/nf_conntrack_snmp.c index b6fce5703fce..109986d5d55e 100644 --- a/net/netfilter/nf_conntrack_snmp.c +++ b/net/netfilter/nf_conntrack_snmp.c @@ -14,8 +14,6 @@ #include #include -#define SNMP_PORT 161 - MODULE_AUTHOR("Jiri Olsa "); MODULE_DESCRIPTION("SNMP service broadcast connection tracking helper"); MODULE_LICENSE("GPL"); @@ -55,7 +53,7 @@ static int __init nf_conntrack_snmp_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, - "snmp", SNMP_PORT, SNMP_PORT, SNMP_PORT, + "snmp", &exp_policy, 0, snmp_conntrack_help, NULL, THIS_MODULE); diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c index be2953c7d702..f4f2d82192d5 100644 --- a/net/netfilter/nf_conntrack_standalone.c +++ b/net/netfilter/nf_conntrack_standalone.c @@ -639,7 +639,7 @@ enum nf_ct_sysctl_index { NF_SYSCTL_CT_LAST_SYSCTL, }; -static struct ctl_table nf_ct_sysctl_table[] = { +static const struct ctl_table nf_ct_sysctl_table[] = { [NF_SYSCTL_CT_MAX] = { .procname = "nf_conntrack_max", .data = &nf_conntrack_max, diff --git a/net/netfilter/nf_conntrack_tftp.c b/net/netfilter/nf_conntrack_tftp.c index 4393c435aa35..e672d74a6817 100644 --- a/net/netfilter/nf_conntrack_tftp.c +++ b/net/netfilter/nf_conntrack_tftp.c @@ -26,12 +26,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_tftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "Port numbers of TFTP servers"); - nf_nat_tftp_hook_fn __rcu *nf_nat_tftp_hook __read_mostly; EXPORT_SYMBOL_GPL(nf_nat_tftp_hook); @@ -75,7 +69,7 @@ static int tftp_help(struct sk_buff *skb, nf_nat_tftp = rcu_dereference(nf_nat_tftp_hook); if (nf_nat_tftp && ct->status & IPS_NAT_MASK) - ret = nf_nat_tftp(skb, ctinfo, exp); + ret = nf_nat_tftp(skb, ct, ctinfo, exp); else if (nf_ct_expect_related(exp, 0) != 0) { nf_ct_helper_log(skb, ct, "cannot add expectation"); ret = NF_DROP; @@ -95,8 +89,8 @@ static int tftp_help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper tftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *tftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper tftp __read_mostly; +static struct nf_conntrack_helper *tftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy tftp_exp_policy = { .max_expected = 1, @@ -105,30 +99,21 @@ static const struct nf_conntrack_expect_policy tftp_exp_policy = { static void __exit nf_conntrack_tftp_fini(void) { - nf_conntrack_helpers_unregister(tftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(tftp_ptr); } static int __init nf_conntrack_tftp_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(0); - if (ports_c == 0) - ports[ports_c++] = TFTP_PORT; + nf_ct_helper_init(&tftp, NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + &tftp_exp_policy, 0, tftp_help, NULL, + THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&tftp[2 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - nf_ct_helper_init(&tftp[2 * i + 1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(tftp, ports_c * 2, tftp_ptr); + ret = nf_conntrack_helper_register(&tftp, &tftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 8b1165f2b5a4..03241d4bfd5e 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -572,6 +572,8 @@ static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table, if (nf_flow_has_expired(flow) || nf_ct_is_dying(flow->ct) || + !nf_flow_dst_check(&flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple) || + !nf_flow_dst_check(&flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple) || nf_flow_custom_gc(flow_table, flow)) { flow_offload_teardown(flow); teardown = true; diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index c9e332fafcb5..c8c29a9a1684 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -147,13 +147,14 @@ static bool ip_has_options(unsigned int thoff) struct nf_flowtable_ctx { const struct net_device *in; + __be16 ether_type; u32 offset; u32 hdrsize; struct { /* Tunnel IP header size */ u32 hdr_size; /* IP tunnel protocol */ - u8 proto; + u8 inner_proto; } tun; }; @@ -161,7 +162,6 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, struct sk_buff *skb, struct flow_offload_tuple *tuple) { - __be16 inner_proto = skb->protocol; struct vlan_ethhdr *veth; struct pppoe_hdr *phdr; struct ipv6hdr *ip6h; @@ -179,33 +179,31 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, veth = (struct vlan_ethhdr *)skb_mac_header(skb); tuple->encap[i].id = ntohs(veth->h_vlan_TCI); tuple->encap[i].proto = skb->protocol; - inner_proto = veth->h_vlan_encapsulated_proto; offset += VLAN_HLEN; break; case htons(ETH_P_PPP_SES): phdr = (struct pppoe_hdr *)skb_network_header(skb); tuple->encap[i].id = ntohs(phdr->sid); tuple->encap[i].proto = skb->protocol; - inner_proto = *((__be16 *)(phdr + 1)); offset += PPPOE_SES_HLEN; break; } - switch (inner_proto) { + switch (ctx->ether_type) { case htons(ETH_P_IP): iph = (struct iphdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPIP) { + if (ctx->tun.inner_proto == IPPROTO_IPIP) { tuple->tun.dst_v4.s_addr = iph->daddr; tuple->tun.src_v4.s_addr = iph->saddr; - tuple->tun.l3_proto = IPPROTO_IPIP; + tuple->tun.inner_proto = IPPROTO_IPIP; } break; case htons(ETH_P_IPV6): ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPV6) { + if (ctx->tun.inner_proto == IPPROTO_IPV6) { tuple->tun.dst_v6 = ip6h->daddr; tuple->tun.src_v6 = ip6h->saddr; - tuple->tun.l3_proto = IPPROTO_IPV6; + tuple->tun.inner_proto = IPPROTO_IPV6; } break; default: @@ -297,14 +295,6 @@ static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu) return true; } -static inline bool nf_flow_dst_check(struct flow_offload_tuple *tuple) -{ - if (!tuple->dst_cache) - return true; - - return dst_check(tuple->dst_cache, tuple->dst_cookie); -} - static unsigned int nf_flow_xmit_xfrm(struct sk_buff *skb, const struct nf_hook_state *state, struct dst_entry *dst) @@ -337,7 +327,7 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx, return false; if (iph->protocol == IPPROTO_IPIP) { - ctx->tun.proto = iph->protocol; + ctx->tun.inner_proto = iph->protocol; ctx->tun.hdr_size = size; ctx->offset += ctx->tun.hdr_size; } @@ -362,7 +352,7 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx, return false; if (ip6h->nexthdr == IPPROTO_IPV6) { - ctx->tun.proto = ip6h->nexthdr; + ctx->tun.inner_proto = ip6h->nexthdr; ctx->tun.hdr_size = sizeof(*ip6h); ctx->offset += ctx->tun.hdr_size; } @@ -376,8 +366,8 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx, static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx, struct sk_buff *skb) { - if (ctx->tun.proto != IPPROTO_IPIP && - ctx->tun.proto != IPPROTO_IPV6) + if (ctx->tun.inner_proto != IPPROTO_IPIP && + ctx->tun.inner_proto != IPPROTO_IPV6) return; skb_pull(skb, ctx->tun.hdr_size); @@ -385,10 +375,10 @@ static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx, } static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx, - struct sk_buff *skb, __be16 proto) + struct sk_buff *skb) { - __be16 inner_proto = skb->protocol; struct vlan_ethhdr *veth; + __be16 ether_type; bool ret = false; switch (skb->protocol) { @@ -397,22 +387,27 @@ static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx, return false; veth = (struct vlan_ethhdr *)skb_mac_header(skb); - if (veth->h_vlan_encapsulated_proto == proto) { - ctx->offset += VLAN_HLEN; - inner_proto = proto; - ret = true; - } + ctx->ether_type = veth->h_vlan_encapsulated_proto; + ctx->offset += VLAN_HLEN; + ret = true; break; case htons(ETH_P_PPP_SES): - if (nf_flow_pppoe_proto(skb, &inner_proto) && - inner_proto == proto) { - ctx->offset += PPPOE_SES_HLEN; - ret = true; - } + if (!nf_flow_pppoe_proto(skb, ðer_type)) + return false; + + ctx->ether_type = ether_type; + ctx->offset += PPPOE_SES_HLEN; + ret = true; break; + case htons(ETH_P_IP): + case htons(ETH_P_IPV6): + ctx->ether_type = skb->protocol; + break; + default: + return false; } - switch (inner_proto) { + switch (ctx->ether_type) { case htons(ETH_P_IP): ret = nf_flow_ip4_tunnel_proto(ctx, skb); break; @@ -464,9 +459,6 @@ nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP))) - return NULL; - if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) return NULL; @@ -620,7 +612,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb, iph->version = 4; iph->ihl = sizeof(*iph) >> 2; iph->frag_off = ip_mtu_locked(&rt->dst) ? 0 : frag_off; - iph->protocol = tuple->tun.l3_proto; + iph->protocol = tuple->tun.inner_proto; iph->tos = tos; iph->daddr = tuple->tun.src_v4.s_addr; iph->saddr = tuple->tun.dst_v4.s_addr; @@ -803,33 +795,17 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb, return NF_STOLEN; } -unsigned int -nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, - const struct nf_hook_state *state) +static int nf_flow_queue_xmit4(struct sk_buff *skb, + struct flow_offload_tuple_rhash *tuplehash, + const struct nf_hook_state *state) { - struct flow_offload_tuple_rhash *tuplehash; - struct nf_flowtable *flow_table = priv; struct flow_offload_tuple *other_tuple; enum flow_offload_tuple_dir dir; - struct nf_flowtable_ctx ctx = { - .in = state->in, - }; struct nf_flow_xmit xmit = {}; struct flow_offload *flow; struct neighbour *neigh; struct rtable *rt; __be32 ip_daddr; - int ret; - - tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); - if (!tuplehash) - return NF_ACCEPT; - - ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); - if (ret < 0) - return NF_DROP; - else if (ret == 0) - return NF_ACCEPT; if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { rt = dst_rtable(tuplehash->tuple.dst_cache); @@ -883,6 +859,36 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, return nf_flow_queue_xmit(state->net, skb, &xmit); } + +unsigned int +nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct flow_offload_tuple_rhash *tuplehash; + struct nf_flowtable *flow_table = priv; + struct nf_flowtable_ctx ctx = { + .in = state->in, + }; + int ret; + + if (!nf_flow_skb_encap_protocol(&ctx, skb)) + return NF_ACCEPT; + + if (unlikely(ctx.ether_type != htons(ETH_P_IP))) + return NF_ACCEPT; + + tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); + if (!tuplehash) + return NF_ACCEPT; + + ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); + if (ret < 0) + return NF_DROP; + else if (ret == 0) + return NF_ACCEPT; + + return nf_flow_queue_xmit4(skb, tuplehash, state); +} EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff, @@ -1111,42 +1117,23 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IPV6))) - return NULL; - if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) return NULL; return flow_offload_lookup(flow_table, &tuple); } -unsigned int -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, - const struct nf_hook_state *state) +static int nf_flow_queue_xmit6(struct sk_buff *skb, + struct flow_offload_tuple_rhash *tuplehash, + const struct nf_hook_state *state) { - struct flow_offload_tuple_rhash *tuplehash; - struct nf_flowtable *flow_table = priv; struct flow_offload_tuple *other_tuple; enum flow_offload_tuple_dir dir; - struct nf_flowtable_ctx ctx = { - .in = state->in, - }; struct nf_flow_xmit xmit = {}; struct in6_addr *ip6_daddr; struct flow_offload *flow; struct neighbour *neigh; struct rt6_info *rt; - int ret; - - tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); - if (tuplehash == NULL) - return NF_ACCEPT; - - ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb); - if (ret < 0) - return NF_DROP; - else if (ret == 0) - return NF_ACCEPT; if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { rt = dst_rt6_info(tuplehash->tuple.dst_cache); @@ -1201,4 +1188,34 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, return nf_flow_queue_xmit(state->net, skb, &xmit); } + +unsigned int +nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct flow_offload_tuple_rhash *tuplehash; + struct nf_flowtable *flow_table = priv; + struct nf_flowtable_ctx ctx = { + .in = state->in, + }; + int ret; + + if (!nf_flow_skb_encap_protocol(&ctx, skb)) + return NF_ACCEPT; + + if (unlikely(ctx.ether_type != htons(ETH_P_IPV6))) + return NF_ACCEPT; + + tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); + if (!tuplehash) + return NF_ACCEPT; + + ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb); + if (ret < 0) + return NF_DROP; + else if (ret == 0) + return NF_ACCEPT; + + return nf_flow_queue_xmit6(skb, tuplehash, state); +} EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook); diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 98c03b487f52..1e55644f2edb 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -42,14 +42,18 @@ static bool nft_is_valid_ether_device(const struct net_device *dev) return true; } -static int nft_dev_fill_forward_path(const struct nf_flow_route *route, - const struct dst_entry *dst_cache, +static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, const struct nf_conn *ct, - enum ip_conntrack_dir dir, u8 *ha, + enum ip_conntrack_dir dir, + u8 *ha, __be16 ether_type, struct net_device_path_stack *stack) { const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; struct net_device *dev = dst_cache->dev; + struct net_device_path_ctx ctx = { + .dev = dev, + .ether_type = ether_type, + }; struct neighbour *n; u8 nud_state; @@ -72,18 +76,20 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route, return -1; out: - return dev_fill_forward_path(dev, ha, stack); + ether_addr_copy(ctx.daddr, ha); + + return dev_fill_forward_path(&ctx, stack); } struct nft_forward_info { - const struct net_device *indev; - const struct net_device *outdev; + const struct net_device *dev; struct id { __u16 id; __be16 proto; } encap[NF_FLOW_TABLE_ENCAP_MAX]; u8 num_encaps; struct flow_offload_tunnel tun; + struct dst_entry *tun_dst; u8 num_tuns; u8 ingress_vlans; u8 h_source[ETH_ALEN]; @@ -92,9 +98,12 @@ struct nft_forward_info { enum flow_offload_xmit_type xmit_type; }; -static int nft_dev_path_info(const struct net_device_path_stack *stack, +static bool nft_flowtable_find_dev(const struct net_device *dev, + struct nft_flowtable *ft); + +static int nft_dev_path_info(struct net_device_path_stack *stack, struct nft_forward_info *info, - unsigned char *ha, struct nf_flowtable *flowtable) + unsigned char *ha, struct nft_flowtable *ft) { const struct net_device_path *path; int i; @@ -109,29 +118,27 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, case DEV_PATH_VLAN: case DEV_PATH_PPPOE: case DEV_PATH_TUN: - info->indev = path->dev; + info->dev = path->dev; if (is_zero_ether_addr(info->h_source)) memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); - if (path->type == DEV_PATH_ETHERNET) + if (path->type == DEV_PATH_ETHERNET || + path->type == DEV_PATH_DSA) break; - if (path->type == DEV_PATH_DSA) { - i = stack->num_paths; - break; - } /* DEV_PATH_VLAN, DEV_PATH_PPPOE and DEV_PATH_TUN */ if (path->type == DEV_PATH_TUN) { if (info->num_tuns) - return -1; + goto err_out; info->tun.src_v6 = path->tun.src_v6; info->tun.dst_v6 = path->tun.dst_v6; - info->tun.l3_proto = path->tun.l3_proto; + info->tun.inner_proto = path->tun.inner_proto; + info->tun_dst = path->tun.dst; info->num_tuns++; } else { if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) - return -1; + goto err_out; info->encap[info->num_encaps].id = path->encap.id; @@ -152,13 +159,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, switch (path->bridge.vlan_mode) { case DEV_PATH_BR_VLAN_UNTAG_HW: if (info->num_encaps == 0) - return -1; + goto err_out; info->ingress_vlans |= BIT(info->num_encaps - 1); break; case DEV_PATH_BR_VLAN_TAG: if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) - return -1; + goto err_out; info->encap[info->num_encaps].id = path->bridge.vlan_id; info->encap[info->num_encaps].proto = path->bridge.vlan_proto; @@ -166,7 +173,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, break; case DEV_PATH_BR_VLAN_UNTAG: if (info->num_encaps == 0) - return -1; + goto err_out; info->num_encaps--; break; @@ -176,16 +183,22 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; break; default: - return -1; + goto err_out; } } - info->outdev = info->indev; - if (nf_flowtable_hw_offload(flowtable) && - nft_is_valid_ether_device(info->indev)) + if (nf_flowtable_hw_offload(&ft->data) && + nft_is_valid_ether_device(info->dev)) info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; + if (!nft_flowtable_find_dev(info->dev, ft)) + goto err_out; + return 0; +err_out: + dev_fill_forward_path_release(stack); + + return -1; } static bool nft_flowtable_find_dev(const struct net_device *dev, @@ -205,44 +218,6 @@ static bool nft_flowtable_find_dev(const struct net_device *dev, return found; } -static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt, - struct flow_offload_tunnel *tun, - struct nf_flow_route *route, - enum ip_conntrack_dir dir) -{ - struct dst_entry *cur_dst = route->tuple[dir].dst; - struct dst_entry *tun_dst = NULL; - struct flowi fl = {}; - - switch (nft_pf(pkt)) { - case NFPROTO_IPV4: - fl.u.ip4.daddr = tun->dst_v4.s_addr; - fl.u.ip4.saddr = tun->src_v4.s_addr; - fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex; - fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); - fl.u.ip4.flowi4_mark = pkt->skb->mark; - fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC; - break; - case NFPROTO_IPV6: - fl.u.ip6.daddr = tun->dst_v6; - fl.u.ip6.saddr = tun->src_v6; - fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex; - fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); - fl.u.ip6.flowi6_mark = pkt->skb->mark; - fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC; - break; - } - - nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt)); - if (!tun_dst) - return -ENOENT; - - route->tuple[dir].dst = tun_dst; - dst_release(cur_dst); - - return 0; -} - static int nft_dev_forward_path(const struct nft_pktinfo *pkt, struct nf_flow_route *route, const struct nf_conn *ct, @@ -255,28 +230,25 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, unsigned char ha[ETH_ALEN]; int i; - if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 || - nft_dev_path_info(&stack, &info, ha, &ft->data) < 0) + if (nft_dev_fill_forward_path(dst, ct, dir, ha, pkt->ethertype, &stack) < 0 || + nft_dev_path_info(&stack, &info, ha, ft) < 0) return -ENOENT; - if (!nft_flowtable_find_dev(info.indev, ft)) - return -ENOENT; + route->tuple[!dir].in.ifindex = info.dev->ifindex; + route->tuple[dir].out.ifindex = info.dev->ifindex; - if (info.outdev) - route->tuple[dir].out.ifindex = info.outdev->ifindex; - - route->tuple[!dir].in.ifindex = info.indev->ifindex; for (i = 0; i < info.num_encaps; i++) { route->tuple[!dir].in.encap[i].id = info.encap[i].id; route->tuple[!dir].in.encap[i].proto = info.encap[i].proto; } - if (info.num_tuns && - !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) { + if (info.num_tuns) { route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6; route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6; - route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto; + route->tuple[!dir].in.tun.inner_proto = info.tun.inner_proto; route->tuple[!dir].in.num_tuns = info.num_tuns; + dst_release(route->tuple[dir].dst); + route->tuple[dir].dst = info.tun_dst; } route->tuple[!dir].in.num_encaps = info.num_encaps; diff --git a/net/netfilter/nf_hooks_lwtunnel.c b/net/netfilter/nf_hooks_lwtunnel.c index 2d890dd04ff8..4e1eef1ba0f1 100644 --- a/net/netfilter/nf_hooks_lwtunnel.c +++ b/net/netfilter/nf_hooks_lwtunnel.c @@ -54,7 +54,7 @@ int nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table *table, int write, } EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_sysctl_handler); -static struct ctl_table nf_lwtunnel_sysctl_table[] = { +static const struct ctl_table nf_lwtunnel_sysctl_table[] = { { .procname = "nf_hooks_lwtunnel", .data = NULL, @@ -66,8 +66,8 @@ static struct ctl_table nf_lwtunnel_sysctl_table[] = { static int __net_init nf_lwtunnel_net_init(struct net *net) { + const struct ctl_table *table; struct ctl_table_header *hdr; - struct ctl_table *table; table = nf_lwtunnel_sysctl_table; if (!net_eq(net, &init_net)) { diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c index 5ffde27d450a..f24288088c0d 100644 --- a/net/netfilter/nf_log_syslog.c +++ b/net/netfilter/nf_log_syslog.c @@ -301,7 +301,7 @@ nf_log_dump_udp_header(struct nf_log_buf *m, /* Max length: 20 "SPT=65535 DPT=65535 " */ nf_log_buf_add(m, "SPT=%u DPT=%u LEN=%u ", - ntohs(uh->source), ntohs(uh->dest), ntohs(uh->len)); + ntohs(uh->source), ntohs(uh->dest), udp_get_len_short(uh)); out: return 0; diff --git a/net/netfilter/nf_nat_amanda.c b/net/netfilter/nf_nat_amanda.c index 8f1054920a85..330415809425 100644 --- a/net/netfilter/nf_nat_amanda.c +++ b/net/netfilter/nf_nat_amanda.c @@ -26,6 +26,7 @@ static struct nf_conntrack_nat_helper nat_helper_amanda = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, @@ -46,15 +47,15 @@ static unsigned int help(struct sk_buff *skb, /* Try to get same port: if not, try to change it. */ port = nf_nat_exp_find_port(exp, ntohs(exp->saved_proto.tcp.port)); if (port == 0) { - nf_ct_helper_log(skb, exp->master, "all ports in use"); + nf_ct_helper_log(skb, ct, "all ports in use"); return NF_DROP; } snprintf(buffer, sizeof(buffer), "%u", port); - if (!nf_nat_mangle_udp_packet(skb, exp->master, ctinfo, + if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, protoff, matchoff, matchlen, buffer, strlen(buffer))) { - nf_ct_helper_log(skb, exp->master, "cannot mangle packet"); + nf_ct_helper_log(skb, ct, "cannot mangle packet"); nf_ct_unexpect_related(exp); return NF_DROP; } diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 63ff6b4d5d21..8ac326e1eb5b 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -39,12 +39,6 @@ static struct hlist_head *nf_nat_bysource __read_mostly; static unsigned int nf_nat_htable_size __read_mostly; static siphash_aligned_key_t nf_nat_hash_rnd; -struct nf_nat_lookup_hook_priv { - struct nf_hook_entries __rcu *entries; - - struct rcu_head rcu_head; -}; - struct nf_nat_hooks_net { struct nf_hook_ops *nat_hook_ops; unsigned int users; diff --git a/net/netfilter/nf_nat_ftp.c b/net/netfilter/nf_nat_ftp.c index c92a436d9c48..25d20e2970ae 100644 --- a/net/netfilter/nf_nat_ftp.c +++ b/net/netfilter/nf_nat_ftp.c @@ -61,6 +61,7 @@ static int nf_nat_ftp_fmt_cmd(struct nf_conn *ct, enum nf_ct_ftp_type type, /* So, this packet has hit the connection tracking matching code. Mangle it, and change the expectation to match the new version. */ static unsigned int nf_nat_ftp(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, enum nf_ct_ftp_type type, unsigned int protoff, @@ -71,7 +72,6 @@ static unsigned int nf_nat_ftp(struct sk_buff *skb, union nf_inet_addr newaddr; u_int16_t port; int dir = CTINFO2DIR(ctinfo); - struct nf_conn *ct = exp->master; char buffer[sizeof("|1||65535|") + INET6_ADDRSTRLEN]; unsigned int buflen; @@ -88,7 +88,7 @@ static unsigned int nf_nat_ftp(struct sk_buff *skb, port = nf_nat_exp_find_port(exp, ntohs(exp->saved_proto.tcp.port)); if (port == 0) { - nf_ct_helper_log(skb, exp->master, "all ports in use"); + nf_ct_helper_log(skb, ct, "all ports in use"); return NF_DROP; } diff --git a/net/netfilter/nf_nat_helper.c b/net/netfilter/nf_nat_helper.c index bf591e6af005..3853f41db499 100644 --- a/net/netfilter/nf_nat_helper.c +++ b/net/netfilter/nf_nat_helper.c @@ -161,7 +161,7 @@ nf_nat_mangle_udp_packet(struct sk_buff *skb, /* update the length of the UDP packet */ datalen = skb->len - protoff; - udph->len = htons(datalen); + udp_set_len_short(udph, datalen); /* fix udp checksum if udp checksum was previously calculated */ if (!udph->check && skb->ip_summed != CHECKSUM_PARTIAL) diff --git a/net/netfilter/nf_nat_irc.c b/net/netfilter/nf_nat_irc.c index 19c4fcc60c50..89b31fe932ba 100644 --- a/net/netfilter/nf_nat_irc.c +++ b/net/netfilter/nf_nat_irc.c @@ -30,6 +30,7 @@ static struct nf_conntrack_nat_helper nat_helper_irc = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, @@ -37,7 +38,6 @@ static unsigned int help(struct sk_buff *skb, struct nf_conntrack_expect *exp) { char buffer[sizeof("4294967296 65635")]; - struct nf_conn *ct = exp->master; union nf_inet_addr newaddr; u_int16_t port; diff --git a/net/netfilter/nf_nat_proto.c b/net/netfilter/nf_nat_proto.c index 07f51fe75fbe..64b9bac228ea 100644 --- a/net/netfilter/nf_nat_proto.c +++ b/net/netfilter/nf_nat_proto.c @@ -770,6 +770,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -777,6 +778,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -784,6 +786,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -791,6 +794,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; @@ -1031,6 +1035,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1038,6 +1043,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -1045,6 +1051,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1052,6 +1059,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index 133bd713fe0c..8c412bcf6cff 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -273,12 +273,17 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, SIP_HDR_CONTACT, &in_header, &matchoff, &matchlen, &addr, &port) > 0) { + int old_len = skb->len, delta; + if (!map_addr(skb, protoff, dataoff, dptr, datalen, matchoff, matchlen, &addr, port)) { nf_ct_helper_log(skb, ct, "cannot mangle contact"); return NF_DROP; } + + delta = (int)skb->len - old_len; + coff += delta; } if (!map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_FROM) || diff --git a/net/netfilter/nf_nat_tftp.c b/net/netfilter/nf_nat_tftp.c index 1a591132d6eb..7121e6704f34 100644 --- a/net/netfilter/nf_nat_tftp.c +++ b/net/netfilter/nf_nat_tftp.c @@ -21,17 +21,16 @@ static struct nf_conntrack_nat_helper nat_helper_tftp = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, struct nf_conntrack_expect *exp) { - const struct nf_conn *ct = exp->master; - exp->saved_proto.udp.port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port; exp->dir = IP_CT_DIR_REPLY; exp->expectfn = nf_nat_follow_master; if (nf_ct_expect_related(exp, 0) != 0) { - nf_ct_helper_log(skb, exp->master, "cannot add expectation"); + nf_ct_helper_log(skb, ct, "cannot add expectation"); return NF_DROP; } return NF_ACCEPT; diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index f062ac210343..5b6a09f46903 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -67,7 +67,7 @@ nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, } static const struct nla_policy nfnl_cthelper_tuple_pol[NFCTH_TUPLE_MAX+1] = { - [NFCTH_TUPLE_L3PROTONUM] = { .type = NLA_U16, }, + [NFCTH_TUPLE_L3PROTONUM] = NLA_POLICY_MAX(NLA_BE16, NFPROTO_IPV6), [NFCTH_TUPLE_L4PROTONUM] = { .type = NLA_U8, }, }; @@ -256,7 +256,8 @@ nfnl_cthelper_create(const struct nlattr * const tb[], helper->data_len = size; helper->flags |= NF_CT_HELPER_F_USERSPACE; - memcpy(&helper->tuple, tuple, sizeof(struct nf_conntrack_tuple)); + helper->nfproto = tuple->src.l3num; + helper->l4proto = tuple->dst.protonum; helper->me = THIS_MODULE; helper->help = nfnl_userspace_cthelper; @@ -453,8 +454,8 @@ static int nfnl_cthelper_new(struct sk_buff *skb, const struct nfnl_info *info, if (strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN)) continue; - if ((tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + if ((tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; if (info->nlh->nlmsg_flags & NLM_F_EXCL) @@ -483,10 +484,10 @@ nfnl_cthelper_dump_tuple(struct sk_buff *skb, goto nla_put_failure; if (nla_put_be16(skb, NFCTH_TUPLE_L3PROTONUM, - htons(helper->tuple.src.l3num))) + htons(helper->nfproto))) goto nla_put_failure; - if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->tuple.dst.protonum)) + if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->l4proto)) goto nla_put_failure; nla_nest_end(skb, nest_parms); @@ -665,8 +666,8 @@ static int nfnl_cthelper_get(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -725,8 +726,8 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; found = true; diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 170d3db860c5..66c2016f6049 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -168,7 +168,7 @@ static int cttimeout_new_timeout(struct sk_buff *skb, if (ret < 0) goto err_free_timeout_policy; - strcpy(timeout->name, nla_data(cda[CTA_TIMEOUT_NAME])); + nla_strscpy(timeout->name, cda[CTA_TIMEOUT_NAME], sizeof(timeout->name)); timeout->timeout->l3num = l3num; timeout->timeout->l4proto = l4proto; refcount_set(&timeout->timeout->refcnt, 1); diff --git a/net/netfilter/nfnetlink_hook.c b/net/netfilter/nfnetlink_hook.c index 5623c18fcd12..95005e9a6066 100644 --- a/net/netfilter/nfnetlink_hook.c +++ b/net/netfilter/nfnetlink_hook.c @@ -190,7 +190,7 @@ static int nfnl_hook_put_nft_ft_info(struct sk_buff *nlskb, static int nfnl_hook_dump_one(struct sk_buff *nlskb, const struct nfnl_dump_hook_data *ctx, - const struct nf_hook_ops *ops, + const struct nf_hook_ops *ops, int priority, int family, unsigned int seq) { u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET); @@ -244,7 +244,7 @@ static int nfnl_hook_dump_one(struct sk_buff *nlskb, if (ret) goto nla_put_failure; - ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority)); + ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(priority)); if (ret) goto nla_put_failure; @@ -337,6 +337,30 @@ nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *de return hook_head; } +static int nfnl_hook_dump_nat(struct sk_buff *nlskb, + const struct nfnl_dump_hook_data *ctx, + const struct nf_hook_ops *ops, + int family, unsigned int seq) +{ + struct nf_nat_lookup_hook_priv *priv = ops->priv; + struct nf_hook_entries *e = rcu_dereference(priv->entries); + struct nf_hook_ops **nat_ops; + int i, err; + + if (!e) + return 0; + + nat_ops = nf_hook_entries_get_hook_ops(e); + + for (i = 0; i < e->num_hook_entries; i++) { + err = nfnl_hook_dump_one(nlskb, ctx, nat_ops[i], + ops->priority, family, seq); + if (err) + return err; + } + return 0; +} + static int nfnl_hook_dump(struct sk_buff *nlskb, struct netlink_callback *cb) { @@ -365,8 +389,13 @@ static int nfnl_hook_dump(struct sk_buff *nlskb, ops = nf_hook_entries_get_hook_ops(e); for (; i < e->num_hook_entries; i++) { - err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family, - cb->nlh->nlmsg_seq); + if (ops[i]->hook_ops_type == NF_HOOK_OP_NAT) + err = nfnl_hook_dump_nat(nlskb, ctx, ops[i], family, + cb->nlh->nlmsg_seq); + else + err = nfnl_hook_dump_one(nlskb, ctx, ops[i], + ops[i]->priority, family, + cb->nlh->nlmsg_seq); if (err) break; } diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 03a88c77e0f0..9dbf127df9c8 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1213,6 +1213,8 @@ struct nft_ct_expect_obj { u8 l4proto; u8 size; u32 timeout; + + struct nf_conntrack_helper *helper; }; static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) @@ -1226,6 +1228,93 @@ static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + +struct nft_ct_expect_data { + struct nft_ct_expect_obj obj; + enum ip_conntrack_dir dir; +}; + +static int ct_expect_help(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, enum ip_conntrack_info ctinfo) +{ + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); + struct nft_ct_expect_data *expect_data; + struct nf_conntrack_expect *exp; + int ret = NF_ACCEPT; + u16 l3num; + + if (nf_ct_is_confirmed(ct)) + return NF_ACCEPT; + + expect_data = nfct_help_data(ct); + if (!expect_data) + return NF_ACCEPT; + + if (expect_data->dir != dir) + return NF_ACCEPT; + + exp = nf_ct_expect_alloc(ct); + if (!exp) + return NF_DROP; + + if (expect_data->obj.l3num == NFPROTO_INET) + l3num = nf_ct_l3num(ct); + else + l3num = expect_data->obj.l3num; + + nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, + &ct->tuplehash[!dir].tuple.src.u3, + &ct->tuplehash[!dir].tuple.dst.u3, + expect_data->obj.l4proto, NULL, &expect_data->obj.dport); + exp->timeout += expect_data->obj.timeout; + +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = expect_data->obj.dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif + if (nf_ct_expect_related(exp, 0) != 0) + ret = NF_ACCEPT; + + nf_ct_expect_put(exp); + + return ret; +} + +static int nft_ct_expect_helper_alloc(struct nft_ct_expect_obj *priv) +{ + struct nf_conntrack_helper *ct_expect_helper; + + ct_expect_helper = kzalloc_obj(struct nf_conntrack_helper, + GFP_KERNEL_ACCOUNT); + if (!ct_expect_helper) + return -ENOMEM; + + snprintf(ct_expect_helper->name, sizeof(ct_expect_helper->name), "%s", + "nft_ct_expect"); + ct_expect_helper->me = THIS_MODULE; + ct_expect_helper->expect_policy[NF_CT_EXPECT_CLASS_DEFAULT].max_expected = priv->size; + rcu_assign_pointer(ct_expect_helper->help, ct_expect_help); + refcount_set(&ct_expect_helper->ct_refcnt, 1); + + /* No need to register this helper, this is internal. */ + priv->helper = ct_expect_helper; + + return 0; +} + static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, const struct nlattr * const tb[], struct nft_object *obj) @@ -1233,6 +1322,8 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, struct nft_ct_expect_obj *priv = nft_obj_data(obj); int err; + NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nft_ct_expect_data)); + if (!tb[NFTA_CT_EXPECT_L4PROTO] || !tb[NFTA_CT_EXPECT_DPORT] || !tb[NFTA_CT_EXPECT_TIMEOUT] || @@ -1272,13 +1363,31 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, priv->dport = nla_get_be16(tb[NFTA_CT_EXPECT_DPORT]); priv->size = nla_get_u8(tb[NFTA_CT_EXPECT_SIZE]); + if (!priv->size) + priv->size = NF_CT_EXPECT_MAX_CNT; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + return err; + + err = nft_ct_expect_helper_alloc(priv); + if (err < 0) { + nf_ct_netns_put(ctx->net, ctx->family); + return err; + } + + return err; } static void nft_ct_expect_obj_destroy(const struct nft_ctx *ctx, - struct nft_object *obj) + struct nft_object *obj) { + const struct nft_ct_expect_obj *priv = nft_obj_data(obj); + struct nf_conntrack_helper *me = priv->helper; + + /* This helper is going away, disable it. */ + rcu_assign_pointer(me->help, NULL); + nf_conntrack_helper_release(me); nf_ct_netns_put(ctx->net, ctx->family); } @@ -1302,11 +1411,9 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, const struct nft_pktinfo *pkt) { const struct nft_ct_expect_obj *priv = nft_obj_data(obj); - struct nf_conntrack_expect *exp; + struct nft_ct_expect_data *expect_data; enum ip_conntrack_info ctinfo; struct nf_conn_help *help; - enum ip_conntrack_dir dir; - u16 l3num = priv->l3num; struct nf_conn *ct; ct = nf_ct_get(pkt->skb, &ctinfo); @@ -1314,38 +1421,30 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, regs->verdict.code = NFT_BREAK; return; } - dir = CTINFO2DIR(ctinfo); help = nfct_help(ct); - if (!help) - help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); + if (help) { + regs->verdict.code = NFT_BREAK; + return; + } + + help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); if (!help) { regs->verdict.code = NF_DROP; return; } - if (help->expecting[NF_CT_EXPECT_CLASS_DEFAULT] >= priv->size) { + expect_data = nfct_help_data(ct); + if (!expect_data) { regs->verdict.code = NFT_BREAK; return; } - if (l3num == NFPROTO_INET) - l3num = nf_ct_l3num(ct); + expect_data->obj = *priv; + expect_data->obj.helper = NULL; + expect_data->dir = CTINFO2DIR(ctinfo); - exp = nf_ct_expect_alloc(ct); - if (exp == NULL) { - regs->verdict.code = NF_DROP; - return; - } - nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, - &ct->tuplehash[!dir].tuple.src.u3, - &ct->tuplehash[!dir].tuple.dst.u3, - priv->l4proto, NULL, &priv->dport); - exp->timeout += priv->timeout; - - if (nf_ct_expect_related(exp, 0) != 0) - regs->verdict.code = NF_DROP; - - nf_ct_expect_put(exp); + if (help && refcount_inc_not_zero(&priv->helper->ct_refcnt)) + rcu_assign_pointer(help->helper, priv->helper); } static const struct nla_policy nft_ct_expect_policy[NFTA_CT_EXPECT_MAX + 1] = { @@ -1375,6 +1474,13 @@ static struct nft_object_type nft_ct_expect_obj_type __read_mostly = { .owner = THIS_MODULE, }; +#if IS_ENABLED(CONFIG_NF_NAT) +static struct nf_ct_helper_expectfn nft_ct_nat __read_mostly = { + .name = "nft_ct-follow-master", + .expectfn = nft_ct_nat_follow_master, +}; +#endif + static int __init nft_ct_module_init(void) { int err; @@ -1400,6 +1506,9 @@ static int __init nft_ct_module_init(void) err = nft_register_obj(&nft_ct_timeout_obj_type); if (err < 0) goto err4; +#endif +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_register(&nft_ct_nat); #endif return 0; @@ -1425,6 +1534,13 @@ static void __exit nft_ct_module_exit(void) nft_unregister_obj(&nft_ct_helper_obj_type); nft_unregister_expr(&nft_notrack_type); nft_unregister_expr(&nft_ct_type); + +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_unregister(&nft_ct_nat); + synchronize_rcu(); + nf_ct_helper_expectfn_destroy(&nft_ct_nat); + synchronize_rcu(); +#endif } module_init(nft_ct_module_init); diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c index 0a43e0787a68..01cfbaa36525 100644 --- a/net/netfilter/nft_meta.c +++ b/net/netfilter/nft_meta.c @@ -20,6 +20,7 @@ #include #include #include +#include #include /* for TCP_TIME_WAIT */ #include #include @@ -279,11 +280,12 @@ static bool nft_meta_get_eval_ifname(enum nft_meta_keys key, u32 *dest, static noinline bool nft_meta_get_eval_rtclassid(const struct sk_buff *skb, u32 *dest) { - const struct dst_entry *dst = skb_dst(skb); + const struct dst_entry *dst; - if (!dst) + if (!skb_valid_dst(skb)) return false; + dst = skb_dst(skb); *dest = dst->tclassid; return true; } diff --git a/net/netfilter/nft_rt.c b/net/netfilter/nft_rt.c index aeb0094eafd8..841c863a08db 100644 --- a/net/netfilter/nft_rt.c +++ b/net/netfilter/nft_rt.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -59,10 +60,11 @@ void nft_rt_get_eval(const struct nft_expr *expr, u32 *dest = ®s->data[priv->dreg]; const struct dst_entry *dst; - dst = skb_dst(skb); - if (!dst) + if (!skb_valid_dst(skb)) goto err; + dst = skb_dst(skb); + switch (priv->key) { #ifdef CONFIG_IP_ROUTE_CLASSID case NFT_RT_CLASSID: diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 6222e9bb57bc..9894832281c4 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -548,8 +548,7 @@ static int nft_array_intervals_alloc(struct nft_array *array, u32 max_intervals) if (!intervals) return -ENOMEM; - if (array->intervals) - kvfree(array->intervals); + kvfree(array->intervals); array->intervals = intervals; array->max_intervals = max_intervals; diff --git a/net/netfilter/nft_xfrm.c b/net/netfilter/nft_xfrm.c index 8cec43064319..c8bba697f993 100644 --- a/net/netfilter/nft_xfrm.c +++ b/net/netfilter/nft_xfrm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -177,9 +178,15 @@ static void nft_xfrm_get_eval_out(const struct nft_xfrm *priv, struct nft_regs *regs, const struct nft_pktinfo *pkt) { - const struct dst_entry *dst = skb_dst(pkt->skb); + const struct dst_entry *dst; int i; + if (!skb_valid_dst(pkt->skb)) { + regs->verdict.code = NFT_BREAK; + return; + } + + dst = skb_dst(pkt->skb); for (i = 0; dst && dst->xfrm; dst = ((const struct xfrm_dst *)dst)->child, i++) { if (i < priv->spnum) diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index 4e6708c23922..e64116bf2637 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c @@ -1920,9 +1920,9 @@ static const struct seq_operations xt_target_seq_ops = { .show = xt_target_seq_show, }; -#define FORMAT_TABLES "_tables_names" -#define FORMAT_MATCHES "_tables_matches" -#define FORMAT_TARGETS "_tables_targets" +#define FORMAT_TABLES "%s_tables_names" +#define FORMAT_MATCHES "%s_tables_matches" +#define FORMAT_TARGETS "%s_tables_targets" #endif /* CONFIG_PROC_FS */ @@ -2033,8 +2033,7 @@ int xt_proto_init(struct net *net, u_int8_t af) root_uid = make_kuid(net->user_ns, 0); root_gid = make_kgid(net->user_ns, 0); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); proc = proc_create_net_data(buf, 0440, net->proc_net, &xt_table_seq_ops, sizeof(struct seq_net_private), (void *)(unsigned long)af); @@ -2043,8 +2042,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_match_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2053,8 +2051,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_target_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2068,13 +2065,11 @@ int xt_proto_init(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS out_remove_matches: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out_remove_tables: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out: return -1; @@ -2087,16 +2082,13 @@ void xt_proto_fini(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS char buf[XT_FUNCTION_MAXNAMELEN]; - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); #endif /*CONFIG_PROC_FS*/ } diff --git a/net/netfilter/xt_TCPOPTSTRIP.c b/net/netfilter/xt_TCPOPTSTRIP.c index 93f064306901..265d21697847 100644 --- a/net/netfilter/xt_TCPOPTSTRIP.c +++ b/net/netfilter/xt_TCPOPTSTRIP.c @@ -16,7 +16,7 @@ #include #include -static inline unsigned int optlen(const u_int8_t *opt, unsigned int offset) +static inline unsigned int optlen(const u8 *opt, unsigned int offset) { /* Beware zero-length options: make finite progress */ if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) @@ -33,8 +33,8 @@ tcpoptstrip_mangle_packet(struct sk_buff *skb, const struct xt_tcpoptstrip_target_info *info = par->targinfo; struct tcphdr *tcph, _th; unsigned int optl, i, j; - u_int16_t n, o; - u_int8_t *opt; + u16 n, o; + u8 *opt; int tcp_hdrlen; /* This is a fragment, no TCP header is available */ @@ -97,7 +97,7 @@ tcpoptstrip_tg6(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ipv6h = ipv6_hdr(skb); int tcphoff; - u_int8_t nexthdr; + u8 nexthdr; __be16 frag_off; nexthdr = ipv6h->nexthdr; diff --git a/net/netfilter/xt_dscp.c b/net/netfilter/xt_dscp.c index fb0169a8f9bb..878f27016e99 100644 --- a/net/netfilter/xt_dscp.c +++ b/net/netfilter/xt_dscp.c @@ -49,6 +49,16 @@ static int dscp_mt_check(const struct xt_mtchk_param *par) return 0; } +static int tos_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tos_match_info *info = par->matchinfo; + + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static bool tos_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_tos_match_info *info = par->matchinfo; @@ -82,6 +92,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV4, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, @@ -90,6 +101,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV6, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c index f72752fa4374..d34831ce3adf 100644 --- a/net/netfilter/xt_recent.c +++ b/net/netfilter/xt_recent.c @@ -400,7 +400,7 @@ static int recent_mt_check(const struct xt_mtchk_param *par, t->nstamps_max_mask = nstamp_mask; memcpy(&t->mask, &info->mask, sizeof(t->mask)); - strcpy(t->name, info->name); + strscpy(t->name, info->name); INIT_LIST_HEAD(&t->lru_list); for (i = 0; i < ip_list_hash_size; i++) INIT_LIST_HEAD(&t->iphash[i]); diff --git a/net/netfilter/xt_tcpmss.c b/net/netfilter/xt_tcpmss.c index b9da8269161d..5f7f97dbace5 100644 --- a/net/netfilter/xt_tcpmss.c +++ b/net/netfilter/xt_tcpmss.c @@ -78,10 +78,23 @@ tcpmss_mt(const struct sk_buff *skb, struct xt_action_param *par) return false; } +static int tcpmss_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tcpmss_match_info *info = par->matchinfo; + + if (info->mss_min > info->mss_max) + return -EINVAL; + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static struct xt_match tcpmss_mt_reg[] __read_mostly = { { .name = "tcpmss", .family = NFPROTO_IPV4, + .checkentry = tcpmss_mt_check, .match = tcpmss_mt, .matchsize = sizeof(struct xt_tcpmss_match_info), .proto = IPPROTO_TCP, @@ -90,6 +103,7 @@ static struct xt_match tcpmss_mt_reg[] __read_mostly = { { .name = "tcpmss", .family = NFPROTO_IPV6, + .checkentry = tcpmss_mt_check, .match = tcpmss_mt, .matchsize = sizeof(struct xt_tcpmss_match_info), .proto = IPPROTO_TCP, diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 5202fe0b0867..e6b1d9758c9c 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -145,7 +145,7 @@ DEFINE_RWLOCK(nl_table_lock); EXPORT_SYMBOL_GPL(nl_table_lock); static atomic_t nl_table_users = ATOMIC_INIT(0); -#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock)); +#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock)) static BLOCKING_NOTIFIER_HEAD(netlink_chain); diff --git a/net/nfc/digital_core.c b/net/nfc/digital_core.c index 7cb1e6aaae90..18236221d898 100644 --- a/net/nfc/digital_core.c +++ b/net/nfc/digital_core.c @@ -127,7 +127,7 @@ static void digital_wq_cmd_complete(struct work_struct *work) mutex_unlock(&ddev->cmd_lock); - if (!IS_ERR(cmd->resp)) + if (!IS_ERR_OR_NULL(cmd->resp)) print_hex_dump_debug("DIGITAL RX: ", DUMP_PREFIX_NONE, 16, 1, cmd->resp->data, cmd->resp->len, false); diff --git a/net/nfc/digital_technology.c b/net/nfc/digital_technology.c index ae63c5eb06fa..ae6487c10a25 100644 --- a/net/nfc/digital_technology.c +++ b/net/nfc/digital_technology.c @@ -778,6 +778,8 @@ static void digital_in_recv_sensf_res(struct nfc_digital_dev *ddev, void *arg, sensf_res = (struct digital_sensf_res *)resp->data; + resp->len = min_t(unsigned int, resp->len, NFC_SENSF_RES_MAXSIZE); + memcpy(target.sensf_res, sensf_res, resp->len); target.sensf_res_len = resp->len; diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index 291f26facbf3..ca89fe967d6a 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -193,7 +193,8 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -201,9 +202,15 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, return -ENODEV; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { @@ -243,7 +250,8 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -251,9 +259,15 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, return -ENOTCONN; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index dc65c719f35f..cac1b5487064 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -849,13 +849,16 @@ static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local, static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) { u8 type, length; - const u8 *tlv = &skb->data[2]; - size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0; + const u8 *tlv = &skb->data[LLCP_HEADER_SIZE]; + const u8 *tlv_end = skb_tail_pointer(skb); - while (offset < tlv_array_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + pr_debug("type 0x%x length %d\n", type, length); if (type == LLCP_TLV_SN) { @@ -863,7 +866,6 @@ static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) return &tlv[2]; } - offset += length + 2; tlv += length + 2; } @@ -1286,10 +1288,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, { struct nfc_llcp_sock *llcp_sock; u8 dsap, ssap, type, length, tid, sap; - const u8 *tlv; - u16 tlv_len, offset; + const u8 *tlv, *tlv_end; const char *service_name; - size_t service_name_len; + int service_name_len; struct nfc_llcp_sdp_tlv *sdp; HLIST_HEAD(llc_sdres_list); size_t sdres_tlvs_len; @@ -1305,22 +1306,34 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, return; } + /* + * Walk the SNL TLV list in the linear part of the skb only, + * bounded by skb_tail_pointer(). Each TLV needs a two-byte + * header (type, length) and its declared length must fit before + * the end; this also keeps the walk safe for very short frames. + */ tlv = &skb->data[LLCP_HEADER_SIZE]; - tlv_len = skb->len - LLCP_HEADER_SIZE; - offset = 0; + tlv_end = skb_tail_pointer(skb); sdres_tlvs_len = 0; - while (offset < tlv_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + switch (type) { case LLCP_TLV_SDREQ: + if (length < 1) + break; + tid = tlv[2]; service_name = (char *) &tlv[3]; service_name_len = length - 1; - pr_debug("Looking for %.16s\n", service_name); + pr_debug("Looking for %.*s\n", service_name_len, + service_name); if (service_name_len == strlen("urn:nfc:sn:sdp") && !strncmp(service_name, "urn:nfc:sn:sdp", @@ -1380,6 +1393,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; case LLCP_TLV_SDRES: + if (length != 2) + break; + mutex_lock(&local->sdreq_lock); pr_debug("LLCP_TLV_SDRES: searching tid %d\n", tlv[2]); @@ -1408,7 +1424,6 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; } - offset += length + 2; tlv += length + 2; } @@ -1552,6 +1567,11 @@ static void nfc_llcp_rx_work(struct work_struct *work) static void __nfc_llcp_recv(struct nfc_llcp_local *local, struct sk_buff *skb) { + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE)) { + kfree_skb(skb); + return; + } + local->rx_pending = skb; timer_delete(&local->link_timer); schedule_work(&local->rx_work); diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index feab29fc62f4..5558d8a4d48b 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -319,14 +319,22 @@ static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, if (get_user(len, optlen)) return -EFAULT; - local = llcp_sock->local; - if (!local) - return -ENODEV; + if (len < 0) + return -EINVAL; + + if (len < sizeof(u32)) + return -EINVAL; len = min_t(u32, len, sizeof(u32)); lock_sock(sk); + local = llcp_sock->local; + if (!local) { + release_sock(sk); + return -ENODEV; + } + switch (optname) { case NFC_LLCP_RW: rw = llcp_sock->rw > LLCP_MAX_RW ? local->rw : llcp_sock->rw; diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c index 5f98c73db5af..4253edea5c8d 100644 --- a/net/nfc/nci/data.c +++ b/net/nfc/nci/data.c @@ -46,11 +46,11 @@ void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb, timer_delete_sync(&ndev->data_timer); clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags); - /* Mark the exchange as done before calling the callback. - * The callback (e.g. rawsock_data_exchange_complete) may - * want to immediately queue another data exchange. - */ - clear_bit(NCI_DATA_EXCHANGE, &ndev->flags); + /* Claim completion atomically -- both close and rx_work may race here */ + if (!test_and_clear_bit(NCI_DATA_EXCHANGE, &ndev->flags)) { + kfree_skb(skb); + return; + } if (cb) { /* forward skb to nfc core */ diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index c96512bb8653..f5c9a8ab7ec1 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -440,7 +440,7 @@ void nci_clear_target_list(struct nci_dev *ndev) static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { - struct nci_rf_discover_ntf ntf; + struct nci_rf_discover_ntf ntf = {}; const __u8 *data; bool add_target = true; @@ -525,15 +525,19 @@ static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_nfca_poll_iso_dep *nfca_poll; struct activation_params_nfcb_poll_iso_dep *nfcb_poll; switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfca_poll = &ntf->activation_params.nfca_poll_iso_dep; nfca_poll->rats_res_len = min_t(__u8, *data++, NFC_ATS_MAXSIZE); + data_len--; + nfca_poll->rats_res_len = min_t(__u8, nfca_poll->rats_res_len, data_len); pr_debug("rats_res_len %d\n", nfca_poll->rats_res_len); if (nfca_poll->rats_res_len > 0) { memcpy(nfca_poll->rats_res, @@ -542,8 +546,12 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, break; case NCI_NFC_B_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfcb_poll = &ntf->activation_params.nfcb_poll_iso_dep; nfcb_poll->attrib_res_len = min_t(__u8, *data++, 50); + data_len--; + nfcb_poll->attrib_res_len = min_t(__u8, nfcb_poll->attrib_res_len, data_len); pr_debug("attrib_res_len %d\n", nfcb_poll->attrib_res_len); if (nfcb_poll->attrib_res_len > 0) { memcpy(nfcb_poll->attrib_res, @@ -562,7 +570,7 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_poll_nfc_dep *poll; struct activation_params_listen_nfc_dep *listen; @@ -570,9 +578,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: case NCI_NFC_F_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; poll = &ntf->activation_params.poll_nfc_dep; poll->atr_res_len = min_t(__u8, *data++, NFC_ATR_RES_MAXSIZE - 2); + data_len--; + poll->atr_res_len = min_t(__u8, poll->atr_res_len, data_len); pr_debug("atr_res_len %d\n", poll->atr_res_len); if (poll->atr_res_len > 0) memcpy(poll->atr_res, data, poll->atr_res_len); @@ -580,9 +592,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, case NCI_NFC_A_PASSIVE_LISTEN_MODE: case NCI_NFC_F_PASSIVE_LISTEN_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; listen = &ntf->activation_params.listen_nfc_dep; listen->atr_req_len = min_t(__u8, *data++, NFC_ATR_REQ_MAXSIZE - 2); + data_len--; + listen->atr_req_len = min_t(__u8, listen->atr_req_len, data_len); pr_debug("atr_req_len %d\n", listen->atr_req_len); if (listen->atr_req_len > 0) memcpy(listen->atr_req, data, listen->atr_req_len); @@ -603,6 +619,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev, struct nfc_target *target; int rc; + /* This is a new target, check if we've enough room */ + if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) { + pr_debug("not enough room, ignoring new target...\n"); + return; + } + target = &ndev->targets[ndev->n_targets]; rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol, @@ -688,7 +710,7 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { struct nci_conn_info *conn_info; - struct nci_rf_intf_activated_ntf ntf; + struct nci_rf_intf_activated_ntf ntf = {}; const __u8 *data; int err = NCI_STATUS_OK; @@ -806,12 +828,14 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, switch (ntf.rf_interface) { case NCI_RF_INTERFACE_ISO_DEP: err = nci_extract_activation_params_iso_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_NFC_DEP: err = nci_extract_activation_params_nfc_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_FRAME: diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c index 9eeb862825c5..b0ab4f5acbce 100644 --- a/net/nfc/nci/rsp.c +++ b/net/nfc/nci/rsp.c @@ -50,11 +50,27 @@ static u8 nci_core_init_rsp_packet_v1(struct nci_dev *ndev, const struct nci_core_init_rsp_1 *rsp_1 = (void *)skb->data; const struct nci_core_init_rsp_2 *rsp_2; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status 0x%x\n", rsp_1->status); if (rsp_1->status != NCI_STATUS_OK) return rsp_1->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp_1)) + return NCI_STATUS_SYNTAX_ERROR; + + /* Ensure the variable-length rf_interfaces array and trailing + * rsp_2 structure are fully contained within the skb. + */ + if (skb_headlen(skb) < sizeof(*rsp_1) + + rsp_1->num_supported_rf_interfaces + + sizeof(*rsp_2)) + return NCI_STATUS_SYNTAX_ERROR; + ndev->nfcc_features = __le32_to_cpu(rsp_1->nfcc_features); ndev->num_supported_rf_interfaces = rsp_1->num_supported_rf_interfaces; @@ -87,15 +103,25 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, const struct sk_buff *skb) { const struct nci_core_init_rsp_nci_ver2 *rsp = (void *)skb->data; - const u8 *supported_rf_interface = rsp->supported_rf_interfaces; + const u8 *supported_rf_interface; u8 rf_interface_idx = 0; u8 rf_extension_cnt = 0; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status %x\n", rsp->status); if (rsp->status != NCI_STATUS_OK) return rsp->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp)) + return NCI_STATUS_SYNTAX_ERROR; + + supported_rf_interface = rsp->supported_rf_interfaces; + ndev->nfcc_features = __le32_to_cpu(rsp->nfcc_features); ndev->num_supported_rf_interfaces = rsp->num_supported_rf_interfaces; @@ -104,13 +130,22 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, NCI_MAX_SUPPORTED_RF_INTERFACES); while (rf_interface_idx < ndev->num_supported_rf_interfaces) { - ndev->supported_rf_interfaces[rf_interface_idx++] = *supported_rf_interface++; + /* Each entry: [rf_interface_type (1B)] [ext_count (1B)] [ext...] */ + if (supported_rf_interface + 2 > skb_tail_pointer(skb)) + break; + ndev->supported_rf_interfaces[rf_interface_idx] = *supported_rf_interface++; - /* skip rf extension parameters */ rf_extension_cnt = *supported_rf_interface++; + if (supported_rf_interface + rf_extension_cnt > skb_tail_pointer(skb)) + break; + + /* Only count the entry after full validation */ + rf_interface_idx++; supported_rf_interface += rf_extension_cnt; } + ndev->num_supported_rf_interfaces = rf_interface_idx; + ndev->max_logical_connections = rsp->max_logical_connections; ndev->max_routing_table_size = __le16_to_cpu(rsp->max_routing_table_size); @@ -336,6 +371,7 @@ static void nci_core_conn_close_rsp_packet(struct nci_dev *ndev, list_del(&conn_info->list); if (conn_info == ndev->rf_conn_info) ndev->rf_conn_info = NULL; + devm_kfree(&ndev->nfc_dev->dev, conn_info->dest_params); devm_kfree(&ndev->nfc_dev->dev, conn_info); } } diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig index e6aaee92dba4..19ac9ae18f1e 100644 --- a/net/openvswitch/Kconfig +++ b/net/openvswitch/Kconfig @@ -40,38 +40,3 @@ config OPENVSWITCH called openvswitch. If unsure, say N. - -config OPENVSWITCH_GRE - tristate "Open vSwitch GRE tunneling support" - depends on OPENVSWITCH - depends on NET_IPGRE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create GRE - vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_VXLAN - tristate "Open vSwitch VXLAN tunneling support" - depends on OPENVSWITCH - depends on VXLAN - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create vxlan vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_GENEVE - tristate "Open vSwitch Geneve tunneling support" - depends on OPENVSWITCH - depends on GENEVE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create geneve vport. - - Say N to exclude this support and reduce the binary size. diff --git a/net/openvswitch/Makefile b/net/openvswitch/Makefile index 28982630bef3..46a27ab369f9 100644 --- a/net/openvswitch/Makefile +++ b/net/openvswitch/Makefile @@ -22,8 +22,4 @@ ifneq ($(CONFIG_NF_CONNTRACK),) openvswitch-y += conntrack.o endif -obj-$(CONFIG_OPENVSWITCH_VXLAN)+= vport-vxlan.o -obj-$(CONFIG_OPENVSWITCH_GENEVE)+= vport-geneve.o -obj-$(CONFIG_OPENVSWITCH_GRE) += vport-gre.o - CFLAGS_openvswitch_trace.o = -I$(src) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 95697d4e16e6..49eb2b0d234d 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -603,7 +603,7 @@ static bool skb_nfct_cached(struct net *net, if (nf_ct_is_confirmed(ct)) nf_ct_delete(ct, 0, 0); - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, 0); return false; } @@ -745,8 +745,7 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, /* Associate skb with specified zone. */ if (tmpl) { - ct = nf_ct_get(skb, &ctinfo); - nf_ct_put(ct); + nf_reset_ct(skb); nf_conntrack_get(&tmpl->ct_general); nf_ct_set(skb, tmpl, IP_CT_NEW); } @@ -1075,12 +1074,7 @@ int ovs_ct_execute(struct net *net, struct sk_buff *skb, int ovs_ct_clear(struct sk_buff *skb, struct sw_flow_key *key) { - enum ip_conntrack_info ctinfo; - struct nf_conn *ct; - - ct = nf_ct_get(skb, &ctinfo); - - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); if (key) @@ -2001,6 +1995,7 @@ int ovs_ct_init(struct net *net) { unsigned int n_bits = sizeof(struct ovs_key_ct_labels) * BITS_PER_BYTE; struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + int err = 0; if (nf_connlabels_get(net, n_bits - 1)) { ovs_net->xt_label = false; @@ -2010,10 +2005,11 @@ int ovs_ct_init(struct net *net) } #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - return ovs_ct_limit_init(net, ovs_net); -#else - return 0; + err = ovs_ct_limit_init(net, ovs_net); + if (err && ovs_net->xt_label) + nf_connlabels_put(net); #endif + return err; } void ovs_ct_exit(struct net *net) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index ae69b2cabab9..69999f9cc44c 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1473,33 +1473,34 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) goto unlock; } + reply = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts), + &flow->id, info, false, ufid_flags); + if (IS_ERR(reply)) { + netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, + PTR_ERR(reply)); + reply = NULL; + } + + if (likely(reply)) { + err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, + reply, info->snd_portid, + info->snd_seq, 0, + OVS_FLOW_CMD_DEL, ufid_flags); + if (WARN_ON_ONCE(err < 0)) { + kfree_skb(reply); + reply = NULL; + } + } + /* Removal has to happen after ovs_flow_cmd_fill_info(), as it uses + * the flow->mask that can be scheduled to be freed by the + * ovs_flow_tbl_remove() and we're not holding the RCU read lock. + */ ovs_flow_tbl_remove(&dp->table, flow); ovs_unlock(); - reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts, - &flow->id, info, false, ufid_flags); - if (likely(reply)) { - if (!IS_ERR(reply)) { - rcu_read_lock(); /*To keep RCU checker happy. */ - err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, - reply, info->snd_portid, - info->snd_seq, 0, - OVS_FLOW_CMD_DEL, - ufid_flags); - rcu_read_unlock(); - if (WARN_ON_ONCE(err < 0)) { - kfree_skb(reply); - goto out_free; - } + if (likely(reply)) + ovs_notify(&dp_flow_genl_family, reply, info); - ovs_notify(&dp_flow_genl_family, reply, info); - } else { - netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, - PTR_ERR(reply)); - } - } - -out_free: ovs_flow_free(flow, true); return 0; unlock: @@ -1879,7 +1880,6 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info) /* Set up our datapath device. */ parms.name = nla_data(a[OVS_DP_ATTR_NAME]); parms.type = OVS_VPORT_TYPE_INTERNAL; - parms.options = NULL; parms.dp = dp; parms.port_no = OVSP_LOCAL; parms.upcall_portids = a[OVS_DP_ATTR_UPCALL_PID]; @@ -2195,10 +2195,6 @@ static int ovs_vport_cmd_fill_info(struct vport *vport, struct sk_buff *skb, if (ovs_vport_get_upcall_portids(vport, skb)) goto nla_put_failure; - err = ovs_vport_get_options(vport, skb); - if (err == -EMSGSIZE) - goto error; - genlmsg_end(skb, ovs_header); return 0; @@ -2206,7 +2202,6 @@ static int ovs_vport_cmd_fill_info(struct vport *vport, struct sk_buff *skb, rcu_read_unlock(); nla_put_failure: err = -EMSGSIZE; -error: genlmsg_cancel(skb, ovs_header); return err; } @@ -2233,12 +2228,6 @@ static size_t ovs_vport_cmd_msg_size(void) /* OVS_VPORT_ATTR_UPCALL_PID */ msgsize += nla_total_size(nr_cpu_ids * sizeof(u32)); - /* OVS_VPORT_ATTR_OPTIONS(OVS_TUNNEL_ATTR_DST_PORT + - * OVS_TUNNEL_ATTR_EXTENSION(OVS_VXLAN_EXT_GBP)) - */ - msgsize += nla_total_size(nla_total_size(sizeof(u16)) + - nla_total_size(nla_total_size(0))); - return msgsize; } @@ -2366,7 +2355,6 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) return -ENOMEM; ovs_lock(); -restart: dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex); err = -ENODEV; if (!dp) @@ -2390,7 +2378,6 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) } parms.name = nla_data(a[OVS_VPORT_ATTR_NAME]); - parms.options = a[OVS_VPORT_ATTR_OPTIONS]; parms.dp = dp; parms.port_no = port_no; parms.upcall_portids = a[OVS_VPORT_ATTR_UPCALL_PID]; @@ -2399,11 +2386,8 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) vport = new_vport(&parms); err = PTR_ERR(vport); - if (IS_ERR(vport)) { - if (err == -EAGAIN) - goto restart; + if (IS_ERR(vport)) goto exit_unlock_free; - } err = ovs_vport_cmd_fill_info(vport, reply, genl_info_net(info), info->snd_portid, info->snd_seq, 0, @@ -2452,12 +2436,11 @@ static int ovs_vport_cmd_set(struct sk_buff *skb, struct genl_info *info) } if (a[OVS_VPORT_ATTR_OPTIONS]) { - err = ovs_vport_set_options(vport, a[OVS_VPORT_ATTR_OPTIONS]); - if (err) - goto exit_unlock_free; + /* There are no vport types that support legacy options. */ + err = -EOPNOTSUPP; + goto exit_unlock_free; } - if (a[OVS_VPORT_ATTR_UPCALL_PID]) { struct nlattr *ids = a[OVS_VPORT_ATTR_UPCALL_PID]; @@ -2631,7 +2614,7 @@ static const struct nla_policy vport_policy[OVS_VPORT_ATTR_MAX + 1] = { [OVS_VPORT_ATTR_PORT_NO] = { .type = NLA_U32 }, [OVS_VPORT_ATTR_TYPE] = { .type = NLA_U32 }, [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NLA_UNSPEC }, - [OVS_VPORT_ATTR_OPTIONS] = { .type = NLA_NESTED }, + [OVS_VPORT_ATTR_OPTIONS] = { .type = NLA_NESTED }, /* Unused. */ [OVS_VPORT_ATTR_IFINDEX] = NLA_POLICY_MIN(NLA_S32, 0), [OVS_VPORT_ATTR_NETNSID] = { .type = NLA_S32 }, [OVS_VPORT_ATTR_UPCALL_STATS] = { .type = NLA_NESTED }, diff --git a/net/openvswitch/vport-geneve.c b/net/openvswitch/vport-geneve.c deleted file mode 100644 index cb5ea4424ffc..000000000000 --- a/net/openvswitch/vport-geneve.c +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (c) 2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_geneve_vport_ops; -/** - * struct geneve_port - Keeps track of open UDP ports - * @dst_port: destination port. - */ -struct geneve_port { - u16 dst_port; -}; - -static inline struct geneve_port *geneve_vport(const struct vport *vport) -{ - return vport_priv(vport); -} - -static int geneve_get_options(const struct vport *vport, - struct sk_buff *skb) -{ - struct geneve_port *geneve_port = geneve_vport(vport); - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, geneve_port->dst_port)) - return -EMSGSIZE; - return 0; -} - -static struct vport *geneve_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct geneve_port *geneve_port; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - u16 dst_port; - int err; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - dst_port = nla_get_u16(a); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(sizeof(struct geneve_port), - &ovs_geneve_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - geneve_port = geneve_vport(vport); - geneve_port->dst_port = dst_port; - - rtnl_lock(); - dev = geneve_dev_create_fb(net, parms->name, NET_NAME_USER, dst_port); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *geneve_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = geneve_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_geneve_vport_ops = { - .type = OVS_VPORT_TYPE_GENEVE, - .create = geneve_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = geneve_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_geneve_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_geneve_vport_ops); -} - -static void __exit ovs_geneve_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_geneve_vport_ops); -} - -module_init(ovs_geneve_tnl_init); -module_exit(ovs_geneve_tnl_exit); - -MODULE_DESCRIPTION("OVS: Geneve switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-5"); diff --git a/net/openvswitch/vport-gre.c b/net/openvswitch/vport-gre.c deleted file mode 100644 index 6cb5a697b396..000000000000 --- a/net/openvswitch/vport-gre.c +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2007-2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_gre_vport_ops; - -static struct vport *gre_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct net_device *dev; - struct vport *vport; - int err; - - vport = ovs_vport_alloc(0, &ovs_gre_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - rtnl_lock(); - dev = gretap_fb_dev_create(net, parms->name, NET_NAME_USER); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_PTR(err); - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -} - -static struct vport *gre_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = gre_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_gre_vport_ops = { - .type = OVS_VPORT_TYPE_GRE, - .create = gre_create, - .send = dev_queue_xmit, - .destroy = ovs_netdev_tunnel_destroy, -}; - -static int __init ovs_gre_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_gre_vport_ops); -} - -static void __exit ovs_gre_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_gre_vport_ops); -} - -module_init(ovs_gre_tnl_init); -module_exit(ovs_gre_tnl_exit); - -MODULE_DESCRIPTION("OVS: GRE switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-3"); diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index e7e8490a53d8..44808cd0fcff 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -73,7 +73,7 @@ static struct net_device *get_dpdev(const struct datapath *dp) return local->dev; } -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) +static struct vport *ovs_netdev_link(struct vport *vport) { int err; @@ -112,15 +112,12 @@ struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) error_master_upper_dev_unlink: netdev_upper_dev_unlink(vport->dev, get_dpdev(vport->dp)); error_put_unlock: - if (tunnel && vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); netdev_put(vport->dev, &vport->dev_tracker); rtnl_unlock(); error_free_vport: ovs_vport_free(vport); return ERR_PTR(err); } -EXPORT_SYMBOL_GPL(ovs_netdev_link); static struct vport *netdev_create(const struct vport_parms *parms) { @@ -152,7 +149,7 @@ static struct vport *netdev_create(const struct vport_parms *parms) goto error_put; } - return ovs_netdev_link(vport, false); + return ovs_netdev_link(vport); error_put: netdev_put(vport->dev, &vport->dev_tracker); error_free_vport: @@ -204,28 +201,6 @@ static void netdev_destroy(struct vport *vport) call_rcu(&vport->rcu, vport_netdev_free); } -void ovs_netdev_tunnel_destroy(struct vport *vport) -{ - rtnl_lock(); - if (netif_is_ovs_port(vport->dev)) - ovs_netdev_detach_dev(vport); - - /* We can be invoked by both explicit vport deletion and - * underlying netdev deregistration; delete the link only - * if it's not already shutting down. - */ - if (vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); - - /* We can't put the device reference yet, since it can still be in - * use, but rtnl_unlock()->netdev_run_todo() will block until all - * the references are released, so the RCU call must be before it. - */ - call_rcu(&vport->rcu, vport_netdev_free); - rtnl_unlock(); -} -EXPORT_SYMBOL_GPL(ovs_netdev_tunnel_destroy); - /* Returns null if this device is not attached to a datapath. */ struct vport *ovs_netdev_get_vport(struct net_device *dev) { diff --git a/net/openvswitch/vport-netdev.h b/net/openvswitch/vport-netdev.h index 6c0d7366f986..880506f8317e 100644 --- a/net/openvswitch/vport-netdev.h +++ b/net/openvswitch/vport-netdev.h @@ -13,11 +13,9 @@ struct vport *ovs_netdev_get_vport(struct net_device *dev); -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel); void ovs_netdev_detach_dev(struct vport *); int __init ovs_netdev_init(void); void ovs_netdev_exit(void); -void ovs_netdev_tunnel_destroy(struct vport *vport); #endif /* vport_netdev.h */ diff --git a/net/openvswitch/vport-vxlan.c b/net/openvswitch/vport-vxlan.c deleted file mode 100644 index c1b37b50d29e..000000000000 --- a/net/openvswitch/vport-vxlan.c +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2014 Nicira, Inc. - * Copyright (c) 2013 Cisco Systems, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_vxlan_netdev_vport_ops; - -static int vxlan_get_options(const struct vport *vport, struct sk_buff *skb) -{ - struct vxlan_dev *vxlan = netdev_priv(vport->dev); - __be16 dst_port = vxlan->cfg.dst_port; - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port))) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP) { - struct nlattr *exts; - - exts = nla_nest_start_noflag(skb, OVS_TUNNEL_ATTR_EXTENSION); - if (!exts) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP && - nla_put_flag(skb, OVS_VXLAN_EXT_GBP)) - return -EMSGSIZE; - - nla_nest_end(skb, exts); - } - - return 0; -} - -static const struct nla_policy exts_policy[OVS_VXLAN_EXT_MAX + 1] = { - [OVS_VXLAN_EXT_GBP] = { .type = NLA_FLAG, }, -}; - -static int vxlan_configure_exts(struct vport *vport, struct nlattr *attr, - struct vxlan_config *conf) -{ - struct nlattr *exts[OVS_VXLAN_EXT_MAX + 1]; - int err; - - if (nla_len(attr) < sizeof(struct nlattr)) - return -EINVAL; - - err = nla_parse_nested_deprecated(exts, OVS_VXLAN_EXT_MAX, attr, - exts_policy, NULL); - if (err < 0) - return err; - - if (exts[OVS_VXLAN_EXT_GBP]) - conf->flags |= VXLAN_F_GBP; - - return 0; -} - -static struct vport *vxlan_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - int err; - struct vxlan_config conf = { - .no_share = true, - .flags = VXLAN_F_COLLECT_METADATA | VXLAN_F_UDP_ZERO_CSUM6_RX, - /* Don't restrict the packets that can be sent by MTU */ - .mtu = IP_MAX_MTU, - }; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - conf.dst_port = htons(nla_get_u16(a)); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(0, &ovs_vxlan_netdev_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_EXTENSION); - if (a) { - err = vxlan_configure_exts(vport, a, &conf); - if (err) { - ovs_vport_free(vport); - goto error; - } - } - - rtnl_lock(); - dev = vxlan_dev_create(net, parms->name, NET_NAME_USER, &conf); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *vxlan_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = vxlan_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_vxlan_netdev_vport_ops = { - .type = OVS_VPORT_TYPE_VXLAN, - .create = vxlan_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = vxlan_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_vxlan_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_vxlan_netdev_vport_ops); -} - -static void __exit ovs_vxlan_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_vxlan_netdev_vport_ops); -} - -module_init(ovs_vxlan_tnl_init); -module_exit(ovs_vxlan_tnl_exit); - -MODULE_DESCRIPTION("OVS: VXLAN switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-4"); diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 12741485c939..083f7a8d9d4e 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -57,7 +57,7 @@ static struct hlist_head *hash_bucket(const struct net *net, const char *name) return &dev_table[hash & (VPORT_HASH_BUCKETS - 1)]; } -int __ovs_vport_ops_register(struct vport_ops *ops) +int ovs_vport_ops_register(struct vport_ops *ops) { int err = -EEXIST; struct vport_ops *o; @@ -73,7 +73,6 @@ int __ovs_vport_ops_register(struct vport_ops *ops) ovs_unlock(); return err; } -EXPORT_SYMBOL_GPL(__ovs_vport_ops_register); void ovs_vport_ops_unregister(struct vport_ops *ops) { @@ -81,7 +80,6 @@ void ovs_vport_ops_unregister(struct vport_ops *ops) list_del(&ops->list); ovs_unlock(); } -EXPORT_SYMBOL_GPL(ovs_vport_ops_unregister); /** * ovs_vport_locate - find a port that has already been created @@ -159,7 +157,6 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, kfree(vport); return ERR_PTR(err); } -EXPORT_SYMBOL_GPL(ovs_vport_alloc); /** * ovs_vport_free - uninitialize and free vport @@ -180,7 +177,6 @@ void ovs_vport_free(struct vport *vport) free_percpu(vport->upcall_stats); kfree(vport); } -EXPORT_SYMBOL_GPL(ovs_vport_free); static struct vport_ops *ovs_vport_lookup(const struct vport_parms *parms) { @@ -210,14 +206,9 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) if (ops) { struct hlist_head *bucket; - if (!try_module_get(ops->owner)) - return ERR_PTR(-EAFNOSUPPORT); - vport = ops->create(parms); - if (IS_ERR(vport)) { - module_put(ops->owner); + if (IS_ERR(vport)) return vport; - } bucket = hash_bucket(ovs_dp_get_net(vport->dp), ovs_vport_name(vport)); @@ -225,34 +216,7 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) return vport; } - /* Unlock to attempt module load and return -EAGAIN if load - * was successful as we need to restart the port addition - * workflow. - */ - ovs_unlock(); - request_module("vport-type-%d", parms->type); - ovs_lock(); - - if (!ovs_vport_lookup(parms)) - return ERR_PTR(-EAFNOSUPPORT); - else - return ERR_PTR(-EAGAIN); -} - -/** - * ovs_vport_set_options - modify existing vport device (for kernel callers) - * - * @vport: vport to modify. - * @options: New configuration. - * - * Modifies an existing device with the specified configuration (which is - * dependent on device type). ovs_mutex must be held. - */ -int ovs_vport_set_options(struct vport *vport, struct nlattr *options) -{ - if (!vport->ops->set_options) - return -EOPNOTSUPP; - return vport->ops->set_options(vport, options); + return ERR_PTR(-EAFNOSUPPORT); } /** @@ -266,7 +230,6 @@ int ovs_vport_set_options(struct vport *vport, struct nlattr *options) void ovs_vport_del(struct vport *vport) { hlist_del_rcu(&vport->hash_node); - module_put(vport->ops->owner); vport->ops->destroy(vport); } @@ -348,44 +311,6 @@ int ovs_vport_get_upcall_stats(struct vport *vport, struct sk_buff *skb) return 0; } -/** - * ovs_vport_get_options - retrieve device options - * - * @vport: vport from which to retrieve the options. - * @skb: sk_buff where options should be appended. - * - * Retrieves the configuration of the given device, appending an - * %OVS_VPORT_ATTR_OPTIONS attribute that in turn contains nested - * vport-specific attributes to @skb. - * - * Returns 0 if successful, -EMSGSIZE if @skb has insufficient room, or another - * negative error code if a real error occurred. If an error occurs, @skb is - * left unmodified. - * - * Must be called with ovs_mutex or rcu_read_lock. - */ -int ovs_vport_get_options(const struct vport *vport, struct sk_buff *skb) -{ - struct nlattr *nla; - int err; - - if (!vport->ops->get_options) - return 0; - - nla = nla_nest_start_noflag(skb, OVS_VPORT_ATTR_OPTIONS); - if (!nla) - return -EMSGSIZE; - - err = vport->ops->get_options(vport, skb); - if (err) { - nla_nest_cancel(skb, nla); - return err; - } - - nla_nest_end(skb, nla); - return 0; -} - /** * ovs_vport_set_upcall_portids - set upcall portids of @vport. * diff --git a/net/openvswitch/vport.h b/net/openvswitch/vport.h index 9f67b9dd49f9..930f1ccc8558 100644 --- a/net/openvswitch/vport.h +++ b/net/openvswitch/vport.h @@ -34,9 +34,6 @@ void ovs_vport_get_stats(struct vport *, struct ovs_vport_stats *); int ovs_vport_get_upcall_stats(struct vport *vport, struct sk_buff *skb); -int ovs_vport_set_options(struct vport *, struct nlattr *options); -int ovs_vport_get_options(const struct vport *, struct sk_buff *); - int ovs_vport_set_upcall_portids(struct vport *, const struct nlattr *pids); int ovs_vport_get_upcall_portids(const struct vport *, struct sk_buff *); u32 ovs_vport_find_upcall_portid(const struct vport *, struct sk_buff *); @@ -92,8 +89,6 @@ struct vport { * * @name: New vport's name. * @type: New vport's type. - * @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if - * none was supplied. * @desired_ifindex: New vport's ifindex. * @dp: New vport's datapath. * @port_no: New vport's port number. @@ -104,7 +99,6 @@ struct vport_parms { const char *name; enum ovs_vport_type type; int desired_ifindex; - struct nlattr *options; /* For ovs_vport_alloc(). */ struct datapath *dp; @@ -120,14 +114,8 @@ struct vport_parms { * a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value. * @destroy: Destroys a vport. Must call vport_free() on the vport but not * before an RCU grace period has elapsed. - * @set_options: Modify the configuration of an existing vport. May be %NULL - * if modification is not supported. - * @get_options: Appends vport-specific attributes for the configuration of an - * existing vport to a &struct sk_buff. May be %NULL for a vport that does not - * have any configuration. * @send: Send a packet on the device. * zero for dropped packets or negative for error. - * @owner: Module that implements this vport type. * @list: List entry in the global list of vport types. */ struct vport_ops { @@ -137,11 +125,7 @@ struct vport_ops { struct vport *(*create)(const struct vport_parms *); void (*destroy)(struct vport *); - int (*set_options)(struct vport *, struct nlattr *); - int (*get_options)(const struct vport *, struct sk_buff *); - int (*send)(struct sk_buff *skb); - struct module *owner; struct list_head list; }; @@ -205,12 +189,7 @@ static inline const char *ovs_vport_name(struct vport *vport) return vport->dev->name; } -int __ovs_vport_ops_register(struct vport_ops *ops); -#define ovs_vport_ops_register(ops) \ - ({ \ - (ops)->owner = THIS_MODULE; \ - __ovs_vport_ops_register(ops); \ - }) +int ovs_vport_ops_register(struct vport_ops *ops); void ovs_vport_ops_unregister(struct vport_ops *ops); void ovs_vport_send(struct vport *vport, struct sk_buff *skb, u8 mac_proto); diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 31b29e3ca7bc..bd1cdd00edfa 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -1080,17 +1080,11 @@ static int pep_setsockopt(struct sock *sk, int level, int optname, return err; } -static int pep_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_pep_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct pep_sock *pn = pep_sk(sk); int len, val; - if (level != SOL_PNPIPE) - return -ENOPROTOOPT; - if (get_user(len, optlen)) - return -EFAULT; - switch (optname) { case PNPIPE_ENCAP: val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE; @@ -1114,11 +1108,33 @@ static int pep_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - len = min_t(unsigned int, sizeof(int), len); - if (put_user(len, optlen)) + len = umin(sizeof(int), opt->optlen); + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; - if (put_user(val, (int __user *) optval)) + return 0; +} + +static int pep_getsockopt(struct sock *sk, int level, int optname, + char __user *optval, int __user *optlen) +{ + sockopt_t opt; + int err; + + if (level != SOL_PNPIPE) + return -ENOPROTOOPT; + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_pep_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) return -EFAULT; + return 0; } diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 8b2f178e317c..c9c1a8826b7f 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -231,7 +231,7 @@ static void psp_write_headers(struct net *net, struct sk_buff *skb, __be32 spi, uh->source = udp_flow_src_port(net, skb, 0, 0, false); } uh->check = 0; - uh->len = htons(udp_len); + udp_set_len(uh, udp_len); psph->nexthdr = IPPROTO_TCP; psph->hdrlen = PSP_HDRLEN_NOOPT; diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index 43b066353c65..f91665748dde 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -533,7 +533,8 @@ int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info) } /* Check if device is already associated with a PSP device */ - if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) { + if (unrcu_pointer(cmpxchg(&assoc_dev->psp_dev, NULL, + RCU_INITIALIZER(psd)))) { NL_SET_ERR_MSG(info->extack, "Device already associated with a PSP device"); err = -EBUSY; diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index 07dc4cf741f3..1a2a6b7516b0 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -96,7 +96,7 @@ static void psp_assoc_free(struct work_struct *work) struct psp_dev *psd = pas->psd; mutex_lock(&psd->lock); - if (psd->ops) + if (psp_dev_is_registered(psd)) psp_dev_tx_key_del(psd, pas); mutex_unlock(&psd->lock); psp_dev_put(psd); diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c index 9594ea245f7f..db7e92e7bd29 100644 --- a/net/rds/ib_rdma.c +++ b/net/rds/ib_rdma.c @@ -251,9 +251,7 @@ void __rds_ib_teardown_mr(struct rds_ib_mr *ibmr) /* FIXME we need a way to tell a r/w MR * from a r/o MR */ - WARN_ON(!page->mapping && irqs_disabled()); - set_page_dirty(page); - put_page(page); + unpin_user_pages_dirty_lock(&page, 1, true); } kfree(ibmr->sg); diff --git a/net/rds/info.c b/net/rds/info.c index 21b32eb16559..31e7ad108459 100644 --- a/net/rds/info.c +++ b/net/rds/info.c @@ -32,6 +32,7 @@ */ #include #include +#include #include #include #include @@ -68,6 +69,7 @@ struct rds_info_iterator { unsigned long offset; }; +DEFINE_STATIC_SRCU(rds_info_srcu); static DEFINE_SPINLOCK(rds_info_lock); static rds_info_func rds_info_funcs[RDS_INFO_LAST - RDS_INFO_FIRST + 1]; @@ -78,8 +80,11 @@ void rds_info_register_func(int optname, rds_info_func func) BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST); spin_lock(&rds_info_lock); - BUG_ON(rds_info_funcs[offset]); - rds_info_funcs[offset] = func; + if (WARN_ON_ONCE(rds_info_funcs[offset])) { + spin_unlock(&rds_info_lock); + return; + } + WRITE_ONCE(rds_info_funcs[offset], func); spin_unlock(&rds_info_lock); } EXPORT_SYMBOL_GPL(rds_info_register_func); @@ -91,9 +96,13 @@ void rds_info_deregister_func(int optname, rds_info_func func) BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST); spin_lock(&rds_info_lock); - BUG_ON(rds_info_funcs[offset] != func); - rds_info_funcs[offset] = NULL; + if (WARN_ON_ONCE(rds_info_funcs[offset] != func)) { + spin_unlock(&rds_info_lock); + return; + } + WRITE_ONCE(rds_info_funcs[offset], NULL); spin_unlock(&rds_info_lock); + synchronize_srcu(&rds_info_srcu); } EXPORT_SYMBOL_GPL(rds_info_deregister_func); @@ -162,6 +171,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) rds_info_func func; struct page **pages = NULL; size_t offset0 = 0; + int srcu_idx; int npages = 0; int ret; int len; @@ -214,8 +224,10 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) rdsdebug("len %d nr_pages %lu\n", len, nr_pages); call_func: - func = rds_info_funcs[optname - RDS_INFO_FIRST]; + srcu_idx = srcu_read_lock(&rds_info_srcu); + func = READ_ONCE(rds_info_funcs[optname - RDS_INFO_FIRST]); if (!func) { + srcu_read_unlock(&rds_info_srcu, srcu_idx); ret = -ENOPROTOOPT; goto out; } @@ -225,6 +237,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) iter.offset = offset0; func(sock, len, &iter, &lens); + srcu_read_unlock(&rds_info_srcu, srcu_idx); BUG_ON(lens.each == 0); total = lens.nr * lens.each; diff --git a/net/rds/loop.c b/net/rds/loop.c index ac9295a766b1..e6b0750bbeda 100644 --- a/net/rds/loop.c +++ b/net/rds/loop.c @@ -89,7 +89,11 @@ static int rds_loop_xmit(struct rds_connection *conn, struct rds_message *rm, BUG_ON(hdr_off || sg || off); - rds_inc_init(&rm->m_inc, conn, &conn->c_laddr); + /* rds_send_queue_rm() stored the connection path in this embedded + * inc; use the path init so the re-initialization keeps the field + * valid instead of discarding it. + */ + rds_inc_path_init(&rm->m_inc, &conn->c_path[0], &conn->c_laddr); /* For the embedded inc. Matching put is in loop_inc_free() */ rds_message_addref(rm); diff --git a/net/rds/message.c b/net/rds/message.c index 7feb0eb6537d..f25f2592586f 100644 --- a/net/rds/message.c +++ b/net/rds/message.c @@ -182,6 +182,19 @@ static void rds_message_purge(struct rds_message *rm) kref_put(&rm->atomic.op_rdma_mr->r_kref, __rds_put_mr_final); } +static void rds_message_unpin_worker(struct work_struct *work) +{ + struct rds_message *rm = container_of(work, struct rds_message, + m_unpin_work); + + if (rm->rdma.op_unpin_deferred) + rds_rdma_op_unpin_pages(&rm->rdma); + if (rm->atomic.op_unpin_deferred) + rds_atomic_op_unpin_page(&rm->atomic); + + kfree(rm); +} + void rds_message_put(struct rds_message *rm) { rdsdebug("put rm %p ref %d\n", rm, refcount_read(&rm->m_refcount)); @@ -189,8 +202,21 @@ void rds_message_put(struct rds_message *rm) if (refcount_dec_and_test(&rm->m_refcount)) { BUG_ON(!list_empty(&rm->m_sock_item)); BUG_ON(!list_empty(&rm->m_conn_item)); + rds_message_purge(rm); + /* A final put in atomic context cannot dirty the ops' + * user pages on unpin, so rds_rdma_free_op() and + * rds_atomic_free_op() deferred it. Finish the unpin, + * and the free, from process context. + */ + if (rm->rdma.op_unpin_deferred || + rm->atomic.op_unpin_deferred) { + INIT_WORK(&rm->m_unpin_work, rds_message_unpin_worker); + queue_work(rds_wq, &rm->m_unpin_work); + return; + } + kfree(rm); } } diff --git a/net/rds/rdma.c b/net/rds/rdma.c index 61fb6e45281b..078090d292fa 100644 --- a/net/rds/rdma.c +++ b/net/rds/rdma.c @@ -117,6 +117,7 @@ void __rds_put_mr_final(struct kref *kref) struct rds_mr *mr = container_of(kref, struct rds_mr, r_kref); rds_destroy_mr(mr); + sock_put(rds_rs_to_sk(mr->r_sock)); kfree(mr); } @@ -243,7 +244,11 @@ static int __rds_rdma_map(struct rds_sock *rs, struct rds_get_mr_args *args, kref_init(&mr->r_kref); RB_CLEAR_NODE(&mr->r_rb_node); mr->r_trans = rs->rs_transport; + /* The MR can outlive its socket: a socket reference is held + * until the final kref is dropped in __rds_put_mr_final(). + */ mr->r_sock = rs; + sock_hold(rds_rs_to_sk(rs)); if (args->flags & RDS_RDMA_USE_ONCE) mr->r_use_once = 1; @@ -483,22 +488,36 @@ void rds_rdma_unuse(struct rds_sock *rs, u32 r_key, int force) kref_put(&mr->r_kref, __rds_put_mr_final); } -void rds_rdma_free_op(struct rm_rdma_op *ro) +void rds_rdma_op_unpin_pages(struct rm_rdma_op *ro) { unsigned int i; + for (i = 0; i < ro->op_nents; i++) { + struct page *page = sg_page(&ro->op_sg[i]); + + /* Mark page dirty if it was possibly modified, which + * is the case for a RDMA_READ which copies from remote + * to local memory + */ + unpin_user_pages_dirty_lock(&page, 1, !ro->op_write); + } +} + +void rds_rdma_free_op(struct rm_rdma_op *ro) +{ if (ro->op_odp_mr) { kref_put(&ro->op_odp_mr->r_kref, __rds_put_mr_final); + } else if (in_task() || ro->op_write) { + /* An RDMA write's pages are only read by the remote + * side; unpinning without dirtying does not sleep. + */ + rds_rdma_op_unpin_pages(ro); } else { - for (i = 0; i < ro->op_nents; i++) { - struct page *page = sg_page(&ro->op_sg[i]); - - /* Mark page dirty if it was possibly modified, which - * is the case for a RDMA_READ which copies from remote - * to local memory - */ - unpin_user_pages_dirty_lock(&page, 1, !ro->op_write); - } + /* Dirtying the pages on unpin can sleep; leave them + * pinned and have rds_message_put() finish the unpin + * from process context. + */ + ro->op_unpin_deferred = 1; } kfree(ro->op_notifier); @@ -507,7 +526,7 @@ void rds_rdma_free_op(struct rm_rdma_op *ro) ro->op_odp_mr = NULL; } -void rds_atomic_free_op(struct rm_atomic_op *ao) +void rds_atomic_op_unpin_page(struct rm_atomic_op *ao) { struct page *page = sg_page(ao->op_sg); @@ -515,6 +534,19 @@ void rds_atomic_free_op(struct rm_atomic_op *ao) * is the case for a RDMA_READ which copies from remote * to local memory */ unpin_user_pages_dirty_lock(&page, 1, true); +} + +void rds_atomic_free_op(struct rm_atomic_op *ao) +{ + if (in_task()) { + rds_atomic_op_unpin_page(ao); + } else { + /* Dirtying the page on unpin can sleep; leave it + * pinned and have rds_message_put() finish the unpin + * from process context. + */ + ao->op_unpin_deferred = 1; + } kfree(ao->op_notifier); ao->op_notifier = NULL; @@ -732,7 +764,12 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm, RB_CLEAR_NODE(&local_odp_mr->r_rb_node); kref_init(&local_odp_mr->r_kref); local_odp_mr->r_trans = rs->rs_transport; + /* The MR can outlive its socket: a socket + * reference is held until the final kref is + * dropped in __rds_put_mr_final(). + */ local_odp_mr->r_sock = rs; + sock_hold(rds_rs_to_sk(rs)); local_odp_mr->r_trans_private = rs->rs_transport->get_mr( NULL, 0, rs, &local_odp_mr->r_key, NULL, @@ -741,7 +778,9 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm, ret = PTR_ERR(local_odp_mr->r_trans_private); rdsdebug("get_mr ret %d %p\"", ret, local_odp_mr->r_trans_private); - kfree(local_odp_mr); + local_odp_mr->r_trans_private = NULL; + kref_put(&local_odp_mr->r_kref, + __rds_put_mr_final); ret = -EOPNOTSUPP; goto out_pages; } diff --git a/net/rds/rds.h b/net/rds/rds.h index 6e0790e4b570..2db49573dacd 100644 --- a/net/rds/rds.h +++ b/net/rds/rds.h @@ -320,7 +320,10 @@ struct rds_mr { unsigned int r_invalidate:1; unsigned int r_write:1; - struct rds_sock *r_sock; /* back pointer to the socket that owns us */ + struct rds_sock *r_sock; /* socket that owns us; counted + * reference, dropped by + * __rds_put_mr_final() + */ struct rds_transport *r_trans; void *r_trans_private; }; @@ -445,6 +448,12 @@ struct rds_message { void *m_final_op; + /* Unpins the ops' user pages and frees the message from + * process context when the final put happens in atomic + * context: dirtying the pages on unpin can sleep. + */ + struct work_struct m_unpin_work; + struct { struct rm_atomic_op { int op_type; @@ -468,6 +477,7 @@ struct rds_message { unsigned int op_mapped:1; unsigned int op_silent:1; unsigned int op_active:1; + unsigned int op_unpin_deferred:1; struct scatterlist *op_sg; struct rds_notifier *op_notifier; @@ -483,6 +493,7 @@ struct rds_message { unsigned int op_mapped:1; unsigned int op_silent:1; unsigned int op_active:1; + unsigned int op_unpin_deferred:1; unsigned int op_bytes; unsigned int op_nents; unsigned int op_count; @@ -972,6 +983,8 @@ int rds_cmsg_rdma_map(struct rds_sock *rs, struct rds_message *rm, struct cmsghdr *cmsg); void rds_rdma_free_op(struct rm_rdma_op *ro); void rds_atomic_free_op(struct rm_atomic_op *ao); +void rds_rdma_op_unpin_pages(struct rm_rdma_op *ro); +void rds_atomic_op_unpin_page(struct rm_atomic_op *ao); void rds_rdma_send_complete(struct rds_message *rm, int wc_status); void rds_atomic_send_complete(struct rds_message *rm, int wc_status); int rds_cmsg_atomic(struct rds_sock *rs, struct rds_message *rm, diff --git a/net/rds/recv.c b/net/rds/recv.c index cf3884d87931..6204e577a90a 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -47,6 +47,7 @@ void rds_inc_init(struct rds_incoming *inc, struct rds_connection *conn, refcount_set(&inc->i_refcount, 1); INIT_LIST_HEAD(&inc->i_item); inc->i_conn = conn; + inc->i_conn_path = NULL; inc->i_saddr = *saddr; inc->i_usercopy.rdma_cookie = 0; inc->i_usercopy.rx_tstamp = ktime_set(0, 0); @@ -65,6 +66,8 @@ void rds_inc_path_init(struct rds_incoming *inc, struct rds_conn_path *cp, inc->i_saddr = *saddr; inc->i_usercopy.rdma_cookie = 0; inc->i_usercopy.rx_tstamp = ktime_set(0, 0); + + memset(inc->i_rx_lat_trace, 0, sizeof(inc->i_rx_lat_trace)); } EXPORT_SYMBOL_GPL(rds_inc_path_init); diff --git a/net/rds/send.c b/net/rds/send.c index 68be1bf0e0ad..15a1b97f13e7 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -200,6 +200,14 @@ int rds_send_xmit(struct rds_conn_path *cp) restart: batch_count = 0; + /* The drop processing after over_batch relies on + * rds_send_remove_from_sock() emptying to_be_dropped entry by + * entry; warn if that post-condition ever stops holding, and + * re-initialize the list head. + */ + WARN_ON_ONCE(!list_empty(&to_be_dropped)); + INIT_LIST_HEAD(&to_be_dropped); + /* * sendmsg calls here after having queued its message on the send * queue. We only have one task feeding the connection at a time. If @@ -339,9 +347,21 @@ int rds_send_xmit(struct rds_conn_path *cp) (rm->rdma.op_active && test_bit(RDS_MSG_RETRANSMITTED, &rm->m_flags))) { spin_lock_irqsave(&cp->cp_lock, flags); - if (test_and_clear_bit(RDS_MSG_ON_CONN, &rm->m_flags)) - list_move(&rm->m_conn_item, &to_be_dropped); - spin_unlock_irqrestore(&cp->cp_lock, flags); + if (test_and_clear_bit(RDS_MSG_ON_CONN, + &rm->m_flags)) { + /* our ref is put after the batch */ + list_move(&rm->m_conn_item, + &to_be_dropped); + spin_unlock_irqrestore(&cp->cp_lock, + flags); + } else { + /* already off the conn list; drop + * the ref taken above ourselves + */ + spin_unlock_irqrestore(&cp->cp_lock, + flags); + rds_message_put(rm); + } continue; } @@ -971,11 +991,8 @@ static int rds_rm_size(struct msghdr *msg, int num_sgs, return -EINVAL; if (vct->indx >= vct->len) { vct->len += vct->incr; - tmp_iov = - krealloc(vct->vec, - vct->len * - sizeof(struct rds_iov_vector), - GFP_KERNEL); + tmp_iov = krealloc_array(vct->vec, vct->len, + sizeof(*vct->vec), GFP_KERNEL); if (!tmp_iov) { vct->len -= vct->incr; return -ENOMEM; diff --git a/net/sched/act_csum.c b/net/sched/act_csum.c index 078d3a27130b..5fff52a8ca90 100644 --- a/net/sched/act_csum.c +++ b/net/sched/act_csum.c @@ -276,7 +276,7 @@ static int tcf_csum_ipv4_udp(struct sk_buff *skb, unsigned int ihl, return 0; iph = ip_hdr(skb); - ul = ntohs(udph->len); + ul = udp_get_len_short(udph); if (udplite || udph->check) { @@ -334,7 +334,7 @@ static int tcf_csum_ipv6_udp(struct sk_buff *skb, unsigned int ihl, return 0; ip6h = ipv6_hdr(skb); - ul = ntohs(udph->len); + ul = udp_get_len_short(udph); udph->check = 0; diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index e250969c84ac..dc0b5724785d 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -782,7 +782,7 @@ static bool tcf_ct_skb_nfct_cached(struct net *net, struct sk_buff *skb, return true; drop_ct: - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); return false; @@ -1008,7 +1008,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, qdisc_skb_cb(skb)->post_ct = false; ct = nf_ct_get(skb, &ctinfo); if (ct) { - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); } @@ -1055,7 +1055,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, /* Associate skb with specified zone. */ if (tmpl) { - nf_conntrack_put(skb_nfct(skb)); + nf_reset_ct(skb); nf_conntrack_get(&tmpl->ct_general); nf_ct_set(skb, tmpl, IP_CT_NEW); } @@ -1548,8 +1548,8 @@ static int tcf_ct_dump_helper(struct sk_buff *skb, return 0; if (nla_put_string(skb, TCA_CT_HELPER_NAME, helper->name) || - nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->tuple.src.l3num) || - nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->tuple.dst.protonum)) + nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->nfproto) || + nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->l4proto)) return -1; return 0; diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 4e6a2812a4f3..3271963c945d 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -2248,6 +2248,12 @@ static bool is_ingress_or_clsact(struct tcf_block *block, struct Qdisc *q) return tcf_block_shared(block) || (q && !!(q->flags & TCQ_F_INGRESS)); } +enum tcf_tp_insert_state { + TP_NOT_CREATED = 0, /* did not create and insert a new tp */ + TP_CREATED, /* created and inserted a new tp */ + TP_NOT_OWNED, /* created a proto but failed to insert */ +}; + static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, struct netlink_ext_ack *extack) { @@ -2268,12 +2274,12 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, unsigned long cl; void *fh; int err; - int tp_created; + enum tcf_tp_insert_state tp_state; bool rtnl_held = false; u32 flags; replay: - tp_created = 0; + tp_state = TP_NOT_CREATED; err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX, rtm_tca_policy, extack); @@ -2395,13 +2401,15 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, goto errout_tp; } - tp_created = 1; + tp_state = TP_CREATED; tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio, rtnl_held); if (IS_ERR(tp)) { err = PTR_ERR(tp); goto errout_tp; } + if (tp != tp_new) + tp_state = TP_NOT_OWNED; } else { mutex_unlock(&chain->filter_chain_lock); } @@ -2455,13 +2463,13 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, } errout: - if (err && tp_created) + if (err && tp_state == TP_CREATED) tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL); errout_tp: if (chain) { if (tp && !IS_ERR(tp)) tcf_proto_put(tp, rtnl_held, NULL); - if (!tp_created) + if (tp_state == TP_NOT_CREATED) tcf_chain_put(chain); } tcf_block_release(q, block, rtnl_held); diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index ae991fc25b43..6ced7008ef5c 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -679,12 +679,14 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, rcu_read_lock(); if (cl >= TC_H_MIN_PRIORITY) { struct net_device *dev = qdisc_dev(sch); - struct netdev_tc_txq tc = dev->tc_to_txq[cl & TC_BITMASK]; + struct netdev_tc_txq tc; struct gnet_stats_queue qstats = {0}; struct gnet_stats_basic_sync bstats; u32 qlen = 0; int i; + tc.combined = READ_ONCE(dev->tc_to_txq[cl & TC_BITMASK].combined); + gnet_stats_basic_sync_init(&bstats); for (i = tc.offset; i < tc.offset + tc.count; i++) { diff --git a/net/sched/sch_mqprio_lib.c b/net/sched/sch_mqprio_lib.c index b3a5572c167b..888935e34d43 100644 --- a/net/sched/sch_mqprio_lib.c +++ b/net/sched/sch_mqprio_lib.c @@ -105,11 +105,15 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt int tc, num_tc = netdev_get_num_tc(dev); qopt->num_tc = num_tc; - memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map)); + for (tc = 0; tc <= TC_BITMASK; tc++) + qopt->prio_tc_map[tc] = netdev_get_prio_tc_map(dev, tc); for (tc = 0; tc < num_tc; tc++) { - qopt->count[tc] = dev->tc_to_txq[tc].count; - qopt->offset[tc] = dev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[tc].combined); + qopt->count[tc] = res.count; + qopt->offset[tc] = res.offset; } } EXPORT_SYMBOL_GPL(mqprio_qopt_reconstruct); diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 299234a5f0fe..39ac5b97aa3a 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -762,12 +762,13 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, static void taprio_next_tc_txq(struct net_device *dev, int tc, int *txq) { - int offset = dev->tc_to_txq[tc].offset; - int count = dev->tc_to_txq[tc].count; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[tc].combined); (*txq)++; - if (*txq == offset + count) - *txq = offset; + if (*txq == res.offset + res.count) + *txq = res.offset; } /* Prioritize higher traffic classes, and select among TXQs belonging to the @@ -1184,7 +1185,7 @@ static int taprio_parse_mqprio_opt(struct net_device *dev, bool allow_overlapping_txqs = TXTIME_ASSIST_IS_ENABLED(taprio_flags); if (!qopt) { - if (!dev->num_tc) { + if (!netdev_get_num_tc(dev)) { NL_SET_ERR_MSG(extack, "'mqprio' configuration is necessary"); return -EINVAL; } @@ -1438,18 +1439,18 @@ static void taprio_offload_config_changed(struct taprio_sched *q) static u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask) { + int num_tc = netdev_get_num_tc(dev); u32 i, queue_mask = 0; - for (i = 0; i < dev->num_tc; i++) { - u32 offset, count; + for (i = 0; i < num_tc; i++) { + struct netdev_tc_txq res; if (!(tc_mask & BIT(i))) continue; - offset = dev->tc_to_txq[i].offset; - count = dev->tc_to_txq[i].count; + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); - queue_mask |= GENMASK(offset + count - 1, offset); + queue_mask |= GENMASK(res.offset + res.count - 1, res.offset); } return queue_mask; @@ -1799,16 +1800,20 @@ static int taprio_mqprio_cmp(const struct net_device *dev, { int i; - if (!mqprio || mqprio->num_tc != dev->num_tc) + if (!mqprio || mqprio->num_tc != netdev_get_num_tc(dev)) return -1; - for (i = 0; i < mqprio->num_tc; i++) - if (dev->tc_to_txq[i].count != mqprio->count[i] || - dev->tc_to_txq[i].offset != mqprio->offset[i]) + for (i = 0; i < mqprio->num_tc; i++) { + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); + if (res.count != mqprio->count[i] || + res.offset != mqprio->offset[i]) return -1; + } for (i = 0; i <= TC_BITMASK; i++) - if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i]) + if (netdev_get_prio_tc_map(dev, i) != mqprio->prio_tc_map[i]) return -1; return 0; diff --git a/net/sctp/associola.c b/net/sctp/associola.c index 5be0bed2685e..c0512c827d0f 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -1009,6 +1009,10 @@ static void sctp_assoc_bh_rcv(struct work_struct *work) if (next_hdr->type == SCTP_CID_COOKIE_ECHO) { chunk->auth_chunk = skb_clone(chunk->skb, GFP_ATOMIC); + if (!chunk->auth_chunk) { + chunk->pdiscard = 1; + continue; + } chunk->auth = 1; continue; } diff --git a/net/sctp/auth.c b/net/sctp/auth.c index cc4229ee116d..cbeb8a87a17a 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -688,9 +688,9 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) * zero (as shown in Figure 6) followed by all chunks that are placed * after the AUTH chunk in the SCTP packet. */ -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp) +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp) { struct sctp_auth_bytes *asoc_key; __u16 key_id, hmac_id; @@ -711,7 +711,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, /* ep_key can't be NULL here */ asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); if (!asoc_key) - return; + return -ENOMEM; free_key = 1; } @@ -729,6 +729,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, if (free_key) sctp_auth_key_put(asoc_key); + + return 0; } /* API Helpers */ diff --git a/net/sctp/endpointola.c b/net/sctp/endpointola.c index dfb1719275db..a15b599b20b7 100644 --- a/net/sctp/endpointola.c +++ b/net/sctp/endpointola.c @@ -368,6 +368,10 @@ static void sctp_endpoint_bh_rcv(struct work_struct *work) if (next_hdr->type == SCTP_CID_COOKIE_ECHO) { chunk->auth_chunk = skb_clone(chunk->skb, GFP_ATOMIC); + if (!chunk->auth_chunk) { + chunk->pdiscard = 1; + continue; + } chunk->auth = 1; continue; } diff --git a/net/sctp/output.c b/net/sctp/output.c index 23e96305cad7..3d7ead9d40e1 100644 --- a/net/sctp/output.c +++ b/net/sctp/output.c @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, } if (auth) { - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, - packet->auth->shkey, gfp); + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, + packet->auth->shkey, gfp)) { + sctp_chunk_free(packet->auth); + packet->auth = NULL; + if (gso) + kfree_skb(nskb); + return -ENOMEM; + } /* free auth if no more chunks, or add it back */ if (list_empty(&packet->chunk_list)) sctp_chunk_free(packet->auth); @@ -619,7 +625,7 @@ int sctp_packet_transmit(struct sctp_packet *packet, gfp_t gfp) /* pack up chunks */ pkt_count = sctp_packet_pack(packet, head, gso, gfp); - if (!pkt_count) { + if (pkt_count <= 0) { kfree_skb(head); goto out; } diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 424f10a6fdba..94716406d602 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1332,6 +1332,10 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, sctp_outq_uncork(&asoc->outqueue, gfp); local_cork = 0; } + /* No chunk left in this packet may use this asoc. */ + if (event_type == SCTP_EVENT_T_CHUNK && + chunk->asoc == asoc) + chunk->pdiscard = 1; /* Delete the current association. */ sctp_cmd_delete_tcb(commands, asoc); asoc = NULL; diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 3a8e16b29660..c701cff6ea67 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -4455,9 +4455,12 @@ static enum sctp_ierror sctp_sf_authenticate( memset(digest, 0, sig_len); - sctp_auth_calculate_hmac(asoc, chunk->skb, - (struct sctp_auth_chunk *)chunk->chunk_hdr, - sh_key, GFP_ATOMIC); + if (sctp_auth_calculate_hmac(asoc, chunk->skb, + (struct sctp_auth_chunk *)chunk->chunk_hdr, + sh_key, GFP_ATOMIC)) { + kfree(save_digest); + return SCTP_IERROR_NOMEM; + } /* Discard the packet if the digests do not match */ if (crypto_memneq(save_digest, digest, sig_len)) { diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c index fca840484ebf..2b94c211427d 100644 --- a/net/sctp/sysctl.c +++ b/net/sctp/sysctl.c @@ -92,7 +92,7 @@ static struct ctl_table sctp_table[] = { #define SCTP_PF_RETRANS_IDX 2 #define SCTP_PS_RETRANS_IDX 3 -static struct ctl_table sctp_net_table[] = { +static const struct ctl_table sctp_net_table[] = { [SCTP_RTO_MIN_IDX] = { .procname = "rto_min", .data = &init_net.sctp.rto_min, diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index 00403175b740..e9f93b3ab435 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -409,13 +409,13 @@ void smc_sk_init(struct net *net, struct sock *sk, int protocol) "sk_lock-AF_SMC", &smc_key); spin_lock_init(&smc->accept_q_lock); spin_lock_init(&smc->conn.send_lock); - sk->sk_prot->hash(sk); mutex_init(&smc->clcsock_release_lock); smc_init_saved_callbacks(smc); smc->limit_smc_hs = net->smc.limit_smc_hs; smc->use_fallback = false; /* assume rdma capability first */ smc->fallback_rsn = 0; smc_close_init(smc); + sk->sk_prot->hash(sk); } static struct sock *smc_sock_alloc(struct net *net, struct socket *sock, @@ -3233,7 +3233,8 @@ int smc_ioctl(struct socket *sock, unsigned int cmd, return -EINVAL; } if (smc->sk.sk_state == SMC_INIT || - smc->sk.sk_state == SMC_CLOSED) + smc->sk.sk_state == SMC_CLOSED || + !READ_ONCE(smc->conn.sndbuf_desc)) answ = 0; else answ = smc->conn.sndbuf_desc->len - diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index b4208cb186c5..181647982490 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1209,14 +1209,16 @@ static void smcd_buf_detach(struct smc_connection *conn) { struct smcd_dev *smcd = conn->lgr->smcd; u64 peer_token = conn->peer_token; + struct smc_buf_desc *buf_desc; if (!conn->sndbuf_desc) return; smc_ism_detach_dmb(smcd, peer_token); - kfree(conn->sndbuf_desc); + buf_desc = conn->sndbuf_desc; conn->sndbuf_desc = NULL; + kfree(buf_desc); } static void smc_buf_unuse(struct smc_connection *conn, @@ -1268,11 +1270,10 @@ void smc_conn_free(struct smc_connection *conn) goto lgr_put; if (lgr->is_smcd) { - if (!list_empty(&lgr->list)) - smc_ism_unset_conn(conn); + smc_ism_unset_conn(conn); + tasklet_kill(&conn->rx_tsklet); if (smc_ism_support_dmb_nocopy(lgr->smcd)) smcd_buf_detach(conn); - tasklet_kill(&conn->rx_tsklet); } else { smc_cdc_wait_pend_tx_wr(conn); if (current_work() != &conn->abort_work) @@ -1525,12 +1526,12 @@ static void smc_conn_kill(struct smc_connection *conn, bool soft) smc_sk_wake_ups(smc); if (conn->lgr->is_smcd) { smc_ism_unset_conn(conn); - if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) - smcd_buf_detach(conn); if (soft) tasklet_kill(&conn->rx_tsklet); else tasklet_unlock_wait(&conn->rx_tsklet); + if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) + smcd_buf_detach(conn); } else { smc_cdc_wait_pend_tx_wr(conn); } diff --git a/net/smc/smc_sysctl.c b/net/smc/smc_sysctl.c index b1efed546243..09dad48337f6 100644 --- a/net/smc/smc_sysctl.c +++ b/net/smc/smc_sysctl.c @@ -97,7 +97,7 @@ static int proc_smc_hs_ctrl(const struct ctl_table *ctl, int write, } #endif /* CONFIG_SMC_HS_CTRL_BPF */ -static struct ctl_table smc_table[] = { +static const struct ctl_table smc_table[] = { { .procname = "autocorking_size", .data = &init_net.smc.sysctl_autocorking_size, @@ -195,14 +195,29 @@ static struct ctl_table smc_table[] = { #endif /* CONFIG_SMC_HS_CTRL_BPF */ }; -int __net_init smc_sysctl_net_init(struct net *net) +static const struct ctl_table *smc_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(smc_table); struct ctl_table *table; + int i; + + table = kmemdup(smc_table, sizeof(smc_table), GFP_KERNEL); + if (!table) + return NULL; + + for (i = 0; i < table_size; i++) + table[i].data += (void *)net - (void *)&init_net; + + return table; +} + +int __net_init smc_sysctl_net_init(struct net *net) +{ + size_t table_size = ARRAY_SIZE(smc_table); + const struct ctl_table *table; table = smc_table; if (!net_eq(net, &init_net)) { - int i; #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF) struct smc_hs_ctrl *ctrl; @@ -214,12 +229,9 @@ int __net_init smc_sysctl_net_init(struct net *net) rcu_read_unlock(); #endif /* CONFIG_SMC_HS_CTRL_BPF */ - table = kmemdup(table, sizeof(smc_table), GFP_KERNEL); + table = smc_table_dup(net); if (!table) goto err_alloc; - - for (i = 0; i < table_size; i++) - table[i].data += (void *)net - (void *)&init_net; } net->smc.smc_hdr = register_net_sysctl_sz(net, "net/smc", table, diff --git a/net/smc/smc_tx.h b/net/smc/smc_tx.h index a59f370b8b43..610a945aefd6 100644 --- a/net/smc/smc_tx.h +++ b/net/smc/smc_tx.h @@ -20,11 +20,15 @@ static inline int smc_tx_prepared_sends(struct smc_connection *conn) { + struct smc_buf_desc *sndbuf_desc = READ_ONCE(conn->sndbuf_desc); union smc_host_cursor sent, prep; + if (!sndbuf_desc) + return 0; + smc_curs_copy(&sent, &conn->tx_curs_sent, conn); smc_curs_copy(&prep, &conn->tx_curs_prep, conn); - return smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep); + return smc_curs_diff(sndbuf_desc->len, &sent, &prep); } void smc_tx_pending(struct smc_connection *conn); diff --git a/net/sysctl_net.c b/net/sysctl_net.c index 19e8048241ba..e190a639eef2 100644 --- a/net/sysctl_net.c +++ b/net/sysctl_net.c @@ -114,16 +114,17 @@ __init int net_sysctl_init(void) goto out; } -/* Verify that sysctls for non-init netns are safe by either: +/* Return error when sysctls for non-init netns are unsafe by verifying: * 1) being read-only, or * 2) having a data pointer which points outside of the global kernel/module * data segment, and rather into the heap where a per-net object was * allocated. */ -static void ensure_safe_net_sysctl(struct net *net, const char *path, - struct ctl_table *table, size_t table_size) +static int ensure_safe_net_sysctl(struct net *net, const char *path, + const struct ctl_table *table, + size_t table_size) { - struct ctl_table *ent; + const struct ctl_table *ent; pr_debug("Registering net sysctl (net %p): %s\n", net, path); ent = table; @@ -149,24 +150,24 @@ static void ensure_safe_net_sysctl(struct net *net, const char *path, else continue; - /* If it is writable and points to kernel/module global - * data, then it's probably a netns leak. - */ + /* Warn on netns leak. */ WARN(1, "sysctl %s/%s: data points to %s global data: %ps\n", path, ent->procname, where, ent->data); - /* Make it "safe" by dropping writable perms */ - ent->mode &= ~0222; + return -EACCES; } + + return 0; } struct ctl_table_header *register_net_sysctl_sz(struct net *net, const char *path, - struct ctl_table *table, + const struct ctl_table *table, size_t table_size) { if (!net_eq(net, &init_net)) - ensure_safe_net_sysctl(net, path, table, table_size); + if (ensure_safe_net_sysctl(net, path, table, table_size)) + return NULL; return __register_sysctl_table(&net->sysctls, path, table, table_size); } diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 8c588cdab733..fbb274287aa5 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -424,20 +424,16 @@ static __poll_t tls_sk_poll(struct file *file, struct socket *sock, return mask; } -static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, - int __user *optlen, int tx) +static int do_tls_getsockopt_conf(struct sock *sk, sockopt_t *opt, int tx) { int rc = 0; const struct tls_cipher_desc *cipher_desc; struct tls_context *ctx = tls_get_ctx(sk); struct tls_crypto_info *crypto_info; struct cipher_context *cctx; - int len; + int len = opt->optlen; - if (get_user(len, optlen)) - return -EFAULT; - - if (!optval || (len < sizeof(*crypto_info))) { + if (!opt->iter_out.ubuf || len < sizeof(*crypto_info)) { rc = -EINVAL; goto out; } @@ -462,7 +458,8 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, } if (len == sizeof(*crypto_info)) { - if (copy_to_user(optval, crypto_info, sizeof(*crypto_info))) + if (copy_to_iter(crypto_info, sizeof(*crypto_info), + &opt->iter_out) != sizeof(*crypto_info)) rc = -EFAULT; goto out; } @@ -478,44 +475,38 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, memcpy(crypto_info_rec_seq(crypto_info, cipher_desc), cctx->rec_seq, cipher_desc->rec_seq); - if (copy_to_user(optval, crypto_info, cipher_desc->crypto_info)) + if (copy_to_iter(crypto_info, cipher_desc->crypto_info, + &opt->iter_out) != cipher_desc->crypto_info) rc = -EFAULT; out: return rc; } -static int do_tls_getsockopt_tx_zc(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_tx_zc(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); unsigned int value; - int len; - - if (get_user(len, optlen)) - return -EFAULT; + int len = opt->optlen; if (len != sizeof(value)) return -EINVAL; value = ctx->zerocopy_sendfile; - if (copy_to_user(optval, &value, sizeof(value))) + if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value)) return -EFAULT; return 0; } -static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_no_pad(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); - int value, len; + int value, len = opt->optlen; if (ctx->prot_info.version != TLS_1_3_VERSION) return -EINVAL; - if (get_user(len, optlen)) - return -EFAULT; if (len < sizeof(value)) return -EINVAL; @@ -525,38 +516,31 @@ static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval, if (value < 0) return value; - if (put_user(sizeof(value), optlen)) - return -EFAULT; - if (copy_to_user(optval, &value, sizeof(value))) + opt->optlen = sizeof(value); + if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value)) return -EFAULT; return 0; } -static int do_tls_getsockopt_tx_payload_len(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_tx_payload_len(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); u16 payload_len = ctx->tx_max_payload_len; - int len; - - if (get_user(len, optlen)) - return -EFAULT; + int len = opt->optlen; if (len < sizeof(payload_len)) return -EINVAL; - if (put_user(sizeof(payload_len), optlen)) - return -EFAULT; - - if (copy_to_user(optval, &payload_len, sizeof(payload_len))) + opt->optlen = sizeof(payload_len); + if (copy_to_iter(&payload_len, sizeof(payload_len), + &opt->iter_out) != sizeof(payload_len)) return -EFAULT; return 0; } -static int do_tls_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_tls_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { int rc = 0; @@ -565,17 +549,16 @@ static int do_tls_getsockopt(struct sock *sk, int optname, switch (optname) { case TLS_TX: case TLS_RX: - rc = do_tls_getsockopt_conf(sk, optval, optlen, - optname == TLS_TX); + rc = do_tls_getsockopt_conf(sk, opt, optname == TLS_TX); break; case TLS_TX_ZEROCOPY_RO: - rc = do_tls_getsockopt_tx_zc(sk, optval, optlen); + rc = do_tls_getsockopt_tx_zc(sk, opt); break; case TLS_RX_EXPECT_NO_PAD: - rc = do_tls_getsockopt_no_pad(sk, optval, optlen); + rc = do_tls_getsockopt_no_pad(sk, opt); break; case TLS_TX_MAX_PAYLOAD_LEN: - rc = do_tls_getsockopt_tx_payload_len(sk, optval, optlen); + rc = do_tls_getsockopt_tx_payload_len(sk, opt); break; default: rc = -ENOPROTOOPT; @@ -591,12 +574,25 @@ static int tls_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { struct tls_context *ctx = tls_get_ctx(sk); + sockopt_t opt; + int err; if (level != SOL_TLS) return ctx->sk_proto->getsockopt(sk, level, optname, optval, optlen); - return do_tls_getsockopt(sk, optname, optval, optlen); + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_tls_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int validate_crypto_info(const struct tls_crypto_info *crypto_info, diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c index 61b10c697ecc..6cc222008d95 100644 --- a/net/tls/tls_strp.c +++ b/net/tls/tls_strp.c @@ -430,9 +430,10 @@ static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort) return 0; } -static bool tls_strp_check_queue_ok(struct tls_strparser *strp) +static bool tls_strp_check_queue_ok(struct tls_strparser *strp, + unsigned int len) { - unsigned int len = strp->stm.offset + strp->stm.full_len; + unsigned int remaining = strp->stm.offset + len; struct sk_buff *first, *skb; u32 seq; @@ -443,9 +444,9 @@ static bool tls_strp_check_queue_ok(struct tls_strparser *strp) /* Make sure there's no duplicate data in the queue, * and the decrypted status matches. */ - while (skb->len < len) { + while (skb->len < remaining) { seq += skb->len; - len -= skb->len; + remaining -= skb->len; skb = skb->next; if (TCP_SKB_CB(skb)->seq != seq) @@ -525,6 +526,11 @@ static int tls_strp_read_sock(struct tls_strparser *strp) tls_strp_load_anchor_with_queue(strp, inq); if (!strp->stm.full_len) { + if (inq < TLS_HEADER_SIZE) + return tls_strp_read_copy(strp, true); + if (!tls_strp_check_queue_ok(strp, TLS_HEADER_SIZE)) + return tls_strp_read_copy(strp, false); + sz = tls_rx_msg_size(strp, strp->anchor); if (sz < 0) return sz; @@ -535,7 +541,7 @@ static int tls_strp_read_sock(struct tls_strparser *strp) return tls_strp_read_copy(strp, true); } - if (!tls_strp_check_queue_ok(strp)) + if (!tls_strp_check_queue_ok(strp, strp->stm.full_len)) return tls_strp_read_copy(strp, false); WRITE_ONCE(strp->msg_ready, 1); diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 270a37da293f..13f9926bf205 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname) { switch (optname) { case SO_INQ: + case SO_RIGHTS_NOTRUNC: return true; default: return false; @@ -950,13 +951,21 @@ static int unix_setsockopt(struct socket *sock, int level, int optname, switch (optname) { case SO_INQ: if (sk->sk_type != SOCK_STREAM) - return -EINVAL; + return -ENOPROTOOPT; if (val > 1 || val < 0) return -EINVAL; WRITE_ONCE(u->recvmsg_inq, val); break; + + case SO_RIGHTS_NOTRUNC: + if (val > 1 || val < 0) + return -EINVAL; + + WRITE_ONCE(u->scm_rights_notrunc, val); + break; + default: return -ENOPROTOOPT; } @@ -1006,6 +1015,7 @@ static const struct proto_ops unix_dgram_ops = { #endif .listen = sock_no_listen, .shutdown = unix_shutdown, + .setsockopt = unix_setsockopt, .sendmsg = unix_dgram_sendmsg, .read_skb = unix_read_skb, .recvmsg = unix_dgram_recvmsg, @@ -1030,6 +1040,7 @@ static const struct proto_ops unix_seqpacket_ops = { #endif .listen = unix_listen, .shutdown = unix_shutdown, + .setsockopt = unix_setsockopt, .sendmsg = unix_seqpacket_sendmsg, .recvmsg = unix_seqpacket_recvmsg, .mmap = sock_no_mmap, @@ -1143,9 +1154,10 @@ static int unix_create(struct net *net, struct socket *sock, int protocol, if (protocol && protocol != PF_UNIX) return -EPROTONOSUPPORT; + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); + switch (sock->type) { case SOCK_STREAM: - set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); sock->ops = &unix_stream_ops; break; /* @@ -1738,9 +1750,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad init_peercred(newsk, &peercred); newu = unix_sk(newsk); - newu->listener = other; - RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq); otheru = unix_sk(other); + newu->listener = other; + newu->scm_rights_notrunc = READ_ONCE(otheru->scm_rights_notrunc); + RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq); /* copy address information from listening to new sock * @@ -1860,8 +1873,7 @@ static int unix_accept(struct socket *sock, struct socket *newsock, skb_free_datagram(sk, skb); wake_up_interruptible(&unix_sk(sk)->peer_wait); - if (tsk->sk_type == SOCK_STREAM) - set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags); + set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags); /* attach accepted sock to socket */ unix_state_lock(tsk); diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c index e02ed6e3955c..47660d5726bb 100644 --- a/net/unix/sysctl_net_unix.c +++ b/net/unix/sysctl_net_unix.c @@ -13,7 +13,7 @@ #include "af_unix.h" -static struct ctl_table unix_table[] = { +static const struct ctl_table unix_table[] = { { .procname = "max_dgram_qlen", .data = &init_net.unx.sysctl_max_dgram_qlen, @@ -23,18 +23,29 @@ static struct ctl_table unix_table[] = { }, }; -int __net_init unix_sysctl_register(struct net *net) +static const struct ctl_table *unix_table_dup(struct net *net) { struct ctl_table *table; + table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->unx.sysctl_max_dgram_qlen; + + return table; +} + +int __net_init unix_sysctl_register(struct net *net) +{ + const struct ctl_table *table; + if (net_eq(net, &init_net)) { table = unix_table; } else { - table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL); + table = unix_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->unx.sysctl_max_dgram_qlen; } net->unx.ctl = register_net_sysctl_sz(net, "net/unix", table, diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 622dbd046799..a33b2a2d381d 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -38,10 +38,9 @@ * pending socket. When that socket reaches the connected state, it is removed * from the listener socket's pending list and enqueued in the listener * socket's accept queue. Callers of accept(2) will accept connected sockets - * from the listener socket's accept queue. If the socket cannot be accepted - * for some reason then it is marked rejected. Once the connection is - * accepted, it is owned by the user process and the responsibility for cleanup - * falls with that user process. + * from the listener socket's accept queue. Once the connection is accepted, + * it is owned by the user process and the responsibility for cleanup falls + * with that user process. * * - It is possible that these pending sockets will never reach the connected * state; in fact, we may never receive another packet after the connection @@ -49,9 +48,7 @@ * future, after some amount of time passes where a connection should have been * established. This function ensures that the socket is off all lists so it * cannot be retrieved, then drops all references to the socket so it is cleaned - * up (sock_put() -> sk_free() -> our sk_destruct implementation). Note this - * function will also cleanup rejected sockets, those that reach the connected - * state but leave it before they have been accepted. + * up (sock_put() -> sk_free() -> our sk_destruct implementation). * * - Lock ordering for pending or accept queue sockets is: * @@ -774,11 +771,10 @@ static void vsock_pending_work(struct work_struct *work) if (vsock_is_pending(sk)) { vsock_remove_pending(listener, sk); - } else if (!vsk->rejected) { - /* We are not on the pending list and accept() did not reject - * us, so we must have been accepted by our user process. We - * just need to drop our references to the sockets and be on - * our way. + } else { + /* We are not on the pending list so we must have been accepted + * by our user process. We just need to drop our references to + * the sockets and be on our way. */ cleanup = false; goto out; @@ -942,7 +938,6 @@ static struct sock *__vsock_create(struct net *net, vsk->listener = NULL; INIT_LIST_HEAD(&vsk->pending_links); INIT_LIST_HEAD(&vsk->accept_queue); - vsk->rejected = false; vsk->sent_request = false; vsk->ignore_connecting_rst = false; WRITE_ONCE(vsk->peer_shutdown, 0); @@ -1847,12 +1842,10 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr, prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); } - if (sk->sk_err) { - err = -sk->sk_err; + err = sock_error(sk); + if (err) { sk->sk_state = TCP_CLOSE; sock->state = SS_UNCONNECTED; - } else { - err = 0; } out_wait: @@ -1893,7 +1886,7 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, timeout = sock_rcvtimeo(listener, arg->flags & O_NONBLOCK); while ((connected = vsock_dequeue_accept(listener)) == NULL && - listener->sk_err == 0 && timeout != 0) { + timeout != 0) { prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE); release_sock(listener); timeout = schedule_timeout(timeout); @@ -1906,39 +1899,24 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, } } - if (listener->sk_err) { - err = -listener->sk_err; - } else if (!connected) { + if (!connected) { err = -EAGAIN; - } - - if (connected) { + } else { sk_acceptq_removed(listener); lock_sock_nested(connected, SINGLE_DEPTH_NESTING); vconnected = vsock_sk(connected); - /* If the listener socket has received an error, then we should - * reject this socket and return. Note that we simply mark the - * socket rejected, drop our reference, and let the cleanup - * function handle the cleanup; the fact that we found it in - * the listener's accept queue guarantees that the cleanup - * function hasn't run yet. - */ - if (err) { - vconnected->rejected = true; - } else { - newsock->state = SS_CONNECTED; - sock_graft(connected, newsock); + newsock->state = SS_CONNECTED; + sock_graft(connected, newsock); - set_bit(SOCK_CUSTOM_SOCKOPT, + set_bit(SOCK_CUSTOM_SOCKOPT, + &connected->sk_socket->flags); + + if (vsock_msgzerocopy_allow(vconnected->transport)) + set_bit(SOCK_SUPPORT_ZC, &connected->sk_socket->flags); - if (vsock_msgzerocopy_allow(vconnected->transport)) - set_bit(SOCK_SUPPORT_ZC, - &connected->sk_socket->flags); - } - release_sock(connected); sock_put(connected); } @@ -2899,7 +2877,7 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write, return 0; } -static struct ctl_table vsock_table[] = { +static const struct ctl_table vsock_table[] = { { .procname = "ns_mode", .data = &init_net.vsock.mode, @@ -2925,20 +2903,31 @@ static struct ctl_table vsock_table[] = { }, }; -static int __net_init vsock_sysctl_register(struct net *net) +static const struct ctl_table *vsock_table_dup(struct net *net) { struct ctl_table *table; + table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->vsock.mode; + table[1].data = &net->vsock.child_ns_mode; + table[2].data = &net->vsock.g2h_fallback; + + return table; +} + +static int __net_init vsock_sysctl_register(struct net *net) +{ + const struct ctl_table *table; + if (net_eq(net, &init_net)) { table = vsock_table; } else { - table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL); + table = vsock_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->vsock.mode; - table[1].data = &net->vsock.child_ns_mode; - table[2].data = &net->vsock.g2h_fallback; } net->vsock.sysctl_hdr = register_net_sysctl_sz(net, "net/vsock", table, diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 8becad81279c..e4ebaa70f6c2 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -335,38 +335,35 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW) return pkt_len; - if (info->msg) { - /* If zerocopy is not enabled by 'setsockopt()', we behave as - * there is no MSG_ZEROCOPY flag set. + if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) { + /* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW. + * 'MSG_ZEROCOPY' flag handling here is based on the same flag + * handling from 'tcp_sendmsg_locked()'. */ - if (!sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) - info->msg->msg_flags &= ~MSG_ZEROCOPY; - - if (info->msg->msg_flags & MSG_ZEROCOPY) + if (info->msg->msg_ubuf) { + uarg = info->msg->msg_ubuf; can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + } else if (sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) { + uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len, + NULL, false); + if (!uarg) { + virtio_transport_put_credit(vvs, pkt_len); + return -ENOMEM; + } + can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + if (!can_zcopy) + uarg_to_msgzc(uarg)->zerocopy = 0; + + have_uref = true; + } + + /* 'can_zcopy' means that this transmission will be + * in zerocopy way (e.g. using 'frags' array). + */ if (can_zcopy) max_skb_len = min_t(u32, VIRTIO_VSOCK_MAX_PKT_BUF_SIZE, (MAX_SKB_FRAGS * PAGE_SIZE)); - - if (info->msg->msg_flags & MSG_ZEROCOPY && - info->op == VIRTIO_VSOCK_OP_RW) { - uarg = info->msg->msg_ubuf; - - if (!uarg) { - uarg = msg_zerocopy_realloc(sk_vsock(vsk), - pkt_len, NULL, false); - if (!uarg) { - virtio_transport_put_credit(vvs, pkt_len); - return -ENOMEM; - } - - if (!can_zcopy) - uarg_to_msgzc(uarg)->zerocopy = 0; - - have_uref = true; - } - } } rest_len = pkt_len; diff --git a/net/wireless/core.c b/net/wireless/core.c index 610238d723ff..d13310fef691 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -237,6 +237,7 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, if (!wdev_running(wdev)) return; + cfg80211_pmsr_wdev_down(wdev); rdev_stop_p2p_device(rdev, wdev); wdev->is_running = false; @@ -264,6 +265,8 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev, if (!wdev_running(wdev)) return; + cfg80211_pmsr_wdev_down(wdev); + /* * If there is a scheduled update pending, mark it as canceled, so the * empty schedule will be accepted diff --git a/net/wireless/core.h b/net/wireless/core.h index ac6ce9f967ec..b4610f6685dc 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -402,7 +402,7 @@ void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev); int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); void cfg80211_oper_and_ht_capa(struct ieee80211_ht_cap *ht_capa, const struct ieee80211_ht_cap *ht_capa_mask); void cfg80211_oper_and_vht_capa(struct ieee80211_vht_cap *vht_capa, @@ -443,8 +443,9 @@ void cfg80211_sme_abandon_assoc(struct wireless_dev *wdev); /* internal helpers */ bool cfg80211_supported_cipher_suite(struct wiphy *wiphy, u32 cipher); -bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev, - int key_idx, bool pairwise); +bool cfg80211_valid_key_idx(struct wireless_dev *wdev, + int key_idx, bool pairwise, + const u8 *mac_addr); int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct key_params *params, int key_idx, diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index 7824b7ac2770..a0d1cde26f0c 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -894,7 +894,7 @@ static bool cfg80211_allowed_random_address(struct wireless_dev *wdev, int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { const struct ieee80211_mgmt *mgmt; u16 stype; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 5adcb6bd0fc5..44f2bad08670 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -441,7 +441,7 @@ static int validate_uhr_operation(const struct nlattr *attr, const u8 *data = nla_data(attr); unsigned int len = nla_len(attr); - if (!ieee80211_uhr_oper_size_ok(data, len, false)) + if (!ieee80211_uhr_oper_size_ok(data, len)) return -EINVAL; return 0; } @@ -1095,6 +1095,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 }, [NL80211_ATTR_NPCA_PUNCT_BITMAP] = NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), + [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -2446,7 +2447,7 @@ static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev, } if (rdev->wiphy.max_sched_scan_reqs) CMD(sched_scan_start, START_SCHED_SCAN); - CMD(probe_client, PROBE_CLIENT); + CMD(probe_peer, PROBE_PEER); CMD(set_noack_map, SET_NOACK_MAP); if (rdev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) { i++; @@ -4628,6 +4629,10 @@ int nl80211_send_chandef(struct sk_buff *msg, const struct cfg80211_chan_def *ch return -ENOBUFS; if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, chandef->center_freq1)) return -ENOBUFS; + if (chandef->freq1_offset && + nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET, + chandef->freq1_offset)) + return -ENOBUFS; if (chandef->center_freq2 && nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, chandef->center_freq2)) return -ENOBUFS; @@ -5410,7 +5415,7 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info) if (!rdev->ops->get_key) return -EOPNOTSUPP; - if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) + if (!cfg80211_valid_key_idx(wdev, key_idx, pairwise, mac_addr)) return -ENOENT; msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -5664,8 +5669,9 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) key.type != NL80211_KEYTYPE_GROUP) return -EINVAL; - if (!cfg80211_valid_key_idx(rdev, key.idx, - key.type == NL80211_KEYTYPE_PAIRWISE)) + if (!cfg80211_valid_key_idx(wdev, key.idx, + key.type == NL80211_KEYTYPE_PAIRWISE, + mac_addr)) return -EINVAL; if (!rdev->ops->del_key) @@ -5673,10 +5679,6 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) err = nl80211_key_allowed(wdev); - if (key.type == NL80211_KEYTYPE_GROUP && mac_addr && - !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) - err = -ENOENT; - if (!err) err = nl80211_validate_key_link_id(info, wdev, link_id, key.type == NL80211_KEYTYPE_PAIRWISE); @@ -7882,7 +7884,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, goto nla_put_failure; \ } while (0) - link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!link_sinfoattr) goto nla_put_failure; @@ -7948,8 +7950,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg, PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32); if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, + NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -7991,8 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -8005,7 +8006,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8041,36 +8042,15 @@ static int nl80211_fill_link_station(struct sk_buff *msg, return -EMSGSIZE; } -static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, - u32 seq, int flags, - struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev, - const u8 *mac_addr, struct station_info *sinfo, - bool link_stats) +static int nl80211_put_sta_info_common(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo) { - void *hdr; struct nlattr *sinfoattr, *bss_param; - struct link_station_info *link_sinfo; - struct nlattr *links, *link; - int link_id; - hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); - if (!hdr) { - cfg80211_sinfo_release_content(sinfo); - return -1; - } - - if ((wdev->netdev && - nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || - nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), - NL80211_ATTR_PAD) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || - nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) - goto nla_put_failure; - - sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!sinfoattr) - goto nla_put_failure; + return -EMSGSIZE; #define PUT_SINFO(attr, memb, type) do { \ BUILD_BUG_ON(sizeof(type) == sizeof(u64)); \ @@ -8161,8 +8141,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, PUT_SINFO_U64(T_OFFSET, t_offset); if (sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -8204,8 +8183,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -8218,7 +8196,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8248,6 +8226,37 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, } nla_nest_end(msg, sinfoattr); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, sinfoattr); + return -EMSGSIZE; +} + +static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, + u32 seq, int flags, + struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev, + const u8 *mac_addr, struct station_info *sinfo) +{ + void *hdr; + + hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); + if (!hdr) { + cfg80211_sinfo_release_content(sinfo); + return -1; + } + + if ((wdev->netdev && + nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || + nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), + NL80211_ATTR_PAD) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || + nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) + goto nla_put_failure; + + if (nl80211_put_sta_info_common(msg, rdev, sinfo)) + goto nla_put_failure; if (sinfo->assoc_req_ies_len && nla_put(msg, NL80211_ATTR_IE, sinfo->assoc_req_ies_len, @@ -8270,45 +8279,11 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, goto nla_put_failure; } - if (link_stats && sinfo->valid_links) { - links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); - if (!links) - goto nla_put_failure; - - for_each_valid_link(sinfo, link_id) { - link_sinfo = sinfo->links[link_id]; - - if (WARN_ON_ONCE(!link_sinfo)) - continue; - - if (!is_valid_ether_addr(link_sinfo->addr)) - continue; - - link = nla_nest_start(msg, link_id + 1); - if (!link) - goto nla_put_failure; - - if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, - link_id)) - goto nla_put_failure; - - if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, - link_sinfo->addr)) - goto nla_put_failure; - - if (nl80211_fill_link_station(msg, rdev, link_sinfo)) - goto nla_put_failure; - - nla_nest_end(msg, link); - } - nla_nest_end(msg, links); - } - cfg80211_sinfo_release_content(sinfo); genlmsg_end(msg, hdr); return 0; - nla_put_failure: +nla_put_failure: cfg80211_sinfo_release_content(sinfo); genlmsg_cancel(msg, hdr); return -EMSGSIZE; @@ -8502,82 +8477,261 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +enum nl80211_dump_station_phase { + NL80211_DUMP_STA_PHASE_AGGREGATED = 0, + NL80211_DUMP_STA_PHASE_PER_LINK = 1, +}; + +struct nl80211_dump_station_ctx { + int sta_idx; + int link_idx; + enum nl80211_dump_station_phase phase; + bool dump_link_stats; + bool filter_mac; + u8 filter_mac_addr[ETH_ALEN]; + u8 mac_addr[ETH_ALEN]; + struct station_info sinfo; +}; + +static int nl80211_put_link_station_payload(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo, + int link_idx) +{ + struct link_station_info *link_sinfo = sinfo->links[link_idx]; + struct nlattr *links, *link; + + if (WARN_ON_ONCE(!link_sinfo)) + return -ENOENT; + + if (!is_valid_ether_addr(link_sinfo->addr)) + return -EADDRNOTAVAIL; + + links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); + if (!links) + return -EMSGSIZE; + + link = nla_nest_start(msg, link_idx + 1); + if (!link) + goto nla_put_failure; + + if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr)) + goto nla_put_failure; + + if (nl80211_fill_link_station(msg, rdev, link_sinfo)) + goto nla_put_failure; + + nla_nest_end(msg, link); + nla_nest_end(msg, links); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, links); + return -EMSGSIZE; +} + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { - struct station_info sinfo; struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - u8 mac_addr[ETH_ALEN]; - int sta_idx = cb->args[2]; - bool sinfo_alloc = false; - int err, i; + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + struct nlattr **attrbuf __free(kfree) = NULL; + int err; - err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); + if (!ctx) { + attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); + if (!attrbuf) + return -ENOMEM; + } + + err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf); if (err) return err; /* nl80211_prepare_wdev_dump acquired it in the successful case */ __acquire(&rdev->wiphy.mtx); + if (!ctx) { + ctx = kzalloc_obj(*ctx); + if (!ctx) { + err = -ENOMEM; + goto out_err; + } + cb->args[2] = (long)ctx; + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + ctx->dump_link_stats = + !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; + if (attrbuf[NL80211_ATTR_MAC]) { + const u8 *mac = nla_data(attrbuf[NL80211_ATTR_MAC]); + + if (!is_valid_ether_addr(mac)) { + kfree(ctx); + cb->args[2] = 0; + err = -EINVAL; + goto out_err; + } + ctx->filter_mac = true; + memcpy(ctx->filter_mac_addr, mac, ETH_ALEN); + } + } + if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) { err = -EINVAL; goto out_err; } - if (!rdev->ops->dump_station) { + if (ctx->filter_mac) { + if (!rdev->ops->get_station) { + err = -EOPNOTSUPP; + goto out_err; + } + } else if (!rdev->ops->dump_station) { err = -EOPNOTSUPP; goto out_err; } - while (1) { - memset(&sinfo, 0, sizeof(sinfo)); + while (true) { + void *hdr; + int ret; - for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - sinfo.links[i] = - kzalloc_obj(*sinfo.links[0]); - if (!sinfo.links[i]) { - err = -ENOMEM; - goto out_err; + /* AGGREGATED phase: fetch sinfo from driver once per station */ + if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { + err = -ENOMEM; + goto out_err_release; + } + } + + if (ctx->filter_mac) { + if (ctx->sta_idx > 0) { + err = skb->len; + goto out_err_release; + } + err = rdev_get_station(rdev, wdev, + ctx->filter_mac_addr, + &ctx->sinfo); + if (!err) + memcpy(ctx->mac_addr, + ctx->filter_mac_addr, ETH_ALEN); + } else { + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, + &ctx->sinfo); + } + if (err == -ENOENT) { + err = skb->len; + goto out_err_release; + } + if (err) + goto out_err_release; + + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + } else { + /* PER_LINK phase: advance to next valid link */ + while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS && + !(ctx->sinfo.valid_links & BIT(ctx->link_idx))) + ctx->link_idx++; + + if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + continue; } - sinfo_alloc = true; } - err = rdev_dump_station(rdev, wdev, sta_idx, - mac_addr, &sinfo); - if (err == -ENOENT) + /* Build common header for both phases */ + hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + NL80211_CMD_NEW_STATION); + if (!hdr) { + err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; + goto out_err_release; + } + + if ((wdev->netdev && + nla_put_u32(skb, NL80211_ATTR_IFINDEX, + wdev->netdev->ifindex)) || + nla_put_u64_64bit(skb, NL80211_ATTR_WDEV, + wdev_id(wdev), NL80211_ATTR_PAD) || + nla_put(skb, NL80211_ATTR_MAC, ETH_ALEN, ctx->mac_addr) || + nla_put_u32(skb, NL80211_ATTR_GENERATION, + ctx->sinfo.generation)) { + genlmsg_cancel(skb, hdr); + err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; + goto out_err_release; + } + + switch (ctx->phase) { + case NL80211_DUMP_STA_PHASE_AGGREGATED: + ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo); + if (ret) { + genlmsg_cancel(skb, hdr); + err = ret; + goto out_err_release; + } + genlmsg_end(skb, hdr); + + if (ctx->dump_link_stats && ctx->sinfo.valid_links) { + ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK; + ctx->link_idx = 0; + } else { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + } break; - if (err) - goto out_err; - if (sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&sinfo); - - /* reset the sinfo_alloc flag as nl80211_send_station() - * always releases sinfo - */ - sinfo_alloc = false; - - if (nl80211_send_station(skb, NL80211_CMD_NEW_STATION, - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, NLM_F_MULTI, - rdev, wdev, mac_addr, - &sinfo, false) < 0) - goto out; - - sta_idx++; + case NL80211_DUMP_STA_PHASE_PER_LINK: + ret = nl80211_put_link_station_payload(skb, rdev, + &ctx->sinfo, + ctx->link_idx); + if (ret == -EMSGSIZE) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err; + } + if (ret) { + /* skip invalid link, do not abort the dump */ + genlmsg_cancel(skb, hdr); + ctx->link_idx++; + continue; + } + genlmsg_end(skb, hdr); + ctx->link_idx++; + break; + } } - out: - cb->args[2] = sta_idx; - err = skb->len; - out_err: - if (sinfo_alloc) - cfg80211_sinfo_release_content(&sinfo); +out_err_release: + cfg80211_sinfo_release_content(&ctx->sinfo); + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); +out_err: wiphy_unlock(&rdev->wiphy); return err; } +static int nl80211_dump_station_done(struct netlink_callback *cb) +{ + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + + if (ctx) { + cfg80211_sinfo_release_content(&ctx->sinfo); + kfree(ctx); + } + return 0; +} + static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -8625,7 +8779,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, info->snd_portid, info->snd_seq, 0, - rdev, wdev, mac_addr, &sinfo, false) < 0) { + rdev, wdev, mac_addr, &sinfo) < 0) { nlmsg_free(msg); return -ENOBUFS; } @@ -12748,9 +12902,11 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) return -EINVAL; } - req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, - IEEE80211_BSS_TYPE_ESS, - IEEE80211_PRIVACY_ANY); + req.bss = __cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, + IEEE80211_BSS_TYPE_ESS, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, + info->extack); if (!req.bss) return -ENOENT; @@ -12895,6 +13051,7 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev, } static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device *rdev, + struct genl_info *info, const u8 *ssid, int ssid_len, struct nlattr **attrs, int assoc_link_id, int link_id) @@ -12904,8 +13061,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device const u8 *bssid; u32 freq, use_for = 0; - if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { + GENL_SET_ERR_MSG(info, "BSSID or frequency missing"); return ERR_PTR(-EINVAL); + } bssid = nla_data(attrs[NL80211_ATTR_MAC]); @@ -12914,8 +13073,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device freq += nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]); chan = nl80211_get_valid_chan(&rdev->wiphy, freq); - if (!chan) + if (!chan) { + GENL_SET_ERR_MSG(info, "invalid or disabled channel"); return ERR_PTR(-EINVAL); + } if (assoc_link_id >= 0) use_for = NL80211_BSS_USE_FOR_MLD_LINK; @@ -12926,7 +13087,7 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device ssid, ssid_len, IEEE80211_BSS_TYPE_ESS, IEEE80211_PRIVACY_ANY, - use_for); + use_for, info->extack); if (!bss) return ERR_PTR(-ENOENT); @@ -12965,13 +13126,13 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } links[link_id].bss = - nl80211_assoc_bss(rdev, ssid, ssid_len, attrs, + nl80211_assoc_bss(rdev, info, ssid, ssid_len, attrs, assoc_link_id, link_id); if (IS_ERR(links[link_id].bss)) { err = PTR_ERR(links[link_id].bss); links[link_id].bss = NULL; - NL_SET_ERR_MSG_ATTR(info->extack, link, - "Error fetching BSS for link"); + /* the BSS lookup set the specific message already */ + NL_SET_BAD_ATTR(info->extack, link); return err; } @@ -13187,7 +13348,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) if (req.link_id >= 0) return -EINVAL; - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, info->attrs, -1, -1); if (IS_ERR(req.bss)) return PTR_ERR(req.bss); @@ -14481,8 +14642,9 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, goto free_msg; } + cookie = cfg80211_assign_cookie(rdev); err = rdev_remain_on_channel(rdev, wdev, chandef.chan, - duration, &cookie, rx_addr); + duration, cookie, rx_addr); if (err) goto free_msg; @@ -14722,7 +14884,8 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) } params.chan = chandef.chan; - err = cfg80211_mlme_mgmt_tx(rdev, wdev, ¶ms, &cookie); + cookie = cfg80211_assign_cookie(rdev); + err = cfg80211_mlme_mgmt_tx(rdev, wdev, ¶ms, cookie); if (err) goto free_msg; @@ -16162,26 +16325,41 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb, return 0; } -static int nl80211_probe_client(struct sk_buff *skb, - struct genl_info *info) +static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct net_device *dev = info->user_ptr[1]; struct wireless_dev *wdev = dev->ieee80211_ptr; struct sk_buff *msg; void *hdr; - const u8 *addr; + const u8 *addr = NULL; u64 cookie; int err; - if (wdev->iftype != NL80211_IFTYPE_AP && - wdev->iftype != NL80211_IFTYPE_P2P_GO) + /* Allow in AP, STA, and their P2P counterparts */ + switch (wdev->iftype) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + if (!info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_PROBE_AP)) + return -EOPNOTSUPP; + if (!wdev->connected) + return -ENOLINK; + /* STA/P2P-client probes the currently associated AP/GO. */ + if (info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + break; + default: return -EOPNOTSUPP; + } - if (!info->attrs[NL80211_ATTR_MAC]) - return -EINVAL; - - if (!rdev->ops->probe_client) + if (!rdev->ops->probe_peer) return -EOPNOTSUPP; msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -16189,15 +16367,14 @@ static int nl80211_probe_client(struct sk_buff *skb, return -ENOMEM; hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0, - NL80211_CMD_PROBE_CLIENT); + NL80211_CMD_PROBE_PEER); if (!hdr) { err = -ENOBUFS; goto free_msg; } - addr = nla_data(info->attrs[NL80211_ATTR_MAC]); - - err = rdev_probe_client(rdev, dev, addr, &cookie); + cookie = cfg80211_assign_cookie(rdev); + err = rdev_probe_peer(rdev, dev, addr, cookie); if (err) goto free_msg; @@ -18412,10 +18589,11 @@ static int nl80211_tx_control_port(struct sk_buff *skb, struct genl_info *info) link_id = nl80211_link_id_or_invalid(info->attrs); + cookie = dont_wait_for_ack ? 0 : cfg80211_assign_cookie(rdev); err = rdev_tx_control_port(rdev, dev, buf, len, dest, cpu_to_be16(proto), noencrypt, link_id, - dont_wait_for_ack ? NULL : &cookie); - if (!err && !dont_wait_for_ack) + cookie); + if (!err && cookie) nl_set_extack_cookie_u64(info->extack, cookie); return err; } @@ -18750,15 +18928,15 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info) if (!wdev->links[params.link_id].ap.beacon_interval) return -EINVAL; + tb = kzalloc_objs(*tb, NL80211_ATTR_MAX + 1); + if (!tb) + return -ENOMEM; + err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_next, wdev->links[params.link_id].ap.chandef.chan, info->extack); if (err) - return err; - - tb = kzalloc_objs(*tb, NL80211_ATTR_MAX + 1); - if (!tb) - return -ENOMEM; + goto out; err = nla_parse_nested(tb, NL80211_ATTR_MAX, info->attrs[NL80211_ATTR_COLOR_CHANGE_ELEMS], @@ -19542,6 +19720,14 @@ static const struct genl_ops nl80211_ops[] = { /* can be retrieved by unprivileged users */ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY), }, + { + .cmd = NL80211_CMD_GET_STATION, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = nl80211_get_station, + .dumpit = nl80211_dump_station, + .done = nl80211_dump_station_done, + .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), + }, }; static const struct genl_small_ops nl80211_small_ops[] = { @@ -19641,13 +19827,6 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_MLO_VALID_LINK_ID), }, - { - .cmd = NL80211_CMD_GET_STATION, - .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_get_station, - .dumpit = nl80211_dump_station, - .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), - }, { .cmd = NL80211_CMD_SET_STATION, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, @@ -20054,9 +20233,9 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV), }, { - .cmd = NL80211_CMD_PROBE_CLIENT, + .cmd = NL80211_CMD_PROBE_PEER, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_probe_client, + .doit = nl80211_probe_peer, .flags = GENL_UNS_ADMIN_PERM, .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP), }, @@ -20902,6 +21081,9 @@ void cfg80211_rx_unprot_mlme_mgmt(struct net_device *dev, const u8 *buf, } else if (ieee80211_is_disassoc(mgmt->frame_control)) { event.cmd = NL80211_CMD_UNPROT_DISASSOCIATE; } else if (ieee80211_is_beacon(mgmt->frame_control)) { + if (wdev->iftype == NL80211_IFTYPE_AP || + wdev->iftype == NL80211_IFTYPE_P2P_GO) + return; if (wdev->unprot_beacon_reported && elapsed_jiffies_msecs(wdev->unprot_beacon_reported) < 10000) return; @@ -21647,7 +21829,7 @@ void cfg80211_new_sta(struct wireless_dev *wdev, const u8 *mac_addr, return; if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } @@ -21677,7 +21859,7 @@ void cfg80211_del_sta_sinfo(struct wireless_dev *wdev, const u8 *mac_addr, } if (nl80211_send_station(msg, NL80211_CMD_DEL_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } @@ -21864,6 +22046,10 @@ static void nl80211_frame_tx_status(struct wireless_dev *wdev, struct sk_buff *msg; void *hdr; + /* userspace not interested in zero-cookie status */ + if (!status->cookie) + return; + if (command == NL80211_CMD_FRAME_TX_STATUS) trace_cfg80211_mgmt_tx_status(wdev, status->cookie, status->ack); @@ -22610,8 +22796,8 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac, } EXPORT_SYMBOL(cfg80211_sta_opmode_change_notify); -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp) { struct wireless_dev *wdev = dev->ieee80211_ptr; @@ -22619,14 +22805,14 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, struct sk_buff *msg; void *hdr; - trace_cfg80211_probe_status(dev, addr, cookie, acked); + trace_cfg80211_probe_status(dev, peer, cookie, acked); msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); if (!msg) return; - hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_CLIENT); + hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_PEER); if (!hdr) { nlmsg_free(msg); return; @@ -22634,12 +22820,18 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) || + (peer && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, peer)) || nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, cookie, - NL80211_ATTR_PAD) || - (acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || - (is_valid_ack_signal && nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, - ack_signal))) + NL80211_ATTR_PAD)) + goto nla_put_failure; + + if (link_id >= 0 && + nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) + goto nla_put_failure; + + if ((acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || + (is_valid_ack_signal && + nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, ack_signal))) goto nla_put_failure; genlmsg_end(msg, hdr); diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 63c26e8b1139..46849fe8d0b3 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -736,14 +736,14 @@ static inline int rdev_remain_on_channel(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, const u8 *rx_addr) + unsigned int duration, u64 cookie, const u8 *rx_addr) { int ret; trace_rdev_remain_on_channel(&rdev->wiphy, wdev, chan, duration, rx_addr); ret = rdev->ops->remain_on_channel(&rdev->wiphy, wdev, chan, duration, cookie, rx_addr); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } @@ -761,12 +761,12 @@ rdev_cancel_remain_on_channel(struct cfg80211_registered_device *rdev, static inline int rdev_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { int ret; trace_rdev_mgmt_tx(&rdev->wiphy, wdev, params); ret = rdev->ops->mgmt_tx(&rdev->wiphy, wdev, params, cookie); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } @@ -775,7 +775,7 @@ static inline int rdev_tx_control_port(struct cfg80211_registered_device *rdev, const void *buf, size_t len, const u8 *dest, __be16 proto, const bool noencrypt, int link, - u64 *cookie) + u64 cookie) { int ret; trace_rdev_tx_control_port(&rdev->wiphy, dev, buf, len, @@ -783,7 +783,7 @@ static inline int rdev_tx_control_port(struct cfg80211_registered_device *rdev, ret = rdev->ops->tx_control_port(&rdev->wiphy, dev, buf, len, dest, proto, noencrypt, link, cookie); if (cookie) - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); else trace_rdev_return_int(&rdev->wiphy, ret); return ret; @@ -948,14 +948,14 @@ static inline int rdev_tdls_oper(struct cfg80211_registered_device *rdev, return ret; } -static inline int rdev_probe_client(struct cfg80211_registered_device *rdev, - struct net_device *dev, const u8 *peer, - u64 *cookie) +static inline int rdev_probe_peer(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *peer, + u64 cookie) { int ret; - trace_rdev_probe_client(&rdev->wiphy, dev, peer); - ret = rdev->ops->probe_client(&rdev->wiphy, dev, peer, cookie); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_probe_peer(&rdev->wiphy, dev, peer); + ret = rdev->ops->probe_peer(&rdev->wiphy, dev, peer, cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 1e8214d6b6d8..a8336baf85dc 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -3792,7 +3792,8 @@ static void print_regdomain(const struct ieee80211_regdomain *rd) } } - pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); + pr_debug(" DFS Master region: %s\n", + reg_dfs_region_str(rd->dfs_region)); print_rd_rules(rd); } diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 071083cc3367..9e934b185e34 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1612,10 +1612,12 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for) + u32 use_for, + struct netlink_ext_ack *extack) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss, *res = NULL; + bool expired = false, unusable = false; unsigned long now = jiffies; int bss_privacy; @@ -1637,22 +1639,48 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, continue; if (!is_valid_ether_addr(bss->pub.bssid)) continue; - if ((bss->pub.use_for & use_for) != use_for) + if (!is_bss(&bss->pub, bssid, ssid, ssid_len)) continue; + + /* + * The identity checks above must all come first so that + * the expired/unusable classification below only ever + * applies to entries that actually match the request. + */ + /* Don't get expired BSS structs */ if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) && - !atomic_read(&bss->hold)) + !atomic_read(&bss->hold)) { + expired = true; continue; - if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { - res = bss; - bss_ref_get(rdev, res); - break; } + + if ((bss->pub.use_for & use_for) != use_for) { + unusable = true; + continue; + } + + res = bss; + bss_ref_get(rdev, res); + break; } spin_unlock_bh(&rdev->bss_lock); - if (!res) + if (!res) { + if (expired && unusable) + NL_SET_ERR_MSG(extack, + "BSS entries are expired or cannot be used for the requested operation"); + else if (unusable) + NL_SET_ERR_MSG(extack, + "BSS cannot be used for the requested operation"); + else if (expired) + NL_SET_ERR_MSG(extack, + "BSS entry in scan results is expired"); + else + NL_SET_ERR_MSG(extack, + "BSS not found in scan results"); return NULL; + } trace_cfg80211_return_bss(&res->pub); return &res->pub; } @@ -2406,12 +2434,11 @@ cfg80211_inform_single_bss_data(struct wiphy *wiphy, return NULL; } -static const struct element -*cfg80211_get_profile_continuation(const u8 *ie, size_t ielen, - const struct element *mbssid_elem, - const struct element *sub_elem) +static bool cfg80211_iter_profile_continuation(const u8 *ie, size_t ielen, + const struct element **mbssid, + const struct element **sub_elem) { - const u8 *mbssid_end = mbssid_elem->data + mbssid_elem->datalen; + const u8 *mbssid_end = (*mbssid)->data + (*mbssid)->datalen; const struct element *next_mbssid; const struct element *next_sub; @@ -2423,30 +2450,34 @@ static const struct element * If it is not the last subelement in current MBSSID IE or there isn't * a next MBSSID IE - profile is complete. */ - if ((sub_elem->data + sub_elem->datalen < mbssid_end - 1) || + if (((*sub_elem)->data + (*sub_elem)->datalen < mbssid_end - 1) || !next_mbssid) - return NULL; + return false; - /* For any length error, just return NULL */ + /* For any length error, just return false to stop iteration */ if (next_mbssid->datalen < 4) - return NULL; + return false; next_sub = (void *)&next_mbssid->data[1]; if (next_mbssid->data + next_mbssid->datalen < next_sub->data + next_sub->datalen) - return NULL; + return false; if (next_sub->id != 0 || next_sub->datalen < 2) - return NULL; + return false; /* * Check if the first element in the next sub element is a start * of a new profile */ - return next_sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP ? - NULL : next_mbssid; + if (next_sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP) + return false; + + *mbssid = next_mbssid; + *sub_elem = next_sub; + return true; } size_t cfg80211_merge_profile(const u8 *ie, size_t ielen, @@ -2455,26 +2486,20 @@ size_t cfg80211_merge_profile(const u8 *ie, size_t ielen, u8 *merged_ie, size_t max_copy_len) { size_t copied_len = sub_elem->datalen; - const struct element *next_mbssid; if (sub_elem->datalen > max_copy_len) return 0; memcpy(merged_ie, sub_elem->data, sub_elem->datalen); - while ((next_mbssid = cfg80211_get_profile_continuation(ie, ielen, - mbssid_elem, - sub_elem))) { - const struct element *next_sub = (void *)&next_mbssid->data[1]; - - if (copied_len + next_sub->datalen > max_copy_len) + while (cfg80211_iter_profile_continuation(ie, ielen, + &mbssid_elem, + &sub_elem)) { + if (copied_len + sub_elem->datalen > max_copy_len) break; - memcpy(merged_ie + copied_len, next_sub->data, - next_sub->datalen); - copied_len += next_sub->datalen; - - mbssid_elem = next_mbssid; - sub_elem = next_sub; + memcpy(merged_ie + copied_len, sub_elem->data, + sub_elem->datalen); + copied_len += sub_elem->datalen; } return copied_len; diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index b1a9c1466d6c..8c20278b5d3a 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -402,6 +402,124 @@ static void test_inform_bss_ssid_only(struct kunit *test) cfg80211_put_bss(wiphy, bss); } +static void test_get_bss_miss_reason(struct kunit *test) +{ + struct inform_bss ctx = { + .test = test, + }; + struct wiphy *wiphy = T_WIPHY(test, ctx); + struct cfg80211_inform_bss inform_bss = { + .signal = 50, + .drv_data = &ctx, + }; + const u8 bssid[ETH_ALEN] = { 0x10, 0x22, 0x33, 0x44, 0x55, 0x66 }; + const u8 other_bssid[ETH_ALEN] = { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }; + static const u8 ies[] = { + [0] = WLAN_EID_SSID, + [1] = 4, + [2] = 'T', 'E', 'S', 'T' + }; + struct cfg80211_internal_bss *ibss; + struct netlink_ext_ack extack = {}; + struct cfg80211_bss *bss, *bss2, *found; + + inform_bss.chan = ieee80211_get_channel_khz(wiphy, MHZ_TO_KHZ(2412)); + KUNIT_ASSERT_NOT_NULL(test, inform_bss.chan); + + bss = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, bssid, 0, + 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss); + ibss = container_of(bss, struct cfg80211_internal_bss, pub); + + /* Fresh usable entry: found, no message is set */ + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + + /* No entry at all for this BSSID */ + found = __cfg80211_get_bss(wiphy, NULL, other_bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, "BSS not found in scan results"); + + /* Fresh entry that is not usable for the requested use */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS cannot be used for the requested operation"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired entry, > IEEE80211_SCAN_RESULT_EXPIRE (30s) old */ + extack._msg = NULL; + ibss->ts = jiffies - 60 * HZ; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + + /* An entry both expired and unusable reports expired */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired but held entries are still usable, no message is set */ + extack._msg = NULL; + atomic_set(&ibss->hold, 1); + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + atomic_set(&ibss->hold, 0); + + /* + * With one matching entry expired and another current but + * unusable, both reasons are reported. + */ + bss2 = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, other_bssid, + 0, 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss2); + bss2->use_for = 0; + extack._msg = NULL; + found = __cfg80211_get_bss(wiphy, NULL, NULL, "TEST", 4, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entries are expired or cannot be used for the requested operation"); + + cfg80211_put_bss(wiphy, bss2); + cfg80211_put_bss(wiphy, bss); +} + static struct inform_bss_ml_sta_case { const char *desc; int mld_id; @@ -617,7 +735,7 @@ static void test_inform_bss_ml_sta(struct kunit *test) link_bss = __cfg80211_get_bss(wiphy, NULL, sta_prof.bssid, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY, - 0); + 0, NULL); KUNIT_ASSERT_NOT_NULL(test, link_bss); KUNIT_EXPECT_EQ(test, link_bss->signal, 0); KUNIT_EXPECT_EQ(test, link_bss->beacon_interval, @@ -855,6 +973,7 @@ kunit_test_suite(gen_new_ie); static struct kunit_case inform_bss_test_cases[] = { KUNIT_CASE(test_inform_bss_ssid_only), + KUNIT_CASE(test_get_bss_miss_reason), KUNIT_CASE_PARAM(test_inform_bss_ml_sta, inform_bss_ml_sta_gen_params), {} }; diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 94944f2a39a4..8c2a91b85c39 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2132,7 +2132,7 @@ DECLARE_EVENT_CLASS(rdev_pmksa, WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->bssid) ); -TRACE_EVENT(rdev_probe_client, +TRACE_EVENT(rdev_probe_peer, TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *peer), TP_ARGS(wiphy, netdev, peer), diff --git a/net/wireless/util.c b/net/wireless/util.c index 24527bf321b2..3e584d0ca3e2 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -241,10 +241,8 @@ bool cfg80211_supported_cipher_suite(struct wiphy *wiphy, u32 cipher) return false; } -static bool -cfg80211_igtk_cipher_supported(struct cfg80211_registered_device *rdev) +static bool cfg80211_igtk_cipher_supported(struct wiphy *wiphy) { - struct wiphy *wiphy = &rdev->wiphy; int i; for (i = 0; i < wiphy->n_cipher_suites; i++) { @@ -260,27 +258,86 @@ cfg80211_igtk_cipher_supported(struct cfg80211_registered_device *rdev) return false; } -bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev, - int key_idx, bool pairwise) +bool cfg80211_valid_key_idx(struct wireless_dev *wdev, + int key_idx, bool pairwise, + const u8 *mac_addr) { - int max_key_idx; - - if (pairwise) - max_key_idx = 3; - else if (wiphy_ext_feature_isset(&rdev->wiphy, - NL80211_EXT_FEATURE_BEACON_PROTECTION) || - wiphy_ext_feature_isset(&rdev->wiphy, - NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT)) - max_key_idx = 7; - else if (cfg80211_igtk_cipher_supported(rdev)) - max_key_idx = 5; - else - max_key_idx = 3; - - if (key_idx < 0 || key_idx > max_key_idx) + if (WARN_ON(!wdev)) return false; - return true; + if (key_idx < 0) + return false; + + /* + * Can't differentiate ciphers here so allow 0..3. + * Pairwise keys must be for a station (MAC address given). + */ + if (pairwise) { + if (!mac_addr) + return false; + + return key_idx < 4; + } + + /* + * For group keys, mac_addr==NULL means setting a group key + * for TX, which is only supported on some interface types, + * except for STATION/P2P_CLIENT, where it's setting the RX + * key with the current AP (for legacy reasons.) + * + * Apart from that exception, a non-NULL mac_addr means RX + * key being set. + */ + + switch (wdev->iftype) { + case NL80211_IFTYPE_ADHOC: + if (!(wdev->wiphy->flags & WIPHY_FLAG_IBSS_RSN)) + return false; + fallthrough; + case NL80211_IFTYPE_MESH_POINT: + /* no support for IGTK/BIGTK (yet?) */ + return key_idx < 4; + case NL80211_IFTYPE_NAN_DATA: + /* these always need to support per-STA GTK */ + return key_idx < 4; + case NL80211_IFTYPE_NAN: + /* no data */ + if (key_idx < 4) + return false; + /* NAN reused this flag */ + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION)) + return key_idx <= 7; + return key_idx <= 5; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + /* see note about exception above */ + if (mac_addr) + return false; + /* BIGTK support implies IGTK support */ + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT)) + return key_idx <= 7; + fallthrough; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + /* no RX with [B]IGTK */ + if (mac_addr) + return false; + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION)) + return key_idx <= 7; + fallthrough; + case NL80211_IFTYPE_AP_VLAN: + /* no RX with GTK */ + if (mac_addr) + return false; + if (cfg80211_igtk_cipher_supported(wdev->wiphy)) + return key_idx <= 5; + return key_idx <= 3; + default: + return false; + } } int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, @@ -288,13 +345,7 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, struct key_params *params, int key_idx, bool pairwise, const u8 *mac_addr) { - if (!cfg80211_valid_key_idx(rdev, key_idx, pairwise)) - return -EINVAL; - - if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) - return -EINVAL; - - if (pairwise && !mac_addr) + if (!cfg80211_valid_key_idx(wdev, key_idx, pairwise, mac_addr)) return -EINVAL; switch (params->cipher) { diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index 5dbf3ef4b257..d45bc08c0de4 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -454,8 +454,7 @@ static int cfg80211_set_encryption(struct cfg80211_registered_device *rdev, rejoin = true; } - if (!pairwise && addr && - !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) + if (!cfg80211_valid_key_idx(wdev, idx, pairwise, addr)) err = -ENOENT; else err = rdev_del_key(rdev, wdev, -1, idx, pairwise, diff --git a/net/wireless/wext-core.c b/net/wireless/wext-core.c index c19dece2bc6e..db77912b3994 100644 --- a/net/wireless/wext-core.c +++ b/net/wireless/wext-core.c @@ -660,8 +660,7 @@ struct iw_statistics *get_wireless_stats(struct net_device *dev) dev->ieee80211_ptr->wiphy->wext && dev->ieee80211_ptr->wiphy->wext->get_wireless_stats) { wireless_warn_cfg80211_wext(); - if (dev->ieee80211_ptr->wiphy->flags & (WIPHY_FLAG_SUPPORTS_MLO | - WIPHY_FLAG_DISABLE_WEXT)) + if (dev->ieee80211_ptr->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) return NULL; return dev->ieee80211_ptr->wiphy->wext->get_wireless_stats(dev); } @@ -703,8 +702,7 @@ static iw_handler get_handler(struct net_device *dev, unsigned int cmd) #ifdef CONFIG_CFG80211_WEXT if (dev->ieee80211_ptr && dev->ieee80211_ptr->wiphy) { wireless_warn_cfg80211_wext(); - if (dev->ieee80211_ptr->wiphy->flags & (WIPHY_FLAG_SUPPORTS_MLO | - WIPHY_FLAG_DISABLE_WEXT)) + if (dev->ieee80211_ptr->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) return NULL; handlers = dev->ieee80211_ptr->wiphy->wext; } diff --git a/net/xdp/xsk_queue.c b/net/xdp/xsk_queue.c index 4dd01b7d858e..d95b6d0d94aa 100644 --- a/net/xdp/xsk_queue.c +++ b/net/xdp/xsk_queue.c @@ -9,6 +9,8 @@ #include #include +#include + #include "xsk_queue.h" static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue) @@ -21,6 +23,14 @@ static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue) return struct_size(rxtx_ring, desc, q->nentries); } +static void *xskq_vmalloc_user(unsigned long size) +{ + return __vmalloc_node_range(size, SHMLBA, VMALLOC_START, VMALLOC_END, + GFP_KERNEL_ACCOUNT | __GFP_ZERO, PAGE_KERNEL, + VM_USERMAP, NUMA_NO_NODE, + __builtin_return_address(0)); +} + struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) { struct xsk_queue *q; @@ -46,7 +56,7 @@ struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) size = PAGE_ALIGN(size); - q->ring = vmalloc_user(size); + q->ring = xskq_vmalloc_user(size); if (!q->ring) { kfree(q); return NULL; diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index eb1b6f67739e..0e21ab72f0fe 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -135,7 +135,7 @@ static void nat_keepalive_send(struct nat_keepalive *ka) uh = skb_push(skb, sizeof(*uh)); uh->source = ka->encap_sport; uh->dest = ka->encap_dport; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); uh->check = 0; skb->mark = ka->smark; diff --git a/net/xfrm/xfrm_sysctl.c b/net/xfrm/xfrm_sysctl.c index ca003e8a0376..357152a50faf 100644 --- a/net/xfrm/xfrm_sysctl.c +++ b/net/xfrm/xfrm_sysctl.c @@ -13,7 +13,7 @@ static void __net_init __xfrm_sysctl_init(struct net *net) } #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm_table[] = { +static const struct ctl_table xfrm_table[] = { { .procname = "xfrm_aevent_etime", .maxlen = sizeof(u32), diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h index 2ec07de1d73b..2c85525704c1 100644 --- a/tools/include/uapi/linux/if_tun.h +++ b/tools/include/uapi/linux/if_tun.h @@ -67,6 +67,10 @@ #define IFF_TAP 0x0002 #define IFF_NAPI 0x0010 #define IFF_NAPI_FRAGS 0x0020 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps index 43d06ecbae93..2771375339d9 100644 --- a/tools/net/ynl/Makefile.deps +++ b/tools/net/ynl/Makefile.deps @@ -35,6 +35,7 @@ CFLAGS_nfsd:=$(call get_hdr_inc,_LINUX_NFSD_NETLINK_H,nfsd_netlink.h) CFLAGS_ovpn:=$(call get_hdr_inc,_LINUX_OVPN_H,ovpn.h) CFLAGS_ovs_datapath:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_ovs_flow:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) +CFLAGS_ovs_packet:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_ovs_vport:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_psp:=$(call get_hdr_inc,_LINUX_PSP_H,psp.h) CFLAGS_rt-addr:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \ diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py index e69de29bb2d1..d8f59c132ab7 100644 --- a/tools/net/ynl/pyynl/__init__.py +++ b/tools/net/ynl/pyynl/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" Python YNL (YAML Netlink) library. """ + +# Re-export the public library API so it can be imported straight from the +# package, e.g. `from pyynl import YnlFamily`. +# pylint: disable=wildcard-import,unused-wildcard-import +from .lib import * +from .lib import __all__ diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py index 8275a806cf73..b6a6ce12b4a7 100755 --- a/tools/net/ynl/pyynl/cli.py +++ b/tools/net/ynl/pyynl/cli.py @@ -17,9 +17,7 @@ import textwrap # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException - -SYS_SCHEMA_DIR='/usr/share/ynl' -RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink' +from lib import list_families # pylint: disable=too-few-public-methods,too-many-locals class Colors: @@ -48,30 +46,6 @@ def term_width(): """ Get terminal width in columns (80 if stdout is not a terminal) """ return shutil.get_terminal_size().columns -def schema_dir(): - """ - Return the effective schema directory, preferring in-tree before - system schema directory. - """ - script_dir = os.path.dirname(os.path.abspath(__file__)) - schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") - if not os.path.isdir(schema_dir_): - schema_dir_ = SYS_SCHEMA_DIR - if not os.path.isdir(schema_dir_): - raise YnlException(f"Schema directory {schema_dir_} does not exist") - return schema_dir_ - -def spec_dir(): - """ - Return the effective spec directory, relative to the effective - schema directory. - """ - spec_dir_ = schema_dir() + '/specs' - if not os.path.isdir(spec_dir_): - raise YnlException(f"Spec directory {spec_dir_} does not exist") - return spec_dir_ - - class YnlEncoder(json.JSONEncoder): """A custom encoder for emitting JSON with ynl-specific instance types""" def default(self, o): @@ -272,9 +246,8 @@ def main(): pprint.pprint(msg, width=term_width(), compact=True) if args.list_families: - for filename in sorted(os.listdir(spec_dir())): - if filename.endswith('.yaml'): - print(filename.removesuffix('.yaml')) + for family in list_families(): + print(family) return if args.no_schema: @@ -284,28 +257,23 @@ def main(): if args.json_text: attrs = json.loads(args.json_text) - if args.family: - spec = f"{spec_dir()}/{args.family}.yaml" - else: - spec = args.spec - if not os.path.isfile(spec): - raise YnlException(f"Spec file {spec} does not exist") + if args.spec and not os.path.isfile(args.spec): + raise YnlException(f"Spec file {args.spec} does not exist") + # Spec/YnlFamily will raise if both or neither spec and family are given if args.validate: + # Force validation even for installed specs (schema=True), unless the + # user explicitly picked a schema or opted out with --no-schema. + schema = True if args.schema is None else args.schema try: - SpecFamily(spec, args.schema) + SpecFamily(args.spec, schema_path=schema, family=args.family) except SpecException as error: print(error) sys.exit(1) return - if args.family: # set behaviour when using installed specs - if args.schema is None and spec.startswith(SYS_SCHEMA_DIR): - args.schema = '' # disable schema validation when installed - if args.process_unknown is None: - args.process_unknown = True - - ynl = YnlFamily(spec, args.schema, args.process_unknown, + ynl = YnlFamily(args.spec, schema=args.schema, family=args.family, + process_unknown=args.process_unknown, recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py index be741985ae4e..aa4263c8cba9 100644 --- a/tools/net/ynl/pyynl/lib/__init__.py +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -5,12 +5,13 @@ from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \ SpecException +from .specdir import list_families from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException from .doc_generator import YnlDocGenerator __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", - "SpecException", + "SpecException", "list_families", "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException", "YnlDocGenerator"] diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py index 0469a0e270d0..b4ec59814ab1 100644 --- a/tools/net/ynl/pyynl/lib/nlspec.py +++ b/tools/net/ynl/pyynl/lib/nlspec.py @@ -12,6 +12,8 @@ import importlib import os import yaml as pyyaml +from .specdir import find_spec, SYS_SCHEMA_DIR + class SpecException(Exception): """Netlink spec exception. @@ -444,7 +446,23 @@ class SpecFamily(SpecElement): except AttributeError: _yaml_loader = pyyaml.SafeLoader - def __init__(self, spec_path, schema_path=None, exclude_ops=None): + def __init__(self, spec_path=None, schema_path=None, exclude_ops=None, + family=None): + # schema_path selects how the spec is validated: + # None -- no preference: validate against the default schema, + # but trust (skip) installed specs selected by family= + # True -- always validate against the default schema + # path -- validate against this schema + # '' -- do not validate + if (spec_path is None) == (family is None): + raise ValueError("Specify exactly one of spec path or family name") + if family is not None: + spec_path = find_spec(family) + # Installed specs are assumed correct, so skip schema validation + # to save cycles unless the caller asked to validate. + if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR): + schema_path = '' + with open(spec_path, "r", encoding='utf-8') as stream: prefix = '# SPDX-License-Identifier: ' first = stream.readline().strip() @@ -465,7 +483,7 @@ class SpecFamily(SpecElement): self.proto = self.yaml.get('protocol', 'genetlink') self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - if schema_path is None: + if schema_path is None or schema_path is True: schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' if schema_path: with open(schema_path, "r", encoding='utf-8') as stream: diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py new file mode 100644 index 000000000000..fcea9b9fb7b0 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/specdir.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" +Locating YNL spec and schema files on disk. + +Resolves the directory holding the YAML specs (preferring an in-tree copy +over the installed system path) and maps family names to spec files. +""" + +import os + +SYS_SCHEMA_DIR='/usr/share/ynl' +RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink' + + +def schema_dir(): + """ + Return the effective schema directory, preferring in-tree before + system schema directory. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") + if not os.path.isdir(schema_dir_): + schema_dir_ = SYS_SCHEMA_DIR + if not os.path.isdir(schema_dir_): + raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist") + return schema_dir_ + +def spec_dir(): + """ + Return the effective spec directory, relative to the effective + schema directory. + """ + spec_dir_ = schema_dir() + '/specs' + if not os.path.isdir(spec_dir_): + raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist") + return spec_dir_ + + +def find_spec(family): + """ Return the path to the YAML spec file for a family by name. """ + spec = f"{spec_dir()}/{family}.yaml" + if not os.path.isfile(spec): + raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}") + return spec + + +def list_families(): + """ Return the sorted names of all families with an installed spec. """ + return sorted(f.removesuffix('.yaml') + for f in os.listdir(spec_dir()) if f.endswith('.yaml')) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 092d132edec1..8682bf588e1f 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -661,6 +661,14 @@ class YnlFamily(SpecFamily): """ YNL family -- a Netlink interface built from a YAML spec. + The spec can be selected either by file path (def_path=) or, when it + ships in a well-known location, by family name (family="xyz"); exactly + one of the two must be given. For example: + + from pyynl import YnlFamily + + ynl = YnlFamily(family="netdev") + Primary use of the class is to execute Netlink commands: ynl.(attrs, ...) @@ -691,11 +699,16 @@ class YnlFamily(SpecFamily): ynl.get_policy(op_name, mode) -- query kernel policy for an op """ - def __init__(self, def_path, schema=None, process_unknown=False, - recv_size=0): - super().__init__(def_path, schema) + def __init__(self, def_path=None, schema=None, process_unknown=None, + recv_size=0, family=None): + super().__init__(def_path, schema, family=family) self.include_raw = False + # Specs from /usr (selected by family=) have a higher chance of being + # stale, default to ignoring unknown attrs. In-tree users, and users + # who bundle the spec need to make a conscious decision. + if process_unknown is None: + process_unknown = family is not None self.process_unknown = process_unknown try: diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index cdc3646f2642..2b3483db1b60 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -526,8 +526,10 @@ class TypeString(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len + 1);", + return [f"{var}->{self.c_name} = malloc(len + 1);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", f"{var}->{self.c_name}[len] = 0;"], \ ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \ @@ -582,8 +584,10 @@ class TypeBinary(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len);", + return [f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -601,11 +605,13 @@ class TypeBinaryStruct(TypeBinary): def _attr_get(self, ri, var): struct_sz = 'sizeof(struct ' + c_lower(self.get("struct")) + ')' len_mem = var + '->_' + self.presence_type() + '.' + self.c_name - return [f"{len_mem} = len;", - f"if (len < {struct_sz})", + return [f"if (len < {struct_sz})", f"{var}->{self.c_name} = calloc(1, {struct_sz});", "else", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -631,9 +637,11 @@ class TypeBinaryScalarArray(TypeBinary): def _attr_get(self, ri, var): len_mem = var + '->_count.' + self.c_name - return [f"{len_mem} = len / sizeof(__{self.get('sub-type')});", - f"len = {len_mem} * sizeof(__{self.get('sub-type')});", + return [f"len = (len / sizeof(__{self.get('sub-type')})) * sizeof(__{self.get('sub-type')});", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len / sizeof(__{self.get('sub-type')});", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -2227,6 +2235,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2252,6 +2262,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): aspec = struct[arg] ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2275,6 +2287,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.nl() ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));') ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);') + ri.cw.p(f"if (!dst->{aspec.c_name}[i])") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;") ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);") ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;") @@ -2434,6 +2448,8 @@ def print_req(ri): if 'reply' in ri.op[ri.op_mode]: ri.cw.p('rsp = calloc(1, sizeof(*rsp));') + ri.cw.p('if (!rsp)') + ri.cw.p(f'return {ret_err};') ri.cw.p('yrs.yarg.data = rsp;') ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") if ri.op.value is not None: @@ -2747,6 +2763,9 @@ def print_dump_type_free(ri): ri.cw.block_start() ri.cw.p(f"{sub_type} *next = rsp;") ri.cw.nl() + ri.cw.p('if (!next)') + ri.cw.p('return;') + ri.cw.nl() ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') _free_type_members_iter(ri, ri.struct['reply']) ri.cw.p('rsp = next;') diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py index db3b62c652e7..0ee0c8e87686 100755 --- a/tools/net/ynl/tests/ethtool.py +++ b/tools/net/ynl/tests/ethtool.py @@ -11,12 +11,10 @@ import pathlib import pprint import sys import re -import os # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) # pylint: disable=import-error -from cli import schema_dir, spec_dir from lib import YnlFamily @@ -173,10 +171,7 @@ def main(): args = parser.parse_args() - spec = os.path.join(spec_dir(), 'ethtool.yaml') - schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') - - ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv) + ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 38ce6060b8fa..4549358cc8c2 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -1164,8 +1164,8 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, bool test_timeout) { u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len; + struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem_real; struct pkt_stream *pkt_stream = xsk->pkt_stream; - struct xsk_umem_info *umem = xsk->umem; bool use_poll = ifobject->use_poll; struct pollfd fds = { }; int ret; @@ -1513,7 +1513,7 @@ static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobjec umem_tx->base_addr = 0; umem_tx->next_buffer = 0; - ret = xsk_configure(test, ifobject, umem_tx, true); + ret = xsk_configure(test, ifobject, umem_rx, true); if (ret) return ret; ifobject->xsk = &ifobject->xsk_arr[0]; diff --git a/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh b/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh index 167aa4a4a12a..903c7a6c7287 100644 --- a/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh +++ b/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh @@ -48,7 +48,7 @@ gateway_create() ip -n ${g_ns} link add br0 type bridge ip -n ${g_ns} link set br0 up ip -n ${g_ns} addr add ${g_ip4}/24 dev br0 - ip -n ${g_ns} addr add ${g_ip6}/24 dev br0 + ip -n ${g_ns} addr add ${g_ip6}/24 dev br0 nodad } gateway_destroy() @@ -75,7 +75,7 @@ server_create() ip -n ${s_ns} link set bond0 up ip -n ${s_ns} addr add ${s_ip4}/24 dev bond0 - ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 + ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 nodad } # Reset bond with new mode and options @@ -97,9 +97,7 @@ bond_reset() ip -n ${s_ns} link set bond0 up ip -n ${s_ns} addr add ${s_ip4}/24 dev bond0 - ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 - # Wait for IPv6 address ready as it needs DAD - slowwait 2 ip netns exec ${s_ns} ping6 ${c_ip6} -c 1 -W 0.1 &> /dev/null + ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 nodad } server_destroy() @@ -124,7 +122,7 @@ client_create() ip -n ${c_ns} link set eth0 up ip -n ${c_ns} addr add ${c_ip4}/24 dev eth0 - ip -n ${c_ns} addr add ${c_ip6}/24 dev eth0 + ip -n ${c_ns} addr add ${c_ip6}/24 dev eth0 nodad } client_destroy() diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index 2070e890e064..b6989c7d3d9d 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -4,8 +4,12 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n CONFIG_INET_PSP=y CONFIG_IPV6=y CONFIG_MACSEC=m +CONFIG_NET_ACT_SKBEDIT=m CONFIG_NET_CLS_ACT=y CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_MATCHALL=m CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y CONFIG_NETCONSOLE_EXTENDED_LOG=y @@ -14,6 +18,7 @@ CONFIG_NETKIT=y CONFIG_NET_SCH_ETF=m CONFIG_NET_SCH_FQ=m CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_PRIO=m CONFIG_PPP=y CONFIG_PPPOE=y CONFIG_VLAN_8021Q=m diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index fd0535a96d84..78bb0169350b 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -19,7 +19,7 @@ TEST_GEN_FILES := \ TEST_PROGS = \ csum.py \ - devlink_port_split.py \ + devlink_rate_cross_esw.py \ devlink_rate_tc_bw.py \ devmem.py \ ethtool.sh \ @@ -53,6 +53,10 @@ TEST_PROGS = \ xsk_reconfig.py \ # +TEST_PROGS_EXTENDED := \ + devlink_port_split.py \ +# end of TEST_PROGS_EXTENDED + TEST_FILES := \ devmem_lib.py \ ethtool_lib.sh \ diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config index ed8642b68094..d89a9ba17655 100644 --- a/tools/testing/selftests/drivers/net/hw/config +++ b/tools/testing/selftests/drivers/net/hw/config @@ -15,11 +15,13 @@ CONFIG_IPV6_SIT=y CONFIG_IPV6_TUNNEL=y CONFIG_NET_CLS_ACT=y CONFIG_NET_CLS_BPF=y +CONFIG_NET_DEVMEM=y CONFIG_NET_IPGRE=y CONFIG_NET_IPGRE_DEMUX=y CONFIG_NET_IPIP=y CONFIG_NETKIT=y CONFIG_NET_SCH_INGRESS=y +CONFIG_SYNC_FILE=y CONFIG_UDMABUF=y CONFIG_USER_NS=y CONFIG_VXLAN=y diff --git a/tools/testing/selftests/drivers/net/hw/csum.py b/tools/testing/selftests/drivers/net/hw/csum.py index 3e3a89a34afe..0e99198f8d39 100755 --- a/tools/testing/selftests/drivers/net/hw/csum.py +++ b/tools/testing/selftests/drivers/net/hw/csum.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -"""Run the tools/testing/selftests/net/csum testsuite.""" +"""Run the tools/testing/selftests/net/lib/csum testsuite.""" from os import path diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py new file mode 100755 index 000000000000..4416f024cb76 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Devlink Rate Cross-eswitch Scheduling Test Suite +================================================== + +Control-plane tests for cross-eswitch TX scheduling via devlink-rate. +Validates that VFs from different PFs on the same chip can share +rate groups using the cross-device parent-dev attribute. + +Preconditions: +- NETIF points to a bond device with exactly two interfaces. +- the interfaces must be two PFs from different devices sharing the same chip. +- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG: + - devlink dev eswitch set $DEV1 mode switchdev + - devlink dev eswitch set $DEV2 mode switchdev + - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime + - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime +- test cases will be skipped if: + - the number of interfaces in the bond device is != 2. + - the kernel doesn't support devlink rates. + - the devlink API doesn't support cross-device parents (ENODEV). + - cross-esw rate scheduling returns EOPNOTSUPP. +""" + +import errno +import glob +import os +import time + +from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit +from lib.py import KsftSkipEx, KsftFailEx +from lib.py import NetDrvEnv, DevlinkFamily +from lib.py import NlError +from lib.py import cmd, defer, ip, tool + + +# --- Discovery and setup --- + + +def get_bond_slaves(bond_ifname): + """Returns sorted list of slave netdev names for a bond.""" + pattern = f"/sys/class/net/{bond_ifname}/lower_*" + lowers = glob.glob(pattern) + if not lowers: + raise KsftSkipEx(f"No bond slaves for {bond_ifname}") + slaves = [] + for path in sorted(lowers): + name = os.path.basename(path) + if name.startswith("lower_"): + name = name[len("lower_"):] + slaves.append(name) + return slaves + + +def discover_pfs(cfg): + """Discovers both PFs from bond slaves.""" + slaves = get_bond_slaves(cfg.ifname) + if len(slaves) != 2: + raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}") + + pf0, pf1 = slaves[0], slaves[1] + ksft_pr(f"PF0: {pf0} PF1: {pf1}") + return pf0, pf1 + + +def get_pci_addr(ifname): + """Resolves PCI address for a network interface.""" + return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device")) + + +def get_vf_port_index(pf_pci): + """Finds devlink port-index for vf0 under pf_pci.""" + ports = tool("devlink", "port show", json=True)["port"] + for port_name, props in ports.items(): + if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0: + return int(port_name.split("/")[-1]) + raise KsftSkipEx(f"VF port not found for {pf_pci}") + + +def cleanup_esw(pf): + """Removes VFs if created by tests.""" + cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False) + + +def setup_esw(pf): + """Creates 1 VF on 'pf'.""" + path = f"/sys/class/net/{pf}/device/sriov_numvfs" + cmd(f"echo 0 > {path}", shell=True) + cmd(f"echo 1 > {path}", shell=True) + defer(cleanup_esw, pf) + time.sleep(2) + + vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net" + entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else [] + if not entries: + raise KsftSkipEx(f"VF not found for {pf}") + ip(f"link set dev {entries[0]} up") + + pf_pci = get_pci_addr(pf) + vf_idx = get_vf_port_index(pf_pci) + ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})") + return pf_pci, vf_idx + + +# --- Rate operation helpers --- + + +def rate_new(devnl, dev_pci, node_name, **kwargs): + """Creates rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + try: + devnl.rate_new(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_new not supported") from e + raise KsftFailEx("rate_new failed") from e + + +def rate_get(devnl, dev_pci, node_name): + """Gets rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + return devnl.rate_get(params) + + +def rate_get_leaf(devnl, dev_pci, port_index): + """Gets rate leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + return devnl.rate_get(params) + + +def rate_del(devnl, dev_pci, node_name): + """Deletes rate node.""" + devnl.rate_del({ + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + }) + + +def rate_set_leaf(devnl, dev_pci, port_index, **kwargs): + """Sets rate attributes on a leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + params.update(kwargs) + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_set_leaf_parent(devnl, dev_pci, port_index, + parent_name, parent_dev_pci=None): + """Sets a leaf's parent, optionally cross-esw.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + "rate-parent-node-name": parent_name, + } + if parent_dev_pci: + params["parent-dev"] = { + "bus-name": "pci", + "dev-name": parent_dev_pci, + } + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + if parent_dev_pci and e.error == errno.ENODEV: + raise KsftSkipEx("Cross-esw scheduling not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_clear_leaf_parent(devnl, dev_pci, port_index): + """Clears a leaf's parent.""" + rate_set_leaf_parent(devnl, dev_pci, port_index, "") + + +def rate_set_node(devnl, dev_pci, node_name, **kwargs): + """Sets rate attributes on a node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + devnl.rate_set(params) + + +# --- Test cases --- + + +def test_same_esw_parent(cfg): + """Assigns PF0's VF to PF0's group (same esw baseline).""" + pf0, _ = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + + rate_new(cfg.devnl, pf0_pci, "group0") + defer(rate_del, cfg.devnl, pf0_pci, "group0") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + + ksft_pr("Same-esw parent assignment succeeded") + + +def test_cross_esw_parent(cfg): + """Sets cross-esw parent, then clear it.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, _ = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group1") + defer(rate_del, cfg.devnl, pf0_pci, "group1") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group1", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + + ksft_pr("Cross-esw parent set and clear succeeded") + + +def test_tx_rates_on_cross_esw(cfg): + """Sets tx_max on group and tx_share on leaves in a cross-esw setup.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000}) + defer(rate_del, cfg.devnl, pf0_pci, "group2") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group2", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + ksft_pr("set parent cross-esw succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + ksft_pr("set parent same esw succeeded") + + rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000}) + rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx) + ksft_eq(rate["rate-tx-share"], 1000000) + rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000}) + rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx) + ksft_eq(rate["rate-tx-share"], 2000000) + rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000}) + rate = rate_get(cfg.devnl, pf0_pci, "group2") + ksft_eq(rate["rate-tx-max"], 250000000) + + ksft_pr("tx_max and tx_share set on cross-esw group") + + +def main() -> None: + """Main function.""" + + with NetDrvEnv(__file__, nsim_test=False) as cfg: + cfg.devnl = DevlinkFamily() + + ksft_run( + cases=[ + test_same_esw_parent, + test_cross_esw_parent, + test_tx_rates_on_cross_esw, + ], + args=(cfg,), + ) + ksft_exit() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py index 031cf9905f65..82c11ffc4add 100755 --- a/tools/testing/selftests/drivers/net/hw/devmem.py +++ b/tools/testing/selftests/drivers/net/hw/devmem.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: GPL-2.0 from os import path -from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds +from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds, + run_rx_large_niov) from lib.py import ksft_run, ksft_exit, ksft_disruptive from lib.py import NetDrvEpEnv @@ -30,11 +31,17 @@ def check_rx_hds(cfg) -> None: run_rx_hds(cfg) +def check_rx_large_niov(cfg) -> None: + """Run the devmem RX test with rx-page-size = 16 KiB.""" + run_rx_large_niov(cfg) + + def main() -> None: """Run the devmem test cases.""" with NetDrvEpEnv(__file__) as cfg: setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem")) - ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds], + ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds, + check_rx_large_niov], args=(cfg,)) ksft_exit() diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py index 0921ff03eb81..3554954a6691 100644 --- a/tools/testing/selftests/drivers/net/hw/devmem_lib.py +++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 +# pylint: disable=invalid-name,too-many-arguments """Shared helpers for devmem TCP selftests.""" +import os import re from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, @@ -8,19 +10,82 @@ from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, NetdevFamily) -def require_devmem(cfg): - """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported.""" - if not hasattr(cfg, "devmem_probed"): - probe_command = f"{cfg.bin_local} -f {cfg.ifname}" - cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0 - cfg.devmem_probed = True +RX_PAGE_SIZE_DEFAULT = 0 +RX_PAGE_SIZE_16K = 16384 - if not cfg.devmem_supported: +PROBE_RX_PAGE_SIZES = (RX_PAGE_SIZE_DEFAULT, RX_PAGE_SIZE_16K) + +NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages" + + +def _is_aligned(value, alignment): + """Equivalent of the kernel IS_ALIGNED(value, alignment). + + alignment must be a power of two. + """ + return (value & (alignment - 1)) == 0 + + +def _restore_nr_hugepages(nr_hugepages): + with open(NR_HUGEPAGES_FILE, 'w', encoding='utf-8') as f: + f.write(str(nr_hugepages)) + + +def _reserve_hugepages(want=64): + """Raise nr_hugepages to @want and arrange for it to be restored.""" + with open(NR_HUGEPAGES_FILE, 'r+', encoding='utf-8') as f: + nr_hugepages = int(f.read().strip()) + if nr_hugepages >= want: + return + f.seek(0) + f.write(str(want)) + defer(_restore_nr_hugepages, nr_hugepages) + + +def _probe_devmem(cfg, rx_page_size): + """Return True if ncdevmem can bind cfg.ifname at @rx_page_size.""" + probe_command = f"{cfg.bin_local} -f {cfg.ifname}" + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + probe_command += f" -b {rx_page_size}" + return cmd(probe_command, fail=False, shell=True).ret == 0 + + +def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT): + """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported.""" + if rx_page_size not in PROBE_RX_PAGE_SIZES: + raise RuntimeError( + f"rx-page-size={rx_page_size} is missing from " + f"PROBE_RX_PAGE_SIZES, so it was never probed.") + + if not hasattr(cfg, "devmem_supported"): + _reserve_hugepages() + # Probe every size upfront: in nk tests a leased queue may land in + # ncdevmem's queue range and cause the probe to fail. + cfg.devmem_supported = {size: _probe_devmem(cfg, size) + for size in PROBE_RX_PAGE_SIZES} + + if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]: raise KsftSkipEx("Test requires devmem support") + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + page_size = os.sysconf("SC_PAGE_SIZE") + if not _is_aligned(rx_page_size, page_size): + raise KsftSkipEx( + f"rx-page-size={rx_page_size} is invalid for this platform " + f"(must be a multiple of PAGE_SIZE={page_size})") + + if not cfg.devmem_supported[rx_page_size]: + raise KsftSkipEx( + f"Test requires devmem rx-page-size={rx_page_size} support") + def configure_nic(cfg): """Channels, rings, RSS, queue lease for netkit devmem.""" + if not hasattr(cfg, "devmem_supported"): + raise RuntimeError( + "require_devmem() must be called before configure_nic(), which " + "may lease a queue away and make later probes fail.") + if not hasattr(cfg, 'netns'): return @@ -37,14 +102,13 @@ def configure_nic(cfg): rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}}) orig_rx_rings = rings['rx'] orig_hds_thresh = rings.get('hds-thresh', 0) - orig_data_split = rings.get('tcp-data-split', 'unknown') ethnl.rings_set({'header': {'dev-index': cfg.ifindex}, 'tcp-data-split': 'enabled', 'hds-thresh': 0, 'rx': min(64, orig_rx_rings)}) defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex}, - 'tcp-data-split': orig_data_split, + 'tcp-data-split': 'unknown', 'hds-thresh': orig_hds_thresh, 'rx': orig_rx_rings}) @@ -76,7 +140,8 @@ def set_flow_rule(cfg, port): return int(re.search(r'ID (\d+)', output).group(1)) -def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False): +def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False, + rx_page_size=RX_PAGE_SIZE_DEFAULT): """Build the ncdevmem RX listener command.""" if hasattr(cfg, 'netns'): flow_rule_id = set_flow_rule(cfg, port) @@ -96,6 +161,8 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False): extras.append("-v 7") if fail_on_linear: extras.append("-L") + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + extras.append(f"-b {rx_page_size}") parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}", f"-p {port}", *extras] @@ -202,6 +269,33 @@ def run_tx_chunks(cfg): ksft_eq(socat.stdout.strip(), "hello\nworld") +def run_rx_large_niov(cfg): + """Run the devmem RX test with a large niov (rx-page-size > PAGE_SIZE). + + Sweep payload sizes that straddle the niov boundary: below, equal to, + and above rx_page_size, to exercise sub-niov, exact-niov, and multi-niov + RX paths. + """ + require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_16K) + _reserve_hugepages() + configure_nic(cfg) + netns = getattr(cfg, "netns", None) + + for size in [1024, 4096, 8192, 16384, 32768, 65536]: + port = rand_port() + socat = socat_send(cfg, port) + listen_cmd = ncdevmem_rx(cfg, port, + flow_steer=not netns, + rx_page_size=RX_PAGE_SIZE_16K) + data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | " + f"head -c {size} | {socat}") + with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem: + wait_port_listen(port, proto="tcp", ns=netns) + cmd(data_pipe, host=cfg.remote, shell=True) + ksft_eq(ncdevmem.ret, 0, + f"large-niov failed for payload size {size}") + + def run_rx_hds(cfg): """Run the HDS test by running devmem RX across a segment size sweep.""" require_devmem(cfg) diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh index 2ec19edddfaa..a074834cbe59 100755 --- a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh +++ b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh @@ -65,6 +65,8 @@ bucket_test() run_on "$iface" \ "$MZ" "$iface" -q -c "$num_tx" -p "$len" -a own -b bcast -d 10us + hw_stats_settle "$iface" + after=$(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \ jq -r ".[0].rmon[\"${set}-pktsNtoM\"][$bucket].val") diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh index c085d2a4c989..09f8128c51f3 100755 --- a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh +++ b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh @@ -43,10 +43,12 @@ traffic_test() done # shellcheck disable=SC2086 # needs split options - run_on "$iface" "$MZ" "$iface" -q -c "$num_tx" $pkt_format + run_on "$iface" "$MZ" "$iface" -q -d 10usec -c "$num_tx" $pkt_format # shellcheck disable=SC2086 # needs split options - run_on "$neigh" "$MZ" "$neigh" -q -c "$num_rx" $pkt_format + run_on "$neigh" "$MZ" "$neigh" -q -d 10usec -c "$num_rx" $pkt_format + + hw_stats_settle "$int" for i in "${!counters[@]}"; do read -r int grp cnt target exact_check xfail_message \ diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c index d96e8a3b5a65..918e3b51f3b8 100644 --- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c +++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,7 @@ #include #include +#include #include #include #include @@ -79,6 +81,7 @@ #define PAGE_SHIFT 12 #define TEST_PREFIX "ncdevmem" #define NUM_PAGES 16000 +#define MB(x) ((x) << 20) #ifndef MSG_SOCK_DEVMEM #define MSG_SOCK_DEVMEM 0x2000000 @@ -100,6 +103,7 @@ static unsigned int dmabuf_id; static uint32_t tx_dmabuf_id; static int waittime_ms = 500; static bool fail_on_linear; +static uint32_t rx_page_size; /* System state loaded by current_config_load() */ #define MAX_FLOWS 8 @@ -142,6 +146,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size) { struct udmabuf_create create; struct memory_buffer *ctx; + unsigned int memfd_flags; int ret; ctx = malloc(sizeof(*ctx)); @@ -150,15 +155,20 @@ static struct memory_buffer *udmabuf_alloc(size_t size) ctx->size = size; - ctx->devfd = open("/dev/udmabuf", O_RDWR); + ctx->devfd = open("/dev/udmabuf", O_RDONLY); if (ctx->devfd < 0) { pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]"); goto err_free_ctx; } - ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING); + memfd_flags = MFD_ALLOW_SEALING; + if (rx_page_size > getpagesize()) + memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB; + + ctx->memfd = memfd_create("udmabuf-test", memfd_flags); if (ctx->memfd < 0) { - pr_err("[skip,no-memfd]"); + pr_err("[skip,no-memfd%s]", + (memfd_flags & MFD_HUGETLB) ? " (need hugepages)" : ""); goto err_close_dev; } @@ -168,6 +178,11 @@ static struct memory_buffer *udmabuf_alloc(size_t size) goto err_close_memfd; } + if (memfd_flags & MFD_HUGETLB) { + size = roundup(size, MB(2)); + ctx->size = size; + } + ret = ftruncate(ctx->memfd, size); if (ret == -1) { pr_err("[FAIL,memfd-truncate]"); @@ -699,6 +714,8 @@ static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd, netdev_bind_rx_req_set_ifindex(req, ifindex); netdev_bind_rx_req_set_fd(req, dmabuf_fd); __netdev_bind_rx_req_set_queues(req, queues, n_queue_index); + if (rx_page_size) + netdev_bind_rx_req_set_rx_page_size(req, rx_page_size); rsp = netdev_bind_rx(*ys, req); if (!rsp) { @@ -1411,7 +1428,7 @@ int main(int argc, char *argv[]) int is_server = 0, opt; int ret, err = 1; - while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) { + while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:")) != -1) { switch (opt) { case 'L': fail_on_linear = true; @@ -1446,6 +1463,19 @@ int main(int argc, char *argv[]) case 'n': skip_config = 1; break; + case 'b': { + unsigned long val; + + errno = 0; + val = strtoul(optarg, NULL, 0); + if ((val == ULONG_MAX && errno == ERANGE) || + val > UINT32_MAX) { + pr_err("invalid rx_page_size: %s", optarg); + return 1; + } + rx_page_size = val; + break; + } case '?': fprintf(stderr, "unknown option: %c\n", optopt); break; diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py index 300ed2a70ab4..61c6f31f01e5 100755 --- a/tools/testing/selftests/drivers/net/hw/nk_devmem.py +++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py @@ -3,7 +3,8 @@ """Test devmem TCP with netkit.""" import os -from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds +from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds, + run_rx_large_niov) from lib.py import ksft_run, ksft_exit, ksft_disruptive from lib.py import NetDrvContEnv @@ -31,6 +32,11 @@ def check_nk_rx_hds(cfg) -> None: run_rx_hds(cfg) +def check_nk_rx_large_niov(cfg) -> None: + """Run the devmem RX large-niov test through netkit.""" + run_rx_large_niov(cfg) + + def main() -> None: """Run the netkit devmem test cases.""" with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg: @@ -38,7 +44,7 @@ def main() -> None: os.path.join(os.path.dirname(os.path.abspath(__file__)), "ncdevmem")) ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks, - check_nk_rx_hds], args=(cfg,)) + check_nk_rx_hds, check_nk_rx_large_niov], args=(cfg,)) ksft_exit() diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py index f36f76d6ca59..5b25fa89c629 100755 --- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py +++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py @@ -651,9 +651,14 @@ def test_rss_context_overlap(cfg, other_ctx=0): ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") # Test the main context - cnts = _get_rx_cnts(cfg) - GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) - cnts = _get_rx_cnts(cfg, prev=cnts) + attempts = 3 + for attempt in range(attempts): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + if sum(cnts[:2]) >= 7000 and sum(cnts[2:4]) >= 7000: + break + ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts)) ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index cd7e080e6f84..571732198b93 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -21,6 +21,8 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx ETH_RSS_HASH_TOP = 1 # Must match RPS_MAX_CPUS in toeplitz.c RPS_MAX_CPUS = 16 +# Cap Rx queues so IRQ pinning leaves free CPUs in the RPS_MAX_CPUS range +QUEUE_CAP = 8 def _check_rps_and_rfs_not_configured(cfg): @@ -48,6 +50,25 @@ def _get_cpu_for_irq(irq): return int(data) +def _cap_queue_count(cfg): + ehdr = {"header": {"dev-index": cfg.ifindex}} + chans = cfg.ethnl.channels_get(ehdr) + + config = {} + restore = {} + for key in ("combined-count", "rx-count"): + cur = chans.get(key, 0) + if cur > QUEUE_CAP: + config[key] = QUEUE_CAP + restore[key] = cur + + if not config: + return + + cfg.ethnl.channels_set(ehdr | config) + defer(cfg.ethnl.channels_set, ehdr | restore) + + def _get_irq_cpus(cfg): """ Read the list of IRQs for the device Rx queues. @@ -177,6 +198,7 @@ def test(cfg, proto_flag, ipver, grp): ] if grp: + _cap_queue_count(cfg) _check_rps_and_rfs_not_configured(cfg) if grp == "rss": irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py index 802bb4868046..67f6c9ca9a64 100755 --- a/tools/testing/selftests/drivers/net/hw/tso.py +++ b/tools/testing/selftests/drivers/net/hw/tso.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -"""Run the tools/testing/selftests/net/csum testsuite.""" +"""A simple test for TSO.""" import fcntl import socket diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index e4ab99b905b1..25903f580b40 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -7,7 +7,7 @@ import time import json from pathlib import Path from lib.py import KsftSkipEx, KsftXfailEx -from lib.py import ksft_setup, wait_file +from lib.py import ksft_pr, ksft_setup, wait_file from lib.py import cmd, ethtool, ip, CmdExitFailure from lib.py import NetNS, NetdevSimDev, UserNetNS from .remote import Remote @@ -31,6 +31,7 @@ class NetDrvEnvBase: # Following attrs must be set be inheriting classes self.dev = None + self.ifname = None def _load_env_file(self): env = os.environ.copy() @@ -58,6 +59,22 @@ class NetDrvEnvBase: def __del__(self): pass + def _print_dev_info(self): + """ + Show whether the test ran on real hardware or netdevsim. + Useful to confirm when results are shared on the mailing list. + """ + driver = "unknown" + try: + info = ethtool(f"-i {self.ifname}").stdout + for line in info.splitlines(): + if line.startswith("driver:"): + driver = line.split(':', 1)[1].strip() or driver + break + except (CmdExitFailure, FileNotFoundError): + pass + ksft_pr(f"Interface: {self.ifname}, driver: {driver}") + def __enter__(self): ip(f"link set dev {self.dev['ifname']} up") wait_file(f"/sys/class/net/{self.dev['ifname']}/carrier", @@ -94,6 +111,7 @@ class NetDrvEnv(NetDrvEnvBase): self.dev = self._ns.nsims[0].dev self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] + self._print_dev_info() def __del__(self): if self._ns: @@ -114,10 +132,11 @@ class NetDrvEpEnv(NetDrvEnvBase): nsim_v4_pfx = "192.0.2." nsim_v6_pfx = "2001:db8::" - def __init__(self, src_path, nsim_test=None): + def __init__(self, src_path, nsim_test=None, queue_count=None): super().__init__(src_path) self._stats_settle_time = None + self._queue_count = queue_count # Things we try to destroy self.remote = None @@ -159,16 +178,11 @@ class NetDrvEpEnv(NetDrvEnvBase): self.remote = Remote(kind, args, src_path) - self.addr_ipver = "6" if self.addr_v["6"] else "4" - self.addr = self.addr_v[self.addr_ipver] - self.remote_addr = self.remote_addr_v[self.addr_ipver] - - # Bracketed addresses, some commands need IPv6 to be inside [] - self.baddr = f"[{self.addr_v['6']}]" if self.addr_v["6"] else self.addr_v["4"] - self.remote_baddr = f"[{self.remote_addr_v['6']}]" if self.remote_addr_v["6"] else self.remote_addr_v["4"] + self.set_ipver("6" if self.addr_v["6"] else "4") self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] + self._print_dev_info() # resolve remote interface name self.remote_ifname = self.resolve_remote_ifc() @@ -179,9 +193,13 @@ class NetDrvEpEnv(NetDrvEnvBase): self._required_cmd = {} def create_local(self): + nsim_kwargs = {} + if self._queue_count: + nsim_kwargs["queue_count"] = self._queue_count + self._netns = NetNS() - self._ns = NetdevSimDev() - self._ns_peer = NetdevSimDev(ns=self._netns) + self._ns = NetdevSimDev(**nsim_kwargs) + self._ns_peer = NetdevSimDev(ns=self._netns, **nsim_kwargs) with open("/proc/self/ns/net") as nsfd0, \ open("/var/run/netns/" + self._netns.name) as nsfd1: @@ -252,6 +270,25 @@ class NetDrvEpEnv(NetDrvEnvBase): if not self.addr_v[ipver] or not self.remote_addr_v[ipver]: raise KsftSkipEx(f"Test requires IPv{ipver} connectivity") + def set_ipver(self, ipver): + """ + Modify the IP version used by the generic address fields. + """ + if ipver == getattr(self, "addr_ipver", None): + return + + self.require_ipver(ipver) + + self.addr_ipver = ipver + self.addr = self.addr_v[ipver] + self.remote_addr = self.remote_addr_v[ipver] + + # Bracketed addresses, some commands need IPv6 to be inside [] + self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6" + else self.addr_v["4"]) + self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6" + else self.remote_addr_v["4"]) + def require_nsim(self, nsim_test=True): """Require or exclude netdevsim for this test""" if nsim_test and self._ns is None: diff --git a/tools/testing/selftests/drivers/net/netconsole/Makefile b/tools/testing/selftests/drivers/net/netconsole/Makefile index b56c70b7e274..f0674c0017fc 100644 --- a/tools/testing/selftests/drivers/net/netconsole/Makefile +++ b/tools/testing/selftests/drivers/net/netconsole/Makefile @@ -13,6 +13,7 @@ TEST_PROGS := \ netcons_resume.sh \ netcons_sysdata.sh \ netcons_torture.sh \ + netcons_userdata.sh \ # end of TEST_PROGS include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh new file mode 100755 index 000000000000..113903f4ce1c --- /dev/null +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 + +# Exercise the netconsole userdata payload. +# +# The first part checks that the payload the target transmits follows what +# configfs says: a value shows up in the next message, an update replaces the +# previous one, clearing the value drops the entry, and so does removing the +# key. +# +# The second part rewrites values, creates and deletes keys, and clears the +# payload entirely while messages are being sent, so the transmit path keeps +# picking up payloads that are being replaced underneath it. It runs twice, +# once with a payload small enough to fit in a single packet and once large +# enough to be fragmented. +# +# Author: Breno Leitao + +set -euo pipefail + +SCRIPTDIR=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")") + +source "${SCRIPTDIR}"/../lib/sh/lib_netcons.sh + +# Number of times each torture worker loops +ITERATIONS=${1:-200} + +# Keys owned by each torture worker. Workers do not share keys, so a failing +# configfs operation means a real problem and not a lost race. +CHURN_KEY="churnkey" +TRANSIENT_KEY="transientkey" +# Number of keys used to push a message past MAX_PRINT_CHUNK +BULK_KEYS=8 + +USERDATA_DIR="${NETCONS_PATH}/userdata" +# Values are capped at MAX_EXTRADATA_VALUE_LEN(200) bytes, so ${BULK_KEYS} +# entries of this size are enough to force fragmentation +LONG_VALUE=$(printf -- 'v%.0s' {1..190}) + +function write_key() { + local KEY="${1}" + local VALUE="${2}" + + mkdir -p "${USERDATA_DIR}/${KEY}" + echo "${VALUE}" > "${USERDATA_DIR}/${KEY}/value" +} + +# Send a single message and capture it on the destination interface +function send_and_capture() { + rm -f "${OUTPUT_FILE}" + + listen_port_and_save_to "${OUTPUT_FILE}" & + wait_for_port "${NAMESPACE}" "${PORT}" "${IP_VERSION}" + echo "${MSG}: ${TARGET}" > /dev/kmsg + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true + pkill_socat + validate_msg "${OUTPUT_FILE}" +} + +function expect_in_msg() { + local WANTED="${1}" + + if ! grep -q -- "${WANTED}" "${OUTPUT_FILE}"; then + echo "FAIL: '${WANTED}' not found in ${OUTPUT_FILE}" >&2 + cat "${OUTPUT_FILE}" >&2 + exit "${ksft_fail}" + fi +} + +function expect_not_in_msg() { + local UNWANTED="${1}" + + if grep -q -- "${UNWANTED}" "${OUTPUT_FILE}"; then + echo "FAIL: '${UNWANTED}' found in ${OUTPUT_FILE}" >&2 + cat "${OUTPUT_FILE}" >&2 + exit "${ksft_fail}" + fi +} + +# Every write publishes a new payload and frees the previous one. An empty +# value is skipped when the payload is formatted, so this also drives the +# target through having no payload at all. +function churn_value() { + local i + + for i in $(seq "${ITERATIONS}") + do + echo "value${i}" > "${USERDATA_DIR}/${CHURN_KEY}/value" + echo > "${USERDATA_DIR}/${CHURN_KEY}/value" + done +} + +# Create and delete a key underneath the sender +function churn_key() { + local i + + for i in $(seq "${ITERATIONS}") + do + mkdir "${USERDATA_DIR}/${TRANSIENT_KEY}" + echo "transient${i}" > "${USERDATA_DIR}/${TRANSIENT_KEY}/value" + rmdir "${USERDATA_DIR}/${TRANSIENT_KEY}" + done +} + +# Keep the transmit path busy while the payload is being replaced +function send_messages() { + local i + + for i in $(seq "${ITERATIONS}") + do + echo "${MSG}: ${TARGET} ${i}" > /dev/kmsg + done +} + +# Run the workers concurrently and fail if any of them hits an error +function run_workers() { + local PIDS=() + local WORKER + local RET=0 + local PID + + for WORKER in "$@" + do + "${WORKER}" & + PIDS+=("$!") + done + + # Reap every worker before reporting a failure, otherwise a surviving + # worker keeps writing to configfs while the exit trap cleans it up. + for PID in "${PIDS[@]}" + do + wait "${PID}" || RET=1 + done + + if [[ "${RET}" -ne 0 ]] + then + echo "FAIL: userdata torture worker failed" >&2 + exit "${ksft_fail}" + fi +} + +function create_bulk_keys() { + local i + + for i in $(seq "${BULK_KEYS}") + do + write_key "bulk${i}" "${LONG_VALUE}" + done +} + +function delete_bulk_keys() { + local i + + for i in $(seq "${BULK_KEYS}") + do + rmdir "${USERDATA_DIR}/bulk${i}" + done +} + +# ========== # +# Start here # +# ========== # + +modprobe netdevsim 2> /dev/null || true +modprobe netconsole 2> /dev/null || true + +IP_VERSION="ipv4" +# The content of kmsg will be saved to the following file +OUTPUT_FILE="/tmp/${TARGET}" + +# Check for basic system dependency and exit if not found +check_for_dependencies +# Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5) +echo "6 5" > /proc/sys/kernel/printk +# Remove the namespace, interfaces and netconsole target on exit +trap cleanup EXIT +# Create one namespace and two interfaces +set_network "${IP_VERSION}" +# Create a dynamic target for netconsole +create_dynamic_target + +# =================================================== +# TEST #1 +# A value written to configfs reaches the destination +# =================================================== +write_key "${USERDATA_KEY}" "first" +send_and_capture +expect_in_msg "${USERDATA_KEY}=first" + +# =================================================== +# TEST #2 +# Updating the value replaces the previous payload +# =================================================== +write_key "${USERDATA_KEY}" "second" +send_and_capture +expect_in_msg "${USERDATA_KEY}=second" +expect_not_in_msg "${USERDATA_KEY}=first" + +# =================================================== +# TEST #3 +# Clearing the value drops the entry +# =================================================== +echo > "${USERDATA_DIR}/${USERDATA_KEY}/value" +send_and_capture +expect_not_in_msg "${USERDATA_KEY}=" + +# =================================================== +# TEST #4 +# Removing the key drops the entry +# =================================================== +write_key "${USERDATA_KEY}" "third" +rmdir "${USERDATA_DIR}/${USERDATA_KEY}" +send_and_capture +expect_not_in_msg "${USERDATA_KEY}=" +rm "${OUTPUT_FILE}" + +# =================================================== +# TEST #5 +# Torture the payload while messages are being sent, +# first unfragmented and then fragmented +# =================================================== +write_key "${CHURN_KEY}" "${USERDATA_VALUE}" +run_workers churn_value churn_key send_messages + +create_bulk_keys +run_workers churn_value churn_key send_messages +delete_bulk_keys + +exit "${ksft_pass}" diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh index 9adfba8f87e6..b9fcafad4258 100755 --- a/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh @@ -116,12 +116,14 @@ done # bool settings which ethtool displays on the same line ethtool -C $NSIM_NETDEV adaptive-rx on -s=$(ethtool -c $NSIM_NETDEV | grep -q "Adaptive RX: on TX: off") -check $? "$s" "" +s=$(ethtool -c $NSIM_NETDEV) +echo "$s" | grep -q "Adaptive RX: on TX: off" +check $? "" "" ethtool -C $NSIM_NETDEV adaptive-tx on -s=$(ethtool -c $NSIM_NETDEV | grep -q "Adaptive RX: on TX: on") -check $? "$s" "" +s=$(ethtool -c $NSIM_NETDEV) +echo "$s" | grep -q "Adaptive RX: on TX: on" +check $? "" "" if [ $num_errors -eq 0 ]; then echo "PASSED all $((num_passes)) checks" diff --git a/tools/testing/selftests/drivers/net/ring_reconfig.py b/tools/testing/selftests/drivers/net/ring_reconfig.py index f9530a8b0856..2bc329b77134 100755 --- a/tools/testing/selftests/drivers/net/ring_reconfig.py +++ b/tools/testing/selftests/drivers/net/ring_reconfig.py @@ -5,10 +5,25 @@ Test channel and ring size configuration via ethtool (-L / -G). """ +import socket +import struct +import time + from lib.py import ksft_run, ksft_exit, ksft_pr from lib.py import ksft_eq +from lib.py import KsftSkipEx, KsftXfailEx from lib.py import NetDrvEpEnv, EthtoolFamily, GenerateTraffic -from lib.py import defer, NlError +from lib.py import cmd, defer, rand_port, tc, NlError + +# Added in Python 3.13; fallback to 61 for x86/ARM/MIPS +SO_TXTIME = getattr(socket, "SO_TXTIME", 61) + +# Not always exported by the socket module; asm-generic value (x86/ARM/MIPS). +SO_SNDBUFFORCE = getattr(socket, "SO_SNDBUFFORCE", 32) + +# TX ring size the test shrinks to so the ring fills quickly. +MIN_TX_RING = 32 +MAX_TX_RING = 1024 def channels(cfg) -> None: @@ -151,14 +166,248 @@ def ringparam(cfg) -> None: GenerateTraffic(cfg).wait_pkts_and_stop(10000) +def _write_file(path, val): + """Write val to a file.""" + with open(path, "w", encoding="utf-8") as fp: + fp.write(str(val)) + + +def _write_sysfs(path, val): + """Write val to a sysfs file, restoring the original value on exit.""" + with open(path, "r", encoding="utf-8") as fp: + orig_val = fp.read().strip() + if str(val) == orig_val: + return + _write_file(path, val) + defer(_write_file, path, orig_val) + + +def _get_qdisc_backlog(cfg, mq_handle, queue): + """Return the qdisc backlog (bytes) for the given TX queue's leaf.""" + target_parent = f"{mq_handle}{queue + 1:x}" + for q in tc(f"-s qdisc show dev {cfg.ifname}", json=True): + if q.get("parent", "") == target_parent: + return q.get("backlog") or 0 + return 0 + + +def _setup_fq_qdisc(cfg, port, target_queue, other_queue, flow_limit): + """Put an fq qdisc on target_queue's leaf and return the mq handle in use. + + We must not disturb the device's existing TX/RX qdisc policy. On a real + NIC the root mq already has an addressable handle, so we leave the root + and every other queue alone and only swap this one leaf, restoring its + original qdisc afterwards. + + @flow_limit raises fq's per-flow packet limit (default 100) so a single + flow can back up more packets than the Tx ring holds and thus overflow it. + """ + qdiscs = tc(f"qdisc show dev {cfg.ifname}", json=True) + root = next((q for q in qdiscs if q.get("root")), None) + + if root and root["kind"] == "mq" and root["handle"] != "0:": + # Addressable mq (previously-configured): touch only the target queue's + # leaf and restore its original qdisc afterwards. + mq_handle = root["handle"] + parent = f"{mq_handle}{target_queue + 1:x}" + orig = next((q for q in qdiscs if q.get("parent") == parent), None) + orig_kind = orig["kind"] if orig else \ + cmd("sysctl -n net.core.default_qdisc").stdout.strip() + defer(tc, f"qdisc replace dev {cfg.ifname} parent {parent} {orig_kind}") + elif root is None or root["kind"] in ("mq", "noqueue"): + # The auto-attached root mq has handle 0: on any device (real or sim), + # which the kernel rejects as a qdisc parent. A 0: handle means the mq + # is the untouched kernel default - no custom child qdiscs can hang off + # an unaddressable parent - so installing a real handle and restoring + # the default mq on exit preserves the device's effective policy. + mq_handle = "1:" + tc(f"qdisc replace dev {cfg.ifname} root handle {mq_handle} mq") + defer(tc, f"qdisc replace dev {cfg.ifname} root mq") + parent = f"{mq_handle}{target_queue + 1:x}" + else: + raise KsftSkipEx(f"root qdisc '{root['kind']}' is not mq; " + "refusing to disturb existing qdisc policy") + + try: + tc(f"qdisc replace dev {cfg.ifname} parent {parent} fq " + f"flow_limit {flow_limit} limit {flow_limit * 2}") + except Exception as exc: + raise KsftSkipEx( + f"fq not available (CONFIG_NET_SCH_FQ): {exc}") from exc + + qdisc_j = tc(f"qdisc show dev {cfg.ifname}", json=True) + has_clsact = any(q['kind'] == 'clsact' for q in qdisc_j) + if not has_clsact: + tc(f"qdisc add dev {cfg.ifname} clsact") + defer(tc, f"qdisc del dev {cfg.ifname} clsact") + + proto = "ipv6" if int(cfg.addr_ipver) == 6 else "ip" + try: + tc(f"filter add dev {cfg.ifname} egress protocol {proto} " + f"pref 1 flower ip_proto udp dst_port {port} " + f"action skbedit queue_mapping {target_queue}") + except Exception as exc: + raise KsftSkipEx("tc flower/act_skbedit not available") from exc + defer(tc, f"filter del dev {cfg.ifname} egress pref 1") + + tc(f"filter add dev {cfg.ifname} egress pref 101 " + f"matchall action skbedit queue_mapping {other_queue}") + defer(tc, f"filter del dev {cfg.ifname} egress pref 101") + + return mq_handle + + +def _create_sotxtime_socket(cfg, sndbuf): + """Create a UDP socket with SO_TXTIME enabled, bound to the test device.""" + sock = socket.socket(socket.AF_INET6 if cfg.addr_ipver == "6" + else socket.AF_INET, socket.SOCK_DGRAM) + try: + sock.setsockopt(socket.SOL_SOCKET, SO_TXTIME, struct.pack("Ii", 1, 0)) + except OSError as exc: + sock.close() + raise KsftSkipEx("SO_TXTIME not supported") from exc + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + cfg.ifname.encode()) + # Deferred completions keep every in-flight skb charged to the socket, so + # size the send buffer to hold the whole burst. SO_SNDBUFFORCE bypasses + # net.core.wmem_max (the test runs as root). + try: + sock.setsockopt(socket.SOL_SOCKET, SO_SNDBUFFORCE, sndbuf) + except OSError: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, sndbuf) + return sock + + +def _send_sotxtime_burst(cfg, sock, port, count, delay_ns, pkt_size): + """Send count UDP packets scheduled delay_ns ahead using SO_TXTIME.""" + payload = b'\x00' * pkt_size + txtime_ns = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + delay_ns + + ancdata = [(socket.SOL_SOCKET, SO_TXTIME, struct.pack("Q", txtime_ns))] + if int(cfg.addr_ipver) == 6: + dest = (cfg.remote_addr, port, 0, 0) + else: + dest = (cfg.remote_addr, port) + for _ in range(count): + sock.sendmsg([payload], ancdata, 0, dest) + + +def _set_small_tx_ring(cfg, ehdr): + """Set the Tx ring to the smallest size the driver accepts. + + Start at 32 so the ring fills quickly, then grow exponentially (64, + 128, 256, ...) up to 1024. Some drivers enforce a minimum well above 32 + (e.g. bnxt needs a large ring for software UDP segmentation), so raise + the lower bound until the driver accepts it, giving up past 1024. + """ + size = MIN_TX_RING + while size <= MAX_TX_RING: + try: + cfg.eth.rings_set(ehdr | {'tx': size}) + return size + except NlError: + size = size * 2 + continue + raise KsftSkipEx("driver rejects all tx ring sizes up to 1024") + + +def reconfig_tx_stall(cfg) -> None: + """Test that qdisc backlog drains after ring reconfiguration.""" + target_queue = 1 + other_queue = 0 + + ehdr = {'header': {'dev-index': cfg.ifindex}} + chans = cfg.eth.channels_get(ehdr) + + if "combined-max" not in chans: + raise KsftSkipEx("device does not support combined channels") + if chans.get("combined-max", 0) < 2: + raise KsftSkipEx("device does not support 2+ combined channels") + if chans["combined-count"] < 2: + defer(cfg.eth.channels_set, + ehdr | {"combined-count": chans["combined-count"]}) + cfg.eth.channels_set(ehdr | {"combined-count": 2}) + + rings = cfg.eth.rings_get(ehdr) + if 'rx' not in rings or 'tx' not in rings: + raise KsftSkipEx("device does not expose rx/tx ring params") + tx_cur = rings['tx'] + if tx_cur <= MIN_TX_RING: + raise KsftSkipEx("tx ring size already at minimum") + defer(cfg.eth.rings_set, ehdr | {'tx': tx_cur}) + + # Use the smallest Tx ring the driver accepts (32, growing to 1024). + tx_ring = _set_small_tx_ring(cfg, ehdr) + + # Slow completions so the ring stays full after FQ releases packets + napi_defer = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs" + gro_timeout = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout" + _write_sysfs(napi_defer, 100) + _write_sysfs(gro_timeout, 1000000000) + + port = rand_port() + # A single flow must overflow the ring, so send twice the ring depth and + # let fq hold that many packets for the flow. + pkt_count = tx_ring * 2 + mq_handle = _setup_fq_qdisc(cfg, port, target_queue, other_queue, + tx_ring * 2) + + # Size each packet to one MTU (less L3/L4 headers to avoid fragmentation). + pkt_size = cfg.dev['mtu'] - (48 if int(cfg.addr_ipver) == 6 else 28) + + # Each queued skb charges the socket its truesize (~2x the payload), so + # budget the send buffer for the whole in-flight burst. + sock = _create_sotxtime_socket(cfg, pkt_count * pkt_size * 2) + defer(sock.close) + + for delay_ms in [100, 200, 500]: + _send_sotxtime_burst(cfg, sock, port, pkt_count, + delay_ms * 1_000_000, pkt_size) + ksft_pr(f"Sent {pkt_count} SO_TXTIME packets (+{delay_ms}ms)") + time.sleep(delay_ms / 1000 + 0.3) + + backlog = _get_qdisc_backlog(cfg, mq_handle, target_queue) + if backlog: + break + else: + # A device that completes Tx synchronously (e.g. a software/virtual + # driver like netdevsim) never keeps the ring full long enough for a + # backlog to form, so the wake-vs-start behavior can't be exercised. + # Treat that as an expected failure rather than a hard failure. + raise KsftXfailEx("could not build qdisc backlog") + + ksft_pr(f"Backlog before reconfig: {backlog} bytes") + + # Trigger ring reconfig — driver should call wake, not just start. + # Grow back to the original size so the driver actually switches channels + # (setting the current size is a no-op the driver short-circuits). + cfg.eth.rings_set(ehdr | {'tx': tx_cur}) + + # Let completions proceed normally + _write_sysfs(napi_defer, 0) + _write_sysfs(gro_timeout, 0) + + # Poll for backlog to drain + for _ in range(100): + backlog = _get_qdisc_backlog(cfg, mq_handle, target_queue) + if not backlog: + break + time.sleep(0.1) + + ksft_eq(0, backlog, + comment=f"qdisc backlog stuck on queue {target_queue} " + f"after ring reconfig") + + def main() -> None: """ Ksft boiler plate main """ - with NetDrvEpEnv(__file__) as cfg: + with NetDrvEpEnv(__file__, queue_count=2) as cfg: cfg.eth = EthtoolFamily() ksft_run([channels, - ringparam], + ringparam, + reconfig_tx_stall], args=(cfg, )) ksft_exit() diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index e39d270e688d..a53316726f69 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -1,14 +1,54 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 +# pylint: disable=too-many-lines import errno +import glob from lib.py import ksft_run, ksft_exit -from lib.py import ksft_eq, ksft_raises, ksft_true, KsftSkipEx +from lib.py import ksft_eq, ksft_true, ksft_raises, KsftSkipEx from lib.py import EthtoolFamily, NetshaperFamily from lib.py import NetDrvEnv from lib.py import NlError -from lib.py import cmd +from lib.py import cmd, defer + +def _delete_shaper(cfg, nl_shaper, handle) -> None: + """ Delete the shaper identified by handle, ignoring a missing-shaper error. """ + try: + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': handle}) + except NlError as e: + if e.error != errno.ENOENT: + raise + +def _require_queues(cfg, count): + """ Return the netdev TX queue count, skipping the test if fewer than count exist. """ + qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/tx-*")) + if qcnt < count: + raise KsftSkipEx(f"netdev has {qcnt} queues, {count} required") + return qcnt + +def _cap_get(cfg, nl_shaper, scope): + """ Return the shaper capabilities for the given scope, caching them on cfg. """ + if not hasattr(cfg, 'cap_cache'): + cfg.cap_cache = {} + if scope not in cfg.cap_cache: + cfg.cap_cache[scope] = nl_shaper.cap_get({'ifindex': cfg.ifindex, + 'scope': scope}) + + return cfg.cap_cache[scope] + +def _require_caps(cfg, nl_shaper, scope, caps, msg) -> None: + """ Skip the test unless the given scope advertises all the required caps. """ + try: + supported = _cap_get(cfg, nl_shaper, scope) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx(f"{scope} scope shapers not supported by the device") + raise + + if not set(caps).issubset(supported): + raise KsftSkipEx(msg) def get_shapers(cfg, nl_shaper) -> None: try: @@ -44,17 +84,8 @@ def set_qshapers(cfg, nl_shaper) -> None: if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps") - cfg.queues = True; - netnl = EthtoolFamily() - channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) - if channels['combined-count'] == 0: - cfg.rx_type = 'rx' - cfg.nr_queues = channels['rx-count'] - else: - cfg.rx_type = 'combined' - cfg.nr_queues = channels['combined-count'] - if cfg.nr_queues < 3: - raise KsftSkipEx(f"device does not support enough queues min 3 found {cfg.nr_queues}") + _require_queues(cfg, 3) + cfg.queues = True nl_shaper.set({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': 1}, @@ -137,77 +168,370 @@ def del_nshapers(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) -def basic_groups(cfg, nl_shaper) -> None: - if not cfg.netdev: - raise KsftSkipEx("netdev shaper not supported by the device") - if cfg.nr_queues < 3: - raise KsftSkipEx(f"netdev does not have enough queues min 3 reported {cfg.nr_queues}") +def set_all_supported_attrs(cfg, nl_shaper) -> None: + """ Set every queue-scope attribute the device advertises and verify the read-back. """ + _require_queues(cfg, 1) - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'queue'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-weight' in caps: - raise KsftSkipEx("device does not support queue scope shapers with weight") + _require_caps(cfg, nl_shaper, 'queue', [], + "queue scope shapers not supported by the device") + caps = _cap_get(cfg, nl_shaper, 'queue') + + attrs = {'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}} + expected = {'ifindex': cfg.ifindex, + 'parent': {'scope': 'netdev'}, + 'handle': {'scope': 'queue', 'id': 0}} + + rate_attrs = {'support-bw-min': ('bw-min', 10000, 100), + 'support-bw-max': ('bw-max', 20000, 200), + 'support-burst': ('burst', 3000, 30)} + rate_attr_supported = any(cap in caps for cap in rate_attrs) + bps_supported = 'support-metric-bps' in caps + pps_supported = 'support-metric-pps' in caps + + def add_rate_attrs(metric, value_idx) -> None: + attrs['metric'] = metric + expected['metric'] = metric + for cap, (attr, bps_value, pps_value) in rate_attrs.items(): + if cap not in caps: + continue + + value = bps_value if value_idx == 0 else pps_value + attrs[attr] = value + expected[attr] = value + + if rate_attr_supported: + if bps_supported: + add_rate_attrs('bps', 0) + elif pps_supported: + add_rate_attrs('pps', 1) + + if 'support-priority' in caps: + attrs['priority'] = 1 + expected['priority'] = 1 + if 'support-weight' in caps: + attrs['weight'] = 2 + expected['weight'] = 2 + + if len(attrs) == 2: + raise KsftSkipEx("device does not advertise any supported queue shaper attributes") + + nl_shaper.set(attrs) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, expected) + + if rate_attr_supported and bps_supported and pps_supported: + add_rate_attrs('pps', 1) + nl_shaper.set(attrs) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, expected) + + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def invalid_set_preserves_state(cfg, nl_shaper) -> None: + """ Verify a rejected .set leaves the existing shaper configuration unchanged. """ + nq = _require_queues(cfg, 1) + _require_caps(cfg, nl_shaper, 'queue', + ['support-bw-max', 'support-metric-bps'], + "device does not support queue scope bw_max with bps metric") + + initial = {'ifindex': cfg.ifindex, + 'parent': {'scope': 'netdev'}, + 'handle': {'scope': 'queue', 'id': 0}, + 'metric': 'bps', + 'bw-max': 10000} + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}, + 'metric': 'bps', + 'bw-max': 10000}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + with ksft_raises(NlError): + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': 0}, + 'metric': 'bps', + 'bw-max': 20000}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, initial) + + with ksft_raises(NlError): + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': nq}, + 'metric': 'bps', + 'bw-max': 20000}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, initial) + + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None: + r"""Grouping leaves from different nodes requires an explicit parent. + + netdev netdev + / \ parent=netdev + N1 N2 group N + | | {Q0,Q1} / \ + Q0 Q1 -------> Q0 Q1 + + Without an explicit parent the group is rejected; parent=netdev + collapses the leaves into one new node. + """ + _require_queues(cfg, 2) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + n1_id = n1_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 20000}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + + with ksft_raises(NlError): + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}, + {'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 30000}) + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}) + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}) node_handle = nl_shaper.group({ - 'ifindex': cfg.ifindex, - 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, - 'weight': 1}, - {'handle': {'scope': 'queue', 'id': 2}, - 'weight': 2}], - 'handle': {'scope':'netdev'}, - 'metric': 'bps', - 'bw-max': 10000}) + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}, + {'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 30000}) + node_id = node_handle['handle']['id'] + + for old_id in (n1_id, n2_id): + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': old_id}}) + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}) + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}) + + for i in range(2): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def recursive_empty_node_cleanup(cfg, nl_shaper) -> None: + r"""Deleting the last leaf recursively removes the emptied ancestors. + + netdev netdev + | del Q0 + N1 ------> (N1 and N2 removed too) + | + N2 + | + Q0 + """ + _require_queues(cfg, 1) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support nested node scope shapers") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + n1_id = n1_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'node', 'id': n1_id}, + 'metric': 'bps', + 'bw-max': 5000}) + n2_id = n2_handle['handle']['id'] + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}) + + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + + for handle in ({'scope': 'queue', 'id': 0}, + {'scope': 'node', 'id': n2_id}, + {'scope': 'node', 'id': n1_id}): + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle}) + + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def _group_under_netdev(cfg, nl_shaper, bw_max=None): + r"""Group queues under a netdev-scope node; caller owns node teardown. + + netdev netdev + / \ del Q1,Q2 + Q1 Q2 -------> (netdev node persists) + """ + group_args = { + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 2}], + 'handle': {'scope': 'netdev'}} + if bw_max: + group_args['metric'] = 'bps' + group_args['bw-max'] = bw_max + + node_handle = nl_shaper.group(group_args) ksft_eq(node_handle, {'ifindex': cfg.ifindex, 'handle': {'scope': 'netdev'}}) + del_node = defer(_delete_shaper, cfg, nl_shaper, {'scope': 'netdev'}) + del_queues = [defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': qid}) + for qid in (1, 2)] + shaper = nl_shaper.get({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': 1}}) ksft_eq(shaper, {'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, - 'weight': 1 }) + 'weight': 1}) + for dq in del_queues: + dq.exec() - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'queue', 'id': 2}}) - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'queue', 'id': 1}}) + # Caller owns the node teardown so it can verify the netdev-scope node + # survives leaf deletion before removing it. + return del_node + +def basic_groups(cfg, nl_shaper) -> None: + r"""Group queues under a netdev-scope node, then tear it down. + + netdev + / \ + Q1 Q2 + """ + _require_queues(cfg, 3) + + _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "queue scope not supported with nesting and weight") + + del_node = _group_under_netdev(cfg, nl_shaper) + + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(shapers, [{'ifindex': cfg.ifindex, + 'handle': {'scope': 'netdev'}}]) + + del_node.exec() + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def basic_groups_with_rate(cfg, nl_shaper) -> None: + r"""Rate-limited netdev-scope node outlives deletion of its leaves. + + netdev[10kbps] netdev[10kbps] + / \ del Q1,Q2 + Q1 Q2 -------> (node persists) + """ + bw_max = 10000 + + _require_queues(cfg, 3) + + _require_caps(cfg, nl_shaper, 'netdev', ['support-bw-max', 'support-metric-bps'], + "device does not support netdev scope rate limiting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support queue scope shapers with nesting and weight") + + del_node = _group_under_netdev(cfg, nl_shaper, bw_max=bw_max) # Deleting all the leaves shaper does not affect the node one # when the latter has 'netdev' scope. shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) - ksft_eq(len(shapers), 1) + ksft_eq(shapers, [{'ifindex': cfg.ifindex, + 'handle': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': bw_max}]) - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'netdev'}}) + del_node.exec() def qgroups(cfg, nl_shaper) -> None: - if cfg.nr_queues < 4: - raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'node'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: - raise KsftSkipEx("device does not support node scope shapers with bw_max and metric bps") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'queue'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-nesting' in caps or not 'support-weight' in caps or not 'support-metric-bps' in caps: - raise KsftSkipEx("device does not support nested queue scope shapers with weight") + _require_queues(cfg, 4) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") - cfg.groups = True; node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, @@ -284,18 +608,116 @@ def qgroups(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def set_node_shaper(cfg, nl_shaper) -> None: + """ Verify a node-scope shaper rate can be updated via .set. """ + _require_queues(cfg, 2) + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + node_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + node_id = node_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + + # Update the node's rate via .set + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'metric': 'bps', + 'bw-max': 20000}) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 20000}) + + # Cleanup + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def group_update_rate(cfg, nl_shaper) -> None: + """ Verify re-grouping a node updates its rate while leaving the leaves untouched. """ + _require_queues(cfg, 3) + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + # Create node with Q1, Q2 at bw_max=10000 + node_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + node_id = node_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Update rate via .group on the same node + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node_id}, + 'metric': 'bps', + 'bw-max': 50000}) + + # Verify rate updated + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 50000}) + + # Verify leaves unchanged + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}) + shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}) + + # Make sure we only have 3 shapers including 2 queues and the node + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 3) + + # Cleanup + for i in range(1, 3): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def delegation(cfg, nl_shaper) -> None: - if not cfg.groups: - raise KsftSkipEx("device does not support node scope") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'node'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("node scope shapers not supported by the device") - raise - if not 'support-nesting' in caps: - raise KsftSkipEx("device does not support node scope shapers nesting") + _require_queues(cfg, 4) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, @@ -375,20 +797,466 @@ def delegation(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def nested_depth_limit(cfg, nl_shaper) -> None: + r"""Nest nodes as deep as the device allows to find the max depth. + + netdev + | + N1 -- Q1 + | + N2 -- Q2 + | + N3 -- Q3 + : (deepen until the driver rejects) + """ + bw_max = 10000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + nq = _require_queues(cfg, 3) + + node_ids = [] + cleanups = [] + queue_id = 1 + max_depth = 0 + limit_err = None + + # Create initial node with a queue leaf + node_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}], + 'handle': {'scope': 'node'}, + 'metric': 'bps', + 'bw-max': bw_max})['handle']['id'] + node_ids.append(node_id) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'node', 'id': node_id})) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': queue_id})) + max_depth = 1 + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': queue_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}) + queue_id += 1 + + # Keep nesting deeper until the driver rejects or queues run out. + while queue_id < nq: + parent_id = node_ids[-1] + try: + node_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', + 'id': queue_id}, + 'weight': 1}], + 'handle': {'scope': 'node'}, + 'parent': {'scope': 'node', + 'id': parent_id}, + 'metric': 'bps', + 'bw-max': bw_max})['handle']['id'] + except NlError as e: + # Only treat "cannot nest deeper" errors as the depth limit; + # drivers report it differently (EOPNOTSUPP/ENOSPC/E2BIG/EINVAL). + # Anything else (ENOMEM, EIO, EPERM, driver bug) is a real failure. + if e.error not in (errno.EOPNOTSUPP, errno.ENOSPC, + errno.E2BIG, errno.EINVAL): + raise + limit_err = e + break + + node_ids.append(node_id) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'node', 'id': node_id})) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': queue_id})) + max_depth += 1 + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'node', 'id': parent_id}, + 'metric': 'bps', + 'bw-max': bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', + 'id': queue_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}) + queue_id += 1 + + if limit_err: + print(f"# max nesting depth supported: {max_depth} (errno {limit_err.error})") + else: + print(f"# max nesting depth tested: {max_depth}") + ksft_true(max_depth >= 2, + f"max nesting depth: {max_depth}") + + # Cleanup: exec the deferred deletes in reverse creation order, so each + # queue leaf and deeper node is removed before its parent node. + for cleanup in reversed(cleanups): + cleanup.exec() + ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0) + +def delete_child_reparent(cfg, nl_shaper) -> None: + r"""Deleting a child node reparents its queue leaf to the parent. + + netdev netdev + | | + N1 del N2 N1 + / | \ -----> / | \ + Q1 Q2 N2 Q1 Q2 Q3 + | + Q3 + """ + n1_bw_max = 10000 + n2_bw_max = 5000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create parent node N1 with Q1, Q2 + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + n1_id = n1_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Create child node N2 under N1 with Q3 + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'node', 'id': n1_id}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + + # Delete child N2 - Q3 should reparent to N1 + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + + shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}}) + ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def move_queue_between_nodes(cfg, nl_shaper) -> None: + r"""Move a queue between nodes by re-grouping the destination node. + + netdev netdev + / \ .group N2 / \ + N1 N2 {Q1,Q3} N1 N2 + / \ | -------> | / \ + Q1 Q2 Q3 Q2 Q1 Q3 + """ + n1_bw_max = 10000 + n2_bw_max = 20000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create N1 with Q1, Q2 + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + n1_id = n1_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Create N2 with Q3 + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + + # Move Q1 from N1 to N2 by re-grouping N2 with Q1, Q3 + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}, + {'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': n2_id}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + + shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}}) + ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + shaper_n2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + ksft_eq(shaper_n2, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + + # Verify Q1 moved to N2 + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}) + + # Verify Q2 still under N1 + shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}) + + # Verify Q3 remained under N2 + shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + +def reject_reparenting(cfg, nl_shaper) -> None: + r"""Reject reparenting an existing node; the hierarchy stays intact. + + netdev + / \ rejected: N3 -> netdev + N1 N2 rejected: N1 -> N2 + / \ | (both EOPNOTSUPP) + Q1 N3 Q2 + | + Q3 + """ + node1_bw_max = 10000 + node2_bw_max = 5000 + node3_bw_max = 20000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create Node1 under netdev with Q1. + node1_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node1_bw_max})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node1_id}) + + # Create Node2 under netdev with Q2. + node2_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node2_bw_max})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 2}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node2_id}) + + # Create Node3 nested under Node1 with Q3. + node3_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node3_bw_max, + 'parent': {'scope': 'node', 'id': node1_id}})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node3_id}) + + # Reparenting a nested node up to netdev must fail. + with ksft_raises(NlError) as cm: + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node3_id}, + 'parent': {'scope': 'netdev'}}) + if cm.exception: + ksft_eq(cm.exception.error, errno.EOPNOTSUPP) + + # Reparenting a node under another node must fail as well. + with ksft_raises(NlError) as cm: + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node1_id}, + 'parent': {'scope': 'node', 'id': node2_id}}) + if cm.exception: + ksft_eq(cm.exception.error, errno.EOPNOTSUPP) + + # Updating a node with the same parent must succeed. + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 5}], + 'handle': {'scope':'node', 'id': node1_id}, + 'parent': {'scope': 'netdev'}}) + + # Updating a node without specifying the parent must succeed. + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, + 'weight': 7}], + 'handle': {'scope':'node', 'id': node2_id}}) + + # The rejected reparents must have left the hierarchy intact. + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node1_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': node1_bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node2_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node2_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': node2_bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node3_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node3_id}, + 'parent': {'scope': 'node', 'id': node1_id}, + 'metric': 'bps', + 'bw-max': node3_bw_max}) + + # Verify the leaf weights were updated and parents unchanged. + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node1_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 5}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node2_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 7}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node3_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup. Delete the nodes explicitly instead of relying on the + # empty-node auto-delete: a kernel that wrongly accepts a reparent may + # mishandle the leaf accounting and leave a node behind. Removing them + # by handle keeps a failing run from leaking state into later tests. + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + for nid in (node1_id, node2_id, node3_id): + _delete_shaper(cfg, nl_shaper, {'scope': 'node', 'id': nid}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def queue_update(cfg, nl_shaper) -> None: - if cfg.nr_queues < 4: - raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") + nq = _require_queues(cfg, 4) if not cfg.queues: raise KsftSkipEx("device does not support queue scope") + netnl = EthtoolFamily() + channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) + ch_type = 'combined' if channels['combined-count'] else 'tx' + for i in range(3): nl_shaper.set({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': i}, 'metric': 'bps', 'bw-max': (i + 1) * 1000}) + defer(cmd, f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") + # Delete a channel, with no shapers configured on top of the related # queue: no changes expected - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 3") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, @@ -408,7 +1276,7 @@ def queue_update(cfg, nl_shaper) -> None: # Delete a channel, with a shaper configured on top of the related # queue: the shaper must be deleted, too - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 2") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, @@ -423,7 +1291,7 @@ def queue_update(cfg, nl_shaper) -> None: 'bw-max': 2000}]) # Restore the original channels number, no expected changes - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, @@ -443,34 +1311,57 @@ def queue_update(cfg, nl_shaper) -> None: def dup_leaves(cfg, nl_shaper) -> None: """ Ensure that the kernel rejects duplicate leaves. """ - if not cfg.groups: - raise KsftSkipEx("device does not support node scope") + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + node_handle = None with ksft_raises(NlError) as cm: - nl_shaper.group({ + node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, - 'leaves':[{'handle': {'scope': 'queue', 'id': 0}}, - {'handle': {'scope': 'queue', 'id': 0}}], + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 0}, + 'weight': 2}], 'handle': {'scope':'node'}, 'metric': 'bps', 'bw-max': 10000}) + + # Clean up in case the kernel wrongly accepted the request. + if node_handle: + _delete_shaper(cfg, nl_shaper, node_handle['handle']) + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + # ksft_raises() has already recorded the failure if nothing was raised. + if cm.exception is None: + return ksft_eq(cm.exception.error, errno.EINVAL) def main() -> None: with NetDrvEnv(__file__, queue_count=4) as cfg: cfg.queues = False cfg.netdev = False - cfg.groups = False - cfg.nr_queues = 0 ksft_run([get_shapers, get_caps, set_qshapers, del_qshapers, set_nshapers, del_nshapers, + set_all_supported_attrs, + invalid_set_preserves_state, + mixed_parent_group_requires_parent, + recursive_empty_node_cleanup, basic_groups, + basic_groups_with_rate, qgroups, + set_node_shaper, + group_update_rate, delegation, + nested_depth_limit, + delete_child_reparent, + move_queue_between_nodes, + reject_reparenting, dup_leaves, queue_update], args=(cfg, NetshaperFamily())) diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py index adf6c848d6d8..a097fae0b335 100755 --- a/tools/testing/selftests/drivers/net/so_txtime.py +++ b/tools/testing/selftests/drivers/net/so_txtime.py @@ -12,6 +12,7 @@ import time from lib.py import ksft_exit, ksft_run, ksft_variants from lib.py import KsftNamedVariant, KsftSkipEx from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc +from lib.py import CmdExitFailure def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): @@ -27,7 +28,7 @@ def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): cmd_addr = f"-S {cfg.addr_v[ipver]} -D {cfg.remote_addr_v[ipver]}" cmd_args = f"-{ipver} -c {clockid} -t {tstart} {cmd_addr}" cmd_rx = f"{cfg.bin_remote} {cmd_args} {args_rx} -r" - cmd_tx = f"{cfg.bin_local} {cmd_args} {args_tx}" + cmd_tx = f"{cfg.bin_local} -m 100 {cmd_args} {args_tx}" expect_fail = not expect_success if slow_machine: @@ -45,7 +46,11 @@ def _qdisc_setup(ifname, qdisc, optargs=""): """ orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None) defer(tc, f"qdisc replace dev {ifname} root {orig}") - tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}") + try: + tc(f"qdisc del dev {ifname} root") + except CmdExitFailure: + pass + tc(f"qdisc replace dev {ifname} root handle 1: {qdisc} {optargs}") def _test_variants_fq(): @@ -96,11 +101,21 @@ def _test_variants_etf(): def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail): """Run all variants of etf tests.""" cfg.require_ipver(ipver) + + # root qdisc for background traffic (e.g., bkg()) + _qdisc_setup(cfg.ifname, "prio") + + # leaf ETF qdisc only for intended packets try: - _qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000") + etf_args = "clockid CLOCK_TAI delta 400000" + tc(f"qdisc add dev {cfg.ifname} parent 1:1 handle 10: etf {etf_args}") except Exception as e: raise KsftSkipEx("tc does not support qdisc etf. skipping") from e + # redirect mark 100 to leaf + filter_args = "protocol all handle 100 fw flowid 1:1" + tc(f"filter add dev {cfg.ifname} parent 1: {filter_args}") + test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, expect_fail) diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py index 2ad5932299e8..0369929f3c51 100755 --- a/tools/testing/selftests/drivers/net/xdp.py +++ b/tools/testing/selftests/drivers/net/xdp.py @@ -172,25 +172,45 @@ def _test_pass(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch") -def test_xdp_native_pass_sb(cfg): +_ipvers = [ + KsftNamedVariant("ipv4", "4"), + KsftNamedVariant("ipv6", "6"), +] + + +def _set_ipver_defer_restore(cfg, ipver): + old_ipver = cfg.addr_ipver + cfg.set_ipver(ipver) + defer(cfg.set_ipver, old_ipver) + + +@ksft_variants(_ipvers) +def test_xdp_native_pass_sb(cfg, ipver): """ Tests the XDP_PASS action for single buffer case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_pass(cfg, bpf_info, 256) -def test_xdp_native_pass_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_pass_mb(cfg, ipver): """ Tests the XDP_PASS action for a multi-buff size. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_pass(cfg, bpf_info, 8000) @@ -219,25 +239,33 @@ def _test_drop(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch") -def test_xdp_native_drop_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_sb(cfg, ipver): """ Tests the XDP_DROP action for a signle-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_drop(cfg, bpf_info, 256) -def test_xdp_native_drop_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_mb(cfg, ipver): """ Tests the XDP_DROP action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_drop(cfg, bpf_info, 8000) @@ -287,13 +315,17 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens): ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch") -def test_xdp_native_tx_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_sb(cfg, ipver): """ Tests the XDP_TX action for a single-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) # Ensure there's enough room for an ETH / IP / UDP header @@ -302,13 +334,17 @@ def test_xdp_native_tx_sb(cfg): _test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len]) -def test_xdp_native_tx_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_mb(cfg, ipver): """ Tests the XDP_TX action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) # The first packet ensures we exercise the fragmented code path. @@ -447,13 +483,17 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_tail_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_grow_data(cfg, ipver): """ Tests the XDP tail adjustment by growing packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [1, 16, 32, 64, 128, 256] res = _test_xdp_native_tail_adjst( @@ -465,13 +505,17 @@ def test_xdp_native_adjst_tail_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_tail_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_shrnk_data(cfg, ipver): """ Tests the XDP tail adjustment by shrinking packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [-16, -32, -64, -128, -256] res = _test_xdp_native_tail_adjst( @@ -535,7 +579,7 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): # after we eat into it. We send large-enough packets, but if HDS # is enabled head will only contain headers. Don't try to eat # more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14) - l2_cut_off = 28 if cfg.addr_ipver == 4 else 48 + l2_cut_off = 28 if cfg.addr_ipver == "4" else 48 if pkt_sz > hds_thresh and offset > l2_cut_off: ksft_pr( f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and " @@ -579,18 +623,22 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_head_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_grow_data(cfg, ipver): """ Tests the XDP headroom growth support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully extended for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Negative values result in headroom shrinking, resulting in growing of payload @@ -600,18 +648,22 @@ def test_xdp_native_adjst_head_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_head_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_shrnk_data(cfg, ipver): """ Tests the XDP headroom shrinking support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully shrunk for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Positive values result in headroom growing, resulting in shrinking of payload @@ -621,12 +673,19 @@ def test_xdp_native_adjst_head_shrnk_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -@ksft_variants([ - KsftNamedVariant("pass", XDPAction.PASS), - KsftNamedVariant("drop", XDPAction.DROP), - KsftNamedVariant("tx", XDPAction.TX), -]) -def test_xdp_native_qstats(cfg, act): +def _qstats_variants(): + actions = [ + ("pass", XDPAction.PASS), + ("drop", XDPAction.DROP), + ("tx", XDPAction.TX), + ] + for ipver in ["4", "6"]: + for name, act in actions: + yield KsftNamedVariant(f"{name}_ipv{ipver}", act, ipver) + + +@ksft_variants(_qstats_variants()) +def test_xdp_native_qstats(cfg, act, ipver): """ Send 1000 messages. Expect XDP action specified in @act. Make sure the packets were counted to interface level qstats @@ -634,6 +693,7 @@ def test_xdp_native_qstats(cfg, act): """ cfg.require_cmd("socat") + _set_ipver_defer_restore(cfg, ipver) bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) prog_info = _load_xdp_prog(cfg, bpf_info) diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 708d960ae07d..0f5c178bc224 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -13,6 +13,7 @@ TEST_PROGS := \ arp_ndisc_untracked_subnets.sh \ bareudp.sh \ big_tcp.sh \ + big_tcp_tunnels.sh \ bind_bhash.sh \ bpf_offload.py \ bridge_stp_mode.sh \ @@ -39,6 +40,7 @@ TEST_PROGS := \ fib_rule_tests.sh \ fib_tests.sh \ fin_ack_lat.sh \ + fou_mcast_encap.sh \ fq_band_pktlimit.sh \ gre_gso.sh \ gre_ipv6_lladdr.sh \ @@ -85,6 +87,7 @@ TEST_PROGS := \ rxtimestamp.sh \ sctp_vrf.sh \ skf_net_off.sh \ + srv6_encap_lookup_l3vpn_test.sh \ srv6_end_dt46_l3vpn_test.sh \ srv6_end_dt4_l3vpn_test.sh \ srv6_end_dt6_l3vpn_test.sh \ @@ -147,7 +150,6 @@ TEST_GEN_FILES := \ ip_local_port_range \ ipsec \ ipv6_flowlabel \ - ipv6_flowlabel_mgr \ msg_zerocopy \ nettest \ psock_fanout \ @@ -180,6 +182,7 @@ TEST_GEN_PROGS := \ epoll_busy_poll \ getsockopt_iter \ icmp_rfc4884 \ + ipv6_flowlabel_mgr \ ipv6_fragmentation \ proc_net_pktgen \ reuseaddr_conflict \ diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore index 973176644103..954f0958dd03 100644 --- a/tools/testing/selftests/net/af_unix/.gitignore +++ b/tools/testing/selftests/net/af_unix/.gitignore @@ -3,6 +3,8 @@ msg_oob scm_inq scm_pidfd scm_rights +scm_rights_denial_lsm +scm_rights_denial_lsm.bpf.o so_peek_off unix_connect unix_connreset diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 57d159803a3a..a66f10fb0c23 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -11,10 +11,18 @@ TEST_GEN_PROGS := \ scm_inq \ scm_pidfd \ scm_rights \ + scm_rights_denial_lsm \ so_peek_off \ unix_connect \ unix_connreset \ unix_listen \ # end of TEST_GEN_PROGS +TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o + include ../../lib.mk +include ../bpf.mk + +$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ) +$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include +$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config index 41dbb03c747e..46450fea8407 100644 --- a/tools/testing/selftests/net/af_unix/config +++ b/tools/testing/selftests/net/af_unix/config @@ -1,4 +1,11 @@ CONFIG_AF_UNIX_OOB=y +CONFIG_BPF=y +CONFIG_BPF_EVENTS=y +CONFIG_BPF_JIT=y +CONFIG_BPF_LSM=y +CONFIG_BPF_SYSCALL=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_SECURITY=y CONFIG_UNIX=y CONFIG_UNIX_DIAG=m CONFIG_USER_NS=y diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c new file mode 100644 index 000000000000..4f2414465bfd --- /dev/null +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +struct inode { + unsigned long i_ino; +} __attribute__((preserve_access_index)); + +struct file { + struct inode *f_inode; +} __attribute__((preserve_access_index)); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 16); + __type(key, __u64); /* inode number */ + __type(value, __u32); /* tgid of the receiver being tested */ +} denied_inodes SEC(".maps"); + +SEC("lsm/file_receive") +int BPF_PROG(scm_rights_deny, struct file *file) +{ + __u32 tgid = bpf_get_current_pid_tgid() >> 32; + __u64 ino = file->f_inode->i_ino; + __u32 *owner; + + owner = bpf_map_lookup_elem(&denied_inodes, &ino); + if (owner && *owner == tgid) + return -EPERM; + + return 0; +} diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c new file mode 100644 index 000000000000..55c7ecdbb5fe --- /dev/null +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kselftest_harness.h" + +#ifndef SO_RIGHTS_NOTRUNC +#define SO_RIGHTS_NOTRUNC 85 +#endif + +#define NR_FILES 2 + +/* Per-file content, so a received fd can be matched to the file sent */ +#define SECRET(n) "secret %d", (n) + +/* Indices into the socketpair */ +#define SK_SENDER 0 +#define SK_RECEIVER 1 + +FIXTURE(scm_rights_denial_bpf) +{ + struct bpf_object *obj; + struct bpf_link *link; + int map_fd; + int sk[2]; + int files[NR_FILES]; + __u64 inos[NR_FILES]; + char paths[NR_FILES][64]; +}; + +FIXTURE_VARIANT(scm_rights_denial_bpf) +{ + int sock_type; +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream) +{ + .sock_type = SOCK_STREAM, +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram) +{ + .sock_type = SOCK_DGRAM, +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket) +{ + .sock_type = SOCK_SEQPACKET, +}; + +FIXTURE_SETUP(scm_rights_denial_bpf) +{ + struct bpf_program *prog; + char lsms[256] = {}; + int i, fd; + + if (geteuid() != 0) + SKIP(return, "requires root"); + + fd = open("/sys/kernel/security/lsm", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1)); + close(fd); + + if (!strstr(lsms, "bpf")) + SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)"); + + self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL); + ASSERT_NE(NULL, self->obj); + ASSERT_EQ(0, bpf_object__load(self->obj)); + + prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny"); + ASSERT_NE(NULL, prog); + + self->link = bpf_program__attach_lsm(prog); + ASSERT_NE(NULL, self->link); + + self->map_fd = bpf_object__find_map_fd_by_name(self->obj, + "denied_inodes"); + ASSERT_GE(self->map_fd, 0); + + ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk)); + + for (i = 0; i < NR_FILES; i++) { + struct stat st; + + snprintf(self->paths[i], sizeof(self->paths[i]), + "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i); + self->files[i] = mkstemp(self->paths[i]); + ASSERT_GE(self->files[i], 0); + + ASSERT_LT(0, dprintf(self->files[i], SECRET(i))); + + ASSERT_EQ(0, fstat(self->files[i], &st)); + self->inos[i] = st.st_ino; + } +} + +FIXTURE_TEARDOWN(scm_rights_denial_bpf) +{ + bpf_link__destroy(self->link); + bpf_object__close(self->obj); + + for (int i = 0; i < NR_FILES; i++) { + if (self->files[i] >= 0) { + close(self->files[i]); + unlink(self->paths[i]); + } + } + + close(self->sk[SK_SENDER]); + close(self->sk[SK_RECEIVER]); +} + +static int deny_inode(int map_fd, __u64 ino) +{ + __u32 tgid = getpid(); + + return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY); +} + +static int set_notrunc(int sk) +{ + int one = 1; + + return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC, + &one, sizeof(one)); +} + +static int send_fds(int sk, int *fds, int n) +{ + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {}; + char data = 'x'; + struct iovec iov = { + .iov_base = &data, + .iov_len = sizeof(data), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = CMSG_SPACE(n * sizeof(int)), + }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + int ret; + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(n * sizeof(int)); + memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int)); + + ret = sendmsg(sk, &msg, 0); + if (ret != 1) + return -1; + + return 0; +} + +static int recv_fd_slots(int sk, int *slots, int *msg_flags) +{ + int nr_slots; + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))]; + char data; + struct iovec iov = { + .iov_base = &data, + .iov_len = sizeof(data), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = sizeof(ctrl), + }; + struct cmsghdr *cmsg; + + if (recvmsg(sk, &msg, 0) < 0) + return -1; + + *msg_flags = msg.msg_flags; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) + return 0; + + nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int)); + + return nr_slots; +} + +/* Prove a received fd works by reading back the file's content. */ +static int check_secret(int fd, int idx) +{ + char want[32], got[32] = {}; + + snprintf(want, sizeof(want), SECRET(idx)); + if (pread(fd, got, sizeof(got) - 1, 0) < 0) + return -1; + + return strcmp(want, got); +} + +TEST_F(scm_rights_denial_bpf, all_allowed) +{ + int slots[NR_FILES], nr_slots, flags; + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + for (int i = 0; i < nr_slots; i++) { + ASSERT_GE(slots[i], 0); + EXPECT_EQ(0, check_secret(slots[i], i)); + close(slots[i]); + } +} + +TEST_F(scm_rights_denial_bpf, first_denied) +{ + int slots[NR_FILES], nr_slots, flags; + + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0])); + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + EXPECT_EQ(-EPERM, slots[0]); + for (int i = 1; i < nr_slots; i++) { + ASSERT_GE(slots[i], 0); + EXPECT_EQ(0, check_secret(slots[i], i)); + close(slots[i]); + } +} + +TEST_F(scm_rights_denial_bpf, all_denied) +{ + int slots[NR_FILES], nr_slots, flags, i; + + for (i = 0; i < NR_FILES; i++) + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i])); + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + for (i = 0; i < nr_slots; i++) + EXPECT_EQ(-EPERM, slots[i]); +} + +TEST_F(scm_rights_denial_bpf, denied_without_notrunc) +{ + int slots[NR_FILES], nr_slots, flags; + + /* + * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is + * truncated at the first denied fd and MSG_CTRUNC is set. + */ + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1])); + + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(1, nr_slots); + EXPECT_NE(0, flags & MSG_CTRUNC); + + ASSERT_GE(slots[0], 0); + EXPECT_EQ(0, check_secret(slots[0], 0)); + close(slots[0]); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh new file mode 100755 index 000000000000..d6513ed8d4e8 --- /dev/null +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# +# Testing for IPv4 and IPv6 BIG TCP over VXLAN and GENEVE tunnels. + +SERVER_NS=$(mktemp -u server-XXXXXXXX) +SERVER_IP4="192.168.1.1" +SERVER_IP6="2001:db8::1:1" +SERVER_IP4_TUN="192.168.2.1" +SERVER_IP6_TUN="2001:db8::2:1" + +CLIENT_NS=$(mktemp -u client-XXXXXXXX) +CLIENT_IP4="192.168.1.2" +CLIENT_IP6="2001:db8::1:2" +CLIENT_IP4_TUN="192.168.2.2" +CLIENT_IP6_TUN="2001:db8::2:2" + +: "${PACKETS_THRESHOLD:=1000}" + +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=4 + +setup() { + ip netns add "$SERVER_NS" + ip netns add "$CLIENT_NS" + ip -netns "$SERVER_NS" link add link1 type veth peer name link0 netns "$CLIENT_NS" + + ip -netns "$CLIENT_NS" link set link0 up + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4/24" dev link0 + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6/112" dev link0 nodad + ip -netns "$CLIENT_NS" link set link0 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + ip -netns "$SERVER_NS" link set link1 up + ip -netns "$SERVER_NS" addr replace "$SERVER_IP4/24" dev link1 + ip -netns "$SERVER_NS" addr replace "$SERVER_IP6/112" dev link1 nodad + ip -netns "$SERVER_NS" link set link1 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + + ip netns exec "$SERVER_NS" netserver >/dev/null +} + +setup_tunnel() { + if [ "$2" = 4 ]; then + SERVER_IP="$SERVER_IP4" + CLIENT_IP="$CLIENT_IP4" + echo "Setting up ${1^^} over IPv4, veth tx csum offload $3" + else + SERVER_IP="$SERVER_IP6" + CLIENT_IP="$CLIENT_IP6" + echo "Setting up ${1^^} over IPv6, veth tx csum offload $3" + fi + + if [ "$1" = vxlan ]; then + ip -netns "$CLIENT_NS" link add tun0 type vxlan \ + id 5001 remote "$SERVER_IP" local "$CLIENT_IP" dev link0 dstport 4789 + else + ip -netns "$CLIENT_NS" link add tun0 type geneve \ + id 5001 remote "$SERVER_IP" + fi + ip -netns "$CLIENT_NS" link set tun0 up + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4_TUN/24" dev tun0 + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6_TUN/112" dev tun0 nodad + ip -netns "$CLIENT_NS" link set tun0 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + if [ "$1" = vxlan ]; then + ip -netns "$SERVER_NS" link add tun1 type vxlan \ + id 5001 remote "$CLIENT_IP" local "$SERVER_IP" dev link1 dstport 4789 + else + ip -netns "$SERVER_NS" link add tun1 type geneve \ + id 5001 remote "$CLIENT_IP" + fi + ip -netns "$SERVER_NS" link set tun1 up + ip -netns "$SERVER_NS" addr replace "$SERVER_IP4_TUN/24" dev tun1 + ip -netns "$SERVER_NS" addr replace "$SERVER_IP6_TUN/112" dev tun1 nodad + ip -netns "$SERVER_NS" link set tun1 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + + ip netns exec "$CLIENT_NS" ethtool -K link0 tx-checksumming "$3" > /dev/null + ip netns exec "$SERVER_NS" ethtool -K link1 tx-checksumming "$3" > /dev/null +} + +cleanup_tunnel() { + ip -netns "$CLIENT_NS" link del tun0 + ip -netns "$SERVER_NS" link del tun1 +} + +cleanup() { + ip netns pids "$SERVER_NS" | xargs -r kill + ip netns pids "$CLIENT_NS" | xargs -r kill + ip netns del "$SERVER_NS" + ip netns del "$CLIENT_NS" + rm -rf "$WORKDIR" +} + +do_test() { + # When tx csum offload is off, software GSO is performed before passing the + # packet to veth. Check BIG TCP packets inside the VXLAN tunnel to verify + # the software checksum path: if the checksum code is broken, these packets + # will be dropped. + if [ "$3" = on ]; then + CAPTURE_IFACE='link' + if [ "$1" = 4 ]; then + IPTABLES=iptables + else + IPTABLES=ip6tables + fi + else + CAPTURE_IFACE='tun' + if [ "$2" = 4 ]; then + IPTABLES=iptables + else + IPTABLES=ip6tables + fi + fi + if [ "$2" = 4 ]; then + IPTABLES_SACK=iptables + else + IPTABLES_SACK=ip6tables + fi + + ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -I PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -I OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -I OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack" + + if [ "$2" = 4 ]; then + SERVER_IP="$SERVER_IP4_TUN" + echo "Running IPv4 traffic in the tunnel" + else + SERVER_IP="$SERVER_IP6_TUN" + echo "Running IPv6 traffic in the tunnel" + fi + + ip netns exec "$CLIENT_NS" netperf -t TCP_STREAM -l 5 -H "$SERVER_IP" -- \ + -m 80000 > /dev/null + + PACKETS_SERVER=$(ip netns exec "$SERVER_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}') + PACKETS_CLIENT=$(ip netns exec "$CLIENT_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}') + PACKETS_SACK=$(ip netns exec "$SERVER_NS" "$IPTABLES_SACK-save" -c -t raw | sed -rn '/ --comment sack/{s/^\[([0-9]+):.*/\1/p;q}') + ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -D PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -D OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -D OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack" + + echo "Captured BIG TCP RX packets: $PACKETS_SERVER" + echo "Captured BIG TCP TX packets: $PACKETS_CLIENT" + echo "Captured TCP SACK packets: $PACKETS_SACK" + [ "$PACKETS_SERVER" -gt "$PACKETS_THRESHOLD" ] || return 1 + [ "$PACKETS_CLIENT" -gt "$PACKETS_THRESHOLD" ] || return 1 + [ "$PACKETS_SACK" -lt "$(( PACKETS_CLIENT / 2 ))" ] || return 1 +} + +if ! netperf -V &> /dev/null; then + echo "SKIP: Could not run test without netperf tool" + exit "$ksft_skip" +fi + +if ! iptables --version &> /dev/null; then + echo "SKIP: Could not run test without iptables tool" + exit "$ksft_skip" +fi + +if ! ethtool --version &> /dev/null; then + echo "SKIP: Could not run test without ethtool tool" + exit "$ksft_skip" +fi + +if ! ip link help 2>&1 | grep gso_ipv4_max_size &> /dev/null; then + echo "SKIP: Could not run test without gso/gro_ipv4_max_size supported in ip-link" + exit "$ksft_skip" +fi + +WORKDIR=$(mktemp -d) +trap cleanup EXIT +setup +for tunnel in vxlan geneve; do + for tun_family in 4 6; do + for traffic_family in 4 6; do + for csum_offload in on off; do + setup_tunnel "$tunnel" "$tun_family" "$csum_offload" || exit "$?" + do_test "$tun_family" "$traffic_family" "$csum_offload" || exit "$?" + cleanup_tunnel + done + done + done +done diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index e1ce35c2abbe..d83e11298b01 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -38,6 +38,8 @@ CONFIG_IP_NF_TARGET_REJECT=m CONFIG_IP_NF_TARGET_TTL=m CONFIG_IP_SCTP=m CONFIG_IPV6=y +CONFIG_IPV6_FOU=m +CONFIG_IPV6_FOU_TUNNEL=m CONFIG_IPV6_GRE=m CONFIG_IPV6_ILA=m CONFIG_IPV6_IOAM6_LWTUNNEL=y @@ -80,6 +82,7 @@ CONFIG_NETFILTER=y CONFIG_NETFILTER_ADVANCED=y CONFIG_NETFILTER_XTABLES_LEGACY=y CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=y CONFIG_NETFILTER_XT_MATCH_LENGTH=m CONFIG_NETFILTER_XT_MATCH_POLICY=m CONFIG_NETFILTER_XT_NAT=m @@ -114,9 +117,6 @@ CONFIG_NFT_COMPAT=m CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PAGE_POOL_STATS=y CONFIG_PROC_SYSCTL=y CONFIG_PSAMPLE=m diff --git a/tools/testing/selftests/net/fib_nexthops.sh b/tools/testing/selftests/net/fib_nexthops.sh index ac868a731694..3d347126730a 100755 --- a/tools/testing/selftests/net/fib_nexthops.sh +++ b/tools/testing/selftests/net/fib_nexthops.sh @@ -30,6 +30,7 @@ IPV4_TESTS=" ipv4_large_res_grp ipv4_compat_mode ipv4_fdb_grp_fcnal + ipv4_fdb_port_fcnal ipv4_mpath_select ipv4_torture ipv4_res_torture @@ -44,6 +45,7 @@ IPV6_TESTS=" ipv6_large_res_grp ipv6_compat_mode ipv6_fdb_grp_fcnal + ipv6_fdb_port_fcnal ipv6_mpath_select ipv6_torture ipv6_res_torture @@ -432,6 +434,15 @@ check_nexthop_fdb_support() fi } +check_nexthop_fdb_port_support() +{ + $IP nexthop help 2>&1 | grep -q "dst_port" + if [ $? -ne 0 ]; then + echo "SKIP: iproute2 too old, missing nexthop dst_port support" + return $ksft_skip + fi +} + check_nexthop_res_support() { $IP nexthop help 2>&1 | grep -q resilient @@ -541,6 +552,42 @@ ipv6_fdb_grp_fcnal() $IP link del dev vx10 } +ipv6_fdb_port_fcnal() +{ + echo + echo "IPv6 fdb nexthop dst_port functional" + echo "------------------------------------" + + check_nexthop_fdb_port_support + if [ $? -eq $ksft_skip ]; then + return $ksft_skip + fi + + # NHA_DST_PORT: optional per-nexthop VXLAN destination UDP port, + # letting an fdb nexthop group balance a flow across legs that share + # an underlay IP but listen on different UDP ports. + run_cmd "$IP nexthop add id 80 via 2001:db8:91::2 fdb dst_port 4790" + check_nexthop "id 80" \ + "id 80 via 2001:db8:91::2 scope link fdb dst_port 4790" + log_test $? 0 "Fdb nexthop with dst_port" + + run_cmd "$IP nexthop add id 81 fdb dst_port 4790" + log_test $? 2 "Fdb nexthop with dst_port but no gateway" + + run_cmd "$IP nexthop add id 81 via 2001:db8:91::2 fdb dst_port 0" + log_test $? 2 "Fdb nexthop with dst_port 0" + + run_cmd "$IP nexthop add id 82 via 2001:db8:91::2 fdb dst_port 4789" + run_cmd "$IP nexthop add id 83 via 2001:db8:91::3 fdb dst_port 5789" + run_cmd "$IP nexthop add id 106 group 82/83 fdb" + check_nexthop "id 106" "id 106 group 82/83 fdb" + log_test $? 0 "Fdb nexthop group with legs differing in dst_port" + + run_cmd "$IP nexthop add id 84 via 2001:db8:91::2 fdb" + check_nexthop "id 84" "id 84 via 2001:db8:91::2 scope link fdb" + log_test $? 0 "Fdb nexthop without dst_port omits dst_port" +} + ipv4_fdb_grp_fcnal() { local rc @@ -641,6 +688,42 @@ ipv4_fdb_grp_fcnal() $IP link del dev vx10 } +ipv4_fdb_port_fcnal() +{ + echo + echo "IPv4 fdb nexthop dst_port functional" + echo "------------------------------------" + + check_nexthop_fdb_port_support + if [ $? -eq $ksft_skip ]; then + return $ksft_skip + fi + + # NHA_DST_PORT: optional per-nexthop VXLAN destination UDP port, + # letting an fdb nexthop group balance a flow across legs that share + # an underlay IP but listen on different UDP ports. + run_cmd "$IP nexthop add id 30 via 172.16.1.2 fdb dst_port 4790" + check_nexthop "id 30" \ + "id 30 via 172.16.1.2 scope link fdb dst_port 4790" + log_test $? 0 "Fdb nexthop with dst_port" + + run_cmd "$IP nexthop add id 31 fdb dst_port 4790" + log_test $? 2 "Fdb nexthop with dst_port but no gateway" + + run_cmd "$IP nexthop add id 31 via 172.16.1.2 fdb dst_port 0" + log_test $? 2 "Fdb nexthop with dst_port 0" + + run_cmd "$IP nexthop add id 32 via 172.16.1.2 fdb dst_port 4789" + run_cmd "$IP nexthop add id 33 via 172.16.1.3 fdb dst_port 5789" + run_cmd "$IP nexthop add id 105 group 32/33 fdb" + check_nexthop "id 105" "id 105 group 32/33 fdb" + log_test $? 0 "Fdb nexthop group with legs differing in dst_port" + + run_cmd "$IP nexthop add id 34 via 172.16.1.2 fdb" + check_nexthop "id 34" "id 34 via 172.16.1.2 scope link fdb" + log_test $? 0 "Fdb nexthop without dst_port omits dst_port" +} + ipv4_mpath_select() { local rc dev match h addr diff --git a/tools/testing/selftests/net/fin_ack_lat.c b/tools/testing/selftests/net/fin_ack_lat.c index 70187494b57a..4117332eb1a9 100644 --- a/tools/testing/selftests/net/fin_ack_lat.c +++ b/tools/testing/selftests/net/fin_ack_lat.c @@ -69,7 +69,7 @@ static void client(int port) lat = timediff(start, end); sum_lat += lat; nr_lat++; - if (lat < 100000) + if (lat < 1000000) goto close; if (getsockname(sock, (struct sockaddr *)&laddr, &len) == -1) diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh index ac8358bcb22c..05acd4011456 100644 --- a/tools/testing/selftests/net/forwarding/lib.sh +++ b/tools/testing/selftests/net/forwarding/lib.sh @@ -406,6 +406,21 @@ get_ifname_by_ip() __run_on "$target" ip -j addr show to "$ip_addr" | jq -r '.[].ifname' } +# Wait for the device to refresh its HW statistics. Devices latch the stats +# reported via ethtool only every stats-block-usecs, so sample after that. +hw_stats_settle() +{ + local iface=$1; shift + local usecs + + # Match only a non-zero integer; 0 or "n/a" use default (20msec) + usecs=$(run_on "$iface" ethtool -c "$iface" 2>/dev/null | \ + sed -n 's/^stats-block-usecs:[[:space:]]*\([1-9][0-9]*\)$/\1/p') + usecs=${usecs:-20000} + + sleep "$(echo "$usecs * 1.25 / 1000 / 1000" | bc -l)" +} + # Whether the test is conforming to the requirements and usage described in # drivers/net/README.rst. : "${DRIVER_TEST_CONFORMANT:=no}" diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh new file mode 100755 index 000000000000..70210d39fba3 --- /dev/null +++ b/tools/testing/selftests/net/fou_mcast_encap.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test that UDP encapsulation (FOU) correctly handles packet resubmit +# when packets are delivered via the multicast UDP delivery path. +# +# When a FOU-encapsulated packet arrives with a multicast destination IP, +# __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() must resubmit +# it to the inner protocol handler (e.g., GRE) rather than consuming it. +# This test verifies both IPv4 and IPv6 paths by creating a FOU/GRETAP +# tunnel with a multicast remote address and sending ping through it. +# +# The early demux optimization can mask this issue by routing packets via +# the unicast path (udp[6]_unicast_rcv_skb), so we disable it to force +# packets through the multicast delivery function. + +source lib.sh + +NSENDER="" +NRECV="" + +FOU_PORT4=4797 +FOU_PORT6=4798 +MCAST4=239.0.0.1 +MCAST6=ff0e::1 + +TUN4_S=192.168.99.1 +TUN4_R=192.168.99.2 +TUN6_S=2001:db8:99::1 +TUN6_R=2001:db8:99::2 + +cleanup() { + cleanup_all_ns +} + +trap cleanup EXIT + +setup_common() { + setup_ns NSENDER NRECV + + # Create veth pair directly inside namespaces to avoid name + # collisions with devices in the root namespace. + ip link add veth_s netns "$NSENDER" type veth \ + peer name veth_r netns "$NRECV" + + ip -n "$NSENDER" link set veth_s up + ip -n "$NRECV" link set veth_r up + + # Same sysctl controls early demux for both IPv4 and IPv6. + ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0 +} + +setup_ipv4() { + # IPv4 FOU (CONFIG_NET_FOU) is built in on kernels configured for + # these tests, so no module load is needed here. + ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s + ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r + + # Join multicast group on receiver + ip -n "$NRECV" addr add "$MCAST4/32" dev veth_r autojoin + + ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s + ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r + + # Sender: GRETAP with FOU encap (no FOU listener needed on TX side) + ip -n "$NSENDER" link add eoudp4 type gretap \ + remote "$MCAST4" local 10.0.0.1 \ + encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \ + key "$MCAST4" + ip -n "$NSENDER" link set eoudp4 up + ip -n "$NSENDER" addr add "$TUN4_S/24" dev eoudp4 + + # Receiver: FOU listener + GRETAP + ip netns exec "$NRECV" ip fou add port "$FOU_PORT4" ipproto 47 + ip -n "$NRECV" link add eoudp4 type gretap \ + remote "$MCAST4" local 10.0.0.2 \ + encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \ + key "$MCAST4" + ip -n "$NRECV" link set eoudp4 up + ip -n "$NRECV" addr add "$TUN4_R/24" dev eoudp4 + + # Static neigh on sender: ARP replies cannot traverse the + # unidirectional multicast tunnel. + local recv_mac + recv_mac=$(ip -n "$NRECV" link show eoudp4 | awk '/ether/{print $2}') + ip -n "$NSENDER" neigh add "$TUN4_R" lladdr "$recv_mac" dev eoudp4 +} + +setup_ipv6() { + # Skip cleanly if IPv6 or the fou6 module is not available. + [ -e /proc/sys/net/ipv6 ] || return "$ksft_skip" + modprobe -q fou6 || return "$ksft_skip" + + ip -n "$NSENDER" addr add 2001:db8::1/64 dev veth_s nodad + ip -n "$NRECV" addr add 2001:db8::2/64 dev veth_r nodad + + # Join multicast group on receiver + ip -n "$NRECV" addr add "$MCAST6/128" dev veth_r autojoin + + ip -n "$NSENDER" -6 route add ff00::/8 dev veth_s + ip -n "$NRECV" -6 route add ff00::/8 dev veth_r + + # Sender: ip6gretap with FOU encap + ip -n "$NSENDER" link add eoudp6 type ip6gretap \ + remote "$MCAST6" local 2001:db8::1 \ + encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \ + key 42 + ip -n "$NSENDER" link set eoudp6 up + ip -n "$NSENDER" addr add "$TUN6_S/64" dev eoudp6 nodad + + # Receiver: FOU listener (IPv6) + ip6gretap + ip netns exec "$NRECV" ip fou add port "$FOU_PORT6" ipproto 47 -6 + ip -n "$NRECV" link add eoudp6 type ip6gretap \ + remote "$MCAST6" local 2001:db8::2 \ + encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \ + key 42 + ip -n "$NRECV" link set eoudp6 up + ip -n "$NRECV" addr add "$TUN6_R/64" dev eoudp6 nodad + + # Static neigh on sender: neighbor discovery cannot traverse the + # unidirectional multicast tunnel. + local recv_mac + recv_mac=$(ip -n "$NRECV" link show eoudp6 | awk '/ether/{print $2}') + ip -n "$NSENDER" neigh add "$TUN6_R" lladdr "$recv_mac" dev eoudp6 +} + +get_rx_packets() { + local dev="$1" + + ip -n "$NRECV" -s link show "$dev" | awk '/RX:/{getline; print $2}' +} + +run_ping_test() { + local family="$1" + local dev="$2" + local dst="$3" + local name="$4" + local count=100 + local rx_before rx_after rx_delta + + # Warmup: let any initial broadcast/ND traffic settle + ip netns exec "$NSENDER" ping "$family" -c 1 -W 1 "$dst" \ + >/dev/null 2>&1 + sleep 1 + + rx_before=$(get_rx_packets "$dev") + ip netns exec "$NSENDER" ping "$family" -i 0.01 -c $count -W 1 "$dst" \ + >/dev/null 2>&1 + sleep 1 + rx_after=$(get_rx_packets "$dev") + + rx_delta=$((rx_after - rx_before)) + + if [ "$rx_delta" -ge "$count" ]; then + RET=$ksft_pass + else + RET=$ksft_fail + fi + log_test "$name (received $rx_delta/$count)" +} + +setup_common +setup_ipv4 +run_ping_test -4 eoudp4 "$TUN4_R" "FOU/GRETAP IPv4 multicast encap resubmit" + +if setup_ipv6; then + run_ping_test -6 eoudp6 "$TUN6_R" "FOU/ip6gretap IPv6 multicast encap resubmit" +else + log_test_skip "FOU/ip6gretap IPv6 multicast encap resubmit" +fi + +exit "$EXIT_STATUS" diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c index 209569354d0e..6c2408df4612 100644 --- a/tools/testing/selftests/net/getsockopt_iter.c +++ b/tools/testing/selftests/net/getsockopt_iter.c @@ -11,6 +11,8 @@ * that always reports the required buffer length back via optlen, * even when the user buffer is too small to receive any group bits. * - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path. + * - raw: ICMP_FILTER covers a fixed-size struct payload that clamps + * the length down on a short buffer instead of failing. * * Author: Breno Leitao */ @@ -24,12 +26,35 @@ #include #include #include +#include +#include +#include +#include #include +#include #include "kselftest_harness.h" #ifndef AF_VSOCK #define AF_VSOCK 40 #endif +#ifndef SOL_RAW +#define SOL_RAW 255 +#endif +#ifndef ICMP_FILTER +#define ICMP_FILTER 1 +#endif +#ifndef IPV6_HDRINCL +#define IPV6_HDRINCL 36 +#endif +#ifndef IPV6_CHECKSUM +#define IPV6_CHECKSUM 7 +#endif +#ifndef SOL_TLS +#define SOL_TLS 282 +#endif +#ifndef TCP_ULP +#define TCP_ULP 31 +#endif /* ---------- netlink ---------- */ @@ -297,4 +322,315 @@ TEST_F(vsock, connect_timeout_old_exact) ASSERT_EQ(sizeof(tv), optlen); } +/* ---------- raw (ipv4) ---------- */ + +FIXTURE(raw) +{ + int fd; +}; + +FIXTURE_SETUP(raw) +{ + struct icmp_filter filt = { .data = 0xdeadbeef }; + + self->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (self->fd < 0) + SKIP(return, "SOCK_RAW/ICMP socket: %s", strerror(errno)); + + if (setsockopt(self->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof(filt)) < 0) + SKIP(return, "set ICMP_FILTER: %s", strerror(errno)); +} + +FIXTURE_TEARDOWN(raw) +{ + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(raw, icmpfilter_exact) +{ + struct icmp_filter filt = {}; + socklen_t optlen = sizeof(filt); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + &filt, &optlen)); + ASSERT_EQ(sizeof(filt), optlen); + ASSERT_EQ(0xdeadbeef, filt.data); +} + +TEST_F(raw, icmpfilter_oversize_clamped) +{ + char buf[16] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(struct icmp_filter), optlen); +} + +/* Unlike the int/u64 options above, ICMP_FILTER clamps the length down + * to the user buffer instead of returning EINVAL: a short buffer + * succeeds and reports the truncated length back via optlen. + */ +TEST_F(raw, icmpfilter_undersize_clamped) +{ + char buf[2] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(buf), optlen); +} + +TEST_F(raw, icmpfilter_wrong_proto) +{ + struct icmp_filter filt; + socklen_t optlen = sizeof(filt); + int fd; + + fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (fd < 0) + SKIP(return, "SOCK_RAW/UDP socket: %s", strerror(errno)); + + ASSERT_EQ(-1, getsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, &optlen)); + ASSERT_EQ(EOPNOTSUPP, errno); + close(fd); +} + +TEST_F(raw, bad_optname) +{ + socklen_t optlen; + int val; + + optlen = sizeof(val); + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); + ASSERT_EQ(sizeof(val), optlen); +} + +/* ---------- raw (ipv6) ---------- */ + +FIXTURE(rawv6) +{ + int fd; +}; + +FIXTURE_SETUP(rawv6) +{ + self->fd = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP); + if (self->fd < 0) + SKIP(return, "SOCK_RAW/IPv6 socket: %s", strerror(errno)); +} + +FIXTURE_TEARDOWN(rawv6) +{ + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(rawv6, hdrincl_exact) +{ + socklen_t optlen; + int val = -1; + + optlen = sizeof(val); + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_TRUE(val == 0 || val == 1); +} + +TEST_F(rawv6, hdrincl_oversize_clamped) +{ + char buf[16] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + buf, &optlen)); + ASSERT_EQ(sizeof(int), optlen); +} + +/* Raw int options clamp the reported length down to the user buffer + * instead of returning EINVAL on a short buffer. + */ +TEST_F(rawv6, hdrincl_undersize_clamped) +{ + socklen_t optlen = 2; + int val = 0; + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + &val, &optlen)); + ASSERT_EQ(2, optlen); +} + +TEST_F(rawv6, checksum_default) +{ + socklen_t optlen; + int val = 0; + + optlen = sizeof(val); + + /* A non-ICMPv6 raw socket has the checksum disabled, reported as -1. */ + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_CHECKSUM, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_EQ(-1, val); +} + +TEST_F(rawv6, bad_optname) +{ + socklen_t optlen; + int val; + + optlen = sizeof(val); + + /* SOL_RAW reaches do_rawv6_getsockopt() directly. */ + ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); + ASSERT_EQ(sizeof(val), optlen); +} + +/* ---------- tls ---------- */ + +FIXTURE(tls) +{ + int fd; + int sfd; +}; + +FIXTURE_SETUP(tls) +{ + struct sockaddr_in a = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + socklen_t alen = sizeof(a); + int lfd; + + self->fd = -1; + self->sfd = -1; + + lfd = socket(AF_INET, SOCK_STREAM, 0); + if (lfd < 0) + SKIP(return, "TCP socket: %s", strerror(errno)); + if (bind(lfd, (struct sockaddr *)&a, sizeof(a)) || listen(lfd, 1) || + getsockname(lfd, (struct sockaddr *)&a, &alen)) { + close(lfd); + SKIP(return, "listener setup: %s", strerror(errno)); + } + self->fd = socket(AF_INET, SOCK_STREAM, 0); + if (self->fd < 0) { + close(lfd); + SKIP(return, "TCP socket: %s", strerror(errno)); + } + if (connect(self->fd, (struct sockaddr *)&a, sizeof(a))) { + close(lfd); + SKIP(return, "connect: %s", strerror(errno)); + } + self->sfd = accept(lfd, NULL, NULL); + close(lfd); + if (setsockopt(self->fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls"))) + SKIP(return, "TCP_ULP=tls: %s (built without TLS?)", + strerror(errno)); +} + +FIXTURE_TEARDOWN(tls) +{ + if (self->fd >= 0) + close(self->fd); + if (self->sfd >= 0) + close(self->sfd); +} + +/* do_tls_getsockopt_tx_zc(): fixed-size int, exact length required. */ +TEST_F(tls, tx_zerocopy_exact) +{ + socklen_t optlen = sizeof(int); + int val = -1; + + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_TRUE(val == 0 || val == 1); +} + +TEST_F(tls, tx_zerocopy_wrong_len) +{ + socklen_t optlen = 2; + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, + &val, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +/* do_tls_getsockopt_conf(): NULL optval still yields EINVAL -- the + * converted code tests opt->iter_out.ubuf in place of optval. + */ +TEST_F(tls, conf_null_optval) +{ + socklen_t optlen = 64; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, NULL, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +TEST_F(tls, conf_short) +{ + socklen_t optlen = 2; + char buf[2]; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, buf, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +/* TLS_TX before crypto is set reports not-ready. */ +TEST_F(tls, conf_not_ready) +{ + struct tls_crypto_info info; + socklen_t optlen = sizeof(info); + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, &info, &optlen)); + ASSERT_EQ(EBUSY, errno); +} + +/* Set TX crypto, then read it back at the base and full sizes, exercising + * both copy_to_iter() branches. SKIP if AES-GCM is unavailable. + */ +TEST_F(tls, conf_crypto_roundtrip) +{ + struct tls12_crypto_info_aes_gcm_128 tx = { + .info.version = TLS_1_2_VERSION, + .info.cipher_type = TLS_CIPHER_AES_GCM_128, + }; + struct tls12_crypto_info_aes_gcm_128 full; + struct tls_crypto_info base; + socklen_t optlen; + + if (setsockopt(self->fd, SOL_TLS, TLS_TX, &tx, sizeof(tx))) + SKIP(return, "set TLS_TX aes_gcm_128: %s", strerror(errno)); + + optlen = sizeof(base); + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &base, &optlen)); + ASSERT_EQ(sizeof(base), optlen); + ASSERT_EQ(TLS_1_2_VERSION, base.version); + ASSERT_EQ(TLS_CIPHER_AES_GCM_128, base.cipher_type); + + optlen = sizeof(full); + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &full, &optlen)); + ASSERT_EQ(sizeof(full), optlen); + ASSERT_EQ(TLS_CIPHER_AES_GCM_128, full.info.cipher_type); +} + +TEST_F(tls, bad_optname) +{ + socklen_t optlen = sizeof(int); + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile index 31fb9326cf53..2150e487ac7d 100644 --- a/tools/testing/selftests/net/hsr/Makefile +++ b/tools/testing/selftests/net/hsr/Makefile @@ -4,6 +4,7 @@ top_srcdir = ../../../../.. TEST_PROGS := \ hsr_ping.sh \ + hsr_prp_redbox.sh \ hsr_redbox.sh \ link_faults.sh \ prp_ping.sh \ diff --git a/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh new file mode 100755 index 000000000000..479c892225b1 --- /dev/null +++ b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test a PRP RedBox (PRP-SAN): a SAN that sits behind the interlink port must +# reach, and be reached by, a peer DANP on the PRP network with its own MAC +# preserved on the wire, and the RedBox must announce the SAN with a RedBox-MAC +# TLV (terminated by an EOT marker) in its PRP supervision frames. +# +# RB PRP RedBox: prp0 over rb_a/rb_b (LAN A/B) + interlink rb_il +# PEER peer DANP : prp0 over pe_a/pe_b, 100.64.0.2 +# SAN SAN : san_il, own MAC, 100.64.0.51 (behind the interlink) + +ipv6=false + +source ./hsr_common.sh + +check_prerequisites + +if ! command -v tcpdump >/dev/null 2>&1; then + echo "SKIP: This test requires tcpdump" + exit $ksft_skip +fi + +if ! ip link help hsr 2>&1 | grep -q interlink; then + echo "SKIP: iproute2 too old (no hsr interlink support)" + exit $ksft_skip +fi + +setup_ns RB PEER SAN +trap 'cleanup_ns "$RB" "$PEER" "$SAN"' EXIT + +ip link add rb_a netns "$RB" type veth peer name pe_a netns "$PEER" +ip link add rb_b netns "$RB" type veth peer name pe_b netns "$PEER" +ip link add rb_il netns "$RB" type veth peer name san_il netns "$SAN" + +ip -n "$RB" link set rb_a up +ip -n "$RB" link set rb_b up +ip -n "$RB" link set rb_il up +ip -n "$PEER" link set pe_a up +ip -n "$PEER" link set pe_b up +ip -n "$SAN" link set san_il up +ip -n "$SAN" addr add 100.64.0.51/24 dev san_il + +# Feature gate: PRP interlink (RedBox) creation. A kernel without PRP RedBox +# support rejects this with -EINVAL, so SKIP rather than FAIL. +if ! ip -n "$RB" link add name prp0 type hsr slave1 rb_a slave2 rb_b \ + interlink rb_il proto 1 2>/dev/null; then + echo "SKIP: kernel without PRP RedBox (interlink) support" + exit $ksft_skip +fi +ip -n "$RB" link set prp0 up +ip -n "$PEER" link add name prp0 type hsr slave1 pe_a slave2 pe_b proto 1 +ip -n "$PEER" link set prp0 up +ip -n "$PEER" addr add 100.64.0.2/24 dev prp0 +sleep 1 + +san_mac=$(ip -n "$SAN" -br link show san_il | awk '{print $3}') +rb_mac=$(ip -n "$RB" -br link show rb_il | awk '{print $3}') + +# Bidirectional unicast across the interlink. +do_ping "$PEER" 100.64.0.51 +do_ping "$SAN" 100.64.0.2 +stop_if_error "PRP RedBox bidirectional unicast failed" + +# The SAN source MAC must be preserved on the PRP network, not laundered to the +# RedBox MAC: the peer resolves the SAN IP to the SAN's own MAC. +neigh=$(ip -n "$PEER" neigh show 100.64.0.51 | awk '{print $5}') +if [ "$neigh" != "$san_mac" ]; then + echo "SAN MAC preservation [ FAIL ]: peer resolved 100.64.0.51 to" \ + "'$neigh', expected $san_mac" 1>&2 + ret=1 +fi +stop_if_error "SAN MAC not preserved on the PRP network" + +# The proxy-announce supervision frame must carry, in order, the life-check TLV +# (type 0x14, len 6) + MacAddressA == SAN MAC + the RedBox-MAC TLV (type 0x1e, +# len 6) + MacAddressRedBox == RedBox MAC + the EOT marker (0x0000). +ip netns exec "$SAN" ping -i 0.2 -q 100.64.0.2 >/dev/null 2>&1 & +ping_pid=$! +cap=$(ip netns exec "$PEER" timeout 5 tcpdump -i pe_a -nn -x \ + "ether proto 0x88fb and ether src $rb_mac" 2>/dev/null || true) +kill "$ping_pid" 2>/dev/null || true +wait "$ping_pid" 2>/dev/null || true + +san_hex=$(echo "$san_mac" | tr -d ':') +rb_hex=$(echo "$rb_mac" | tr -d ':') +# Reassemble contiguous frame hex: drop the "0x0010:" offset labels and spaces. +frame_hex=$(echo "$cap" | awk '/^[[:space:]]*0x[0-9a-f]+:/ { + sub(/^[[:space:]]*0x[0-9a-f]+:[[:space:]]*/, ""); + gsub(/ /, ""); printf "%s", $0 }') +if ! echo "$frame_hex" | grep -q "1406${san_hex}1e06${rb_hex}0000"; then + echo "supervision RedBox-MAC TLV [ FAIL ]: missing SAN MAC, Type-30" \ + "payload, or EOT" 1>&2 + ret=1 +fi +stop_if_error "PRP RedBox supervision RedBox-MAC TLV/EOT check failed" + +echo "INFO: PRP RedBox (PRP-SAN) conformance checks passed" +exit $ret diff --git a/tools/testing/selftests/net/ipv6_flowlabel.sh b/tools/testing/selftests/net/ipv6_flowlabel.sh index cee95e252bee..5d1b5464c54c 100755 --- a/tools/testing/selftests/net/ipv6_flowlabel.sh +++ b/tools/testing/selftests/net/ipv6_flowlabel.sh @@ -7,9 +7,6 @@ set -e -echo "TEST management" -./in_netns.sh ./ipv6_flowlabel_mgr - echo "TEST datapath" ./in_netns.sh \ sh -c 'sysctl -q -w net.ipv6.auto_flowlabels=0 && ./ipv6_flowlabel -l 1' diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index af95b48acea9..072fb3a9b121 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -6,45 +6,36 @@ #include #include #include +#include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include "kselftest_harness.h" /* uapi/glibc weirdness may leave this undefined */ #ifndef IPV6_FLOWLABEL_MGR #define IPV6_FLOWLABEL_MGR 32 #endif +#ifndef IPV6_FLOWINFO_SEND +#define IPV6_FLOWINFO_SEND 33 +#endif /* from net/ipv6/ip6_flowlabel.c */ #define FL_MIN_LINGER 6 -#define explain(x) \ - do { if (cfg_verbose) fprintf(stderr, " " x "\n"); } while (0) - -#define __expect(x) \ - do { \ - if (!(x)) \ - fprintf(stderr, "[OK] " #x "\n"); \ - else \ - error(1, 0, "[ERR] " #x " (line %d)", __LINE__); \ - } while (0) - -#define expect_pass(x) __expect(x) -#define expect_fail(x) __expect(!(x)) - -static bool cfg_long_running; -static bool cfg_verbose; - static int flowlabel_get(int fd, uint32_t label, uint8_t share, uint16_t flags) { struct in6_flowlabel_req req = { @@ -71,129 +62,484 @@ static int flowlabel_put(int fd, uint32_t label) return setsockopt(fd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req)); } -static void run_tests(int fd) +static int flowlabel_renew(int fd, uint32_t label, uint8_t share, + uint16_t linger) { - int wstatus; + struct in6_flowlabel_req req = { + .flr_action = IPV6_FL_A_RENEW, + .flr_label = htonl(label), + .flr_share = share, + .flr_linger = linger, + }; + + return setsockopt(fd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req)); +} + +static struct sockaddr_in6 loopback_addr(void) +{ + struct sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LOOPBACK_INIT, + .sin6_port = htons(8888), + }; + + return addr; +} + +static int tcp_listen(void) +{ + struct sockaddr_in6 addr = loopback_addr(); + const int one = 1; + int fd; + + fd = socket(PF_INET6, SOCK_STREAM, 0); + if (fd == -1) + error(1, errno, "socket listener"); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) + error(1, errno, "setsockopt SO_REUSEADDR"); + if (bind(fd, (void *)&addr, sizeof(addr))) + error(1, errno, "bind"); + if (listen(fd, 1)) + error(1, errno, "listen"); + + return fd; +} + +static void tcp_connect(int listener, uint32_t flowlabel, + int *client, int *accepted) +{ + struct sockaddr_in6 addr = loopback_addr(); + const int one = 1; + int cfd, afd; + + cfd = socket(PF_INET6, SOCK_STREAM, 0); + if (cfd == -1) + error(1, errno, "socket client"); + + if (flowlabel_get(cfd, flowlabel, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)) + error(1, errno, "flowlabel_get"); + if (setsockopt(cfd, SOL_IPV6, IPV6_FLOWINFO_SEND, &one, sizeof(one))) + error(1, errno, "setsockopt flowinfo_send"); + addr.sin6_flowinfo = htonl(flowlabel); + + if (connect(cfd, (void *)&addr, sizeof(addr))) + error(1, errno, "connect"); + + afd = accept(listener, NULL, NULL); + if (afd == -1) + error(1, errno, "accept"); + + if (flowlabel_put(cfd, flowlabel)) + error(1, errno, "flowlabel_put"); + + *client = cfd; + *accepted = afd; +} + +static int bringup_loopback(void) +{ + struct ifreq ifr = { + .ifr_name = "lo" + }; + int fd; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) + goto err; + + ifr.ifr_flags = ifr.ifr_flags | IFF_UP; + + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) + goto err; + + close(fd); + return 0; + +err: + close(fd); + return -1; +} + +FIXTURE(flowlabel) {}; + +FIXTURE_SETUP(flowlabel) +{ + int ret; + + ret = unshare(CLONE_NEWNET); + ASSERT_EQ(ret, 0) { + TH_LOG("unshare(CLONE_NEWNET) failed: %s", strerror(errno)); + } + + ret = bringup_loopback(); + ASSERT_EQ(ret, 0) TH_LOG("Failed to bring up loopback interface"); +} + +FIXTURE_TEARDOWN(flowlabel) +{ +} + +TEST_F(flowlabel, cannot_get_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 9, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(err) TH_LOG("expected get of a non-existent label to fail"); + EXPECT_EQ(ENOENT, errno) TH_LOG("expected ENOENT, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, cannot_put_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_put(fd, 10); + EXPECT_TRUE(err) TH_LOG("expected put of a non-existent label to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, cannot_create_label_greater_than_20_bits) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected label > 20 bits to be rejected"); + EXPECT_EQ(EINVAL, errno) TH_LOG("expected EINVAL, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, can_create_and_get_and_put_labels) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) TH_LOG("failed to create label (FL_F_CREATE)"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get the label without FL_F_CREATE"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to get it again with create flag set, too"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, + IPV6_FL_F_CREATE | IPV6_FL_F_EXCL); + EXPECT_TRUE(err) + TH_LOG("expected FL_F_EXCL to reject existing label"); + EXPECT_EQ(EEXIST, errno) TH_LOG("expected EEXIST, got %d", errno); + + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put first reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put second reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put third reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(err) + TH_LOG("expected fourth put to fail, no references left"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, exclusive_label_share) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new exclusive label (FL_S_EXCL)"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse in non-exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse in exclusive mode to fail too"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_put(fd, 2); + EXPECT_TRUE(!err) TH_LOG("failed to put the exclusive label"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse to fail, due to linger"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + sleep(FL_MIN_LINGER * 2 + 1); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) TH_LOG("expected reuse to succeed after linger"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, user_private_label_share) +{ + int fd, err, wstatus; pid_t pid; - explain("cannot get non-existent label"); - expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0)); + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); - explain("cannot put non-existent label"); - expect_fail(flowlabel_put(fd, 1)); + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new user-private label (FL_S_USER)"); - explain("cannot create label greater than 20 bits"); - expect_fail(flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); + err = flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(err) TH_LOG("expected get in non-exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); - explain("create a new label (FL_F_CREATE)"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("can get the label (without FL_F_CREATE)"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0)); - explain("can get it again with create flag set, too"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("cannot get it again with the exclusive (FL_FL_EXCL) flag"); - expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE | IPV6_FL_F_EXCL)); - explain("can now put exactly three references"); - expect_pass(flowlabel_put(fd, 1)); - expect_pass(flowlabel_put(fd, 1)); - expect_pass(flowlabel_put(fd, 1)); - expect_fail(flowlabel_put(fd, 1)); + err = flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0); + EXPECT_TRUE(err) TH_LOG("expected get in exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); - explain("create a new exclusive label (FL_S_EXCL)"); - expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); - explain("cannot get it again in non-exclusive mode"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("cannot get it again in exclusive mode either"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); - expect_pass(flowlabel_put(fd, 2)); + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get it again in user mode"); - if (cfg_long_running) { - explain("cannot reuse the label, due to linger"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); - explain("after sleep, can reuse"); - sleep(FL_MIN_LINGER * 2 + 1); - expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); - } - - explain("create a new user-private label (FL_S_USER)"); - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE)); - explain("cannot get it again in non-exclusive mode"); - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0)); - explain("cannot get it again in exclusive mode"); - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0)); - explain("can get it again in user mode"); - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); - explain("child process can get it too, but not after setuid(nobody)"); pid = fork(); - if (pid == -1) - error(1, errno, "fork"); + ASSERT_NE(-1, pid) TH_LOG("fork failed"); if (!pid) { - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(!err) + TH_LOG("child failed to get the user-private label"); + if (setuid(USHRT_MAX)) - fprintf(stderr, "[INFO] skip setuid child test\n"); - else - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); - exit(0); - } - if (wait(&wstatus) == -1) - error(1, errno, "wait"); - if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) - error(1, errno, "wait: unexpected child result"); + exit(KSFT_SKIP); - explain("create a new process-private label (FL_S_PROCESS)"); - expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE)); - explain("can get it again"); - expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0)); - explain("child process cannot can get it"); - pid = fork(); - if (pid == -1) - error(1, errno, "fork"); - if (!pid) { - expect_fail(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0)); + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(err) + TH_LOG("child unexpectedly got label after setuid"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); exit(0); } - if (wait(&wstatus) == -1) - error(1, errno, "wait"); - if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) - error(1, errno, "wait: unexpected child result"); + ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed"); + ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally"); + if (WEXITSTATUS(wstatus) == KSFT_SKIP) + SKIP(return, + "setuid(USHRT_MAX) unavailable (no CAP_SETUID or uid unmapped)"); + EXPECT_EQ(0, WEXITSTATUS(wstatus)) + TH_LOG("child reported unexpected result"); + + EXPECT_EQ(0, close(fd)); } -static void parse_opts(int argc, char **argv) +TEST_F(flowlabel, process_private_label_share) { - int c; + int fd, err, wstatus; + pid_t pid; - while ((c = getopt(argc, argv, "lv")) != -1) { - switch (c) { - case 'l': - cfg_long_running = true; - break; - case 'v': - cfg_verbose = true; - break; - default: - error(1, 0, "%s: parse error", argv[0]); - } + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new process-private label"); + + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get it again"); + + pid = fork(); + ASSERT_NE(-1, pid) TH_LOG("fork failed"); + if (!pid) { + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0); + EXPECT_TRUE(err) + TH_LOG("child unexpectedly got process-private label"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + exit(0); } + ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed"); + ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally"); + EXPECT_EQ(0, WEXITSTATUS(wstatus)) + TH_LOG("child reported unexpected result"); + + EXPECT_EQ(0, close(fd)); } -int main(int argc, char **argv) +TEST_F(flowlabel, cannot_renew_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(err) + TH_LOG("expected renew of a non-existent label to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, can_renew_existing_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new label for renew validation"); + + err = flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(!err) TH_LOG("failed to renew an existing valid label"); + + err = flowlabel_put(fd, 5); + EXPECT_TRUE(!err) TH_LOG("failed to put the label"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, renew_label_linger) +{ + /* RENEW must extend a label's linger period: putting a renewed + * label and waiting out its original linger time must not be + * enough to allow the label to be recreated. + */ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 6, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create label with FL_MIN_LINGER linger time"); + + err = flowlabel_renew(fd, 6, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(!err) + TH_LOG("failed to renew the label to increase its linger time"); + + err = flowlabel_put(fd, 6); + EXPECT_TRUE(!err) TH_LOG("failed to put the label"); + + sleep(FL_MIN_LINGER * 2 + 1); + + err = flowlabel_get(fd, 6, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) + TH_LOG("expected reuse to fail, new linger time not over yet"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, remote_flag) +{ + /* The REMOTE flag, used for getsockopt, is expected to retrieve the + * label from the latest received header. + */ + struct in6_flowlabel_req freq = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REMOTE, + }; + socklen_t freq_len = sizeof(freq); + int listener, cfd, afd, err; + + listener = tcp_listen(); + tcp_connect(listener, 7, &cfd, &afd); + + err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &freq, &freq_len); + EXPECT_TRUE(!err) TH_LOG("getsockopt with IPV6_FL_F_REMOTE failed"); + EXPECT_EQ(7, ntohl(freq.flr_label)) + TH_LOG("unexpected remote flow label"); + + EXPECT_EQ(0, close(afd)); + EXPECT_EQ(0, close(cfd)); + EXPECT_EQ(0, close(listener)); +} + +static bool disable_flowlabel_consistency(void) { int fd; - parse_opts(argc, argv); - - fd = socket(PF_INET6, SOCK_DGRAM, 0); + fd = open("/proc/sys/net/ipv6/flowlabel_consistency", O_WRONLY); if (fd == -1) - error(1, errno, "socket"); + return false; - run_tests(fd); + if (write(fd, "0", 1) != 1) { + close(fd); + return false; + } + close(fd); - if (close(fd)) - error(1, errno, "close"); - - return 0; + return true; } + +TEST_F(flowlabel, reflect_flag) +{ + /* The REFLECT flag acts as a trigger to the REPFLOW bit. When REPFLOW + * is triggered for a socket, it adopts the label received from the + * connected socket. + */ + struct in6_flowlabel_req reflect_on = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REFLECT, + }; + struct in6_flowlabel_req reflect_query = { + .flr_action = IPV6_FL_A_GET, + }; + struct in6_flowlabel_req reflect_off = { + .flr_action = IPV6_FL_A_PUT, + .flr_flags = IPV6_FL_F_REFLECT, + }; + socklen_t reflect_query_len = sizeof(reflect_query); + int listener, cfd, afd, err; + + if (!disable_flowlabel_consistency()) + SKIP(return, + "cannot disable net.ipv6.flowlabel_consistency"); + + listener = tcp_listen(); + err = setsockopt(listener, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_on, sizeof(reflect_on)); + EXPECT_TRUE(!err) TH_LOG("failed to enable REFLECT on the listener"); + + tcp_connect(listener, 8, &cfd, &afd); + + err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_query, &reflect_query_len); + EXPECT_TRUE(!err) + TH_LOG("failed to query the accepted socket's outgoing label"); + EXPECT_EQ(8, ntohl(reflect_query.flr_label)) + TH_LOG("accepted socket did not reflect client's label"); + + err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_off, sizeof(reflect_off)); + EXPECT_TRUE(!err) + TH_LOG("failed to disable REFLECT on the accepted socket"); + + err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_off, sizeof(reflect_off)); + EXPECT_TRUE(err) TH_LOG("expected disabling REFLECT twice to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(afd)); + EXPECT_EQ(0, close(cfd)); + EXPECT_EQ(0, close(listener)); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index e58bdbdc58ee..34935886b6ad 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ wait_file, tool, tc from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ - EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily + EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink __all__ = ["KSRC", @@ -34,4 +34,4 @@ __all__ = ["KSRC", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", - "NlctrlFamily", "RtnlAddrFamily", "Netlink"] + "NlctrlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "Netlink"] diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py index 2e567062aa6c..08deff756f29 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -29,7 +29,7 @@ except ModuleNotFoundError as e: __all__ = [ "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH", - "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", + "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "NetdevFamily", "NetshaperFamily", "NlctrlFamily", "DevlinkFamily", "PSPFamily", ] @@ -54,6 +54,11 @@ class RtnlAddrFamily(YnlFamily): super().__init__((SPEC_PATH / Path('rt-addr.yaml')).as_posix(), schema='', recv_size=recv_size) +class RtnlRouteFamily(YnlFamily): + def __init__(self, recv_size=0): + super().__init__((SPEC_PATH / Path('rt-route.yaml')).as_posix(), + schema='', recv_size=recv_size) + class NetdevFamily(YnlFamily): def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c index cbe573c4ab3a..ea4cb6c1bd5e 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.c +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c @@ -1264,7 +1264,7 @@ static void apply_cmsg_types(int fd, const struct cfg_cmsg_types *cmsg) static void parse_cmsg_types(const char *type) { - char *next = strchr(type, ','); + const char *next = strchr(type, ','); unsigned int len = 0; cfg_cmsg_types.cmsg_enabled = 1; @@ -1292,7 +1292,7 @@ static void parse_cmsg_types(const char *type) static void parse_setsock_options(const char *name) { - char *next = strchr(name, ','); + const char *next = strchr(name, ','); unsigned int len = 0; if (next) { diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.sh b/tools/testing/selftests/net/mptcp/mptcp_connect.sh index d158678fa6ab..7a2a851fa0ad 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.sh @@ -212,8 +212,8 @@ if $checksum; then fi if $capture; then - rndh="${ns1:4}" - mptcp_lib_pr_info "Packet capture files will have this prefix: ${rndh}-" + capprefix="mptcp_connect-${ns1:4}" + mptcp_lib_pr_info "pcap will have this prefix: ${capprefix}-" fi set_ethtool_flags() { @@ -372,8 +372,8 @@ do_transfer() capuser="-Z $SUDO_USER" fi - local capfile="${rndh}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}" - local capopt="-i any -s 65535 -B 32768 ${capuser}" + local capfile="${capprefix}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}" + local capopt="-i any -s 128 -B 32768 ${capuser}" ip netns exec ${listener_ns} tcpdump ${capopt} \ -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 & diff --git a/tools/testing/selftests/net/mptcp/mptcp_diag.c b/tools/testing/selftests/net/mptcp/mptcp_diag.c index 5e222ba977e4..3b8d2c8a6216 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_diag.c +++ b/tools/testing/selftests/net/mptcp/mptcp_diag.c @@ -377,7 +377,8 @@ static void get_subflow_info(char *subflow_addrs) int ret; int fd; - ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d", saddr, &sport, daddr, &dport); + ret = sscanf(subflow_addrs, "%63[^:]:%d %63[^:]:%d", + saddr, &sport, daddr, &dport); if (ret != 4) die_perror("IP PORT Pairs has style problems!"); diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index 7dc91fac4917..18ce7136a2b0 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -75,6 +75,14 @@ unset join_syn_tx unset join_create_err unset join_bind_err unset join_connect_err +unset join_synack_no_mpjoin +unset join_ack_no_mpjoin +unset join_ack_no_ctx +unset join_not_established +unset join_no_id_found + +unset rst_md5sig +unset rst_dss unset fb_ns1 unset fb_ns2 @@ -971,7 +979,7 @@ cond_start_capture() capfile=$(printf "mp_join-%02u-%s.pcap" "$MPTCP_LIB_TEST_COUNTER" "$ns") echo "Capturing traffic for test $MPTCP_LIB_TEST_COUNTER into $capfile" - ip netns exec "$ns" tcpdump -i any -s 65535 -B 32768 $capuser -w "$capfile" > "$capout" 2>&1 & + ip netns exec "$ns" tcpdump -i any -s 128 -B 32768 $capuser -w "$capfile" > "$capout" 2>&1 & cappid=$! sleep 1 @@ -1353,6 +1361,8 @@ chk_rst_nr() local rst_tx=$1 local rst_rx=$2 local ns_invert=${3:-""} + local md5sig=${rst_md5sig:-0} + local dss=${rst_dss:-0} local count local ns_tx=$ns1 local ns_rx=$ns2 @@ -1389,6 +1399,21 @@ chk_rst_nr() else print_ok fi + + # MPTCP_RST_EMPTCP reset-event counters; default 0, gated on + # availability. Fixed namespaces: MD5SigReset fires on the listener + # (server), DssReset on the data receiver (client). + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMD5SigReset") + if [ -n "$count" ] && [ "$count" != "$md5sig" ]; then + print_check "MD5SigReset" + fail_test "got $count MD5SigReset expected $md5sig" + fi + + count=$(mptcp_lib_get_counter ${ns2} "MPTcpExtDssReset") + if [ -n "$count" ] && [ "$count" != "$dss" ]; then + print_check "DssReset" + fail_test "got $count DssReset expected $dss" + fi } chk_infi_nr() @@ -1587,6 +1612,11 @@ chk_join_nr() local rst_nr=${join_rst_nr:-0} local infi_nr=${join_infi_nr:-0} local corrupted_pkts=${join_corrupted_pkts:-0} + local synack_no_mpjoin=${join_synack_no_mpjoin:-0} + local ack_no_mpjoin=${join_ack_no_mpjoin:-0} + local ack_no_ctx=${join_ack_no_ctx:-0} + local not_established=${join_not_established:-0} + local no_id_found=${join_no_id_found:-0} local rc=${KSFT_PASS} local count local with_cookie @@ -1655,6 +1685,44 @@ chk_join_nr() fail_test "got $count JOIN[s] syn rejected expected $syn_rej" fi + # Per-event MPTCP_RST_EMPTCP JOIN counters; default 0, gated on + # availability. Fixed namespaces: the *SynAck* one fires on the + # client receiving the SYN/ACK, the others on the server. + count=$(mptcp_lib_get_counter ${ns2} "MPTcpExtMPJoinSynAckNoMPJoin") + if [ -n "$count" ] && [ "$count" != "$synack_no_mpjoin" ]; then + rc=${KSFT_FAIL} + print_check "synack no mpjoin" + fail_test "got $count JOIN[s] synack no mpjoin expected $synack_no_mpjoin" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinAckNoMPJoin") + if [ -n "$count" ] && [ "$count" != "$ack_no_mpjoin" ]; then + rc=${KSFT_FAIL} + print_check "ack no mpjoin" + fail_test "got $count JOIN[s] ack no mpjoin expected $ack_no_mpjoin" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinAckNoCtx") + if [ -n "$count" ] && [ "$count" != "$ack_no_ctx" ]; then + rc=${KSFT_FAIL} + print_check "ack no ctx" + fail_test "got $count JOIN[s] ack no ctx expected $ack_no_ctx" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinNotEstablished") + if [ -n "$count" ] && [ "$count" != "$not_established" ]; then + rc=${KSFT_FAIL} + print_check "join not established" + fail_test "got $count JOIN[s] not established expected $not_established" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinNoIdFound") + if [ -n "$count" ] && [ "$count" != "$no_id_found" ]; then + rc=${KSFT_FAIL} + print_check "join no id found" + fail_test "got $count JOIN[s] no id found expected $no_id_found" + fi + print_results "join Rx" ${rc} join_syn_tx="${join_syn_tx:-${syn_nr}}" \ @@ -2359,6 +2427,31 @@ signal_address_tests() chk_add_nr 4 4 fi fi + + # signalled address belongs to the client, where a TCP-only + # listener is bound at it: the client's MP_JOIN routes locally + # to the listener and receives a SYN/ACK without MP_JOIN. + # MPJoinSynAckNoMPJoin increments on the client side. + if reset "signal address, TCP-only listener on client"; then + local extra_bind + local port + + pm_nl_set_limits $ns1 0 1 + pm_nl_set_limits $ns2 1 1 + pm_nl_add_endpoint $ns1 10.0.2.2 flags signal + + port=$(get_port) + ip netns exec ${ns2} ./mptcp_connect -l -t -1 -p "$port" \ + -s TCP 10.0.2.2 & + extra_bind=$! + mptcp_lib_wait_local_port_listen "$ns2" "$port" + + run_tests $ns1 $ns2 10.0.1.1 + join_synack_no_mpjoin=1 join_syn_tx=1 \ + chk_join_nr 0 0 0 + + kill ${extra_bind} 2>/dev/null + fi } laminar_endp_tests() diff --git a/tools/testing/selftests/net/mptcp/simult_flows.sh b/tools/testing/selftests/net/mptcp/simult_flows.sh index 7b9aabe10170..3ea3d1efe32e 100755 --- a/tools/testing/selftests/net/mptcp/simult_flows.sh +++ b/tools/testing/selftests/net/mptcp/simult_flows.sh @@ -24,6 +24,7 @@ small="" sout="" cout="" capout="" +capprefix="" size=0 usage() { @@ -70,6 +71,11 @@ setup() mptcp_lib_ns_init ns1 ns2 ns3 + if $capture; then + capprefix="simult_flows-${ns1:4}" + mptcp_lib_pr_info "pcap will have this prefix: ${capprefix}-" + fi + ip link add ns1eth1 netns "$ns1" type veth peer name ns2eth1 netns "$ns2" ip link add ns1eth2 netns "$ns1" type veth peer name ns2eth2 netns "$ns2" ip link add ns2eth3 netns "$ns2" type veth peer name ns3eth1 netns "$ns3" @@ -136,15 +142,14 @@ do_transfer() if $capture; then local capuser - local rndh="${ns1:4}" if [ -z $SUDO_USER ] ; then capuser="" else capuser="-Z $SUDO_USER" fi - local capfile="${rndh}-${port}" - local capopt="-i any -s 65535 -B 32768 ${capuser}" + local capfile="${capprefix}-${port}" + local capopt="-i any -s 108 -B 32768 ${capuser}" ip netns exec ${ns3} tcpdump ${capopt} -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 & local cappid_listener=$! diff --git a/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c b/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c index 5cecb8a1bc94..31b8250ddc53 100644 --- a/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c +++ b/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c @@ -102,7 +102,6 @@ static int conntrack_data_insert(struct mnl_socket *sock, struct nlmsghdr *nlh, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *rplnlh; unsigned int portid; int ret; @@ -216,12 +215,11 @@ static int count_entries(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } -static int conntracK_count_zone(struct mnl_socket *sock, uint16_t zone) +static int conntrack_count_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh, *rplnlh; + struct nlmsghdr *nlh; struct nfgenmsg *nfh; - struct nlattr *nest; unsigned int portid; int ret; @@ -266,9 +264,8 @@ static int conntracK_count_zone(struct mnl_socket *sock, uint16_t zone) static int conntrack_flush_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh, *rplnlh; + struct nlmsghdr *nlh; struct nfgenmsg *nfh; - struct nlattr *nest; unsigned int portid; int ret; @@ -326,7 +323,7 @@ FIXTURE_SETUP(conntrack_dump_flush) ret = mnl_socket_bind(self->sock, 0, MNL_SOCKET_AUTOPID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); if (ret < 0 && errno == EPERM) SKIP(return, "Needs to be run as root"); else if (ret < 0 && errno == EOPNOTSUPP) @@ -423,7 +420,7 @@ FIXTURE_SETUP(conntrack_dump_flush) NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_GE(ret, 2); if (ret > 2) SKIP(return, "kernel does not support filtering by zone"); @@ -437,7 +434,7 @@ TEST_F(conntrack_dump_flush, test_dump_by_zone) { int ret; - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 2); } @@ -447,13 +444,13 @@ TEST_F(conntrack_dump_flush, test_flush_by_zone) ret = conntrack_flush_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 1); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 2); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); + ret = conntrack_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 2); } @@ -463,13 +460,13 @@ TEST_F(conntrack_dump_flush, test_flush_by_zone_default) ret = conntrack_flush_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 1); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 2); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); + ret = conntrack_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); } diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config index c659749cd086..a825e0b5c88e 100644 --- a/tools/testing/selftests/net/openvswitch/config +++ b/tools/testing/selftests/net/openvswitch/config @@ -1,5 +1,6 @@ CONFIG_GENEVE=m CONFIG_INET_DIAG=y +CONFIG_IP_SCTP=y CONFIG_IPV6=y CONFIG_NETFILTER=y CONFIG_NET_IPGRE=m @@ -7,9 +8,6 @@ CONFIG_NET_IPGRE_DEMUX=m CONFIG_NF_CONNTRACK=m CONFIG_NF_CONNTRACK_OVS=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PSAMPLE=m CONFIG_VETH=y CONFIG_VLAN_8021Q=y diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 2954245129a2..a31f7fb6882d 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -26,12 +26,14 @@ tests=" netlink_checks ovsnl: validate netlink attrs and settings upcall_interfaces ovs: test the upcall interfaces tunnel_metadata ovs: test extraction of tunnel metadata - tunnel_refcount ovs: test tunnel vport reference cleanup drop_reason drop: test drop reasons are emitted pop_vlan vlan: POP_VLAN action strips tag dec_ttl ttl: dec_ttl decrements IP TTL flow_set flow-set: Flow modify action_set set: SET action rewrites fields + trunc trunc: output truncation + icmpv6 icmpv6: ICMPv6 echo type match + sctp_connect_v4 sctp: SCTP flow key matching psample psample: Sampling packets with psample" info() { @@ -443,6 +445,261 @@ test_action_set() { return 0 } +# trunc test +# - trunc(14): truncate to ETH_HLEN, strips IP payload, ping fails +# - trunc(1) and trunc(13): kernel rejects below ETH_HLEN (EINVAL) +# - restore normal forwarding and verify recovery +test_trunc() { + sbx_add "test_trunc" || return $? + ovs_add_dp "test_trunc" trunctest || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "test_trunc" "trunctest" \ + "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add 10.0.0.1/24 dev c1 + ip netns exec client ip link set c1 up + ip netns exec server ip addr add 10.0.0.2/24 dev s1 + ip netns exec server ip link set s1 up + + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity without truncation" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + # trunc below ETH_HLEN must be rejected by the kernel + info "verify trunc(1) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(1),2' &> /dev/null \ + && { info "trunc(1) should be rejected"; return 1; } + + info "verify trunc(13) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(13),2' &> /dev/null \ + && { info "trunc(13) should be rejected"; return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + + info "add trunc(14) forwarding flow" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(14),2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify ping fails with trunc(14)" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 >/dev/null 2>&1 \ + && { info "ping should fail with trunc(14)" + return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity restored" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + return 0 +} + +# icmpv6 test +# - static neighbours to bypass NDP (nud permanent) +# - icmpv6(type=128) echo request, icmpv6(type=129) echo reply +# - remove flows and verify ping fails, reinstall and recover +test_icmpv6() { + local t="test_icmpv6" + local v6="eth_type(0x86dd),ipv6(proto=58)" + + sbx_add "$t" || return $? + ovs_add_dp "$t" icmpv6 || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "$t" "icmpv6" \ + "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add fd00::1/64 dev c1 nodad + ip netns exec client ip link set c1 up + ip netns exec server ip addr add fd00::2/64 dev s1 nodad + ip netns exec server ip link set s1 up + + local cl_mac sl_mac + cl_mac=$(ip netns exec client ip link show c1 \ + | awk '/link\/ether/ {print $2}') + [ -z "$cl_mac" ] && \ + { info "failed to get c1 hwaddr"; return 1; } + sl_mac=$(ip netns exec server ip link show s1 \ + | awk '/link\/ether/ {print $2}') + [ -z "$sl_mac" ] && \ + { info "failed to get s1 hwaddr"; return 1; } + ip netns exec client ip -6 neigh add fd00::2 \ + lladdr "$sl_mac" nud permanent dev c1 || return 1 + ip netns exec server ip -6 neigh add fd00::1 \ + lladdr "$cl_mac" nud permanent dev s1 || return 1 + + # Probe: check if kernel supports icmpv6 flow key. + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' &>/dev/null + if [ $? -ne 0 ]; then + info "no support for icmpv6 key - skipping" + ovs_exit_sig + return $ksft_skip + fi + ovs_del_flows "$t" icmpv6 + + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' || return 1 + ovs_add_flow "$t" icmpv6 \ + "in_port(2),eth(),$v6,icmpv6(type=129)" \ + '1' || return 1 + + info "verify ICMPv6 echo with type-specific flows" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 || return 1 + + ovs_del_flows "$t" icmpv6 + + info "verify ping fails without echo flows" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 >/dev/null 2>&1 \ + && { info "ping should fail without flows" + return 1; } + + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' || return 1 + ovs_add_flow "$t" icmpv6 \ + "in_port(2),eth(),$v6,icmpv6(type=129)" \ + '1' || return 1 + + info "verify connectivity restored" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 || return 1 + + return 0 +} + +# Check for an SCTP endpoint via /proc, which works without sctp_diag. +sctp_eps_has() { + ip netns exec "$1" awk -v p="$2" '$6==p' /proc/net/sctp/eps | grep -q . +} + +# sctp_connect_v4 test +# - sctp(dst=4443) matches client-to-server INIT +# - sctp(src=4443) matches server-to-client INIT-ACK +# - remove flows and verify connection fails, reinstall and recover +test_sctp_connect_v4() { + local t="test_sctp_connect_v4" + local srv_ip=172.31.110.20 + + modprobe -q sctp 2>/dev/null || return "$ksft_skip" + socat -V 2>&1 | grep -q "define WITH_SCTP" || return "$ksft_skip" + + sbx_add "$t" || return $? + ovs_add_dp "$t" sctp4 || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "$t" "sctp4" "$ns" \ + "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add 172.31.110.10/24 dev c1 + ip netns exec client ip link set c1 up + ip netns exec server ip addr add "${srv_ip}/24" dev s1 + ip netns exec server ip link set s1 up + + # ARP forwarding + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0806),arp()' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0806),arp()' \ + '1' || return 1 + + # SCTP port matching: dst for request, src for reply + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \ + '1' || return 1 + + # The listener forks a child per association, so one instance serves + # the whole test and the flows stay the only variable. -t 1 bounds + # how long a child lingers after its association closes. + ovs_netns_spawn_daemon "$t" "server" \ + socat -u -t 1 SCTP4-LISTEN:4443,fork STDOUT + ovs_wait sctp_eps_has server 4443 || return 1 + + info "verify SCTP association with port-keyed flows" + ovs_sbx "$t" ip netns exec client \ + timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" /dev/null 2>&1 \ + && { info "connection should fail without flows" + return 1; } + + info "reinstall flows and verify recovery" + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \ + '1' || return 1 + + ovs_sbx "$t" ip netns exec client \ + timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" /dev/null 2>&1 || return 1 - - info "deleting dp - may hang if reference counting is broken" - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - del-dp dp-${tun_type} & - - dev_removed() { - ! ip -netns trefns link show "$1" >/dev/null 2>&1 - } - ovs_wait dev_removed dp-${tun_type} || return 1 - ovs_wait dev_removed ovs-${tun_type}0 || return 1 - done - - return 0 -} - test_pop_vlan() { local sbx="test_pop_vlan" sbx_add "$sbx" || return $? diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index e1ecfad2c03e..1615843c225e 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -1255,11 +1255,16 @@ class ovskey(nla): lambda x: ipaddress.IPv6Address(x).packed if x else 0, convert_ipv6, ), - ("label", "label", "%d", lambda x: int(x) if x else 0), - ("proto", "proto", "%d", lambda x: int(x) if x else 0), - ("tclass", "tclass", "%d", lambda x: int(x) if x else 0), - ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0), - ("frag", "frag", "%d", lambda x: int(x) if x else 0), + ("label", "label", "%d", lambda x: int(x) if x else 0, + convert_int(20)), + ("proto", "proto", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("tclass", "tclass", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("frag", "frag", "%d", lambda x: int(x) if x else 0, + convert_int(8)), ) def __init__( @@ -1344,8 +1349,10 @@ class ovskey(nla): ) fields_map = ( - ("type", "type", "%d", lambda x: int(x) if x else 0), - ("code", "code", "%d", lambda x: int(x) if x else 0), + ("type", "type", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("code", "code", "%d", lambda x: int(x) if x else 0, + convert_int(8)), ) def __init__( @@ -1977,11 +1984,21 @@ class ovskey(nla): "udp", ovskey.ovs_key_udp, ), + ( + "OVS_KEY_ATTR_SCTP", + "sctp", + ovskey.ovs_key_sctp, + ), ( "OVS_KEY_ATTR_ICMP", "icmp", ovskey.ovs_key_icmp, ), + ( + "OVS_KEY_ATTR_ICMPV6", + "icmpv6", + ovskey.ovs_key_icmpv6, + ), ( "OVS_KEY_ATTR_TCP_FLAGS", "tcp_flags", @@ -2352,9 +2369,6 @@ class OvsDatapath(GenericNetlinkSocket): class OvsVport(GenericNetlinkSocket): OVS_VPORT_TYPE_NETDEV = 1 OVS_VPORT_TYPE_INTERNAL = 2 - OVS_VPORT_TYPE_GRE = 3 - OVS_VPORT_TYPE_VXLAN = 4 - OVS_VPORT_TYPE_GENEVE = 5 class ovs_vport_msg(ovs_dp_msg): nla_map = ( @@ -2362,7 +2376,7 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_PORT_NO", "uint32"), ("OVS_VPORT_ATTR_TYPE", "uint32"), ("OVS_VPORT_ATTR_NAME", "asciiz"), - ("OVS_VPORT_ATTR_OPTIONS", "vportopts"), + ("OVS_VPORT_ATTR_OPTIONS", "none"), ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), ("OVS_VPORT_ATTR_STATS", "vportstats"), ("OVS_VPORT_ATTR_PAD", "none"), @@ -2370,13 +2384,6 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_NETNSID", "uint32"), ) - class vportopts(nla): - nla_map = ( - ("OVS_TUNNEL_ATTR_UNSPEC", "none"), - ("OVS_TUNNEL_ATTR_DST_PORT", "uint16"), - ("OVS_TUNNEL_ATTR_EXTENSION", "none"), - ) - class vportstats(nla): fields = ( ("rx_packets", "=Q"), @@ -2394,25 +2401,13 @@ class OvsVport(GenericNetlinkSocket): return "netdev" elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: return "internal" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: - return "gre" - elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: - return "vxlan" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: - return "geneve" raise ValueError("Unknown vport type:%d" % vport_type) def str_to_type(vport_type): - if vport_type == "netdev": + if vport_type in ["netdev", "gre", "vxlan", "geneve"]: return OvsVport.OVS_VPORT_TYPE_NETDEV elif vport_type == "internal": return OvsVport.OVS_VPORT_TYPE_INTERNAL - elif vport_type == "gre": - return OvsVport.OVS_VPORT_TYPE_GRE - elif vport_type == "vxlan": - return OvsVport.OVS_VPORT_TYPE_VXLAN - elif vport_type == "geneve": - return OvsVport.OVS_VPORT_TYPE_GENEVE raise ValueError("Unknown vport type: '%s'" % vport_type) def __init__(self, packet=OvsPacket()): @@ -2445,16 +2440,18 @@ class OvsVport(GenericNetlinkSocket): raise ne return reply - def attach(self, dpindex, vport_ifname, ptype, dport, lwt): + def attach(self, dpindex, vport_ifname, ptype, dport): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_NEW msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex - port_type = OvsVport.str_to_type(ptype) msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) + msg["attrs"].append( + ["OVS_VPORT_ATTR_TYPE", OvsVport.str_to_type(ptype)] + ) msg["attrs"].append( ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] ) @@ -2468,36 +2465,21 @@ class OvsVport(GenericNetlinkSocket): if not dport: dport = tnl[1] - if not lwt: - if tnl[0] == "gre": - # GRE tunnels have no options. - break + ipr = pyroute2.iproute.IPRoute() - vportopt = OvsVport.ovs_vport_msg.vportopts() - vportopt["attrs"].append( - ["OVS_TUNNEL_ATTR_DST_PORT", dport] - ) - msg["attrs"].append( - ["OVS_VPORT_ATTR_OPTIONS", vportopt] - ) - else: - port_type = OvsVport.OVS_VPORT_TYPE_NETDEV - ipr = pyroute2.iproute.IPRoute() - - if tnl[0] == "geneve": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - geneve_port=dport, - geneve_collect_metadata=True, - geneve_udp_zero_csum6_rx=1) - elif tnl[0] == "gre": - ipr.link("add", ifname=vport_ifname, kind="gretap", - gre_collect_metadata=True) - elif tnl[0] == "vxlan": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - vxlan_learning=0, vxlan_collect_metadata=1, - vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) + if tnl[0] == "geneve": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + geneve_port=dport, + geneve_collect_metadata=True, + geneve_udp_zero_csum6_rx=1) + elif tnl[0] == "gre": + ipr.link("add", ifname=vport_ifname, kind="gretap", + gre_collect_metadata=True) + elif tnl[0] == "vxlan": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + vxlan_learning=0, vxlan_collect_metadata=1, + vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) break - msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) try: reply = self.nlm_request( @@ -2925,19 +2907,12 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): for iface in ndb.interfaces: rep = vpl.info(iface.ifname, ifindex) if rep is not None: - opts = "" - vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS") - if vpo: - dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT") - if dpo: - opts += " tnl-dport:%s" % dpo print( - " port %d: %s (%s%s)" + " port %d: %s (%s)" % ( rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), rep.get_attr("OVS_VPORT_ATTR_NAME"), OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), - opts, ) ) @@ -3010,13 +2985,6 @@ def main(argv): default=0, help="Destination port (0 for default)" ) - addifcmd.add_argument( - "-l", - "--lwt", - action=argparse.BooleanOptionalAction, - default=True, - help="Use LWT infrastructure instead of vport (default true)." - ) delifcmd = subparsers.add_parser("del-if") delifcmd.add_argument("dpname", help="Datapath Name") delifcmd.add_argument("delif", help="Interface name for adding") @@ -3096,7 +3064,7 @@ def main(argv): return 1 dpindex = rep["dpifindex"] rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype, - args.dport, args.lwt) + args.dport) msg = "vport '%s'" % args.addif if rep and rep["header"]["error"] is None: msg += " added." diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index edf1e6f80d41..0f6a30b26912 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -39,6 +39,9 @@ static bool cfg_use_gso; static bool cfg_use_qdisc_bypass; static bool cfg_use_vlan; static bool cfg_use_vnet; +static bool cfg_drop; +static bool cfg_aux_data; +static bool cfg_ignore_outgoing; static char *cfg_ifname = "lo"; static int cfg_mtu = 1500; @@ -49,6 +52,8 @@ static uint16_t cfg_port = 8000; /* test sending up to max mtu + 1 */ #define TEST_SZ (sizeof(struct virtio_net_hdr) + ETH_HLEN + ETH_MAX_MTU + 1) +#define BURST_CNT (1000) + static char tbuf[TEST_SZ], rbuf[TEST_SZ]; static unsigned long add_csum_hword(const uint16_t *start, int num_u16) @@ -167,12 +172,12 @@ static int build_packet(int payload_len) return off + payload_len; } -static void do_bind(int fd) +static void do_bind_proto(int fd, uint16_t proto) { struct sockaddr_ll laddr = {0}; laddr.sll_family = AF_PACKET; - laddr.sll_protocol = htons(ETH_P_IP); + laddr.sll_protocol = htons(proto); laddr.sll_ifindex = if_nametoindex(cfg_ifname); if (!laddr.sll_ifindex) error(1, errno, "if_nametoindex"); @@ -181,6 +186,11 @@ static void do_bind(int fd) error(1, errno, "bind"); } +static void do_bind(int fd) +{ + do_bind_proto(fd, ETH_P_IP); +} + static void do_send(int fd, char *buf, int len) { int ret; @@ -212,13 +222,14 @@ static void do_send(int fd, char *buf, int len) if (ret != len) error(1, 0, "write: %u %u", ret, len); - fprintf(stderr, "tx: %u\n", ret); + if (!cfg_drop) + fprintf(stderr, "tx: %u\n", ret); } static int do_tx(void) { const int one = 1; - int fd, len; + int i, fd, len; fd = socket(PF_PACKET, cfg_use_dgram ? SOCK_DGRAM : SOCK_RAW, 0); if (fd == -1) @@ -242,6 +253,10 @@ static int do_tx(void) do_send(fd, tbuf, len); + if (cfg_drop) + for (i = 0; i < BURST_CNT; i++) + do_send(fd, tbuf, len); + if (close(fd)) error(1, errno, "close t"); @@ -271,11 +286,58 @@ static int setup_rx(void) return fd; } -static void do_rx(int fd, int expected_len, char *expected) +static void check_aux_data(struct cmsghdr *cmsg, int expected_len) { + struct tpacket_auxdata *adata; + + if (!cmsg) + error(1, 0, "auxdata null"); + + if (cmsg->cmsg_level != SOL_PACKET) + error(1, 0, "cmsg_level != SOL_PACKET"); + + if (cmsg->cmsg_type != PACKET_AUXDATA) + error(1, 0, "cmsg_type != PACKET_AUXDATA"); + + adata = (struct tpacket_auxdata *)CMSG_DATA(cmsg); + + if (adata->tp_net != ETH_HLEN) + error(1, 0, "cmsg tp_net != ETH_HLEN"); + + if (adata->tp_len != expected_len) + error(1, 0, "cmsg tp_len != %u", expected_len); + + if (adata->tp_snaplen != expected_len) + error(1, 0, "cmsg tp_snaplen != %u", expected_len); +} + +/* expected_pkttype < 0 skips the sll_pkttype check. */ +static void do_rx(int fd, int expected_len, char *expected, bool is_psock, + int expected_pkttype) +{ + char cmsg_buf[1024] __attribute__((aligned(8))) = {}; + bool aux = is_psock && cfg_aux_data; + struct sockaddr_ll saddr = {}; + struct iovec iov = { + .iov_base = rbuf, + .iov_len = sizeof(rbuf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; int ret; - ret = recv(fd, rbuf, sizeof(rbuf), 0); + if (aux) { + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + } + if (is_psock) { + msg.msg_name = &saddr; + msg.msg_namelen = sizeof(saddr); + } + + ret = recvmsg(fd, &msg, 0); if (ret == -1) error(1, errno, "recv"); if (ret != expected_len) @@ -284,12 +346,20 @@ static void do_rx(int fd, int expected_len, char *expected) if (memcmp(rbuf, expected, ret)) error(1, 0, "recv: data mismatch"); + if (aux) + check_aux_data(CMSG_FIRSTHDR(&msg), expected_len); + + if (expected_pkttype >= 0 && saddr.sll_pkttype != expected_pkttype) + error(1, 0, "recv: sll_pkttype %d != %d", + saddr.sll_pkttype, expected_pkttype); + fprintf(stderr, "rx: %u\n", ret); } static int setup_sniffer(void) { struct timeval tv = { .tv_usec = 100 * 1000 }; + const int one = 1; int fd; fd = socket(PF_PACKET, SOCK_RAW, 0); @@ -299,8 +369,23 @@ static int setup_sniffer(void) if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) error(1, errno, "setsockopt rcv timeout"); + if (cfg_drop) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one))) + error(1, errno, "setsockopt SO_RCVBUF"); + + if (cfg_aux_data) + if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, &one, sizeof(one))) + error(1, errno, "setsockopt PACKET_AUXDATA"); + pair_udp_setfilter(fd); - do_bind(fd); + + /* binding to ETH_P_ALL adds the sniffer to ptype_all, which will see + * the dev_queue_xmit_nit copy. ignore_outgoing should suppress this. + */ + if (cfg_ignore_outgoing) + do_bind_proto(fd, ETH_P_ALL); + else + do_bind(fd); return fd; } @@ -309,8 +394,11 @@ static void parse_opts(int argc, char **argv) { int c; - while ((c = getopt(argc, argv, "bcCdgl:qt:vV")) != -1) { + while ((c = getopt(argc, argv, "abcCdDgil:qt:vV")) != -1) { switch (c) { + case 'a': + cfg_aux_data = true; + break; case 'b': cfg_use_bind = true; break; @@ -323,9 +411,15 @@ static void parse_opts(int argc, char **argv) case 'd': cfg_use_dgram = true; break; + case 'D': + cfg_drop = true; + break; case 'g': cfg_use_gso = true; break; + case 'i': + cfg_ignore_outgoing = true; + break; case 'l': cfg_payload_len = strtoul(optarg, NULL, 0); break; @@ -357,6 +451,114 @@ static void parse_opts(int argc, char **argv) if (cfg_use_gso && !cfg_use_csum_off) error(1, 0, "option gso (-g) requires csum offload (-c)"); + + if (cfg_aux_data && cfg_drop) + error(1, 0, "option aux data (-a) conflicts with drop (-D)"); + + if (cfg_ignore_outgoing && (cfg_drop || cfg_aux_data)) + error(1, 0, + "option ignore outgoing (-i) conflicts with -D and -a"); +} + +static void check_packet_stats(int fd, unsigned int expected_packets) +{ + struct tpacket_stats st = {}; + socklen_t len = sizeof(st); + + if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) + error(1, errno, "getsockopt packet statistics"); + + if (cfg_drop) { + /* PACKET_STATISTICS reports all packets seen (including + * drops) in tp_packets + */ + if (st.tp_packets < st.tp_drops) + error(1, 0, "stats: tp_packets %u < tp_drops %u", + st.tp_packets, st.tp_drops); + + if (st.tp_drops == 0) + error(1, 0, "stats: expected drops but tp_drops == 0"); + } else { + if (st.tp_packets != expected_packets) + error(1, 0, "stats: tp_packets %u != %u", + st.tp_packets, expected_packets); + + if (st.tp_drops != 0) + error(1, 0, "stats: tp_drops %u != 0", st.tp_drops); + } + + /* verify clear on read */ + memset(&st, 0xff, sizeof(st)); + len = sizeof(st); + + if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) + error(1, errno, "getsockopt packet statistics"); + + if (st.tp_packets != 0) + error(1, 0, "stats: tp_packets %u != 0 after clear", st.tp_packets); + + if (st.tp_drops != 0) + error(1, 0, "stats: tp_drops %u != 0 after clear", st.tp_drops); +} + +static void set_ignore_outgoing(int fd, int val) +{ + socklen_t len = sizeof(int); + int got = -1; + + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val))) + error(1, errno, "setsockopt PACKET_IGNORE_OUTGOING %d", val); + + if (getsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &got, &len)) + error(1, errno, "getsockopt PACKET_IGNORE_OUTGOING"); + if (got != val) + error(1, 0, "getsockopt: expected %d got %d", val, got); +} + +static void check_ignore_outgoing_range(int fd) +{ + int val; + + /* Values outside [0, 1] must be rejected with -EINVAL. */ + val = 2; + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val)) != -1 || errno != EINVAL) + error(1, errno, + "setsockopt PACKET_IGNORE_OUTGOING val=2: expected EINVAL"); + + val = -1; + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val)) != -1 || errno != EINVAL) + error(1, errno, + "setsockopt PACKET_IGNORE_OUTGOING val=-1: expected EINVAL"); +} + +static void test_ignore_outgoing(int fds) +{ + char *expected = tbuf + sizeof(struct virtio_net_hdr); + int expected_len; + + /* ptype_all sniffer on loopback should produce two copies per packet + * (RX and TX). + */ + expected_len = do_tx(); + expected_len -= sizeof(struct virtio_net_hdr); + do_rx(fds, expected_len, expected, true, PACKET_OUTGOING); + do_rx(fds, expected_len, expected, true, PACKET_HOST); + check_packet_stats(fds, 2); + + /* 0 and 1 accepted; anything else rejected. */ + set_ignore_outgoing(fds, 0); + set_ignore_outgoing(fds, 1); + check_ignore_outgoing_range(fds); + + /* With PACKET_IGNORE_OUTGOING set, only the rx copy survives. */ + do_tx(); + do_rx(fds, expected_len, expected, true, PACKET_HOST); + if (recv(fds, rbuf, sizeof(rbuf), 0) != -1 || errno != EAGAIN) + error(1, errno, "expected EAGAIN, got extra packet"); + check_packet_stats(fds, 1); } static void run_test(void) @@ -366,15 +568,28 @@ static void run_test(void) fdr = setup_rx(); fds = setup_sniffer(); + if (cfg_ignore_outgoing) { + test_ignore_outgoing(fds); + goto out; + } + total_len = do_tx(); + if (cfg_drop) { + check_packet_stats(fds, 0); + goto out; + } + /* BPF filter accepts only this length, vlan changes MAC */ - if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) + if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), - tbuf + sizeof(struct virtio_net_hdr)); + tbuf + sizeof(struct virtio_net_hdr), true, -1); + check_packet_stats(fds, 1); + } - do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len); + do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false, -1); +out: if (close(fds)) error(1, errno, "close s"); if (close(fdr)) diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh index 1cbfeb5052ec..7fa0a3297988 100755 --- a/tools/testing/selftests/net/psock_snd.sh +++ b/tools/testing/selftests/net/psock_snd.sh @@ -92,4 +92,19 @@ echo "raw gso max size" echo "raw gso max size + 1 (expected to fail)" (! ./in_netns.sh ./psock_snd -v -c -g -l "${max_mss_exceeds}") +# test drops statistics + +echo "test drops statistics" +./in_netns.sh ./psock_snd -D + +# test aux data + +echo "test aux data" +./in_netns.sh ./psock_snd -a + +# test ignore outgoing + +echo "test ignore outgoing" +./in_netns.sh ./psock_snd -i + echo "OK. All tests passed" diff --git a/tools/testing/selftests/net/reuseport_bpf_numa.c b/tools/testing/selftests/net/reuseport_bpf_numa.c index 8ec52fc5ef41..6e4817ef57c5 100644 --- a/tools/testing/selftests/net/reuseport_bpf_numa.c +++ b/tools/testing/selftests/net/reuseport_bpf_numa.c @@ -104,6 +104,26 @@ static void attach_bpf(int fd) close(bpf_fd); } +/* + * Return true if it is a cpuless node. Return false if it isn't or any + * error (very unlikely) happens during the libnuma calls. + */ +static bool is_cpuless_node(int node_id) +{ + struct bitmask *cpumask; + bool ret = false; + + cpumask = numa_allocate_cpumask(); + if (!cpumask) + return ret; + + if (!numa_node_to_cpus(node_id, cpumask) && !numa_bitmask_weight(cpumask)) + ret = true; + + numa_bitmask_free(cpumask); + return ret; +} + static void send_from_node(int node_id, int family, int proto) { struct sockaddr_storage saddr, daddr; @@ -213,6 +233,8 @@ static void test(int *rcv_fd, int len, int family, int proto) for (node = 0; node < len; ++node) { if (!numa_bitmask_isbitset(numa_nodes_ptr, node)) continue; + if (is_cpuless_node(node)) + continue; send_from_node(node, family, proto); receive_on_node(rcv_fd, len, epfd, node, proto); } @@ -221,6 +243,8 @@ static void test(int *rcv_fd, int len, int family, int proto) for (node = len - 1; node >= 0; --node) { if (!numa_bitmask_isbitset(numa_nodes_ptr, node)) continue; + if (is_cpuless_node(node)) + continue; send_from_node(node, family, proto); receive_on_node(rcv_fd, len, epfd, node, proto); } diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py index 3622413d793d..5cc3ebdcf08d 100755 --- a/tools/testing/selftests/net/rtnetlink.py +++ b/tools/testing/selftests/net/rtnetlink.py @@ -2,27 +2,108 @@ # SPDX-License-Identifier: GPL-2.0 import socket +import struct import time -from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_ge, ksft_true, KsftSkipEx -from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily +from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx +from lib.py import ksft_not_in, ksft_not_none +from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily +from lib.py import defer IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01' +IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01' +IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123') + + +def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int): + """Return mc-users for grp on ifindex, or 0 if absent.""" + + addrs = rtnl.getmulticast({"ifa-family": family}, dump=True) + matches = [addr for addr in addrs + if addr['multicast'] == grp and addr['ifa-index'] == ifindex] + if not matches: + return 0 + if 'mc-users' not in matches[0]: + return None + + return matches[0]['mc-users'] + def dump_mcaddr_check() -> None: """ - Verify that at least one interface has the IPv4 all-hosts multicast address. - At least the loopback interface should have this address. + Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST. """ - rtnl = RtnlAddrFamily() - addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) - all_host_multicasts = [ - addr for addr in addresses if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST - ] + all_host_multicasts = [ + addr for addr in addresses + if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST + ] + + ksft_ge(len(all_host_multicasts), 1, + "No interface found with the IPv4 all-hosts multicast address") + + mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1') + before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + after_join = _users_for(rtnl, socket.AF_INET, + IPV4_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + ksft_eq(after_join - before, 2, + f"users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() + + +def dump_mcaddr6_check() -> None: + """ + Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST. + """ + + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + before = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + + mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx) + s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) + s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) + + after_join = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + ksft_eq(after_join - before, 2, + f"IPv6 users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() - ksft_ge(len(all_host_multicasts), 1, - "No interface found with the IPv4 all-hosts multicast address") def ipv4_devconf_notify() -> None: """ @@ -55,8 +136,189 @@ def ipv4_devconf_notify() -> None: ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout, f"No 'forwarding on' notificiation found for interface {ifname}") +def _rtnl_route_subscribe(ns): + with NetNSEnter(str(ns)): + rtnl = RtnlRouteFamily() + defer(rtnl.close) + rtnl.ntf_subscribe("rtnlgrp-ipv6-route") + return rtnl + + +def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10): + """Return the attrs of the first matching notification, None on timeout.""" + + for msg in rtnl.poll_ntf(duration=deadline): + if msg['name'] != name: + continue + attrs = msg['msg'] + if attrs['rtm-dst-len'] != dst_len: + continue + if dst is not None and attrs.get('dst') != dst: + continue + return attrs + return None + + +def _collect_route_ntfs(rtnl, name, want, deadline=10): + """Gather attrs of matching notifications, keyed by (dst_len, dst).""" + + seen = {} + for msg in rtnl.poll_ntf(duration=deadline): + if msg['name'] != name: + continue + attrs = msg['msg'] + key = (attrs['rtm-dst-len'], attrs.get('dst')) + if key in want: + seen[key] = attrs + if len(seen) == len(want): + break + return seen + + +def _write_ipv6_sysctl(name, value): + with open(f"/proc/sys/net/ipv6/{name}", "w") as f: + f.write(f"{value}\n") + + +def ipv6_route_del_reason_expired() -> None: + """An expired route reports RTA_DEL_REASON == expired.""" + + with NetNS() as ns: + rtnl = _rtnl_route_subscribe(ns) + with NetNSEnter(str(ns)): + _write_ipv6_sysctl("route/gc_interval", 2) + ip("link add name dummy1 type dummy", ns=str(ns)) + ip("link set dev dummy1 up", ns=str(ns)) + ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=str(ns)) + + attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::', + deadline=15) + ksft_not_none(attrs, "no RTM_DELROUTE for the expired route") + if attrs is not None: + ksft_eq(attrs.get('del-reason'), 'expired') + + +def _send_ra(sock, ifindex, lifetime, rio=None, pio=None): + """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.""" + + # type, code, cksum, hop limit, flags, router lifetime, + # reachable time, retrans timer + ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0) + if rio is not None: + prefix, plen, rio_lifetime = rio + # RFC 4191 route information option, /64 prefix (8 bytes) + ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime) + ra += socket.inet_pton(socket.AF_INET6, prefix)[:8] + if pio is not None: + prefix, plen, valid_lft = pio + # RFC 4861 prefix information option, on-link only (L set, A clear) + ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0) + ra += socket.inet_pton(socket.AF_INET6, prefix) + sock.sendto(ra, ('ff02::1', 0, 0, ifindex)) + + +def _ra_router_sock(ns_r, ifname): + with NetNSEnter(str(ns_r)): + sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, + socket.IPPROTO_ICMPV6) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255) + defer(sock.close) + return sock, socket.if_nametoindex(ifname) + + +def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts): + """ + Sending fails with EADDRNOTAVAIL while the router's link-local + address is still tentative. addrconf_dad_start() only queues + addrconf_dad_work(), and IFA_F_TENTATIVE is cleared when that work + item runs, so retry until it does. + """ + + seen = {} + for _ in range(10): + try: + _send_ra(sock, ifindex, **ra_opts) + except OSError: + time.sleep(0.2) + continue + seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf', + want - set(seen.keys()), deadline=2)) + if len(seen) == len(want): + break + return seen + + +def ipv6_route_del_reason_ra_withdrawn() -> None: + """ + Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861 + PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn. + """ + + # (rtm-dst-len, dst); the default route carries no RTA_DST + routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')} + + with NetNS() as ns_h, NetNS() as ns_r: + ip(f"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}") + with NetNSEnter(str(ns_h)): + _write_ipv6_sysctl("conf/veth0/accept_ra", 2) + _write_ipv6_sysctl("conf/veth0/forwarding", 0) + try: + _write_ipv6_sysctl("conf/veth0/accept_ra_rt_info_max_plen", 64) + except FileNotFoundError: + raise KsftSkipEx("no CONFIG_IPV6_ROUTE_INFO") + with NetNSEnter(str(ns_r)): + # skip the DAD probe so the router's link-local source only + # has to wait for addrconf_dad_work() to clear IFA_F_TENTATIVE + _write_ipv6_sysctl("conf/veth1/accept_dad", 0) + ip("link set dev veth0 up", ns=str(ns_h)) + ip("link set dev veth1 up", ns=str(ns_r)) + + rtnl = _rtnl_route_subscribe(ns_h) + sock, ifindex = _ra_router_sock(ns_r, "veth1") + + seen = _ra_advertise_routes(rtnl, sock, ifindex, routes, + lifetime=1800, + rio=('2001:db8:5::', 64, 600), + pio=('2001:db8:6::', 64, 600)) + ksft_eq(set(seen), routes, "not all RA routes were installed") + if set(seen) != routes: + return + + _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0), + pio=('2001:db8:6::', 64, 0)) + seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes) + for key in routes: + attrs = seen.get(key) + ksft_not_none(attrs, f"no RTM_DELROUTE for {key}") + if attrs is not None: + ksft_eq(attrs.get('del-reason'), 'ra-withdrawn') + + +def ipv6_route_del_reason_absent() -> None: + """ + A deletion path that records no cause (here a userspace request) + must not carry RTA_DEL_REASON at all. + """ + + with NetNS() as ns: + rtnl = _rtnl_route_subscribe(ns) + ip("link add name dummy1 type dummy", ns=str(ns)) + ip("link set dev dummy1 up", ns=str(ns)) + ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=str(ns)) + ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=str(ns)) + + attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::') + ksft_not_none(attrs, "no RTM_DELROUTE for 2001:db8:1::/64") + if attrs is not None: + ksft_not_in('del-reason', attrs, + "user deletion must not carry del-reason") + + def main() -> None: - ksft_run([dump_mcaddr_check, ipv4_devconf_notify]) + ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify, + ipv6_route_del_reason_expired, + ipv6_route_del_reason_ra_withdrawn, + ipv6_route_del_reason_absent]) ksft_exit() if __name__ == "__main__": diff --git a/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh new file mode 100755 index 000000000000..d6249303b7ea --- /dev/null +++ b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh @@ -0,0 +1,1027 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# author: Andrea Mayer + +# This test evaluates the SRv6 encap "lookup" attribute. After encapsulation +# the router looks up the route for the first SID, that is the outer IPv6 +# destination of the encapsulated packet. The attribute selects the FIB table +# used for this post-encap SID route lookup. +# +# Two routers (rt-1, rt-2) provide L3 VPN services over an IPv6 underlay +# (fd00::/64). Each router uses a separate VRF per tenant, with default +# blackhole routes (IPv4 and IPv6) to prevent traffic from leaking out of +# the VRF. Tenant traffic is encapsulated, then decapsulated with an +# End.DT46. Each router proxies both NDP and ARP. +# +# The routes that match the first SIDs are installed in a dedicated underlay +# table (500) rather than the main table (254). The encap routes use +# "lookup 500" to select this table for the post-encap SID route lookup. +# +# Without the "lookup" attribute, the route for the first SID cannot be found: +# - on the input path (forwarded traffic), the lookup stays in the VRF +# and hits the blackhole; +# - on the output path (locally originated traffic), the lookup falls +# through to the main table, with no route to the first SID. +# +# +# Legend (specific per-tenant addresses are in the instantiation tables below): +# X = tenant id and VRF table id; two tenants: 100 and 200 +# ("tX" means tenant X, e.g. t100, t200) +# a, b = the two host ids of the tenant +# HA, HB = addresses of host a, host b +# RLO1, RLO2 = rlo-X router loopback address on rt-1, rt-2 (tenant gateway +# for the output path, dual-stack) +# vrf-X = per-tenant VRF on each router (table X) +# +# Constants (same for every tenant): +# underlay = table 500; post-encap SID route lookup (via "lookup 500") +# localsid = table 90; holds the decap SIDs (End.DT46) +# fd00::/64 = underlay link between rt-1 and rt-2 +# veth-tX = cafe::254/10.0.0.254 (tenant gateway on veth, both routers) +# +# +# +-------------------+ +-------------------+ +# | | | | +# | hs-tX-a netns | | hs-tX-b netns | +# | | | | +# | +-------------+ | | +-------------+ | +# | | veth0 | | | | veth0 | | +# | | HA | | | | HB | | +# | +-------------+ | | +-------------+ | +# | . | | . | +# +-------------------+ +-------------------+ +# . . +# . . +# +-----------------------------------+ +-----------------------------------+ +# | . | | . | +# | +---------------+ | | +---------------+ | +# | | veth-tX | +----------+ | | +----------+ | veth-tX | | +# | | ::254/.254 | | localsid | | | | localsid | | ::254/.254 | | +# | +-------+-------+ +----------+ | | +----------+ +-------+-------+ | +# | | +----------+ | | +----------+ | | +# | +----+----+ | underlay | | | | underlay | +----+----+ | +# | | vrf-X | +----------+ | | +----------+ | vrf-X | | +# | +----+----+ | | +----+----+ | +# | | | | | | +# | +-----+----+ +------------+ | | +------------+ +----+-----+ | +# | | rlo-X | | veth0 | | | | veth0 | | rlo-X | | +# | | RLO1 | | fd00::1/64 |..|...|..| fd00::2/64 | | RLO2 | | +# | +----------+ +------------+ | | +------------+ +----------+ | +# | rt-1 netns | | rt-2 netns | +# +-----------------------------------+ +-----------------------------------+ +# +# +# Per-tenant instantiation: +# +-----+------+-------------------+-------------------+ +# | X | a, b | HA | HB | +# +-----+------+-------------------+-------------------+ +# | 100 | 1, 2 | cafe::1, 10.0.0.1 | cafe::2, 10.0.0.2 | +# | 200 | 3, 4 | cafe::3, 10.0.0.3 | cafe::4, 10.0.0.4 | +# +-----+------+-------------------+-------------------+ +# +# Router loopback (rlo-X) addresses, per tenant: +# +-----+-----------------------+-----------------------+ +# | X | RLO1 (rt-1) | RLO2 (rt-2) | +# +-----+-----------------------+-----------------------+ +# | 100 | cafe::101, 10.0.0.101 | cafe::102, 10.0.0.102 | +# | 200 | cafe::201, 10.0.0.201 | cafe::202, 10.0.0.202 | +# +-----+-----------------------+-----------------------+ +# +# +# Network configuration +# ===================== +# +# rt-1: localsid table (table 90) +# +--------+--------------------+----------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+----------------------------------+ +# | 100 | fc00:2:1:100::0d46 | apply SRv6 End.DT46 vrftable 100 | +# | 200 | fc00:2:1:200::0d46 | apply SRv6 End.DT46 vrftable 200 | +# +--------+--------------------+----------------------------------+ +# +# rt-1: underlay table (table 500) - post-encap SID route lookup +# +--------+--------------------+-------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+-------------------------------+ +# | 100 | fc00:1:2:100::0d46 | forward via fd00::2 dev veth0 | +# | 200 | fc00:1:2:200::0d46 | forward via fd00::2 dev veth0 | +# +--------+--------------------+-------------------------------+ +# +# rt-1: VRF tables (per tenant: vrf-X = table X) +# +--------+------------+------------------------------------------+ +# | tenant | dst | encap action | +# +--------+------------+------------------------------------------+ +# | 100 | cafe::2 | encap segs fc00:1:2:100::0d46 lookup 500 | +# | | 10.0.0.2 | | +# | | cafe::102 | | +# | | 10.0.0.102 | | +# | 200 | cafe::4 | encap segs fc00:1:2:200::0d46 lookup 500 | +# | | 10.0.0.4 | | +# | | cafe::202 | | +# | | 10.0.0.202 | | +# +--------+------------+------------------------------------------+ +# +# +# rt-2: localsid table (table 90) +# +--------+--------------------+----------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+----------------------------------+ +# | 100 | fc00:1:2:100::0d46 | apply SRv6 End.DT46 vrftable 100 | +# | 200 | fc00:1:2:200::0d46 | apply SRv6 End.DT46 vrftable 200 | +# +--------+--------------------+----------------------------------+ +# +# rt-2: underlay table (table 500) - post-encap SID route lookup +# +--------+--------------------+-------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+-------------------------------+ +# | 100 | fc00:2:1:100::0d46 | forward via fd00::1 dev veth0 | +# | 200 | fc00:2:1:200::0d46 | forward via fd00::1 dev veth0 | +# +--------+--------------------+-------------------------------+ +# +# rt-2: VRF tables (per tenant: vrf-X = table X) +# +--------+------------+------------------------------------------+ +# | tenant | dst | encap action | +# +--------+------------+------------------------------------------+ +# | 100 | cafe::1 | encap segs fc00:2:1:100::0d46 lookup 500 | +# | | 10.0.0.1 | | +# | | cafe::101 | | +# | | 10.0.0.101 | | +# | 200 | cafe::3 | encap segs fc00:2:1:200::0d46 lookup 500 | +# | | 10.0.0.3 | | +# | | cafe::201 | | +# | | 10.0.0.201 | | +# +--------+------------+------------------------------------------+ +# Within a tenant, a single SID reaches the adjacent router (its loopback) +# and the remote host connected to it, in both IPv4 and IPv6. +# +# For both rt-1 and rt-2, each VRF also has the connected host prefix (cafe::/64 +# or 10.0.0.0/24) and a default blackhole (IPv4 and IPv6). +# +# +# Locally originated traffic (output path) +# ======================================== +# +# The configuration above covers forwarded traffic, where packets arrive from +# a host and are encapsulated by the router. To also test router-originated +# traffic, each router pings the other router's loopback address through +# the VPN. +# +# Example (tenant 100), rt-1 pings cafe::102 (rt-2's loopback): +# 1. rt-1 looks up cafe::102 in vrf-100 and encapsulates it (SID +# fc00:1:2:100::0d46), then "lookup 500" finds the route for the SID in the +# underlay table (next hop fd00::2) and forwards it; +# 2. rt-2 decapsulates it (localsid, End.DT46) and delivers it locally +# (cafe::102 is on the rlo-100 interface); +# 3. rt-2 replies with destination cafe::101 (rt-1's loopback). rt-2 looks up +# cafe::101 in vrf-100 and encapsulates it back to rt-1 (again via "lookup +# 500"). rt-1 decapsulates it and delivers it. + +# shellcheck source=lib.sh +source lib.sh + +readonly LOCALSID_TABLE_ID=90 +readonly UNDERLAY_TABLE_ID=500 +readonly IPv6_RT_NETWORK=fd00 +readonly IPv6_HS_NETWORK=cafe +readonly IPv4_HS_NETWORK=10.0.0 +readonly VPN_LOCATOR_SERVICE=fc00 +readonly DT46_FUNC=0d46 +readonly DUMMY_DEVNAME=dum0 +readonly IPv6_TESTS_ADDR=2001:db8::1 +readonly TESTS_TABLE_ID=54321 +PING_TIMEOUT_SEC=4 + +SETUP_ERR=1 + +ret=${ksft_skip} +nsuccess=0 +nfail=0 + +PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} + +log_test() +{ + local rc="$1" + local expected="$2" + local msg="$3" + + if [ "${rc}" -eq "${expected}" ]; then + nsuccess=$((nsuccess+1)) + printf "\n TEST: %-60s [ OK ]\n" "${msg}" + else + ret=1 + nfail=$((nfail+1)) + printf "\n TEST: %-60s [FAIL]\n" "${msg}" + if [ "${PAUSE_ON_FAIL}" = "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read -r a + [ "$a" = "q" ] && exit 1 + fi + fi +} + +print_log_test_results() +{ + printf "\nTests passed: %3d\n" "${nsuccess}" + printf "Tests failed: %3d\n" "${nfail}" + + # when a test fails, the value of 'ret' is set to 1 (error code). + # Conversely, when all tests are passed successfully, the 'ret' value + # is set to 0 (success code). + if [ "${ret}" -ne 1 ]; then + ret=0 + fi +} + +log_section() +{ + echo + echo "################################################################################" + echo "TEST SECTION: $*" + echo "################################################################################" +} + +get_rtname() +{ + local rtid="$1" + + echo "rt_${rtid}" +} + +get_rt_nsname() +{ + local rtid="$1" + local varname + + varname="$(get_rtname "${rtid}")" + echo "${!varname}" +} + +get_hsname() +{ + local tid="$1" + local hsid="$2" + + echo "hs_t${tid}_${hsid}" +} + +get_hs_nsname() +{ + local tid="$1" + local hsid="$2" + local varname + + varname="$(get_hsname "${tid}" "${hsid}")" + echo "${!varname}" +} + +cleanup() +{ + ip link del veth-rt-1 2>/dev/null || true + ip link del veth-rt-2 2>/dev/null || true + + cleanup_all_ns + + # check whether the setup phase was completed successfully or not. In + # case of an error during the setup phase of the testing environment, + # the selftest is considered as "skipped". + if [ "${SETUP_ERR}" -ne 0 ]; then + echo "SKIP: Setting up the testing environment failed" + exit "${ksft_skip}" + fi + + exit "${ret}" +} + +# Host id of the router loopback (rlo) for a (router, tenant) pair. +# E.g. rt-1/tenant 100 -> 101, rt-2/tenant 200 -> 202. +get_rlo_hostid() +{ + local rtid="$1" + local tid="$2" + + echo "$((tid + rtid))" +} + +build_vpn_sid() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + + echo "${VPN_LOCATOR_SERVICE}:${rtsrc}:${rtdst}:${tid}::${DT46_FUNC}" +} + +# Install a dual-stack (IPv6 and IPv4) encap route in a VRF on the given +# router. +# args: +# $1 - router id +# $2 - host part of the IPv6 destination +# $3 - host part of the IPv4 destination +# $4 - SRv6 SID used as the encap destination +# $5 - tenant id +# $6 - if "true", add the "lookup" attribute to the encap route +__set_encap_route() +{ + local rt="$1" + local dst6="$2" + local dst4="$3" + local sid="$4" + local tid="$5" + local use_lookup="$6" + local lookup='' + local rtname + + rtname="$(get_rt_nsname "${rt}")" + + if [ "${use_lookup}" = "true" ]; then + lookup="lookup ${UNDERLAY_TABLE_ID}" + fi + + # shellcheck disable=SC2086 + ip -netns "${rtname}" -6 route replace \ + "${IPv6_HS_NETWORK}::${dst6}/128" vrf "vrf-${tid}" \ + encap seg6 mode encap segs "${sid}" ${lookup} dev veth0 + + # shellcheck disable=SC2086 + ip -netns "${rtname}" -4 route replace \ + "${IPv4_HS_NETWORK}.${dst4}/32" vrf "vrf-${tid}" \ + encap seg6 mode encap segs "${sid}" ${lookup} dev veth0 +} + +# Install the dual-stack encap route for a tenant host on rt, with the +# "lookup" attribute so the first SID is looked up in the underlay table. +# args: +# $1 - router id where the encap route is installed +# $2 - host destination id (host part of cafe::/128 and 10.0.0./32) +# $3 - SRv6 SID used as the encap destination +# $4 - tenant id +set_host_encap_route() +{ + local rt="$1" + local hsdst="$2" + local sid="$3" + local tid="$4" + + __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" true +} + +set_host_encap_route_nolookup() +{ + local rt="$1" + local hsdst="$2" + local sid="$3" + local tid="$4" + + __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" false +} + +# Install the dual-stack encap route on rtsrc toward rtdst's rlo loopback +# (RLO1 or RLO2, see header), with the "lookup" attribute so the first +# SID is looked up in the underlay table. +# args: +# $1 - router id where the encap route is installed +# $2 - router id whose loopback address is the route destination +# $3 - SRv6 SID used as the encap destination +# $4 - tenant id +set_gw_encap_route() +{ + local rtsrc="$1" + local rtdst="$2" + local sid="$3" + local tid="$4" + local dst + + dst="$(get_rlo_hostid "${rtdst}" "${tid}")" + + __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" true +} + +set_gw_encap_route_nolookup() +{ + local rtsrc="$1" + local rtdst="$2" + local sid="$3" + local tid="$4" + local dst + + dst="$(get_rlo_hostid "${rtdst}" "${tid}")" + + __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" false +} + +# Setup the basic networking for a router +setup_rt_networking() +{ + local id="$1" + local nsname + + nsname="$(get_rt_nsname "${id}")" + + ip link set "veth-rt-${id}" netns "${nsname}" + ip -netns "${nsname}" link set "veth-rt-${id}" name veth0 + + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0 + + ip -netns "${nsname}" addr add "${IPv6_RT_NETWORK}::${id}/64" dev veth0 nodad + ip -netns "${nsname}" link set veth0 up + + ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=1 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=1 +} + +# Setup a host namespace and attach it to its gateway +setup_hs() +{ + local hid="$1" + local rid="$2" + local tid="$3" + local rtveth="veth-t${tid}" + local hsname + local rtname + + hsname="$(get_hs_nsname "${tid}" "${hid}")" + rtname="$(get_rt_nsname "${rid}")" + + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0 + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0 + + ip -netns "${hsname}" link add veth0 type veth peer name "${rtveth}" + ip -netns "${hsname}" link set "${rtveth}" netns "${rtname}" + + ip -netns "${hsname}" addr add \ + "${IPv6_HS_NETWORK}::${hid}/64" dev veth0 nodad + ip -netns "${hsname}" addr add \ + "${IPv4_HS_NETWORK}.${hid}/24" dev veth0 + + ip -netns "${hsname}" link set veth0 up +} + +# Setup the per-tenant VRF on a router (gateway, loopback, blackhole) +setup_rt() +{ + local rid="$1" + local tid="$2" + local rtveth="veth-t${tid}" + local rlo_dev="rlo-${tid}" + local rtname + local gw_addr_v6 + local gw_addr_v4 + + rtname="$(get_rt_nsname "${rid}")" + + gw_addr_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rid}" "${tid}")" + gw_addr_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rid}" "${tid}")" + + ip -netns "${rtname}" link add "vrf-${tid}" type vrf table "${tid}" + ip -netns "${rtname}" link set "vrf-${tid}" up + + ip -netns "${rtname}" link set "${rtveth}" master "vrf-${tid}" + + ip -netns "${rtname}" addr add \ + "${IPv6_HS_NETWORK}::254/64" dev "${rtveth}" nodad + ip -netns "${rtname}" addr add \ + "${IPv4_HS_NETWORK}.254/24" dev "${rtveth}" + + ip -netns "${rtname}" link set "${rtveth}" up + + ip netns exec "${rtname}" \ + sysctl -wq "net.ipv6.conf.${rtveth}.proxy_ndp=1" + ip netns exec "${rtname}" \ + sysctl -wq "net.ipv4.conf.${rtveth}.proxy_arp=1" + + ip netns exec "${rtname}" sh -c "echo 1 > /proc/sys/net/vrf/strict_mode" + + # router loopback interface for locally originated traffic + ip -netns "${rtname}" link add "${rlo_dev}" type dummy + ip -netns "${rtname}" link set "${rlo_dev}" master "vrf-${tid}" + + ip -netns "${rtname}" addr add "${gw_addr_v6}/128" \ + dev "${rlo_dev}" nodad + ip -netns "${rtname}" addr add "${gw_addr_v4}/32" \ + dev "${rlo_dev}" + + ip -netns "${rtname}" link set "${rlo_dev}" up + + # default blackhole routes in the VRF: any traffic that does not match + # a specific route is dropped. Without the "lookup" attribute on the + # encap route, the route for the first SID cannot be found from within + # the VRF. + ip -netns "${rtname}" -6 route add blackhole default metric 4278198272 \ + vrf "vrf-${tid}" + ip -netns "${rtname}" -4 route add blackhole default metric 4278198272 \ + vrf "vrf-${tid}" +} + +# Configure a one-way VPN path towards hsdst (on rtdst) for tenant tid. +# The encap side is set up on rtsrc and the decap side on rtdst. +# args: +# $1 - router id where the encap side is set up +# $2 - host id of the destination host +# $3 - router id of the destination router (connected to the destination host) +# $4 - tenant id +setup_vpn_config() +{ + local rtsrc="$1" + local hsdst="$2" + local rtdst="$3" + local tid="$4" + local rtveth="veth-t${tid}" + local rtsrc_name + local rtdst_name + local vpn_sid + + rtsrc_name="$(get_rt_nsname "${rtsrc}")" + rtdst_name="$(get_rt_nsname "${rtdst}")" + vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + ip -netns "${rtsrc_name}" -6 neigh add proxy \ + "${IPv6_HS_NETWORK}::${hsdst}" dev "${rtveth}" + set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" + + ip -netns "${rtsrc_name}" -6 route add "${vpn_sid}/128" \ + table "${UNDERLAY_TABLE_ID}" \ + via "fd00::${rtdst}" dev veth0 + + # set the decap route for decapsulating packets arriving from rtsrc + # and destined to hsdst + ip -netns "${rtdst_name}" -6 route add "${vpn_sid}/128" \ + table "${LOCALSID_TABLE_ID}" \ + encap seg6local action End.DT46 \ + vrftable "${tid}" dev "vrf-${tid}" + + # all SIDs for VPNs start with a common locator which is fc00::/16. + # Routes for handling the SRv6 End.DT* behavior instances are grouped + # together in the 'localsid' table. + # + # NOTE: added only once + if ! ip -netns "${rtdst_name}" -6 rule show | \ + grep -q "to ${VPN_LOCATOR_SERVICE}::/16 lookup ${LOCALSID_TABLE_ID}"; then + ip -netns "${rtdst_name}" -6 rule add \ + to "${VPN_LOCATOR_SERVICE}::/16" \ + lookup "${LOCALSID_TABLE_ID}" prio 999 + fi +} + +# Configure rtsrc to reach rtdst's loopback address through the VPN. +# args: +# $1 - router id where the encap route is installed +# $2 - router id whose loopback is the destination +# $3 - tenant id +setup_vpn_gw_encap() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + local sid + + sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}" +} + +setup() +{ + ip link add veth-rt-1 type veth peer name veth-rt-2 + setup_ns rt_1 rt_2 + setup_rt_networking 1 + setup_rt_networking 2 + + # setup two hosts for the tenant 100. + # - host hs-t100-1 is directly connected to the router rt-1; + # - host hs-t100-2 is directly connected to the router rt-2. + setup_ns hs_t100_1 hs_t100_2 + setup_hs 1 1 100 + setup_hs 2 2 100 + + # setup two hosts for the tenant 200. + # - host hs-t200-3 is directly connected to the router rt-1; + # - host hs-t200-4 is directly connected to the router rt-2. + setup_ns hs_t200_3 hs_t200_4 + setup_hs 3 1 200 + setup_hs 4 2 200 + + # configure each router for each tenant: VRF, blackhole routes, + # router loopback interface + setup_rt 1 100 + setup_rt 2 100 + setup_rt 1 200 + setup_rt 2 200 + + # setup the L3 VPN which connects the host hs-t100-1 and host hs-t100-2 + # within the same tenant 100. + setup_vpn_config 1 2 2 100 + setup_vpn_config 2 1 1 100 + + # setup the L3 VPN which connects the host hs-t200-3 and host hs-t200-4 + # within the same tenant 200. + setup_vpn_config 1 4 2 200 + setup_vpn_config 2 3 1 200 + + # allow each router to reach the other's loopback through the VPN + setup_vpn_gw_encap 2 1 100 + setup_vpn_gw_encap 1 2 100 + setup_vpn_gw_encap 2 1 200 + setup_vpn_gw_encap 1 2 200 + + # testing environment was set up successfully + SETUP_ERR=0 +} + +check_rt_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local nsname + + nsname="$(get_rt_nsname "${rtsrc}")" + + ip netns exec "${nsname}" ping -c 1 -W 1 "${IPv6_RT_NETWORK}::${rtdst}" \ + >/dev/null 2>&1 +} + +check_and_log_rt_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + + check_rt_connectivity "${rtsrc}" "${rtdst}" + log_test $? 0 "Routers connectivity: rt-${rtsrc} -> rt-${rtdst}" +} + +check_hs_ipv6_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + local nsname + + nsname="$(get_hs_nsname "${tid}" "${hssrc}")" + + ip netns exec "${nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + "${IPv6_HS_NETWORK}::${hsdst}" >/dev/null 2>&1 +} + +check_hs_ipv4_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + local nsname + + nsname="$(get_hs_nsname "${tid}" "${hssrc}")" + + ip netns exec "${nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + "${IPv4_HS_NETWORK}.${hsdst}" >/dev/null 2>&1 +} + +check_and_log_hs_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})" +} + +check_and_log_hs_isolation() +{ + local hssrc="$1" + local tidsrc="$2" + local hsdst="$3" + local tiddst="$4" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tidsrc}" + log_test $? 1 "IPv6 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tidsrc}" + log_test $? 1 "IPv4 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}" +} + +check_and_log_hs2gw_connectivity() +{ + local hssrc="$1" + local tid="$2" + + check_hs_ipv6_connectivity "${hssrc}" 254 "${tid}" + log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" 254 "${tid}" + log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})" +} + +router_tests() +{ + log_section "IPv6 routers connectivity test" + + check_and_log_rt_connectivity 1 2 + check_and_log_rt_connectivity 2 1 +} + +host2gateway_tests() +{ + log_section "Connectivity test among hosts and gateway" + + check_and_log_hs2gw_connectivity 1 100 + check_and_log_hs2gw_connectivity 2 100 + + check_and_log_hs2gw_connectivity 3 200 + check_and_log_hs2gw_connectivity 4 200 +} + +host_vpn_tests() +{ + log_section "SRv6 VPN connectivity test among hosts in the same tenant" + + check_and_log_hs_connectivity 1 2 100 + check_and_log_hs_connectivity 2 1 100 + + check_and_log_hs_connectivity 3 4 200 + check_and_log_hs_connectivity 4 3 200 +} + +host_vpn_isolation_tests() +{ + local l1="1 2" + local l2="3 4" + local t1=100 + local t2=200 + local i + local j + local tmp + + log_section "SRv6 VPN isolation test among hosts in different tenants" + + for _ in 0 1; do + for i in ${l1}; do + for j in ${l2}; do + check_and_log_hs_isolation "${i}" "${t1}" "${j}" "${t2}" + done + done + + # let us test the reverse path + tmp="${l1}"; l1="${l2}"; l2="${tmp}" + tmp=${t1}; t1=${t2}; t2=${tmp} + done +} + +__test_nolookup() +{ + local hssrc="$1" + local hsdst="$2" + local rtsrc="$3" + local rtdst="$4" + local tid="$5" + local vpn_sid + + vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + # replace encap route(s) without "lookup" attribute + set_host_encap_route_nolookup "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 1 "IPv6 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 1 "IPv4 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})" + + # restore encap route(s) with "lookup" for subsequent tests + set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" +} + +host_vpn_nolookup_tests() +{ + log_section "SRv6 VPN connectivity test among hosts w/o lookup" + + __test_nolookup 1 2 1 2 100 + __test_nolookup 2 1 2 1 100 + + __test_nolookup 3 4 1 2 200 + __test_nolookup 4 3 2 1 200 +} + +check_gw_ipv6_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + local rtname + local src_v6 + local dst_v6 + + rtname="$(get_rt_nsname "${rtsrc}")" + src_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtsrc}" "${tidsrc}")" + dst_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtdst}" "${tiddst}")" + + ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \ + ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + -I "${src_v6}" "${dst_v6}" >/dev/null 2>&1 +} + +check_gw_ipv4_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + local rtname + local src_v4 + local dst_v4 + + rtname="$(get_rt_nsname "${rtsrc}")" + src_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtsrc}" "${tidsrc}")" + dst_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtdst}" "${tiddst}")" + + ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \ + ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + -I "${src_v4}" "${dst_v4}" >/dev/null 2>&1 +} + +check_and_log_gw_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 0 "IPv6 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 0 "IPv4 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})" +} + +check_and_log_gw_isolation() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}" + log_test $? 1 "IPv6 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}" + log_test $? 1 "IPv4 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})" +} + +gw_vpn_isolation_tests() +{ + log_section "SRv6 VPN isolation test among routers in different tenants" + + check_and_log_gw_isolation 1 2 100 200 + check_and_log_gw_isolation 2 1 100 200 + + check_and_log_gw_isolation 1 2 200 100 + check_and_log_gw_isolation 2 1 200 100 +} + +gw_vpn_tests() +{ + log_section "SRv6 VPN connectivity test among routers in the same tenant" + + check_and_log_gw_connectivity 1 2 100 + check_and_log_gw_connectivity 2 1 100 + + check_and_log_gw_connectivity 1 2 200 + check_and_log_gw_connectivity 2 1 200 +} + +__test_gw_nolookup() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + local sid + + sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + # replace gw encap route without "lookup" attribute + set_gw_encap_route_nolookup "${rtsrc}" "${rtdst}" "${sid}" "${tid}" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 1 "IPv6 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 1 "IPv4 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})" + + # restore gw encap route with "lookup" for subsequent tests + set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}" +} + +gw_vpn_nolookup_tests() +{ + log_section "SRv6 VPN connectivity test among routers w/o lookup" + + __test_gw_nolookup 1 2 100 + __test_gw_nolookup 2 1 100 + + __test_gw_nolookup 1 2 200 + __test_gw_nolookup 2 1 200 +} + +test_command_or_ksft_skip() +{ + local cmd="$1" + + if [ ! -x "$(command -v "${cmd}")" ]; then + echo "SKIP: Could not run test without \"${cmd}\" tool" + exit "${ksft_skip}" + fi +} + +test_vrf_or_ksft_skip() +{ + modprobe vrf &>/dev/null || true + if [ ! -e /proc/sys/net/vrf/strict_mode ]; then + echo "SKIP: vrf sysctl does not exist" + exit "${ksft_skip}" + fi +} + +test_dummy_dev_or_ksft_skip() +{ + local test_netns + + setup_ns test_netns + + modprobe dummy &>/dev/null || true + if ! ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" \ + type dummy; then + cleanup_ns "${test_netns}" + echo "SKIP: dummy dev not supported" + exit "${ksft_skip}" + fi + + cleanup_ns "${test_netns}" +} + +test_encap_lookup_supp_or_ksft_skip() +{ + local nsname + + setup_ns nsname + + ip -netns "${nsname}" link add "${DUMMY_DEVNAME}" type dummy + ip -netns "${nsname}" link set "${DUMMY_DEVNAME}" up + + if ! ip -netns "${nsname}" -6 route add "${IPv6_TESTS_ADDR}/128" \ + encap seg6 mode encap segs fc00::1 \ + lookup "${TESTS_TABLE_ID}" \ + dev "${DUMMY_DEVNAME}" 2>/dev/null; then + cleanup_ns "${nsname}" + echo "SKIP: seg6 encap lookup attribute not supported" + exit "${ksft_skip}" + fi + + # An old kernel with a recent iproute2 accepts the route but + # silently ignores the lookup attribute. Dump the route and check + # the attribute is really there, otherwise the test falsely passes. + if ! ip -netns "${nsname}" -6 route show "${IPv6_TESTS_ADDR}/128" | \ + grep -q "lookup ${TESTS_TABLE_ID}"; then + cleanup_ns "${nsname}" + echo "SKIP: seg6 encap lookup attribute not supported" + exit "${ksft_skip}" + fi + + cleanup_ns "${nsname}" +} + +if [ "$(id -u)" -ne 0 ]; then + echo "SKIP: Need root privileges" + exit "${ksft_skip}" +fi + +# required programs to carry out this selftest +test_command_or_ksft_skip ip +test_command_or_ksft_skip ping +test_command_or_ksft_skip sysctl +test_command_or_ksft_skip grep + +test_dummy_dev_or_ksft_skip +test_vrf_or_ksft_skip +test_encap_lookup_supp_or_ksft_skip + +set -e +trap cleanup EXIT + +setup +set +e + +router_tests +host2gateway_tests +host_vpn_tests +host_vpn_isolation_tests +host_vpn_nolookup_tests +gw_vpn_tests +gw_vpn_isolation_tests +gw_vpn_nolookup_tests + +print_log_test_results diff --git a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh index a5e959a080bb..50e37d3217ea 100755 --- a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh +++ b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh @@ -536,6 +536,14 @@ host_vpn_isolation_tests() done } +test_iproute2_supp_or_ksft_skip() +{ + if ! ip route add help 2>&1 | grep -qo "End.DT46"; then + echo "SKIP: Missing SRv6 End.DT46 support in iproute2" + exit "${ksft_skip}" + fi +} + if [ "$(id -u)" -ne 0 ];then echo "SKIP: Need root privileges" exit $ksft_skip @@ -546,6 +554,8 @@ if [ ! -x "$(command -v ip)" ]; then exit $ksft_skip fi +test_iproute2_supp_or_ksft_skip + modprobe vrf &>/dev/null if [ ! -e /proc/sys/net/vrf/strict_mode ]; then echo "SKIP: vrf sysctl does not exist" diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c index 2544ae35d07a..487ae659a1f1 100644 --- a/tools/testing/selftests/net/tcp_mmap.c +++ b/tools/testing/selftests/net/tcp_mmap.c @@ -141,12 +141,12 @@ static void *mmap_large_buffer(size_t need, size_t *allocated) buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); - if (buffer == (void *)-1) { + if (buffer == MAP_FAILED) { sz = need; buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); - if (buffer != (void *)-1) + if (buffer != MAP_FAILED) fprintf(stderr, "MAP_HUGETLB attempt failed, look at /sys/kernel/mm/hugepages for optimal performance\n"); } *allocated = sz; @@ -189,13 +189,13 @@ void *child_thread(void *arg) fcntl(fd, F_SETFL, O_NDELAY); buffer = mmap_large_buffer(chunk_size, &buffer_sz); - if (buffer == (void *)-1) { + if (buffer == MAP_FAILED) { perror("mmap"); goto error; } if (zflg) { raddr = mmap(NULL, chunk_size + map_align, PROT_READ, flags, fd, 0); - if (raddr == (void *)-1) { + if (raddr == MAP_FAILED) { perror("mmap"); zflg = 0; } else { @@ -547,7 +547,7 @@ int main(int argc, char *argv[]) } buffer = mmap_large_buffer(chunk_size, &buffer_sz); - if (buffer == (unsigned char *)-1) { + if (buffer == MAP_FAILED) { perror("mmap"); exit(1); } diff --git a/tools/testing/selftests/net/test_vxlan_nh.sh b/tools/testing/selftests/net/test_vxlan_nh.sh index 20f3369f776b..5ce6f27f6cf4 100755 --- a/tools/testing/selftests/net/test_vxlan_nh.sh +++ b/tools/testing/selftests/net/test_vxlan_nh.sh @@ -56,6 +56,17 @@ tc_stats_get() tc_rule_handle_stats_get "dev dummy1 egress" 101 ".packets" "-n $ns1" } +nh_stats_get_port() +{ + ip -n "$ns1" -s -j nexthop show id 20 | \ + jq ".[][\"group_stats\"][][\"packets\"]" +} + +tc_stats_get_port() +{ + tc_rule_handle_stats_get "dev dummy1 egress" 102 ".packets" "-n $ns1" +} + basic_tx_common() { local af_str=$1; shift @@ -90,6 +101,31 @@ basic_tx_common() busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" tc_stats_get > /dev/null check_err $? "tc filter stats did not increase" + # Add a second FDB nexthop group whose nexthop carries a per-nexthop + # destination port (NHA_DST_PORT) that differs from the VXLAN device + # default. Matching outer traffic must egress with that port, so a + # separate flower filter keyed on the new port catches it. + run_cmd "tc -n $ns1 filter add dev dummy1 egress proto $proto \ + pref 1 handle 102 flower ip_proto udp dst_ip $remote_addr \ + dst_port 4790 action pass" + + run_cmd "ip -n $ns1 nexthop add id 2 via $remote_addr fdb dst_port 4790" + run_cmd "ip -n $ns1 nexthop add id 20 group 2 fdb" + + run_cmd "bridge -n $ns1 fdb add 00:11:22:33:44:66 dev vx0 \ + self static nhid 20" + + run_cmd "ip netns exec $ns1 mausezahn vx0 -a own \ + -b 00:11:22:33:44:66 -c 1 -q" + + busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" \ + nh_stats_get_port > /dev/null + check_err $? "FDB nexthop group stats did not increase (with port)" + + busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" \ + tc_stats_get_port > /dev/null + check_err $? "tc filter stats did not increase (with port)" + log_test "VXLAN FDB nexthop: $af_str basic Tx" } @@ -210,8 +246,8 @@ require_command arping require_command ndisc6 require_command jq -if ! ip nexthop help 2>&1 | grep -q "stats"; then - echo "SKIP: iproute2 ip too old, missing nexthop stats support" +if ! ip nexthop help 2>&1 | grep -q "dst_port"; then + echo "SKIP: iproute2 ip too old, missing nexthop dst_port support" exit "$ksft_skip" fi diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json index a1f97a4b606e..0cf12c50fb74 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json +++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json @@ -1540,5 +1540,169 @@ "$TC qdisc del dev $DUMMY root", "$IP addr del 10.10.10.10/24 dev $DUMMY || true" ] + }, + { + "id": "fb6c", + "name": "Force multiq to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 2: protocol all prio 1 matchall action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH parent 1:", + "matchJSON": [ + { + "kind": "multiq", + "handle": "2:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "1922", + "name": "Force multiq to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 2: protocol ip prio 1 u32 match ip dst 10.10.11.1 action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "476f", + "name": "Force taprio to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc add dev $ETH parent 1:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH", + "matchJSON": [ + { + "kind": "taprio", + "handle": "1:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "0235", + "name": "Force taprio to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "taprio", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc replace dev $ETH parent 1:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] } ]