Documentation/admin-guide/media/amdisp4-1.rst | 63 ++ Documentation/admin-guide/media/amdisp4.dot | 6 + Documentation/admin-guide/media/v4l-drivers.rst | 1 + Documentation/crypto/krb5.rst | 17 +- MAINTAINERS | 25 + Makefile | 30 + arch/arm/Kconfig | 4 +- arch/arm/boot/dts/broadcom/bcm2835-common.dtsi | 7 +- arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi | 16 +- arch/arm64/Kconfig | 2 +- .../boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi | 55 +- arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts | 24 + arch/arm64/boot/dts/broadcom/bcm2712.dtsi | 29 +- arch/arm64/boot/dts/broadcom/rp1-common.dtsi | 105 ++ arch/arm64/boot/dts/freescale/imx93.dtsi | 24 + arch/arm64/kernel/setup.c | 27 + arch/s390/include/asm/ipl.h | 1 + arch/s390/kernel/ipl.c | 5 + arch/s390/kernel/setup.c | 4 + arch/x86/kernel/setup.c | 22 +- crypto/krb5/krb5_api.c | 54 +- crypto/sig.c | 3 +- crypto/testmgr.c | 2 +- drivers/acpi/apei/hest.c | 8 + drivers/acpi/irq.c | 17 +- drivers/acpi/scan.c | 9 + drivers/ata/libahci.c | 18 + drivers/char/ipmi/ipmi_dmi.c | 15 + drivers/char/ipmi/ipmi_msghandler.c | 16 +- drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/efi.c | 124 ++- drivers/firmware/efi/libstub/fdt.c | 5 + drivers/firmware/efi/libstub/secureboot.c | 14 +- drivers/firmware/efi/secureboot.c | 38 + drivers/gpu/drm/i915/display/intel_dp_tunnel.c | 11 +- drivers/hid/hid-rmi.c | 66 -- drivers/hwtracing/coresight/coresight-etm4x-core.c | 19 + drivers/input/rmi4/rmi_driver.c | 124 ++- drivers/iommu/iommu.c | 22 + drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/amd/Kconfig | 3 + drivers/media/platform/amd/Makefile | 3 + drivers/media/platform/amd/isp4/Kconfig | 15 + drivers/media/platform/amd/isp4/Makefile | 10 + drivers/media/platform/amd/isp4/isp4.c | 240 +++++ drivers/media/platform/amd/isp4/isp4.h | 20 + drivers/media/platform/amd/isp4/isp4_debug.c | 271 +++++ drivers/media/platform/amd/isp4/isp4_debug.h | 41 + drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h | 318 ++++++ drivers/media/platform/amd/isp4/isp4_hw_reg.h | 124 +++ drivers/media/platform/amd/isp4/isp4_interface.c | 832 ++++++++++++++++ drivers/media/platform/amd/isp4/isp4_interface.h | 144 +++ drivers/media/platform/amd/isp4/isp4_subdev.c | 1047 ++++++++++++++++++++ drivers/media/platform/amd/isp4/isp4_subdev.h | 127 +++ drivers/media/platform/amd/isp4/isp4_video.c | 797 +++++++++++++++ drivers/media/platform/amd/isp4/isp4_video.h | 57 ++ drivers/mfd/bcm2835-pm.c | 9 +- drivers/net/ethernet/cadence/macb.h | 35 + drivers/net/ethernet/cadence/macb_main.c | 736 ++++++++------ drivers/nfc/nxp-nci/i2c.c | 21 +- drivers/pci/quirks.c | 24 + drivers/usb/core/hub.c | 7 + include/crypto/krb5.h | 9 +- include/linux/efi.h | 22 +- include/linux/lsm_hook_defs.h | 1 + include/linux/mfd/bcm2835-pm.h | 7 + include/linux/rmi.h | 1 + include/linux/security.h | 9 + include/trace/events/rxrpc.h | 1 + kernel/module/signing.c | 9 +- net/core/gro.c | 3 + net/ipv4/esp4.c | 5 +- net/ipv6/esp6.c | 5 +- net/rxrpc/ar-internal.h | 14 +- net/rxrpc/call_event.c | 22 +- net/rxrpc/call_object.c | 2 + net/rxrpc/conn_event.c | 32 +- net/rxrpc/insecure.c | 8 +- net/rxrpc/recvmsg.c | 68 +- net/rxrpc/rxgk.c | 160 ++- net/rxrpc/rxgk_app.c | 46 +- net/rxrpc/rxgk_common.h | 66 +- net/rxrpc/rxkad.c | 115 +-- net/sched/act_pedit.c | 72 +- scripts/Makefile.lib | 3 + scripts/tags.sh | 2 + security/integrity/platform_certs/load_uefi.c | 6 +- security/lockdown/Kconfig | 13 + security/lockdown/lockdown.c | 11 + sound/soc/amd/acp/acp-sdw-legacy-mach.c | 18 +- sound/soc/codecs/rt722-sdca.c | 77 +- sound/soc/codecs/rt722-sdca.h | 4 + tools/testing/selftests/bpf/Makefile | 2 +- tools/testing/selftests/bpf/prog_tests/ksyms_btf.c | 31 - 95 files changed, 5872 insertions(+), 888 deletions(-) diff --git a/Documentation/admin-guide/media/amdisp4-1.rst b/Documentation/admin-guide/media/amdisp4-1.rst new file mode 100644 index 000000000000..878141154f96 --- /dev/null +++ b/Documentation/admin-guide/media/amdisp4-1.rst @@ -0,0 +1,63 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +==================================== +AMD Image Signal Processor (amdisp4) +==================================== + +Introduction +============ + +This file documents the driver for the AMD ISP4 that is part of +AMD Ryzen AI Max 300 Series. + +The driver is located under drivers/media/platform/amd/isp4 and uses +the Media-Controller API. + +The driver exposes one video capture device to userspace and provide +web camera like interface. Internally the video device is connected +to the isp4 sub-device responsible for communication with the CCPU FW. + +Topology +======== + +.. _amdisp4_topology_graph: + +.. kernel-figure:: amdisp4.dot + :alt: Diagram of the media pipeline topology + :align: center + + + +The driver has 1 sub-device: Representing isp4 image signal processor. +The driver has 1 video device: Capture device for retrieving images. + +- ISP4 Image Signal Processing Subdevice Node + +--------------------------------------------- + +The isp4 is represented as a single V4L2 subdev, the sub-device does not +provide interface to the user space. The sub-device is connected to one video node +(isp4_capture) with immutable active link. The sub-device represents ISP with +connected sensor similar to smart cameras (sensors with integrated ISP). +sub-device has only one link to the video device for capturing the frames. +The sub-device communicates with CCPU FW for streaming configuration and +buffer management. + + +- isp4_capture - Frames Capture Video Node + +------------------------------------------ + +Isp4_capture is a capture device to capture frames to memory. +The entity is connected to isp4 sub-device. The video device +provides web camera like interface to userspace. It supports +mmap and dma buf types of memory. + +Capturing Video Frames Example +============================== + +.. code-block:: bash + + v4l2-ctl "-d" "/dev/video0" "--set-fmt-video=width=1920,height=1080,pixelformat=NV12" "--stream-mmap" "--stream-count=10" diff --git a/Documentation/admin-guide/media/amdisp4.dot b/Documentation/admin-guide/media/amdisp4.dot new file mode 100644 index 000000000000..978f30c1a31a --- /dev/null +++ b/Documentation/admin-guide/media/amdisp4.dot @@ -0,0 +1,6 @@ +digraph board { + rankdir=TB + n00000001 [label="{{} | amd isp4\n | { 0}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port0 -> n00000003 [style=bold] + n00000003 [label="Preview\n/dev/video0", shape=box, style=filled, fillcolor=yellow] +} diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index 393f83e8dc4d..0fb88449fffd 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -9,6 +9,7 @@ Video4Linux (V4L) driver-specific documentation .. toctree:: :maxdepth: 2 + amdisp4-1 bttv c3-isp cafe_ccic diff --git a/Documentation/crypto/krb5.rst b/Documentation/crypto/krb5.rst index beffa0133446..f62e07ac6811 100644 --- a/Documentation/crypto/krb5.rst +++ b/Documentation/crypto/krb5.rst @@ -158,13 +158,22 @@ returned. When a message has been received, the location and size of the data with the message can be determined by calling:: - void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, - enum krb5_crypto_mode mode, - size_t *_offset, size_t *_len); + int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t *_offset, size_t *_len); The caller provides the offset and length of the message to the function, which then alters those values to indicate the region containing the data (plus any -padding). It is up to the caller to determine how much padding there is. +padding). It is up to the caller to determine how much padding there is. The +function returns an error if the length is too small or if the mode is +unsupported. An additional function:: + + int crypto_krb5_check_data_len(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t len, size_t min_content); + +is provided to just do a basic check that the decrypted/verified message would +have a sufficient minimum payload. Preparation Functions --------------------- diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f..985c66951475 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1160,6 +1160,31 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git F: drivers/iommu/amd/ F: include/linux/amd-iommu.h +AMD ISP4 DRIVER +M: Bin Du +M: Nirujogi Pratap +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/admin-guide/media/amdisp4-1.rst +F: Documentation/admin-guide/media/amdisp4.dot +F: drivers/media/platform/amd/Kconfig +F: drivers/media/platform/amd/Makefile +F: drivers/media/platform/amd/isp4/Kconfig +F: drivers/media/platform/amd/isp4/Makefile +F: drivers/media/platform/amd/isp4/isp4.c +F: drivers/media/platform/amd/isp4/isp4.h +F: drivers/media/platform/amd/isp4/isp4_debug.c +F: drivers/media/platform/amd/isp4/isp4_debug.h +F: drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h +F: drivers/media/platform/amd/isp4/isp4_hw_reg.h +F: drivers/media/platform/amd/isp4/isp4_interface.c +F: drivers/media/platform/amd/isp4/isp4_interface.h +F: drivers/media/platform/amd/isp4/isp4_subdev.c +F: drivers/media/platform/amd/isp4/isp4_subdev.h +F: drivers/media/platform/amd/isp4/isp4_video.c +F: drivers/media/platform/amd/isp4/isp4_video.h + AMD KFD M: Felix Kuehling L: amd-gfx@lists.freedesktop.org diff --git a/Makefile b/Makefile index a95f0b3d26bf..99b2c764c4c9 100644 --- a/Makefile +++ b/Makefile @@ -356,6 +356,17 @@ ifneq ($(filter install,$(MAKECMDGOALS)),) endif endif +# CKI/cross compilation hack +# Do we need to rebuild scripts after cross compilation? +# If kernel was cross-compiled, these scripts have arch of build host. +REBUILD_SCRIPTS_FOR_CROSS:=0 + +# Regenerating config with incomplete source tree will produce different +# config options. Disable it. +ifeq ($(REBUILD_SCRIPTS_FOR_CROSS),1) +may-sync-config:= +endif + ifdef mixed-build # =========================================================================== # We're called with mixed targets (*config and build targets). @@ -1366,6 +1377,8 @@ uapi-asm-generic: # Generate some files # --------------------------------------------------------------------------- +include $(srctree)/Makefile.rhelver + # KERNELRELEASE can change from a few different places, meaning version.h # needs to be updated, so this check is forced on all builds @@ -2031,6 +2044,23 @@ endif ifdef CONFIG_MODULES +scripts_build: + $(MAKE) $(build)=scripts/basic + $(MAKE) $(build)=scripts/mod + $(MAKE) $(build)=scripts scripts/module.lds + $(MAKE) $(build)=scripts scripts/unifdef + $(MAKE) $(build)=scripts + +prepare_after_cross: + # disable STACK_VALIDATION to avoid building objtool + sed -i '/^CONFIG_STACK_VALIDATION/d' ./include/config/auto.conf || true + # build minimum set of scripts and resolve_btfids to allow building + # external modules + $(MAKE) KBUILD_EXTMOD="" M="" scripts_build V=1 + $(MAKE) -C tools/bpf/resolve_btfids + +PHONY += prepare_after_cross scripts_build + modules.order: $(build-dir) @: diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index ec33376f8e2b..255679ba5351 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1213,9 +1213,9 @@ config HIGHMEM If unsure, say n. config HIGHPTE - bool "Allocate 2nd-level pagetables from highmem" if EXPERT + bool "Allocate 2nd-level pagetables from highmem" depends on HIGHMEM && !PREEMPT_RT - default y + default n help The VM uses one page of physical memory for each page table. For systems with a lot of processes, this can use a lot of diff --git a/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi index 9261b67dbee1..1e76b290510d 100644 --- a/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi @@ -141,9 +141,10 @@ v3d: v3d@7ec00000 { interrupts = <1 10>; }; - vc4: gpu { - compatible = "brcm,bcm2835-vc4"; - }; + }; + + vc4: gpu { + compatible = "brcm,bcm2835-vc4"; }; }; diff --git a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi index e9bf41b9f5c1..46c91468f4c5 100644 --- a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi @@ -1,8 +1,8 @@ #include / { - soc { - firmware: firmware { + firmware { + firmware: rpi-firmware { compatible = "raspberrypi,bcm2835-firmware", "simple-mfd"; mboxes = <&mailbox>; @@ -10,14 +10,16 @@ firmware_clocks: clocks { compatible = "raspberrypi,firmware-clocks"; #clock-cells = <1>; }; - }; - power: power { - compatible = "raspberrypi,bcm2835-power"; - firmware = <&firmware>; - #power-domain-cells = <1>; + power: power { + compatible = "raspberrypi,bcm2835-power"; + firmware = <&firmware>; + #power-domain-cells = <1>; + }; }; + }; + soc { vchiq: mailbox@7e00b840 { compatible = "brcm,bcm2835-vchiq"; reg = <0x7e00b840 0x3c>; diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 9ea19b74b6c3..6d2800a2a061 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1454,7 +1454,7 @@ endchoice config ARM64_FORCE_52BIT bool "Force 52-bit virtual addresses for userspace" - depends on ARM64_VA_BITS_52 && EXPERT + depends on ARM64_VA_BITS_52 help For systems with 52-bit userspace VAs enabled, the kernel will attempt to maintain compatibility with older software by providing 48-bit VAs diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi index 04738bf281eb..b7a6bc34ae1a 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi @@ -44,6 +44,30 @@ power_button: power-button { }; }; + firmware { + firmware: rpi-firmware { + compatible = "raspberrypi,bcm2835-firmware", "simple-mfd"; + + mboxes = <&mailbox>; + + firmware_clocks: clocks { + compatible = "raspberrypi,firmware-clocks"; + #clock-cells = <1>; + }; + + reset: reset { + compatible = "raspberrypi,firmware-reset"; + #reset-cells = <1>; + }; + + power: power { + compatible = "raspberrypi,bcm2835-power"; + firmware = <&firmware>; + #power-domain-cells = <1>; + }; + }; + }; + sd_io_1v8_reg: sd-io-1v8-reg { compatible = "regulator-gpio"; regulator-name = "vdd-sd-io"; @@ -189,33 +213,6 @@ wifi: wifi@1 { }; }; -&soc { - firmware: firmware { - compatible = "raspberrypi,bcm2835-firmware", "simple-mfd"; - #address-cells = <1>; - #size-cells = <1>; - - mboxes = <&mailbox>; - dma-ranges; - - firmware_clocks: clocks { - compatible = "raspberrypi,firmware-clocks"; - #clock-cells = <1>; - }; - - reset: reset { - compatible = "raspberrypi,firmware-reset"; - #reset-cells = <1>; - }; - }; - - power: power { - compatible = "raspberrypi,bcm2835-power"; - firmware = <&firmware>; - #power-domain-cells = <1>; - }; -}; - /* uarta communicates with the BT module */ &uarta { uart-has-rtscts; @@ -252,3 +249,7 @@ &pcie1 { &pcie2 { status = "okay"; }; + +&v3d { + clocks = <&firmware_clocks 5>; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts index 285608281446..0fc57e72632e 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -68,6 +68,30 @@ usb_vbus_default_state: usb-vbus-default-state { function = "vbus1"; groups = "vbus1"; }; + + rp1_i2c4_default_state: rp1-i2c4-default-state { + function = "i2c4"; + groups = "i2c4_2"; + drive-strength = <12>; + bias-pull-up; + }; + + rp1_i2c6_default_state: rp1-i2c6-default-state { + function = "i2c6"; + groups = "i2c6_0"; + drive-strength = <12>; + bias-pull-up; + }; +}; + +&rp1_i2c4 { + pinctrl-0 = <&rp1_i2c4_default_state>; + pinctrl-names = "default"; +}; + +&rp1_i2c6 { + pinctrl-0 = <&rp1_i2c6_default_state>; + pinctrl-names = "default"; }; &rp1_usb0 { diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi index d57a9b1bff70..761c59d90ffc 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi @@ -1,5 +1,6 @@ // SPDX-License-Identifier: (GPL-2.0 OR MIT) #include +#include / { compatible = "brcm,bcm2712"; @@ -508,10 +509,6 @@ axi: axi { <0x18 0x00000000 0x18 0x00000000 0x04 0x00000000>, <0x1c 0x00000000 0x1c 0x00000000 0x04 0x00000000>; - vc4: gpu { - compatible = "brcm,bcm2712-vc6"; - }; - pcie0: pcie@1000100000 { compatible = "brcm,bcm2712-pcie"; reg = <0x10 0x00100000 0x00 0x9310>; @@ -646,6 +643,30 @@ mip1: msi-controller@1000131000 { msi-ranges = <&gicv2 GIC_SPI 247 IRQ_TYPE_EDGE_RISING 8>; brcm,msi-offset = <8>; }; + + isp: isp@1000880000 { + compatible = "brcm,bcm2712-pispbe", "raspberrypi,pispbe"; + reg = <0x10 0x00880000 0x0 0x4000>; + interrupts = ; + clocks = <&firmware_clocks 7>; + }; + + v3d: gpu@1002000000 { + compatible = "brcm,2712-v3d"; + reg = <0x10 0x02000000 0x00 0x4000>, + <0x10 0x02008000 0x00 0x6000>, + <0x10 0x02030800 0x00 0x0700>; + reg-names = "hub", "core0", "sms"; + + power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>; + resets = <&pm BCM2835_RESET_V3D>; + interrupts = , + ; + }; + }; + + vc4: gpu { + compatible = "brcm,bcm2712-vc6"; }; timer { diff --git a/arch/arm64/boot/dts/broadcom/rp1-common.dtsi b/arch/arm64/boot/dts/broadcom/rp1-common.dtsi index 5a815c379794..16f535939583 100644 --- a/arch/arm64/boot/dts/broadcom/rp1-common.dtsi +++ b/arch/arm64/boot/dts/broadcom/rp1-common.dtsi @@ -26,6 +26,83 @@ rp1_clocks: clocks@40018000 { <200000000>; // RP1_CLK_SYS }; + rp1_i2c0: i2c@40070000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40070000 0x0 0x1000>; + interrupts = <7 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c1: i2c@40074000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40074000 0x0 0x1000>; + interrupts = <8 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c2: i2c@40078000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40078000 0x0 0x1000>; + interrupts = <9 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c3: i2c@4007c000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x4007c000 0x0 0x1000>; + interrupts = <10 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c4: i2c@40080000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40080000 0x0 0x1000>; + interrupts = <11 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c5: i2c@40084000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40084000 0x0 0x1000>; + interrupts = <12 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + + rp1_i2c6: i2c@40088000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x40088000 0x0 0x1000>; + interrupts = <13 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + + status = "disabled"; + }; + rp1_gpio: pinctrl@400d0000 { compatible = "raspberrypi,rp1-gpio"; reg = <0x00 0x400d0000 0x0 0xc000>, @@ -56,6 +133,34 @@ rp1_eth: ethernet@40100000 { #size-cells = <0>; }; + rp1_csi0: csi@40110000 { + compatible = "raspberrypi,rp1-cfe"; + reg = <0x0 0x40110000 0x0 0x100>, // CSI2 DMA address + <0x0 0x40114000 0x0 0x100>, // PHY/CSI Host address + <0x0 0x40120000 0x0 0x100>, // MIPI CFG address + <0x0 0x40124000 0x0 0x1000>; // PiSP FE address + interrupts = <47 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>; + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>; + assigned-clock-rates = <25000000>; + + status = "disabled"; + }; + + rp1_csi1: csi@40128000 { + compatible = "raspberrypi,rp1-cfe"; + reg = <0x0 0x40128000 0x0 0x100>, // CSI2 DMA address + <0x0 0x4012c000 0x0 0x100>, // PHY/CSI Host address + <0x0 0x40138000 0x0 0x100>, // MIPI CFG address + <0x0 0x4013c000 0x0 0x1000>; // PiSP FE address + interrupts = <48 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>; + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>; + assigned-clock-rates = <25000000>; + + status = "disabled"; + }; + rp1_usb0: usb@40200000 { compatible = "snps,dwc3"; reg = <0x00 0x40200000 0x0 0x100000>; diff --git a/arch/arm64/boot/dts/freescale/imx93.dtsi b/arch/arm64/boot/dts/freescale/imx93.dtsi index 7b27012dfcb5..27c9d88eaa96 100644 --- a/arch/arm64/boot/dts/freescale/imx93.dtsi +++ b/arch/arm64/boot/dts/freescale/imx93.dtsi @@ -43,6 +43,30 @@ map0 { }; }; }; + + soc@0 { + npu@4a900000 { + compatible = "fsl,imx93-npu", "arm,ethos-u65"; + reg = <0x4a900000 0x1000>; + interrupts = ; + power-domains = <&mlmix>; + clocks = <&clk IMX93_CLK_ML>, <&clk IMX93_CLK_ML_APB>; + clock-names = "core", "apb"; + sram = <&sram>; + assigned-clocks = <&clk IMX93_CLK_ML>, <&clk IMX93_CLK_ML_APB>; + assigned-clock-parents = <&clk IMX93_CLK_SYS_PLL_PFD1>, + <&clk IMX93_CLK_SYS_PLL_PFD1_DIV2>; + assigned-clock-rates = <800000000>, <133000000>; + }; + }; + + sram: sram@20480000 { + compatible = "mmio-sram"; + reg = <0x0 0x20480000 0x0 0x18000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x20480000 0x18000>; + }; }; &aips1 { diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 23c05dc7a8f2..d7b7b2f39e16 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -207,6 +209,24 @@ static void __init setup_machine_fdt(phys_addr_t dt_phys) dump_stack_set_arch_desc("%s (DT)", name); } +static void __init init_secureboot_mode(void) +{ + void *fdt = initial_boot_params; + u64 chosen; + const __be32 *prop; + int len; + + chosen = fdt_path_offset(fdt, "/chosen"); + if (chosen < 0) + return; + + prop = fdt_getprop(fdt, chosen, "secure-boot-mode", &len); + if (!prop || len != sizeof(u32)) + return; + + efi_set_secure_boot((enum efi_secureboot_mode)fdt32_to_cpu(*prop)); +} + static void __init request_standard_resources(void) { struct memblock_region *region; @@ -327,6 +347,13 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p) pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!"); WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND, FW_BUG "Booted with MMU enabled!"); + } else { + init_secureboot_mode(); + +#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT + if (efi_enabled(EFI_SECURE_BOOT)) + security_lock_kernel_down("EFI Secure Boot mode", LOCKDOWN_INTEGRITY_MAX); +#endif } arm64_memblock_init(); diff --git a/arch/s390/include/asm/ipl.h b/arch/s390/include/asm/ipl.h index b0d00032479d..afb9544fb007 100644 --- a/arch/s390/include/asm/ipl.h +++ b/arch/s390/include/asm/ipl.h @@ -139,6 +139,7 @@ int ipl_report_add_component(struct ipl_report *report, struct kexec_buf *kbuf, unsigned char flags, unsigned short cert); int ipl_report_add_certificate(struct ipl_report *report, void *key, unsigned long addr, unsigned long len); +bool ipl_get_secureboot(void); /* * DIAG 308 support diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index 049c557c452f..4c9fffbe6261 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -2521,3 +2521,8 @@ int ipl_report_free(struct ipl_report *report) } #endif + +bool ipl_get_secureboot(void) +{ + return !!ipl_secure_flag; +} diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c index b60284328fe3..e4d37b08ca29 100644 --- a/arch/s390/kernel/setup.c +++ b/arch/s390/kernel/setup.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -917,6 +918,9 @@ void __init setup_arch(char **cmdline_p) log_component_list(); + if (ipl_get_secureboot()) + security_lock_kernel_down("Secure IPL mode", LOCKDOWN_INTEGRITY_MAX); + /* Have one command line that is parsed and saved in /proc/cmdline */ /* boot_command_line has been already set up in early.c */ *cmdline_p = boot_command_line; diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index eebcc9db1a1b..779aca6969df 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -995,6 +996,13 @@ void __init setup_arch(char **cmdline_p) if (efi_enabled(EFI_BOOT)) efi_init(); + efi_set_secure_boot(boot_params.secure_boot); + +#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT + if (efi_enabled(EFI_SECURE_BOOT)) + security_lock_kernel_down("EFI Secure Boot mode", LOCKDOWN_INTEGRITY_MAX); +#endif + reserve_ibft_region(); x86_init.resources.dmi_setup(); @@ -1156,19 +1164,7 @@ void __init setup_arch(char **cmdline_p) /* Allocate bigger log buffer */ setup_log_buf(1); - if (efi_enabled(EFI_BOOT)) { - switch (boot_params.secure_boot) { - case efi_secureboot_mode_disabled: - pr_info("Secure boot disabled\n"); - break; - case efi_secureboot_mode_enabled: - pr_info("Secure boot enabled\n"); - break; - default: - pr_info("Secure boot could not be determined\n"); - break; - } - } + efi_set_secure_boot(boot_params.secure_boot); reserve_initrd(); diff --git a/crypto/krb5/krb5_api.c b/crypto/krb5/krb5_api.c index 23026d4206c8..c7ea40f900a7 100644 --- a/crypto/krb5/krb5_api.c +++ b/crypto/krb5/krb5_api.c @@ -134,27 +134,69 @@ EXPORT_SYMBOL(crypto_krb5_how_much_data); * Find the offset and size of the data in a secure message so that this * information can be used in the metadata buffer which will get added to the * digest by crypto_krb5_verify_mic(). + * + * Return: 0 if successful, -EBADMSG if the message is too short or -EINVAL if + * the mode is unsupported. */ -void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, - enum krb5_crypto_mode mode, - size_t *_offset, size_t *_len) +int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t *_offset, size_t *_len) { switch (mode) { case KRB5_CHECKSUM_MODE: + if (*_len < krb5->cksum_len) + return -EBADMSG; *_offset += krb5->cksum_len; *_len -= krb5->cksum_len; - return; + return 0; case KRB5_ENCRYPT_MODE: + if (*_len < krb5->conf_len + krb5->cksum_len) + return -EBADMSG; *_offset += krb5->conf_len; *_len -= krb5->conf_len + krb5->cksum_len; - return; + return 0; default: WARN_ON_ONCE(1); - return; + return -EINVAL; } } EXPORT_SYMBOL(crypto_krb5_where_is_the_data); +/** + * crypto_krb5_check_data_len - Check a message is big enough + * @krb5: The encoding to use. + * @mode: Mode of operation. + * @len: The length of the secure blob. + * @min_content: Minimum length of the content inside the blob. + * + * Check that a message is large enough to hold whatever bits the encryption + * type wants to glue on (nonce, checksum) plus a minimum amount of content. + * + * Return: 0 if successful, -EBADMSG if the message is too short or -EINVAL if + * the mode is unsupported. + */ +int crypto_krb5_check_data_len(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t len, size_t min_content) +{ + switch (mode) { + case KRB5_CHECKSUM_MODE: + if (len < krb5->cksum_len || + len - krb5->cksum_len < min_content) + return -EBADMSG; + return 0; + case KRB5_ENCRYPT_MODE: + if (len < krb5->conf_len + krb5->cksum_len || + len - (krb5->conf_len + krb5->cksum_len) < min_content) + return -EBADMSG; + return 0; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } +} +EXPORT_SYMBOL(crypto_krb5_check_data_len); + /* * Prepare the encryption with derived key data. */ diff --git a/crypto/sig.c b/crypto/sig.c index beba745b6405..fd41f6d3abf9 100644 --- a/crypto/sig.c +++ b/crypto/sig.c @@ -112,8 +112,7 @@ static int sig_prepare_alg(struct sig_alg *alg) { struct crypto_alg *base = &alg->base; - if (!alg->sign) - alg->sign = sig_default_sign; + alg->sign = sig_default_sign; if (!alg->verify) alg->verify = sig_default_verify; if (!alg->set_priv_key) diff --git a/crypto/testmgr.c b/crypto/testmgr.c index 4985411dedae..71065f31375d 100644 --- a/crypto/testmgr.c +++ b/crypto/testmgr.c @@ -3979,7 +3979,7 @@ static int test_sig_one(struct crypto_sig *tfm, const struct sig_testvec *vecs) * Don't invoke sign test (which requires a private key) * for vectors with only a public key. */ - if (vecs->public_key_vec) + if (1 || vecs->public_key_vec) return 0; sig_size = crypto_sig_maxsize(tfm); diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c index 20d757687e3d..90a13f20f052 100644 --- a/drivers/acpi/apei/hest.c +++ b/drivers/acpi/apei/hest.c @@ -142,6 +142,14 @@ static int apei_hest_parse(apei_hest_func_t func, void *data) if (hest_disable || !hest_tab) return -EINVAL; +#ifdef CONFIG_ARM64 + /* Ignore broken firmware */ + if (!strncmp(hest_tab->header.oem_id, "HPE ", 6) && + !strncmp(hest_tab->header.oem_table_id, "ProLiant", 8) && + MIDR_IMPLEMENTOR(read_cpuid_id()) == ARM_CPU_IMP_APM) + return -EINVAL; +#endif + hest_hdr = (struct acpi_hest_header *)(hest_tab + 1); for (i = 0; i < hest_tab->error_source_count; i++) { len = hest_esrc_len(hest_hdr); diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c index d1595156c86a..9485212f1a6d 100644 --- a/drivers/acpi/irq.c +++ b/drivers/acpi/irq.c @@ -143,6 +143,7 @@ struct acpi_irq_parse_one_ctx { unsigned int index; unsigned long *res_flags; struct irq_fwspec *fwspec; + bool skip_producer_check; }; /** @@ -216,7 +217,8 @@ static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares, return AE_CTRL_TERMINATE; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: eirq = &ares->data.extended_irq; - if (eirq->producer_consumer == ACPI_PRODUCER) + if (!ctx->skip_producer_check && + eirq->producer_consumer == ACPI_PRODUCER) return AE_OK; if (ctx->index >= eirq->interrupt_count) { ctx->index -= eirq->interrupt_count; @@ -252,8 +254,19 @@ static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares, static int acpi_irq_parse_one(acpi_handle handle, unsigned int index, struct irq_fwspec *fwspec, unsigned long *flags) { - struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec }; + struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec, false }; + /* + * Firmware on arm64-based HPE m400 platform incorrectly marks + * its UART interrupt as ACPI_PRODUCER rather than ACPI_CONSUMER. + * Don't do the producer/consumer check for that device. + */ + if (IS_ENABLED(CONFIG_ARM64)) { + struct acpi_device *adev = acpi_get_acpi_dev(handle); + + if (adev && !strcmp(acpi_device_hid(adev), "APMC0D08")) + ctx.skip_producer_check = true; + } acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_irq_parse_one_cb, &ctx); return ctx.rc; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 530547cda8b2..dc046780c74d 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1792,6 +1792,15 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) if (!acpi_match_device_ids(device, ignore_serial_bus_ids)) return false; + /* + * Firmware on some arm64 X-Gene platforms will make the UART + * device appear as both a UART and a slave of that UART. Just + * bail out here for X-Gene UARTs. + */ + if (IS_ENABLED(CONFIG_ARM64) && + !strcmp(acpi_device_hid(device), "APMC0D08")) + return false; + INIT_LIST_HEAD(&resource_list); acpi_dev_get_resources(device, &resource_list, acpi_check_serial_bus_slave, diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index c79abdfcd7a9..e23bfb7f94c7 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -731,6 +731,24 @@ int ahci_stop_engine(struct ata_port *ap) tmp &= ~PORT_CMD_START; writel(tmp, port_mmio + PORT_CMD); +#ifdef CONFIG_ARM64 + /* Rev Ax of Cavium CN99XX needs a hack for port stop */ + if (dev_is_pci(ap->host->dev) && + to_pci_dev(ap->host->dev)->vendor == 0x14e4 && + to_pci_dev(ap->host->dev)->device == 0x9027 && + midr_is_cpu_model_range(read_cpuid_id(), + MIDR_CPU_MODEL(ARM_CPU_IMP_BRCM, BRCM_CPU_PART_VULCAN), + MIDR_CPU_VAR_REV(0, 0), + MIDR_CPU_VAR_REV(0, MIDR_REVISION_MASK))) { + tmp = readl(hpriv->mmio + 0x8000); + udelay(100); + writel(tmp | (1 << 26), hpriv->mmio + 0x8000); + udelay(100); + writel(tmp & ~(1 << 26), hpriv->mmio + 0x8000); + dev_warn(ap->host->dev, "CN99XX SATA reset workaround applied\n"); + } +#endif + /* wait for engine to stop. This could be as long as 500 msec */ tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500); diff --git a/drivers/char/ipmi/ipmi_dmi.c b/drivers/char/ipmi/ipmi_dmi.c index 505e32911c34..26f7ee0ccf40 100644 --- a/drivers/char/ipmi/ipmi_dmi.c +++ b/drivers/char/ipmi/ipmi_dmi.c @@ -215,6 +215,21 @@ static int __init scan_for_dmi_ipmi(void) { const struct dmi_device *dev = NULL; +#ifdef CONFIG_ARM64 + /* RHEL-only + * If this is ARM-based HPE m400, return now, because that platform + * reports the host-side ipmi address as intel port-io space, which + * does not exist in the ARM architecture. + */ + const char *dmistr = dmi_get_system_info(DMI_PRODUCT_NAME); + + if (dmistr && (strcmp("ProLiant m400 Server", dmistr) == 0)) { + pr_debug("%s does not support host ipmi\n", dmistr); + return 0; + } + /* END RHEL-only */ +#endif + while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev))) dmi_decode_ipmi((const struct dmi_header *) dev->device_data); diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index c41f51c82edd..b36f35b54bd6 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #define IPMI_DRIVER_VERSION "39.2" @@ -5643,8 +5644,21 @@ static int __init ipmi_init_msghandler_mod(void) { int rv; - pr_info("version " IPMI_DRIVER_VERSION "\n"); +#ifdef CONFIG_ARM64 + /* RHEL-only + * If this is ARM-based HPE m400, return now, because that platform + * reports the host-side ipmi address as intel port-io space, which + * does not exist in the ARM architecture. + */ + const char *dmistr = dmi_get_system_info(DMI_PRODUCT_NAME); + if (dmistr && (strcmp("ProLiant m400 Server", dmistr) == 0)) { + pr_debug("%s does not support host ipmi\n", dmistr); + return -ENOSYS; + } + /* END RHEL-only */ +#endif + pr_info("version " IPMI_DRIVER_VERSION "\n"); mutex_lock(&ipmi_interfaces_mutex); rv = ipmi_register_driver(); mutex_unlock(&ipmi_interfaces_mutex); diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 8efbcf699e4f..96d5a1ca981d 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -25,6 +25,7 @@ subdir-$(CONFIG_EFI_STUB) += libstub obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o obj-$(CONFIG_EFI_TEST) += test/ obj-$(CONFIG_EFI_DEV_PATH_PARSER) += dev-path-parser.o +obj-$(CONFIG_EFI) += secureboot.o obj-$(CONFIG_APPLE_PROPERTIES) += apple-properties.o obj-$(CONFIG_EFI_RCI2_TABLE) += rci2-table.o obj-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += embedded-firmware.o diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index b2fb92a4bbd1..533e132e1bd8 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -1021,40 +1022,101 @@ int efi_mem_type(unsigned long phys_addr) return -EINVAL; } +struct efi_error_code { + efi_status_t status; + int errno; + const char *description; +}; + +static const struct efi_error_code efi_error_codes[] = { + { EFI_SUCCESS, 0, "Success"}, +#if 0 + { EFI_LOAD_ERROR, -EPICK_AN_ERRNO, "Load Error"}, +#endif + { EFI_INVALID_PARAMETER, -EINVAL, "Invalid Parameter"}, + { EFI_UNSUPPORTED, -ENOSYS, "Unsupported"}, + { EFI_BAD_BUFFER_SIZE, -ENOSPC, "Bad Buffer Size"}, + { EFI_BUFFER_TOO_SMALL, -ENOSPC, "Buffer Too Small"}, + { EFI_NOT_READY, -EAGAIN, "Not Ready"}, + { EFI_DEVICE_ERROR, -EIO, "Device Error"}, + { EFI_WRITE_PROTECTED, -EROFS, "Write Protected"}, + { EFI_OUT_OF_RESOURCES, -ENOMEM, "Out of Resources"}, +#if 0 + { EFI_VOLUME_CORRUPTED, -EPICK_AN_ERRNO, "Volume Corrupt"}, + { EFI_VOLUME_FULL, -EPICK_AN_ERRNO, "Volume Full"}, + { EFI_NO_MEDIA, -EPICK_AN_ERRNO, "No Media"}, + { EFI_MEDIA_CHANGED, -EPICK_AN_ERRNO, "Media changed"}, +#endif + { EFI_NOT_FOUND, -ENOENT, "Not Found"}, +#if 0 + { EFI_ACCESS_DENIED, -EPICK_AN_ERRNO, "Access Denied"}, + { EFI_NO_RESPONSE, -EPICK_AN_ERRNO, "No Response"}, + { EFI_NO_MAPPING, -EPICK_AN_ERRNO, "No mapping"}, + { EFI_TIMEOUT, -EPICK_AN_ERRNO, "Time out"}, + { EFI_NOT_STARTED, -EPICK_AN_ERRNO, "Not started"}, + { EFI_ALREADY_STARTED, -EPICK_AN_ERRNO, "Already started"}, +#endif + { EFI_ABORTED, -EINTR, "Aborted"}, +#if 0 + { EFI_ICMP_ERROR, -EPICK_AN_ERRNO, "ICMP Error"}, + { EFI_TFTP_ERROR, -EPICK_AN_ERRNO, "TFTP Error"}, + { EFI_PROTOCOL_ERROR, -EPICK_AN_ERRNO, "Protocol Error"}, + { EFI_INCOMPATIBLE_VERSION, -EPICK_AN_ERRNO, "Incompatible Version"}, +#endif + { EFI_SECURITY_VIOLATION, -EACCES, "Security Policy Violation"}, +#if 0 + { EFI_CRC_ERROR, -EPICK_AN_ERRNO, "CRC Error"}, + { EFI_END_OF_MEDIA, -EPICK_AN_ERRNO, "End of Media"}, + { EFI_END_OF_FILE, -EPICK_AN_ERRNO, "End of File"}, + { EFI_INVALID_LANGUAGE, -EPICK_AN_ERRNO, "Invalid Languages"}, + { EFI_COMPROMISED_DATA, -EPICK_AN_ERRNO, "Compromised Data"}, + + // warnings + { EFI_WARN_UNKOWN_GLYPH, -EPICK_AN_ERRNO, "Warning Unknown Glyph"}, + { EFI_WARN_DELETE_FAILURE, -EPICK_AN_ERRNO, "Warning Delete Failure"}, + { EFI_WARN_WRITE_FAILURE, -EPICK_AN_ERRNO, "Warning Write Failure"}, + { EFI_WARN_BUFFER_TOO_SMALL, -EPICK_AN_ERRNO, "Warning Buffer Too Small"}, +#endif +}; + +static int +efi_status_cmp_bsearch(const void *key, const void *item) +{ + u64 status = (u64)(uintptr_t)key; + struct efi_error_code *code = (struct efi_error_code *)item; + + if (status < code->status) + return -1; + if (status > code->status) + return 1; + return 0; +} + int efi_status_to_err(efi_status_t status) { - int err; - - switch (status) { - case EFI_SUCCESS: - err = 0; - break; - case EFI_INVALID_PARAMETER: - err = -EINVAL; - break; - case EFI_OUT_OF_RESOURCES: - err = -ENOSPC; - break; - case EFI_DEVICE_ERROR: - err = -EIO; - break; - case EFI_WRITE_PROTECTED: - err = -EROFS; - break; - case EFI_SECURITY_VIOLATION: - err = -EACCES; - break; - case EFI_NOT_FOUND: - err = -ENOENT; - break; - case EFI_ABORTED: - err = -EINTR; - break; - default: - err = -EINVAL; - } + struct efi_error_code *found; + size_t num = sizeof(efi_error_codes) / sizeof(struct efi_error_code); - return err; + found = bsearch((void *)(uintptr_t)status, efi_error_codes, + num, sizeof(struct efi_error_code), + efi_status_cmp_bsearch); + if (!found) + return -EINVAL; + return found->errno; +} + +const char * +efi_status_to_str(efi_status_t status) +{ + struct efi_error_code *found; + size_t num = sizeof(efi_error_codes) / sizeof(struct efi_error_code); + + found = bsearch((void *)(uintptr_t)status, efi_error_codes, + num, sizeof(struct efi_error_code), + efi_status_cmp_bsearch); + if (!found) + return "Unknown error code"; + return found->description; } EXPORT_SYMBOL_GPL(efi_status_to_err); diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index 6a337f1f8787..89244e0d9fa8 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -132,6 +132,11 @@ static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size, } } + fdt_val32 = cpu_to_fdt32((u32)efi_get_secureboot()); + status = fdt_setprop_var(fdt, node, "secure-boot-mode", fdt_val32); + if (status) + goto fdt_set_fail; + /* Shrink the FDT back to its minimum size: */ fdt_pack(fdt); diff --git a/drivers/firmware/efi/libstub/secureboot.c b/drivers/firmware/efi/libstub/secureboot.c index 516f4f0069bd..380354755108 100644 --- a/drivers/firmware/efi/libstub/secureboot.c +++ b/drivers/firmware/efi/libstub/secureboot.c @@ -29,10 +29,13 @@ enum efi_secureboot_mode efi_get_secureboot(void) { u32 attr; unsigned long size; - enum efi_secureboot_mode mode; + static enum efi_secureboot_mode mode; efi_status_t status; u8 moksbstate; + if (mode != efi_secureboot_mode_unset) + return mode; + mode = efi_get_secureboot_mode(get_var); if (mode == efi_secureboot_mode_unknown) { efi_err("Could not determine UEFI Secure Boot status.\n"); @@ -53,10 +56,13 @@ enum efi_secureboot_mode efi_get_secureboot(void) /* If it fails, we don't care why. Default to secure */ if (status != EFI_SUCCESS) goto secure_boot_enabled; - if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1) - return efi_secureboot_mode_disabled; + if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1) { + mode = efi_secureboot_mode_disabled; + return mode; + } secure_boot_enabled: efi_info("UEFI Secure Boot is enabled.\n"); - return efi_secureboot_mode_enabled; + mode = efi_secureboot_mode_enabled; + return mode; } diff --git a/drivers/firmware/efi/secureboot.c b/drivers/firmware/efi/secureboot.c new file mode 100644 index 000000000000..de0a3714a5d4 --- /dev/null +++ b/drivers/firmware/efi/secureboot.c @@ -0,0 +1,38 @@ +/* Core kernel secure boot support. + * + * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +/* + * Decide what to do when UEFI secure boot mode is enabled. + */ +void __init efi_set_secure_boot(enum efi_secureboot_mode mode) +{ + if (efi_enabled(EFI_BOOT)) { + switch (mode) { + case efi_secureboot_mode_disabled: + pr_info("Secure boot disabled\n"); + break; + case efi_secureboot_mode_enabled: + set_bit(EFI_SECURE_BOOT, &efi.flags); + pr_info("Secure boot enabled\n"); + break; + default: + pr_warn("Secure boot could not be determined (mode %u)\n", + mode); + break; + } + } +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c index 55b423fd6b6f..7894ae25e2a7 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c @@ -150,11 +150,9 @@ static int allocate_initial_tunnel_bw_for_pipes(struct intel_dp *intel_dp, u8 pi drm_dp_tunnel_name(intel_dp->tunnel), encoder->base.base.id, encoder->base.name, ERR_PTR(err)); - - return err; } - return update_tunnel_state(intel_dp); + return err; } static int allocate_initial_tunnel_bw(struct intel_dp *intel_dp, @@ -200,10 +198,13 @@ static int detect_new_tunnel(struct intel_dp *intel_dp, struct drm_modeset_acqui } ret = allocate_initial_tunnel_bw(intel_dp, ctx); - if (ret < 0) + if (ret < 0) { intel_dp_tunnel_destroy(intel_dp); - return ret; + return ret; + } + + return update_tunnel_state(intel_dp); } /** diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c index d4af17fdba46..154f0403cbf4 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -321,21 +321,12 @@ static int rmi_input_event(struct hid_device *hdev, u8 *data, int size) { struct rmi_data *hdata = hid_get_drvdata(hdev); struct rmi_device *rmi_dev = hdata->xport.rmi_dev; - unsigned long flags; if (!(test_bit(RMI_STARTED, &hdata->flags))) return 0; - pm_wakeup_event(hdev->dev.parent, 0); - - local_irq_save(flags); - rmi_set_attn_data(rmi_dev, data[1], &data[2], size - 2); - generic_handle_irq(hdata->rmi_irq); - - local_irq_restore(flags); - return 1; } @@ -589,56 +580,6 @@ static const struct rmi_transport_ops hid_rmi_ops = { .reset = rmi_hid_reset, }; -static void rmi_irq_teardown(void *data) -{ - struct rmi_data *hdata = data; - struct irq_domain *domain = hdata->domain; - - if (!domain) - return; - - irq_dispose_mapping(irq_find_mapping(domain, 0)); - - irq_domain_remove(domain); - hdata->domain = NULL; - hdata->rmi_irq = 0; -} - -static int rmi_irq_map(struct irq_domain *h, unsigned int virq, - irq_hw_number_t hw_irq_num) -{ - irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); - - return 0; -} - -static const struct irq_domain_ops rmi_irq_ops = { - .map = rmi_irq_map, -}; - -static int rmi_setup_irq_domain(struct hid_device *hdev) -{ - struct rmi_data *hdata = hid_get_drvdata(hdev); - int ret; - - hdata->domain = irq_domain_create_linear(hdev->dev.fwnode, 1, - &rmi_irq_ops, hdata); - if (!hdata->domain) - return -ENOMEM; - - ret = devm_add_action_or_reset(&hdev->dev, &rmi_irq_teardown, hdata); - if (ret) - return ret; - - hdata->rmi_irq = irq_create_mapping(hdata->domain, 0); - if (hdata->rmi_irq <= 0) { - hid_err(hdev, "Can't allocate an IRQ\n"); - return hdata->rmi_irq < 0 ? hdata->rmi_irq : -ENXIO; - } - - return 0; -} - static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct rmi_data *data = NULL; @@ -711,18 +652,11 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) mutex_init(&data->page_mutex); - ret = rmi_setup_irq_domain(hdev); - if (ret) { - hid_err(hdev, "failed to allocate IRQ domain\n"); - return ret; - } - if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) rmi_hid_pdata.gpio_data.disable = true; data->xport.dev = hdev->dev.parent; data->xport.pdata = rmi_hid_pdata; - data->xport.pdata.irq = data->rmi_irq; data->xport.proto_name = "hid"; data->xport.ops = &hid_rmi_ops; diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index d565a73f0042..0e64fc7c1004 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -2527,6 +2528,16 @@ static const struct amba_id etm4_ids[] = { {}, }; +static const struct dmi_system_id broken_coresight[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HPE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Apollo 70"), + }, + }, + { } /* terminating entry */ +}; + MODULE_DEVICE_TABLE(amba, etm4_ids); static struct amba_driver etm4x_amba_driver = { @@ -2601,6 +2612,11 @@ static int __init etm4x_init(void) { int ret; + if (dmi_check_system(broken_coresight)) { + pr_info("ETM4 disabled due to firmware bug\n"); + return 0; + } + ret = etm4_pm_setup(); /* etm4_pm_setup() does its own cleanup - exit on error */ @@ -2627,6 +2643,9 @@ static int __init etm4x_init(void) static void __exit etm4x_exit(void) { + if (dmi_check_system(broken_coresight)) + return; + amba_driver_unregister(&etm4x_amba_driver); platform_driver_unregister(&etm4_platform_driver); etm4_pm_clear(); diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index ccd9338a44db..5c54e522e8a4 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -183,34 +183,47 @@ void rmi_set_attn_data(struct rmi_device *rmi_dev, unsigned long irq_status, attn_data.data = fifo_data; kfifo_put(&drvdata->attn_fifo, attn_data); + + schedule_work(&drvdata->attn_work); } EXPORT_SYMBOL_GPL(rmi_set_attn_data); -static irqreturn_t rmi_irq_fn(int irq, void *dev_id) +static void attn_callback(struct work_struct *work) { - struct rmi_device *rmi_dev = dev_id; - struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev); + struct rmi_driver_data *drvdata = container_of(work, + struct rmi_driver_data, + attn_work); struct rmi4_attn_data attn_data = {0}; int ret, count; count = kfifo_get(&drvdata->attn_fifo, &attn_data); - if (count) { - *(drvdata->irq_status) = attn_data.irq_status; - drvdata->attn_data = attn_data; - } + if (!count) + return; - ret = rmi_process_interrupt_requests(rmi_dev); + *(drvdata->irq_status) = attn_data.irq_status; + drvdata->attn_data = attn_data; + + ret = rmi_process_interrupt_requests(drvdata->rmi_dev); if (ret) - rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev, + rmi_dbg(RMI_DEBUG_CORE, &drvdata->rmi_dev->dev, "Failed to process interrupt request: %d\n", ret); - if (count) { - kfree(attn_data.data); - drvdata->attn_data.data = NULL; - } + kfree(attn_data.data); + drvdata->attn_data.data = NULL; if (!kfifo_is_empty(&drvdata->attn_fifo)) - return rmi_irq_fn(irq, dev_id); + schedule_work(&drvdata->attn_work); +} + +static irqreturn_t rmi_irq_fn(int irq, void *dev_id) +{ + struct rmi_device *rmi_dev = dev_id; + int ret; + + ret = rmi_process_interrupt_requests(rmi_dev); + if (ret) + rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev, + "Failed to process interrupt request: %d\n", ret); return IRQ_HANDLED; } @@ -218,7 +231,6 @@ static irqreturn_t rmi_irq_fn(int irq, void *dev_id) static int rmi_irq_init(struct rmi_device *rmi_dev) { struct rmi_device_platform_data *pdata = rmi_get_platform_data(rmi_dev); - struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev); int irq_flags = irq_get_trigger_type(pdata->irq); int ret; @@ -236,8 +248,6 @@ static int rmi_irq_init(struct rmi_device *rmi_dev) return ret; } - data->enabled = true; - return 0; } @@ -887,23 +897,27 @@ void rmi_enable_irq(struct rmi_device *rmi_dev, bool clear_wake) if (data->enabled) goto out; - enable_irq(irq); - data->enabled = true; - if (clear_wake && device_may_wakeup(rmi_dev->xport->dev)) { - retval = disable_irq_wake(irq); - if (retval) - dev_warn(&rmi_dev->dev, - "Failed to disable irq for wake: %d\n", - retval); - } + if (irq) { + enable_irq(irq); + data->enabled = true; + if (clear_wake && device_may_wakeup(rmi_dev->xport->dev)) { + retval = disable_irq_wake(irq); + if (retval) + dev_warn(&rmi_dev->dev, + "Failed to disable irq for wake: %d\n", + retval); + } - /* - * Call rmi_process_interrupt_requests() after enabling irq, - * otherwise we may lose interrupt on edge-triggered systems. - */ - irq_flags = irq_get_trigger_type(pdata->irq); - if (irq_flags & IRQ_TYPE_EDGE_BOTH) - rmi_process_interrupt_requests(rmi_dev); + /* + * Call rmi_process_interrupt_requests() after enabling irq, + * otherwise we may lose interrupt on edge-triggered systems. + */ + irq_flags = irq_get_trigger_type(pdata->irq); + if (irq_flags & IRQ_TYPE_EDGE_BOTH) + rmi_process_interrupt_requests(rmi_dev); + } else { + data->enabled = true; + } out: mutex_unlock(&data->enabled_mutex); @@ -923,20 +937,22 @@ void rmi_disable_irq(struct rmi_device *rmi_dev, bool enable_wake) goto out; data->enabled = false; - disable_irq(irq); - if (enable_wake && device_may_wakeup(rmi_dev->xport->dev)) { - retval = enable_irq_wake(irq); - if (retval) - dev_warn(&rmi_dev->dev, - "Failed to enable irq for wake: %d\n", - retval); - } - - /* make sure the fifo is clean */ - while (!kfifo_is_empty(&data->attn_fifo)) { - count = kfifo_get(&data->attn_fifo, &attn_data); - if (count) - kfree(attn_data.data); + if (irq) { + disable_irq(irq); + if (enable_wake && device_may_wakeup(rmi_dev->xport->dev)) { + retval = enable_irq_wake(irq); + if (retval) + dev_warn(&rmi_dev->dev, + "Failed to enable irq for wake: %d\n", + retval); + } + } else { + /* make sure the fifo is clean */ + while (!kfifo_is_empty(&data->attn_fifo)) { + count = kfifo_get(&data->attn_fifo, &attn_data); + if (count) + kfree(attn_data.data); + } } out: @@ -979,6 +995,8 @@ static int rmi_driver_remove(struct device *dev) rmi_disable_irq(rmi_dev, false); + cancel_work_sync(&data->attn_work); + rmi_f34_remove_sysfs(rmi_dev); rmi_free_function_list(rmi_dev); @@ -1224,9 +1242,15 @@ static int rmi_driver_probe(struct device *dev) } } - retval = rmi_irq_init(rmi_dev); - if (retval < 0) - goto err_destroy_functions; + if (pdata->irq) { + retval = rmi_irq_init(rmi_dev); + if (retval < 0) + goto err_destroy_functions; + } + + data->enabled = true; + + INIT_WORK(&data->attn_work, attn_callback); if (data->f01_container->dev.driver) { /* Driver already bound, so enable ATTN now. */ diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index ef08c2c4ec95..c22d02658ea4 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -3108,6 +3109,27 @@ int iommu_fwspec_add_ids(struct device *dev, const u32 *ids, int num_ids) } EXPORT_SYMBOL_GPL(iommu_fwspec_add_ids); +#ifdef CONFIG_ARM64 +static int __init iommu_quirks(void) +{ + const char *vendor, *name; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); + name = dmi_get_system_info(DMI_PRODUCT_NAME); + + if (vendor && + (strncmp(vendor, "GIGABYTE", 8) == 0 && name && + (strncmp(name, "R120", 4) == 0 || + strncmp(name, "R270", 4) == 0))) { + pr_warn("Gigabyte %s detected, force iommu passthrough mode", name); + iommu_def_domain_type = IOMMU_DOMAIN_IDENTITY; + } + + return 0; +} +arch_initcall(iommu_quirks); +#endif + /** * iommu_setup_default_domain - Set the default_domain for the group * @group: Group to change diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 3f0b7bb68cc9..0b33e927bd59 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -63,6 +63,7 @@ config VIDEO_MUX # Platform drivers - Please keep it alphabetically sorted source "drivers/media/platform/allegro-dvt/Kconfig" +source "drivers/media/platform/amd/Kconfig" source "drivers/media/platform/amlogic/Kconfig" source "drivers/media/platform/amphion/Kconfig" source "drivers/media/platform/arm/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 6d5f79ddfcc3..16c185752474 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -6,6 +6,7 @@ # Place here, alphabetically sorted by directory # (e. g. LC_ALL=C sort Makefile) obj-y += allegro-dvt/ +obj-y += amd/ obj-y += amlogic/ obj-y += amphion/ obj-y += arm/ diff --git a/drivers/media/platform/amd/Kconfig b/drivers/media/platform/amd/Kconfig new file mode 100644 index 000000000000..25af49f246b2 --- /dev/null +++ b/drivers/media/platform/amd/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +source "drivers/media/platform/amd/isp4/Kconfig" diff --git a/drivers/media/platform/amd/Makefile b/drivers/media/platform/amd/Makefile new file mode 100644 index 000000000000..8bfc1955f22e --- /dev/null +++ b/drivers/media/platform/amd/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += isp4/ diff --git a/drivers/media/platform/amd/isp4/Kconfig b/drivers/media/platform/amd/isp4/Kconfig new file mode 100644 index 000000000000..55dd2dc453a2 --- /dev/null +++ b/drivers/media/platform/amd/isp4/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ + +config VIDEO_AMD_ISP4_CAPTURE + tristate "AMD ISP4 and camera driver" + depends on DRM_AMD_ISP && VIDEO_DEV && HAS_DMA + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + select VIDEOBUF2_V4L2 + select VIDEOBUF2_VMALLOC + select VIDEO_V4L2_SUBDEV_API + help + This is support for AMD ISP4 and camera subsystem driver. + Say Y here to enable the ISP4 and camera device for video capture. + To compile this driver as a module, choose M here. The module will + be called amd_isp4_capture. diff --git a/drivers/media/platform/amd/isp4/Makefile b/drivers/media/platform/amd/isp4/Makefile new file mode 100644 index 000000000000..3849062e17f3 --- /dev/null +++ b/drivers/media/platform/amd/isp4/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2025 Advanced Micro Devices, Inc. + +obj-$(CONFIG_VIDEO_AMD_ISP4_CAPTURE) += amd_isp4_capture.o +amd_isp4_capture-objs := isp4.o \ + isp4_debug.o \ + isp4_interface.o \ + isp4_subdev.o \ + isp4_video.o diff --git a/drivers/media/platform/amd/isp4/isp4.c b/drivers/media/platform/amd/isp4/isp4.c new file mode 100644 index 000000000000..bf6b8e26c2c0 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_hw_reg.h" + +#define ISP4_DRV_NAME "amd_isp_capture" +#define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \ + (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \ + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK) + +static const struct { + const char *name; + u32 status_mask; + u32 en_mask; + u32 ack_mask; + u32 rb_int_num; +} isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] = { + /* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */ + { + .name = "isp_irq_global", + .status_mask = + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK, + .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK, + .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK, + .rb_int_num = 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */ + }, + { + .name = "isp_irq_stream1", + .status_mask = + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK, + .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK, + .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK, + .rb_int_num = 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */ + }, +}; + +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable) +{ + u32 intr_en; + + /* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */ + spin_lock_irq(&isp_subdev->irq_lock); + intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN); + if (enable) + intr_en |= isp4_irq[index].en_mask; + else + intr_en &= ~isp4_irq[index].en_mask; + + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock_irq(&isp_subdev->irq_lock); +} + +static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 index) +{ + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[index]; + + thread_ctx->resp_ready = true; + wake_up_interruptible(&thread_ctx->waitq); +} + +static irqreturn_t isp4_irq_handler(int irq, void *arg) +{ + struct isp4_subdev *isp_subdev = arg; + u32 intr_ack = 0, intr_en = 0, intr_status; + int seen = 0; + + /* Get the ISP_SYS interrupt status */ + intr_status = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS); + intr_status &= ISP4_FW_RESP_RB_IRQ_STATUS_MASK; + + /* Find which ISP_SYS interrupts fired */ + for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { + if (intr_status & isp4_irq[i].status_mask) { + intr_ack |= isp4_irq[i].ack_mask; + intr_en |= isp4_irq[i].en_mask; + seen |= BIT(i); + } + } + + /* + * Disable the ISP_SYS interrupts that fired. Must be done before waking + * the response threads, since they re-enable interrupts when finished. + * The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt(). + */ + spin_lock(&isp_subdev->irq_lock); + intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en; + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock(&isp_subdev->irq_lock); + + /* + * Clear the ISP_SYS interrupts. This must be done after the interrupts + * are disabled, so that ISP FW won't flag any new interrupts on these + * streams, and thus we don't need to clear interrupts again before + * re-enabling them in the response thread. + */ + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack); + + /* + * The operation `(seen >> i) << i` is logically equivalent to + * `seen &= ~BIT(i)`, with fewer instructions after compilation. + */ + for (int i; (i = ffs(seen)); seen = (seen >> i) << i) + isp4_wake_up_resp_thread(isp_subdev, i - 1); + + return IRQ_HANDLED; +} + +static int isp4_capture_probe(struct platform_device *pdev) +{ + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + struct device *dev = &pdev->dev; + struct isp4_subdev *isp_subdev; + struct isp4_device *isp_dev; + int ret; + + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + dev->init_name = ISP4_DRV_NAME; + + isp_subdev = &isp_dev->isp_subdev; + isp_subdev->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(isp_subdev->mmio)) + return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio), + "isp ioremap fail\n"); + + for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { + irq[i] = platform_get_irq(pdev, isp4_irq[i].rb_int_num); + if (irq[i] < 0) + return dev_err_probe(dev, irq[i], + "fail to get irq %d\n", + isp4_irq[i].rb_int_num); + + ret = devm_request_irq(dev, irq[i], isp4_irq_handler, + IRQF_NO_AUTOEN, isp4_irq[i].name, + isp_subdev); + if (ret) + return dev_err_probe(dev, ret, "fail to req irq %d\n", + irq[i]); + } + + isp_dev->v4l2_dev.mdev = &isp_dev->mdev; + + strscpy(isp_dev->mdev.model, "amd_isp41_mdev", + sizeof(isp_dev->mdev.model)); + isp_dev->mdev.dev = dev; + media_device_init(&isp_dev->mdev); + + snprintf(isp_dev->v4l2_dev.name, sizeof(isp_dev->v4l2_dev.name), + "AMD-V4L2-ROOT"); + ret = v4l2_device_register(dev, &isp_dev->v4l2_dev); + if (ret) { + dev_err_probe(dev, ret, "fail register v4l2 device\n"); + goto err_clean_media; + } + + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + spin_lock_init(&isp_subdev->irq_lock); + ret = isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq); + if (ret) { + dev_err_probe(dev, ret, "fail init isp4 sub dev\n"); + goto err_pm_disable; + } + + ret = media_create_pad_link(&isp_dev->isp_subdev.sdev.entity, + 0, + &isp_dev->isp_subdev.isp_vdev.vdev.entity, + 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err_probe(dev, ret, "fail to create pad link\n"); + goto err_isp4_deinit; + } + + ret = media_device_register(&isp_dev->mdev); + if (ret) { + dev_err_probe(dev, ret, "fail to register media device\n"); + goto err_isp4_deinit; + } + + platform_set_drvdata(pdev, isp_dev); + isp_debugfs_create(isp_dev); + + return 0; + +err_isp4_deinit: + isp4sd_deinit(&isp_dev->isp_subdev); +err_pm_disable: + pm_runtime_disable(dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); +err_clean_media: + media_device_cleanup(&isp_dev->mdev); + + return ret; +} + +static void isp4_capture_remove(struct platform_device *pdev) +{ + struct isp4_device *isp_dev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + isp_debugfs_remove(isp_dev); + + media_device_unregister(&isp_dev->mdev); + isp4sd_deinit(&isp_dev->isp_subdev); + pm_runtime_disable(dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); + media_device_cleanup(&isp_dev->mdev); +} + +static struct platform_driver isp4_capture_drv = { + .probe = isp4_capture_probe, + .remove = isp4_capture_remove, + .driver = { + .name = ISP4_DRV_NAME, + } +}; + +module_platform_driver(isp4_capture_drv); + +MODULE_ALIAS("platform:" ISP4_DRV_NAME); +MODULE_IMPORT_NS("DMA_BUF"); + +MODULE_DESCRIPTION("AMD ISP4 Driver"); +MODULE_AUTHOR("Bin Du "); +MODULE_AUTHOR("Pratap Nirujogi "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amd/isp4/isp4.h b/drivers/media/platform/amd/isp4/isp4.h new file mode 100644 index 000000000000..2db6683d6d8b --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_H_ +#define _ISP4_H_ + +#include +#include "isp4_subdev.h" + +struct isp4_device { + struct v4l2_device v4l2_dev; + struct isp4_subdev isp_subdev; + struct media_device mdev; +}; + +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable); + +#endif /* _ISP4_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_debug.c b/drivers/media/platform/amd/isp4/isp4_debug.c new file mode 100644 index 000000000000..2fc00fc9a194 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_debug.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" + +#define ISP4DBG_FW_LOG_RINGBUF_SIZE (2 * 1024 * 1024) +#define ISP4DBG_MACRO_2_STR(X) #X +#define ISP4DBG_ONE_TIME_LOG_LEN 510 + +#ifdef CONFIG_DEBUG_FS + +void isp_debugfs_create(struct isp4_device *isp_dev) +{ + isp_dev->isp_subdev.debugfs_dir = debugfs_create_dir("amd_isp4", NULL); + debugfs_create_bool("fw_log_enable", 0644, + isp_dev->isp_subdev.debugfs_dir, + &isp_dev->isp_subdev.enable_fw_log); + isp_dev->isp_subdev.fw_log_output = + devm_kzalloc(isp_dev->isp_subdev.dev, + ISP4DBG_FW_LOG_RINGBUF_SIZE + 32, + GFP_KERNEL); +} + +void isp_debugfs_remove(struct isp4_device *isp_dev) +{ + debugfs_remove_recursive(isp_dev->isp_subdev.debugfs_dir); + isp_dev->isp_subdev.debugfs_dir = NULL; +} + +static u32 isp_fw_fill_rb_log(struct isp4_subdev *isp, void *sys, u32 rb_size) +{ + struct isp4_interface *ispif = &isp->ispif; + char *buf = isp->fw_log_output; + struct device *dev = isp->dev; + u32 rd_ptr, wr_ptr; + u32 total_cnt = 0; + u32 offset = 0; + u32 cnt; + + if (!sys || !rb_size) + return 0; + + guard(mutex)(&ispif->isp4if_mutex); + + rd_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_RPTR0); + wr_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_WPTR0); + + do { + if (wr_ptr > rd_ptr) + cnt = wr_ptr - rd_ptr; + else if (wr_ptr < rd_ptr) + cnt = rb_size - rd_ptr; + else + goto quit; + + if (cnt > rb_size) { + dev_err(dev, "fail bad fw log size %u\n", cnt); + goto quit; + } + + memcpy(buf + offset, sys + rd_ptr, cnt); + + offset += cnt; + total_cnt += cnt; + rd_ptr = (rd_ptr + cnt) % rb_size; + } while (rd_ptr < wr_ptr); + + isp4hw_wreg(isp->mmio, ISP_LOG_RB_RPTR0, rd_ptr); + +quit: + return total_cnt; +} + +void isp_fw_log_print(struct isp4_subdev *isp) +{ + struct isp4_interface *ispif = &isp->ispif; + char *fw_log_buf = isp->fw_log_output; + u32 cnt; + + if (!isp->enable_fw_log || !fw_log_buf) + return; + + cnt = isp_fw_fill_rb_log(isp, ispif->fw_log_buf->sys_addr, + ispif->fw_log_buf->mem_size); + + if (cnt) { + char temp_ch; + char *str; + char *end; + /* line end */ + char *le; + + str = (char *)fw_log_buf; + end = ((char *)fw_log_buf + cnt); + fw_log_buf[cnt] = 0; + + while (str < end) { + le = strchr(str, 0x0A); + if ((le && str + ISP4DBG_ONE_TIME_LOG_LEN >= le) || + (!le && str + ISP4DBG_ONE_TIME_LOG_LEN >= end)) { + if (le) + *le = 0; + + if (*str != '\0') + dev_dbg(isp->dev, "%s", str); + + if (le) { + *le = 0x0A; + str = le + 1; + } else { + break; + } + } else { + u32 tmp_len = ISP4DBG_ONE_TIME_LOG_LEN; + + temp_ch = str[tmp_len]; + str[tmp_len] = 0; + dev_dbg(isp->dev, "%s", str); + str[tmp_len] = temp_ch; + str = &str[tmp_len]; + } + } + } +} +#endif + +char *isp4dbg_get_buf_src_str(u32 src) +{ + switch (src) { + case ISP4FW_BUFFER_SOURCE_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_SOURCE_STREAM); + default: + return "Unknown buf source"; + } +} + +char *isp4dbg_get_buf_done_str(u32 status) +{ + switch (status) { + case ISP4FW_BUFFER_STATUS_INVALID: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_INVALID); + case ISP4FW_BUFFER_STATUS_SKIPPED: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_SKIPPED); + case ISP4FW_BUFFER_STATUS_EXIST: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_EXIST); + case ISP4FW_BUFFER_STATUS_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DONE); + case ISP4FW_BUFFER_STATUS_LACK: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_LACK); + case ISP4FW_BUFFER_STATUS_DIRTY: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DIRTY); + case ISP4FW_BUFFER_STATUS_MAX: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_MAX); + default: + return "Unknown Buf Done Status"; + } +} + +char *isp4dbg_get_img_fmt_str(int fmt /* enum isp4fw_image_format * */) +{ + switch (fmt) { + case ISP4FW_IMAGE_FORMAT_NV12: + return "NV12"; + case ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED: + return "YUV422INTERLEAVED"; + default: + return "unknown fmt"; + } +} + +void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, + void *in, void *orig_buf) +{ + struct isp4fw_buffer_meta_info *p; + struct isp4if_img_buf_info *orig; + + if (!in) + return; + + if (!pre) + pre = ""; + + p = in; + orig = orig_buf; + + dev_dbg(dev, "%s(%s) en:%d,stat:%s(%u),src:%s\n", pre, + isp4dbg_get_img_fmt_str(p->image_prop.image_format), + p->enabled, isp4dbg_get_buf_done_str(p->status), p->status, + isp4dbg_get_buf_src_str(p->source)); + + dev_dbg(dev, "%p,0x%llx(%u) %p,0x%llx(%u) %p,0x%llx(%u)\n", + orig->planes[0].sys_addr, orig->planes[0].mc_addr, + orig->planes[0].len, orig->planes[1].sys_addr, + orig->planes[1].mc_addr, orig->planes[1].len, + orig->planes[2].sys_addr, orig->planes[2].mc_addr, + orig->planes[2].len); +} + +char *isp4dbg_get_buf_type(u32 type) +{ + /* enum isp4fw_buffer_type */ + switch (type) { + case ISP4FW_BUFFER_TYPE_PREVIEW: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_PREVIEW); + case ISP4FW_BUFFER_TYPE_META_INFO: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_META_INFO); + case ISP4FW_BUFFER_TYPE_MEM_POOL: + return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_MEM_POOL); + default: + return "unknown type"; + } +} + +char *isp4dbg_get_cmd_str(u32 cmd) +{ + switch (cmd) { + case ISP4FW_CMD_ID_START_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_START_STREAM); + case ISP4FW_CMD_ID_STOP_STREAM: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_STOP_STREAM); + case ISP4FW_CMD_ID_SEND_BUFFER: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SEND_BUFFER); + case ISP4FW_CMD_ID_SET_STREAM_CONFIG: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_STREAM_CONFIG); + case ISP4FW_CMD_ID_SET_OUT_CHAN_PROP: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_OUT_CHAN_PROP); + case ISP4FW_CMD_ID_ENABLE_OUT_CHAN: + return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_ENABLE_OUT_CHAN); + default: + return "unknown cmd"; + } +} + +char *isp4dbg_get_resp_str(u32 cmd) +{ + switch (cmd) { + case ISP4FW_RESP_ID_CMD_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_CMD_DONE); + case ISP4FW_RESP_ID_NOTI_FRAME_DONE: + return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_NOTI_FRAME_DONE); + default: + return "unknown respid"; + } +} + +char *isp4dbg_get_if_stream_str(u32 stream /* enum fw_cmd_resp_stream_id */) +{ + switch (stream) { + case ISP4IF_STREAM_ID_GLOBAL: + return "STREAM_GLOBAL"; + case ISP4IF_STREAM_ID_1: + return "STREAM1"; + default: + return "unknown streamID"; + } +} + +char *isp4dbg_get_out_ch_str(int ch /* enum isp4fw_pipe_out_ch */) +{ + switch ((enum isp4fw_pipe_out_ch)ch) { + case ISP4FW_ISP_PIPE_OUT_CH_PREVIEW: + return "prev"; + default: + return "unknown channel"; + } +} diff --git a/drivers/media/platform/amd/isp4/isp4_debug.h b/drivers/media/platform/amd/isp4/isp4_debug.h new file mode 100644 index 000000000000..d1262e03ae64 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_debug.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_DEBUG_H_ +#define _ISP4_DEBUG_H_ + +#include +#include + +#include "isp4_subdev.h" + +#ifdef CONFIG_DEBUG_FS +struct isp4_device; + +void isp_debugfs_create(struct isp4_device *isp_dev); +void isp_debugfs_remove(struct isp4_device *isp_dev); +void isp_fw_log_print(struct isp4_subdev *isp); + +#else + +/* to avoid checkpatch warning */ +#define isp_debugfs_create(cam) ((void)(cam)) +#define isp_debugfs_remove(cam) ((void)(cam)) +#define isp_fw_log_print(isp) ((void)(isp)) + +#endif /* CONFIG_DEBUG_FS */ + +void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, void *p, + void *orig_buf /* struct sys_img_buf_handle */); +char *isp4dbg_get_img_fmt_str(int fmt /* enum _image_format_t */); +char *isp4dbg_get_out_ch_str(int ch /* enum _isp_pipe_out_ch_t */); +char *isp4dbg_get_cmd_str(u32 cmd); +char *isp4dbg_get_buf_type(u32 type);/* enum _buffer_type_t */ +char *isp4dbg_get_resp_str(u32 resp); +char *isp4dbg_get_buf_src_str(u32 src); +char *isp4dbg_get_buf_done_str(u32 status); +char *isp4dbg_get_if_stream_str(u32 stream); + +#endif /* _ISP4_DEBUG_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h new file mode 100644 index 000000000000..88bacb00355c --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_FW_CMD_RESP_H_ +#define _ISP4_FW_CMD_RESP_H_ + +/* + * Two types of command/response channel. + * Type Global Command has one command/response channel. + * Type Stream Command has one command/response channel. + *----------- ------------ + *| | --------------------------- | | + *| | ---->| Global Command |----> | | + *| | --------------------------- | | + *| | | | + *| | | | + *| | --------------------------- | | + *| | ---->| Stream Command |----> | | + *| | --------------------------- | | + *| | | | + *| | | | + *| | | | + *| HOST | | Firmware | + *| | | | + *| | | | + *| | -------------------------- | | + *| | <----| Global Response |<---- | | + *| | -------------------------- | | + *| | | | + *| | | | + *| | -------------------------- | | + *| | <----| Stream Response |<---- | | + *| | -------------------------- | | + *| | | | + *| | | | + *----------- ------------ + */ + +/* + * cmd_id is in the format of following type: + * type: indicate command type, global/stream commands. + * group: indicate the command group. + * id: A unique command identification in one type and group. + * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->| + * | type | group | id | + */ + +#define ISP4FW_CMD_TYPE_SHIFT 24 +#define ISP4FW_CMD_GROUP_SHIFT 16 +#define ISP4FW_CMD_TYPE_STREAM_CTRL (0x2U << ISP4FW_CMD_TYPE_SHIFT) + +#define ISP4FW_CMD_GROUP_STREAM_CTRL (0x1U << ISP4FW_CMD_GROUP_SHIFT) +#define ISP4FW_CMD_GROUP_STREAM_BUFFER (0x4U << ISP4FW_CMD_GROUP_SHIFT) + +/* Stream Command */ +#define ISP4FW_CMD_ID_SET_STREAM_CONFIG (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x1) +#define ISP4FW_CMD_ID_SET_OUT_CHAN_PROP (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x3) +#define ISP4FW_CMD_ID_ENABLE_OUT_CHAN (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x5) +#define ISP4FW_CMD_ID_START_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x7) +#define ISP4FW_CMD_ID_STOP_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x8) + +/* Stream Buffer Command */ +#define ISP4FW_CMD_ID_SEND_BUFFER (ISP4FW_CMD_TYPE_STREAM_CTRL\ + | ISP4FW_CMD_GROUP_STREAM_BUFFER | 0x1) + +/* + * resp_id is in the format of following type: + * type: indicate command type, global/stream commands. + * group: indicate the command group. + * id: A unique command identification in one type and group. + * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->| + * | type | group | id | + */ + +#define ISP4FW_RESP_GROUP_SHIFT 16 + +#define ISP4FW_RESP_GROUP_GENERAL (0x1 << ISP4FW_RESP_GROUP_SHIFT) +#define ISP4FW_RESP_GROUP_NOTIFICATION (0x3 << ISP4FW_RESP_GROUP_SHIFT) + +/* General Response */ +#define ISP4FW_RESP_ID_CMD_DONE (ISP4FW_RESP_GROUP_GENERAL | 0x1) + +/* Notification */ +#define ISP4FW_RESP_ID_NOTI_FRAME_DONE (ISP4FW_RESP_GROUP_NOTIFICATION | 0x1) + +#define ISP4FW_CMD_STATUS_SUCCESS 0 +#define ISP4FW_CMD_STATUS_FAIL 1 +#define ISP4FW_CMD_STATUS_SKIPPED 2 + +#define ISP4FW_ADDR_SPACE_TYPE_GPU_VA 4 + +#define ISP4FW_MEMORY_POOL_SIZE (100 * 1024 * 1024) + +/* + * standard ISP pipeline: mipicsi=>isp + */ +#define ISP4FW_MIPI0_ISP_PIPELINE_ID 0x5f91 + +enum isp4fw_sensor_id { + /* Sensor id for ISP input from MIPI port 0 */ + ISP4FW_SENSOR_ID_ON_MIPI0 = 0, +}; + +enum isp4fw_stream_id { + ISP4FW_STREAM_ID_INVALID = -1, + ISP4FW_STREAM_ID_1 = 0, + ISP4FW_STREAM_ID_2 = 1, + ISP4FW_STREAM_ID_3 = 2, + ISP4FW_STREAM_ID_MAXIMUM +}; + +enum isp4fw_image_format { + /* 4:2:0,semi-planar, 8-bit */ + ISP4FW_IMAGE_FORMAT_NV12 = 1, + /* interleave, 4:2:2, 8-bit */ + ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED = 7, +}; + +enum isp4fw_pipe_out_ch { + ISP4FW_ISP_PIPE_OUT_CH_PREVIEW = 0, +}; + +enum isp4fw_yuv_range { + ISP4FW_ISP_YUV_RANGE_FULL = 0, /* YUV value range in 0~255 */ + ISP4FW_ISP_YUV_RANGE_NARROW = 1, /* YUV value range in 16~235 */ + ISP4FW_ISP_YUV_RANGE_MAX +}; + +enum isp4fw_buffer_type { + ISP4FW_BUFFER_TYPE_PREVIEW = 8, + ISP4FW_BUFFER_TYPE_META_INFO = 10, + ISP4FW_BUFFER_TYPE_MEM_POOL = 15, +}; + +enum isp4fw_buffer_status { + /* The buffer is INVALID */ + ISP4FW_BUFFER_STATUS_INVALID, + /* The buffer is not filled with image data */ + ISP4FW_BUFFER_STATUS_SKIPPED, + /* The buffer is available and awaiting to be filled */ + ISP4FW_BUFFER_STATUS_EXIST, + /* The buffer is filled with image data */ + ISP4FW_BUFFER_STATUS_DONE, + /* The buffer is unavailable */ + ISP4FW_BUFFER_STATUS_LACK, + /* The buffer is dirty, probably caused by LMI leakage */ + ISP4FW_BUFFER_STATUS_DIRTY, + ISP4FW_BUFFER_STATUS_MAX +}; + +enum isp4fw_buffer_source { + /* The buffer is from the stream buffer queue */ + ISP4FW_BUFFER_SOURCE_STREAM, +}; + +struct isp4fw_error_code { + u32 code1; + u32 code2; + u32 code3; + u32 code4; + u32 code5; +}; + +/* Command Structure for FW */ + +struct isp4fw_cmd { + u32 cmd_seq_num; + u32 cmd_id; + u32 cmd_param[12]; + u16 cmd_stream_id; + u8 cmd_silent_resp; + u8 reserved; + u32 cmd_check_sum; +}; + +struct isp4fw_resp_cmd_done { + /* + * The host2fw command seqNum. + * To indicate which command this response refers to. + */ + u32 cmd_seq_num; + /* The host2fw command id for host double check. */ + u32 cmd_id; + /* + * Indicate the command process status. + * 0 means success. 1 means fail. 2 means skipped + */ + u16 cmd_status; + /* + * If cmd_status is 1, the command failed. The host can check + * isp4fw_error_code for details. + */ + u16 isp4fw_error_code; + /* The response payload type varies by cmd. */ + u8 payload[36]; +}; + +struct isp4fw_resp_param_package { + u32 package_addr_lo; /* The low 32 bit of the pkg address. */ + u32 package_addr_hi; /* The high 32 bit of the pkg address. */ + u32 package_size; /* The total pkg size in bytes. */ + u32 package_check_sum; /* The byte sum of the pkg. */ +}; + +struct isp4fw_resp { + u32 resp_seq_num; + u32 resp_id; + union { + struct isp4fw_resp_cmd_done cmd_done; + struct isp4fw_resp_param_package frame_done; + u32 resp_param[12]; + } param; + u8 reserved[4]; + u32 resp_check_sum; +}; + +struct isp4fw_mipi_pipe_path_cfg { + u32 b_enable; + enum isp4fw_sensor_id isp4fw_sensor_id; +}; + +struct isp4fw_isp_pipe_path_cfg { + u32 isp_pipe_id; /* pipe ids for pipeline construction */ +}; + +struct isp4fw_isp_stream_cfg { + /* Isp mipi path */ + struct isp4fw_mipi_pipe_path_cfg mipi_pipe_path_cfg; + /* Isp pipe path */ + struct isp4fw_isp_pipe_path_cfg isp_pipe_path_cfg; + /* enable TNR */ + u32 b_enable_tnr; + /* + * Number of frames for RTA processing. + * Set to 0 to use the firmware's default value. + */ + u32 rta_frames_per_proc; +}; + +struct isp4fw_image_prop { + enum isp4fw_image_format image_format; + u32 width; + u32 height; + u32 luma_pitch; + u32 chroma_pitch; + enum isp4fw_yuv_range yuv_range; +}; + +struct isp4fw_buffer { + /* + * A check num for debug usage, host can set the buf_tags + * to different numbers + */ + u32 buf_tags; + union { + u32 value; + struct { + u32 space : 16; + u32 vmid : 16; + } bit; + } vmid_space; + u32 buf_base_a_lo; /* Low address of buffer A */ + u32 buf_base_a_hi; /* High address of buffer A */ + u32 buf_size_a; /* Buffer size of buffer A */ + + u32 buf_base_b_lo; /* Low address of buffer B */ + u32 buf_base_b_hi; /* High address of buffer B */ + u32 buf_size_b; /* Buffer size of buffer B */ + + u32 buf_base_c_lo; /* Low address of buffer C */ + u32 buf_base_c_hi; /* High address of buffer C */ + u32 buf_size_c; /* Buffer size of buffer C */ +}; + +struct isp4fw_buffer_meta_info { + u32 enabled; /* enabled flag */ + enum isp4fw_buffer_status status; /* BufferStatus */ + struct isp4fw_error_code err; /* err code */ + enum isp4fw_buffer_source source; /* BufferSource */ + struct isp4fw_image_prop image_prop; /* image_prop */ + struct isp4fw_buffer buffer; /* buffer info */ +}; + +struct isp4fw_meta_info { + u32 poc; /* frame id */ + u32 fc_id; /* frame ctl id */ + u32 time_stamp_lo; /* timestamp low 32 bits */ + u32 time_stamp_hi; /* timestamp_high 32 bits */ + struct isp4fw_buffer_meta_info preview; /* preview BufferMetaInfo */ +}; + +struct isp4fw_cmd_send_buffer { + enum isp4fw_buffer_type buffer_type; + struct isp4fw_buffer buffer; /* buffer info */ +}; + +struct isp4fw_cmd_set_out_ch_prop { + enum isp4fw_pipe_out_ch ch; /* ISP output channel */ + struct isp4fw_image_prop image_prop; /* image property */ +}; + +struct isp4fw_cmd_enable_out_ch { + enum isp4fw_pipe_out_ch ch; /* ISP output channel */ + u32 is_enable; /* If channel is enabled or not */ +}; + +struct isp4fw_cmd_set_stream_cfg { + struct isp4fw_isp_stream_cfg stream_cfg; /* stream path config */ +}; + +#endif /* _ISP4_FW_CMD_RESP_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_hw_reg.h b/drivers/media/platform/amd/isp4/isp4_hw_reg.h new file mode 100644 index 000000000000..09c76f75c5ee --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_hw_reg.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_HW_REG_H_ +#define _ISP4_HW_REG_H_ + +#include + +#define ISP_SOFT_RESET 0x62000 +#define ISP_SYS_INT0_EN 0x62010 +#define ISP_SYS_INT0_STATUS 0x62014 +#define ISP_SYS_INT0_ACK 0x62018 +#define ISP_CCPU_CNTL 0x62054 +#define ISP_STATUS 0x62058 +#define ISP_LOG_RB_BASE_LO0 0x62148 +#define ISP_LOG_RB_BASE_HI0 0x6214c +#define ISP_LOG_RB_SIZE0 0x62150 +#define ISP_LOG_RB_RPTR0 0x62154 +#define ISP_LOG_RB_WPTR0 0x62158 +#define ISP_RB_BASE_LO1 0x62170 +#define ISP_RB_BASE_HI1 0x62174 +#define ISP_RB_SIZE1 0x62178 +#define ISP_RB_RPTR1 0x6217c +#define ISP_RB_WPTR1 0x62180 +#define ISP_RB_BASE_LO2 0x62184 +#define ISP_RB_BASE_HI2 0x62188 +#define ISP_RB_SIZE2 0x6218c +#define ISP_RB_RPTR2 0x62190 +#define ISP_RB_WPTR2 0x62194 +#define ISP_RB_BASE_LO3 0x62198 +#define ISP_RB_BASE_HI3 0x6219c +#define ISP_RB_SIZE3 0x621a0 +#define ISP_RB_RPTR3 0x621a4 +#define ISP_RB_WPTR3 0x621a8 +#define ISP_RB_BASE_LO4 0x621ac +#define ISP_RB_BASE_HI4 0x621b0 +#define ISP_RB_SIZE4 0x621b4 +#define ISP_RB_RPTR4 0x621b8 +#define ISP_RB_WPTR4 0x621bc +#define ISP_RB_BASE_LO5 0x621c0 +#define ISP_RB_BASE_HI5 0x621c4 +#define ISP_RB_SIZE5 0x621c8 +#define ISP_RB_RPTR5 0x621cc +#define ISP_RB_WPTR5 0x621d0 +#define ISP_RB_BASE_LO6 0x621d4 +#define ISP_RB_BASE_HI6 0x621d8 +#define ISP_RB_SIZE6 0x621dc +#define ISP_RB_RPTR6 0x621e0 +#define ISP_RB_WPTR6 0x621e4 +#define ISP_RB_BASE_LO7 0x621e8 +#define ISP_RB_BASE_HI7 0x621ec +#define ISP_RB_SIZE7 0x621f0 +#define ISP_RB_RPTR7 0x621f4 +#define ISP_RB_WPTR7 0x621f8 +#define ISP_RB_BASE_LO8 0x621fc +#define ISP_RB_BASE_HI8 0x62200 +#define ISP_RB_SIZE8 0x62204 +#define ISP_RB_RPTR8 0x62208 +#define ISP_RB_WPTR8 0x6220c +#define ISP_RB_BASE_LO9 0x62210 +#define ISP_RB_BASE_HI9 0x62214 +#define ISP_RB_SIZE9 0x62218 +#define ISP_RB_RPTR9 0x6221c +#define ISP_RB_WPTR9 0x62220 +#define ISP_RB_BASE_LO10 0x62224 +#define ISP_RB_BASE_HI10 0x62228 +#define ISP_RB_SIZE10 0x6222c +#define ISP_RB_RPTR10 0x62230 +#define ISP_RB_WPTR10 0x62234 +#define ISP_RB_BASE_LO11 0x62238 +#define ISP_RB_BASE_HI11 0x6223c +#define ISP_RB_SIZE11 0x62240 +#define ISP_RB_RPTR11 0x62244 +#define ISP_RB_WPTR11 0x62248 +#define ISP_RB_BASE_LO12 0x6224c +#define ISP_RB_BASE_HI12 0x62250 +#define ISP_RB_SIZE12 0x62254 +#define ISP_RB_RPTR12 0x62258 +#define ISP_RB_WPTR12 0x6225c + +#define ISP_POWER_STATUS 0x60000 + +/* ISP_SOFT_RESET */ +#define ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK 0x00000001UL + +/* ISP_CCPU_CNTL */ +#define ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK 0x00040000UL + +/* ISP_STATUS */ +#define ISP_STATUS__CCPU_REPORT_MASK 0x000000feUL + +/* ISP_SYS_INT0_STATUS */ +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK 0x00010000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK 0x00040000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK 0x00100000UL +#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK 0x00400000UL + +/* ISP_SYS_INT0_EN */ +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK 0x00010000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT10_EN_MASK 0x00040000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT11_EN_MASK 0x00100000UL +#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK 0x00400000UL + +/* ISP_SYS_INT0_ACK */ +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK 0x00010000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT10_ACK_MASK 0x00040000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT11_ACK_MASK 0x00100000UL +#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK 0x00400000UL + +/* Helper functions for reading isp registers */ +static inline u32 isp4hw_rreg(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +/* Helper functions for writing isp registers */ +static inline void isp4hw_wreg(void __iomem *base, u32 reg, u32 val) +{ + return writel(val, base + reg); +} + +#endif /* _ISP4_HW_REG_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_interface.c b/drivers/media/platform/amd/isp4/isp4_interface.c new file mode 100644 index 000000000000..4324d4adbcff --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_interface.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include + +#include "isp4_debug.h" +#include "isp4_fw_cmd_resp.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" + +#define ISP4IF_FW_RESP_RB_IRQ_EN_MASK \ + (ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK\ + | ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK) + +#define ISP4IF_FW_CMD_TIMEOUT (HZ / 2) + +struct isp4if_rb_config { + const char *name; + u32 index; + u32 reg_rptr; + u32 reg_wptr; + u32 reg_base_lo; + u32 reg_base_hi; + u32 reg_size; + u32 val_size; + u64 base_mc_addr; + void *base_sys_addr; +}; + +/* FW cmd ring buffer configuration */ +static struct isp4if_rb_config isp4if_cmd_rb_config[ISP4IF_STREAM_ID_MAX] = { + { + .name = "CMD_RB_GBL0", + .index = 3, + .reg_rptr = ISP_RB_RPTR4, + .reg_wptr = ISP_RB_WPTR4, + .reg_base_lo = ISP_RB_BASE_LO4, + .reg_base_hi = ISP_RB_BASE_HI4, + .reg_size = ISP_RB_SIZE4, + }, + { + .name = "CMD_RB_STR1", + .index = 0, + .reg_rptr = ISP_RB_RPTR1, + .reg_wptr = ISP_RB_WPTR1, + .reg_base_lo = ISP_RB_BASE_LO1, + .reg_base_hi = ISP_RB_BASE_HI1, + .reg_size = ISP_RB_SIZE1, + }, + { + .name = "CMD_RB_STR2", + .index = 1, + .reg_rptr = ISP_RB_RPTR2, + .reg_wptr = ISP_RB_WPTR2, + .reg_base_lo = ISP_RB_BASE_LO2, + .reg_base_hi = ISP_RB_BASE_HI2, + .reg_size = ISP_RB_SIZE2, + }, + { + .name = "CMD_RB_STR3", + .index = 2, + .reg_rptr = ISP_RB_RPTR3, + .reg_wptr = ISP_RB_WPTR3, + .reg_base_lo = ISP_RB_BASE_LO3, + .reg_base_hi = ISP_RB_BASE_HI3, + .reg_size = ISP_RB_SIZE3, + }, +}; + +/* FW resp ring buffer configuration */ +static struct isp4if_rb_config isp4if_resp_rb_config[ISP4IF_STREAM_ID_MAX] = { + { + .name = "RES_RB_GBL0", + .index = 3, + .reg_rptr = ISP_RB_RPTR12, + .reg_wptr = ISP_RB_WPTR12, + .reg_base_lo = ISP_RB_BASE_LO12, + .reg_base_hi = ISP_RB_BASE_HI12, + .reg_size = ISP_RB_SIZE12, + }, + { + .name = "RES_RB_STR1", + .index = 0, + .reg_rptr = ISP_RB_RPTR9, + .reg_wptr = ISP_RB_WPTR9, + .reg_base_lo = ISP_RB_BASE_LO9, + .reg_base_hi = ISP_RB_BASE_HI9, + .reg_size = ISP_RB_SIZE9, + }, + { + .name = "RES_RB_STR2", + .index = 1, + .reg_rptr = ISP_RB_RPTR10, + .reg_wptr = ISP_RB_WPTR10, + .reg_base_lo = ISP_RB_BASE_LO10, + .reg_base_hi = ISP_RB_BASE_HI10, + .reg_size = ISP_RB_SIZE10, + }, + { + .name = "RES_RB_STR3", + .index = 2, + .reg_rptr = ISP_RB_RPTR11, + .reg_wptr = ISP_RB_WPTR11, + .reg_base_lo = ISP_RB_BASE_LO11, + .reg_base_hi = ISP_RB_BASE_HI11, + .reg_size = ISP_RB_SIZE11, + }, +}; + +/* FW log ring buffer configuration */ +static struct isp4if_rb_config isp4if_log_rb_config = { + .name = "LOG_RB", + .index = 0, + .reg_rptr = ISP_LOG_RB_RPTR0, + .reg_wptr = ISP_LOG_RB_WPTR0, + .reg_base_lo = ISP_LOG_RB_BASE_LO0, + .reg_base_hi = ISP_LOG_RB_BASE_HI0, + .reg_size = ISP_LOG_RB_SIZE0, +}; + +static struct isp4if_gpu_mem_info * +isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size) +{ + struct isp4if_gpu_mem_info *mem_info; + struct device *dev = ispif->dev; + int ret; + + mem_info = kmalloc(sizeof(*mem_info), GFP_KERNEL); + if (!mem_info) + return NULL; + + mem_info->mem_size = mem_size; + ret = isp_kernel_buffer_alloc(dev, mem_info->mem_size, + &mem_info->mem_handle, + &mem_info->gpu_mc_addr, + &mem_info->sys_addr); + if (ret) { + kfree(mem_info); + return NULL; + } + + return mem_info; +} + +static void isp4if_gpu_mem_free(struct isp4_interface *ispif, + struct isp4if_gpu_mem_info **mem_info_ptr) +{ + struct isp4if_gpu_mem_info *mem_info = *mem_info_ptr; + struct device *dev = ispif->dev; + + if (!mem_info) { + dev_err(dev, "invalid mem_info\n"); + return; + } + + *mem_info_ptr = NULL; + isp_kernel_buffer_free(&mem_info->mem_handle, &mem_info->gpu_mc_addr, + &mem_info->sys_addr); + kfree(mem_info); +} + +static void isp4if_dealloc_fw_gpumem(struct isp4_interface *ispif) +{ + isp4if_gpu_mem_free(ispif, &ispif->fw_mem_pool); + isp4if_gpu_mem_free(ispif, &ispif->fw_cmd_resp_buf); + isp4if_gpu_mem_free(ispif, &ispif->fw_log_buf); + + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) + isp4if_gpu_mem_free(ispif, &ispif->meta_info_buf[i]); +} + +static int isp4if_alloc_fw_gpumem(struct isp4_interface *ispif) +{ + struct device *dev = ispif->dev; + + ispif->fw_mem_pool = isp4if_gpu_mem_alloc(ispif, + ISP4FW_MEMORY_POOL_SIZE); + if (!ispif->fw_mem_pool) + goto error_no_memory; + + ispif->fw_cmd_resp_buf = + isp4if_gpu_mem_alloc(ispif, ISP4IF_RB_PMBMAP_MEM_SIZE); + if (!ispif->fw_cmd_resp_buf) + goto error_no_memory; + + ispif->fw_log_buf = + isp4if_gpu_mem_alloc(ispif, ISP4IF_FW_LOG_RINGBUF_SIZE); + if (!ispif->fw_log_buf) + goto error_no_memory; + + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + ispif->meta_info_buf[i] = + isp4if_gpu_mem_alloc(ispif, ISP4IF_META_INFO_BUF_SIZE); + if (!ispif->meta_info_buf[i]) + goto error_no_memory; + } + + return 0; + +error_no_memory: + dev_err(dev, "failed to allocate gpu memory\n"); + return -ENOMEM; +} + +static u32 isp4if_compute_check_sum(const void *buf, size_t buf_size) +{ + const u8 *surplus_ptr; + const u32 *buffer; + u32 checksum = 0; + size_t i; + + buffer = (const u32 *)buf; + for (i = 0; i < buf_size / sizeof(u32); i++) + checksum += buffer[i]; + + surplus_ptr = (const u8 *)&buffer[i]; + /* add surplus data crc checksum */ + for (i = 0; i < buf_size % sizeof(u32); i++) + checksum += surplus_ptr[i]; + + return checksum; +} + +void isp4if_clear_cmdq(struct isp4_interface *ispif) +{ + struct isp4if_cmd_element *buf_node, *tmp_node; + LIST_HEAD(free_list); + + scoped_guard(spinlock, &ispif->cmdq_lock) + list_splice_init(&ispif->cmdq, &free_list); + + list_for_each_entry_safe(buf_node, tmp_node, &free_list, list) + kfree(buf_node); +} + +static bool isp4if_is_cmdq_rb_full(struct isp4_interface *ispif, + enum isp4if_stream_id stream) +{ + struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + u32 len = rb_config->val_size; + u32 rd_ptr, wr_ptr; + u32 bytes_free; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + + /* + * Read and write pointers are equal, indicating the ring buffer + * is empty + */ + if (wr_ptr == rd_ptr) + return false; + + if (wr_ptr > rd_ptr) + bytes_free = len - (wr_ptr - rd_ptr); + else + bytes_free = rd_ptr - wr_ptr; + + /* + * Ignore one byte from the bytes free to prevent rd_ptr from equaling + * wr_ptr when the ring buffer is full, because rd_ptr == wr_ptr is + * supposed to indicate that the ring buffer is empty. + */ + return bytes_free <= sizeof(struct isp4fw_cmd); +} + +struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif, + u32 seq_num, u32 cmd_id) +{ + struct isp4if_cmd_element *ele; + + guard(spinlock)(&ispif->cmdq_lock); + + list_for_each_entry(ele, &ispif->cmdq, list) { + if (ele->seq_num == seq_num && ele->cmd_id == cmd_id) { + list_del(&ele->list); + return ele; + } + } + + return NULL; +} + +/* Must check that isp4if_is_cmdq_rb_full() == false before calling */ +static int isp4if_insert_isp_fw_cmd(struct isp4_interface *ispif, + enum isp4if_stream_id stream, + const struct isp4fw_cmd *cmd) +{ + struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + void *mem_sys = rb_config->base_sys_addr; + const u32 cmd_sz = sizeof(*cmd); + struct device *dev = ispif->dev; + u32 len = rb_config->val_size; + const void *src = cmd; + u32 rd_ptr, wr_ptr; + u32 bytes_to_end; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + if (rd_ptr >= len || wr_ptr >= len) { + dev_err(dev, + "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, cmd_sz=%u\n", + stream, isp4dbg_get_if_stream_str(stream), rd_ptr, + wr_ptr, len, cmd_sz); + return -EINVAL; + } + + bytes_to_end = len - wr_ptr; + if (bytes_to_end >= cmd_sz) { + /* FW cmd is just a straight copy to the write pointer */ + memcpy(mem_sys + wr_ptr, src, cmd_sz); + isp4hw_wreg(ispif->mmio, wreg, (wr_ptr + cmd_sz) % len); + } else { + /* + * FW cmd is split because the ring buffer needs to wrap + * around + */ + memcpy(mem_sys + wr_ptr, src, bytes_to_end); + memcpy(mem_sys, src + bytes_to_end, cmd_sz - bytes_to_end); + isp4hw_wreg(ispif->mmio, wreg, cmd_sz - bytes_to_end); + } + + return 0; +} + +static inline enum isp4if_stream_id isp4if_get_fw_stream(u32 cmd_id) +{ + return ISP4IF_STREAM_ID_1; +} + +static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id, + const void *package, + u32 package_size, bool sync) +{ + enum isp4if_stream_id stream = isp4if_get_fw_stream(cmd_id); + struct isp4if_cmd_element *ele = NULL; + struct device *dev = ispif->dev; + struct isp4fw_cmd cmd; + u32 seq_num; + int ret; + + if (package_size > sizeof(cmd.cmd_param)) { + dev_err(dev, "fail pkgsize(%u) > %zu cmd:0x%x, stream %d\n", + package_size, sizeof(cmd.cmd_param), cmd_id, stream); + return -EINVAL; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = cmd_id; + switch (stream) { + case ISP4IF_STREAM_ID_GLOBAL: + cmd.cmd_stream_id = ISP4FW_STREAM_ID_INVALID; + break; + case ISP4IF_STREAM_ID_1: + cmd.cmd_stream_id = ISP4FW_STREAM_ID_1; + break; + default: + dev_err(dev, "fail bad stream id %d\n", stream); + return -EINVAL; + } + + /* Allocate the sync command object early and outside of the lock */ + if (sync) { + ele = kmalloc(sizeof(*ele), GFP_KERNEL); + if (!ele) + return -ENOMEM; + + /* Get two references: one for the resp thread, one for us */ + atomic_set(&ele->refcnt, 2); + init_completion(&ele->cmd_done); + } + + if (package && package_size) + memcpy(cmd.cmd_param, package, package_size); + + scoped_guard(mutex, &ispif->isp4if_mutex) { + ret = read_poll_timeout(isp4if_is_cmdq_rb_full, ret, !ret, + ISP4IF_RB_FULL_SLEEP_US, + ISP4IF_RB_FULL_TIMEOUT_US, false, ispif, + stream); + if (ret) { + struct isp4if_rb_config *rb_config = + &isp4if_resp_rb_config[stream]; + u32 rd_ptr = isp4hw_rreg(ispif->mmio, + rb_config->reg_rptr); + u32 wr_ptr = isp4hw_rreg(ispif->mmio, + rb_config->reg_wptr); + + dev_err(dev, + "failed to get free cmdq slot, stream %s(%d),rd %u, wr %u\n", + isp4dbg_get_if_stream_str(stream), stream, + rd_ptr, wr_ptr); + ret = -ETIMEDOUT; + goto free_ele; + } + + seq_num = ispif->host2fw_seq_num++; + cmd.cmd_seq_num = seq_num; + cmd.cmd_check_sum = isp4if_compute_check_sum(&cmd, sizeof(cmd) + - sizeof(u32)); + + /* + * only append the fw cmd to queue when its response needs to + * be waited for, currently there are only two such commands, + * disable channel and stop stream which are only sent after + * close camera + */ + if (ele) { + ele->seq_num = seq_num; + ele->cmd_id = cmd_id; + scoped_guard(spinlock, &ispif->cmdq_lock) + list_add_tail(&ele->list, &ispif->cmdq); + } + + ret = isp4if_insert_isp_fw_cmd(ispif, stream, &cmd); + if (ret) { + dev_err(dev, + "fail for insert_isp_fw_cmd cmd_id %s(0x%08x)\n", + isp4dbg_get_cmd_str(cmd_id), cmd_id); + goto err_dequeue_ele; + } + } + + if (ele) { + ret = wait_for_completion_timeout(&ele->cmd_done, + ISP4IF_FW_CMD_TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto err_dequeue_ele; + } + + ret = 0; + goto put_ele_ref; + } + + return 0; + +err_dequeue_ele: + /* + * Try to remove the command from the queue. If that fails, then it + * means the response thread is currently using the object, and we need + * to use the refcount to avoid a use-after-free by either side. + */ + if (ele && isp4if_rm_cmd_from_cmdq(ispif, seq_num, cmd_id)) + goto free_ele; + +put_ele_ref: + /* Don't free the command if we didn't put the last reference */ + if (ele && atomic_dec_return(&ele->refcnt)) + ele = NULL; + +free_ele: + kfree(ele); + return ret; +} + +static int isp4if_send_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_info *buf_info) +{ + struct isp4fw_cmd_send_buffer cmd; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.buffer_type = ISP4FW_BUFFER_TYPE_PREVIEW; + cmd.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(buf_info->planes[0].mc_addr, + &cmd.buffer.buf_base_a_lo, + &cmd.buffer.buf_base_a_hi); + cmd.buffer.buf_size_a = buf_info->planes[0].len; + + isp4if_split_addr64(buf_info->planes[1].mc_addr, + &cmd.buffer.buf_base_b_lo, + &cmd.buffer.buf_base_b_hi); + cmd.buffer.buf_size_b = buf_info->planes[1].len; + + isp4if_split_addr64(buf_info->planes[2].mc_addr, + &cmd.buffer.buf_base_c_lo, + &cmd.buffer.buf_base_c_hi); + cmd.buffer.buf_size_c = buf_info->planes[2].len; + + return isp4if_send_fw_cmd(ispif, ISP4FW_CMD_ID_SEND_BUFFER, &cmd, + sizeof(cmd), false); +} + +static void isp4if_init_rb_config(struct isp4_interface *ispif, + struct isp4if_rb_config *rb_config) +{ + isp4hw_wreg(ispif->mmio, rb_config->reg_rptr, 0x0); + isp4hw_wreg(ispif->mmio, rb_config->reg_wptr, 0x0); + isp4hw_wreg(ispif->mmio, rb_config->reg_base_lo, + rb_config->base_mc_addr); + isp4hw_wreg(ispif->mmio, rb_config->reg_base_hi, + rb_config->base_mc_addr >> 32); + isp4hw_wreg(ispif->mmio, rb_config->reg_size, rb_config->val_size); +} + +static int isp4if_fw_init(struct isp4_interface *ispif) +{ + u32 aligned_rb_chunk_size = ISP4IF_RB_PMBMAP_MEM_CHUNK & 0xffffffc0; + struct isp4if_rb_config *rb_config; + u32 offset; + unsigned int i; + + /* initialize CMD_RB streams */ + for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) { + rb_config = (isp4if_cmd_rb_config + i); + offset = aligned_rb_chunk_size * rb_config->index; + + rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE; + rb_config->base_sys_addr = + ispif->fw_cmd_resp_buf->sys_addr + offset; + rb_config->base_mc_addr = + ispif->fw_cmd_resp_buf->gpu_mc_addr + offset; + + isp4if_init_rb_config(ispif, rb_config); + } + + /* initialize RESP_RB streams */ + for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) { + rb_config = (isp4if_resp_rb_config + i); + offset = aligned_rb_chunk_size * + (rb_config->index + ISP4IF_RESP_CHAN_TO_RB_OFFSET - 1); + + rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE; + rb_config->base_sys_addr = + ispif->fw_cmd_resp_buf->sys_addr + offset; + rb_config->base_mc_addr = + ispif->fw_cmd_resp_buf->gpu_mc_addr + offset; + + isp4if_init_rb_config(ispif, rb_config); + } + + /* initialize LOG_RB stream */ + rb_config = &isp4if_log_rb_config; + rb_config->val_size = ISP4IF_FW_LOG_RINGBUF_SIZE; + rb_config->base_mc_addr = ispif->fw_log_buf->gpu_mc_addr; + rb_config->base_sys_addr = ispif->fw_log_buf->sys_addr; + + isp4if_init_rb_config(ispif, rb_config); + + return 0; +} + +static int isp4if_wait_fw_ready(struct isp4_interface *ispif, + u32 isp_status_addr) +{ + struct device *dev = ispif->dev; + u32 timeout_ms = 100; + u32 interval_ms = 1; + u32 reg_val; + + /* wait for FW initialize done! */ + if (!read_poll_timeout(isp4hw_rreg, reg_val, reg_val + & ISP_STATUS__CCPU_REPORT_MASK, + interval_ms * 1000, timeout_ms * 1000, false, + ispif->mmio, isp_status_addr)) + return 0; + + dev_err(dev, "ISP CCPU FW boot failed\n"); + + return -ETIME; +} + +static void isp4if_enable_ccpu(struct isp4_interface *ispif) +{ + u32 reg_val; + + reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET); + reg_val &= (~ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK); + isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val); + + usleep_range(100, 150); + + reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL); + reg_val &= (~ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK); + isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val); +} + +static void isp4if_disable_ccpu(struct isp4_interface *ispif) +{ + u32 reg_val; + + reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL); + reg_val |= ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK; + isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val); + + usleep_range(100, 150); + + reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET); + reg_val |= ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK; + isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val); +} + +static int isp4if_fw_boot(struct isp4_interface *ispif) +{ + struct device *dev = ispif->dev; + + if (ispif->status != ISP4IF_STATUS_PWR_ON) { + dev_err(dev, "invalid isp power status %d\n", ispif->status); + return -EINVAL; + } + + isp4if_disable_ccpu(ispif); + + isp4if_fw_init(ispif); + + /* clear ccpu status */ + isp4hw_wreg(ispif->mmio, ISP_STATUS, 0x0); + + isp4if_enable_ccpu(ispif); + + if (isp4if_wait_fw_ready(ispif, ISP_STATUS)) { + isp4if_disable_ccpu(ispif); + return -EINVAL; + } + + /* enable interrupts */ + isp4hw_wreg(ispif->mmio, ISP_SYS_INT0_EN, + ISP4IF_FW_RESP_RB_IRQ_EN_MASK); + + ispif->status = ISP4IF_STATUS_FW_RUNNING; + + dev_dbg(dev, "ISP CCPU FW boot success\n"); + + return 0; +} + +int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream, + struct isp4fw_resp *resp) +{ + struct isp4if_rb_config *rb_config = &isp4if_resp_rb_config[stream]; + u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr; + void *mem_sys = rb_config->base_sys_addr; + const u32 resp_sz = sizeof(*resp); + struct device *dev = ispif->dev; + u32 len = rb_config->val_size; + u32 rd_ptr, wr_ptr; + u32 bytes_to_end; + void *dst = resp; + u32 checksum; + + rd_ptr = isp4hw_rreg(ispif->mmio, rreg); + wr_ptr = isp4hw_rreg(ispif->mmio, wreg); + if (rd_ptr >= len || wr_ptr >= len) + goto err_rb_invalid; + + /* + * Read and write pointers are equal, indicating the ring buffer is + * empty + */ + if (rd_ptr == wr_ptr) + return -ENODATA; + + bytes_to_end = len - rd_ptr; + if (bytes_to_end >= resp_sz) { + /* FW response is just a straight copy from the read pointer */ + if (wr_ptr > rd_ptr && wr_ptr - rd_ptr < resp_sz) + goto err_rb_invalid; + + memcpy(dst, mem_sys + rd_ptr, resp_sz); + isp4hw_wreg(ispif->mmio, rreg, (rd_ptr + resp_sz) % len); + } else { + /* + * FW response is split because the ring buffer wrapped + * around + */ + if (wr_ptr > rd_ptr || wr_ptr < resp_sz - bytes_to_end) + goto err_rb_invalid; + + memcpy(dst, mem_sys + rd_ptr, bytes_to_end); + memcpy(dst + bytes_to_end, mem_sys, resp_sz - bytes_to_end); + isp4hw_wreg(ispif->mmio, rreg, resp_sz - bytes_to_end); + } + + checksum = isp4if_compute_check_sum(resp, resp_sz - sizeof(u32)); + if (checksum != resp->resp_check_sum) { + dev_err(dev, "resp checksum 0x%x,should 0x%x,rptr %u,wptr %u\n", + checksum, resp->resp_check_sum, rd_ptr, wr_ptr); + dev_err(dev, "%s(%u), seqNo %u, resp_id %s(0x%x)\n", + isp4dbg_get_if_stream_str(stream), stream, + resp->resp_seq_num, isp4dbg_get_resp_str(resp->resp_id), + resp->resp_id); + return -EINVAL; + } + + return 0; + +err_rb_invalid: + dev_err(dev, + "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, resp_sz=%u\n", + stream, isp4dbg_get_if_stream_str(stream), rd_ptr, wr_ptr, len, + resp_sz); + return -EINVAL; +} + +int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size) +{ + return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, false); +} + +int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size) +{ + return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, true); +} + +void isp4if_clear_bufq(struct isp4_interface *ispif) +{ + struct isp4if_img_buf_node *buf_node, *tmp_node; + LIST_HEAD(free_list); + + scoped_guard(spinlock, &ispif->bufq_lock) + list_splice_init(&ispif->bufq, &free_list); + + list_for_each_entry_safe(buf_node, tmp_node, &free_list, node) + kfree(buf_node); +} + +void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node) +{ + kfree(buf_node); +} + +struct isp4if_img_buf_node * +isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info) +{ + struct isp4if_img_buf_node *node; + + node = kmalloc(sizeof(*node), GFP_KERNEL); + if (node) + node->buf_info = *buf_info; + + return node; +} + +struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif) +{ + struct isp4if_img_buf_node *buf_node; + + guard(spinlock)(&ispif->bufq_lock); + + buf_node = list_first_entry_or_null(&ispif->bufq, typeof(*buf_node), + node); + if (buf_node) + list_del(&buf_node->node); + + return buf_node; +} + +int isp4if_queue_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_node *buf_node) +{ + int ret; + + ret = isp4if_send_buffer(ispif, &buf_node->buf_info); + if (ret) + return ret; + + scoped_guard(spinlock, &ispif->bufq_lock) + list_add_tail(&buf_node->node, &ispif->bufq); + + return 0; +} + +int isp4if_stop(struct isp4_interface *ispif) +{ + isp4if_disable_ccpu(ispif); + + isp4if_dealloc_fw_gpumem(ispif); + + return 0; +} + +int isp4if_start(struct isp4_interface *ispif) +{ + int ret; + + ret = isp4if_alloc_fw_gpumem(ispif); + if (ret) + return ret; + + ret = isp4if_fw_boot(ispif); + if (ret) + goto failed_fw_boot; + + return 0; + +failed_fw_boot: + isp4if_dealloc_fw_gpumem(ispif); + return ret; +} + +int isp4if_deinit(struct isp4_interface *ispif) +{ + isp4if_clear_cmdq(ispif); + + isp4if_clear_bufq(ispif); + + mutex_destroy(&ispif->isp4if_mutex); + + return 0; +} + +int isp4if_init(struct isp4_interface *ispif, struct device *dev, + void __iomem *isp_mmio) +{ + ispif->dev = dev; + ispif->mmio = isp_mmio; + + spin_lock_init(&ispif->cmdq_lock); /* used for cmdq access */ + spin_lock_init(&ispif->bufq_lock); /* used for bufq access */ + mutex_init(&ispif->isp4if_mutex); /* used for commands sent to ispfw */ + + INIT_LIST_HEAD(&ispif->cmdq); + INIT_LIST_HEAD(&ispif->bufq); + + return 0; +} diff --git a/drivers/media/platform/amd/isp4/isp4_interface.h b/drivers/media/platform/amd/isp4/isp4_interface.h new file mode 100644 index 000000000000..ce3ac9b9e5cd --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_interface.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_INTERFACE_H_ +#define _ISP4_INTERFACE_H_ + +#include +#include +#include +#include + +struct isp4fw_resp; + +#define ISP4IF_RB_MAX 25 +#define ISP4IF_RESP_CHAN_TO_RB_OFFSET 9 +#define ISP4IF_RB_PMBMAP_MEM_SIZE (SZ_16M - 1) +#define ISP4IF_RB_PMBMAP_MEM_CHUNK \ + (ISP4IF_RB_PMBMAP_MEM_SIZE / (ISP4IF_RB_MAX - 1)) +#define ISP4IF_HOST2FW_COMMAND_SIZE sizeof(struct isp4fw_cmd) +#define ISP4IF_MAX_NUM_HOST2FW_COMMAND 40 +#define ISP4IF_FW_CMD_BUF_SIZE \ + (ISP4IF_MAX_NUM_HOST2FW_COMMAND * ISP4IF_HOST2FW_COMMAND_SIZE) +#define ISP4IF_RB_FULL_SLEEP_US (33 * USEC_PER_MSEC) +#define ISP4IF_RB_FULL_TIMEOUT_US (10 * ISP4IF_RB_FULL_SLEEP_US) + +#define ISP4IF_META_INFO_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000) +#define ISP4IF_MAX_STREAM_BUF_COUNT 8 + +#define ISP4IF_FW_LOG_RINGBUF_SIZE SZ_2M + +enum isp4if_stream_id { + ISP4IF_STREAM_ID_GLOBAL = 0, + ISP4IF_STREAM_ID_1 = 1, + ISP4IF_STREAM_ID_MAX = 4 +}; + +enum isp4if_status { + ISP4IF_STATUS_PWR_OFF, + ISP4IF_STATUS_PWR_ON, + ISP4IF_STATUS_FW_RUNNING, + ISP4IF_FSM_STATUS_MAX +}; + +struct isp4if_gpu_mem_info { + u64 mem_size; + u64 gpu_mc_addr; + void *sys_addr; + void *mem_handle; +}; + +struct isp4if_img_buf_info { + struct { + void *sys_addr; + u64 mc_addr; + u32 len; + } planes[3]; +}; + +struct isp4if_img_buf_node { + struct list_head node; + struct isp4if_img_buf_info buf_info; +}; + +struct isp4if_cmd_element { + struct list_head list; + u32 seq_num; + u32 cmd_id; + struct completion cmd_done; + atomic_t refcnt; +}; + +struct isp4_interface { + struct device *dev; + void __iomem *mmio; + + spinlock_t cmdq_lock; /* used for cmdq access */ + spinlock_t bufq_lock; /* used for bufq access */ + struct mutex isp4if_mutex; /* used to send fw cmd and read fw log */ + + struct list_head cmdq; /* commands sent to fw */ + struct list_head bufq; /* buffers sent to fw */ + + enum isp4if_status status; + u32 host2fw_seq_num; + + /* ISP fw buffers */ + struct isp4if_gpu_mem_info *fw_log_buf; + struct isp4if_gpu_mem_info *fw_cmd_resp_buf; + struct isp4if_gpu_mem_info *fw_mem_pool; + struct isp4if_gpu_mem_info *meta_info_buf[ISP4IF_MAX_STREAM_BUF_COUNT]; +}; + +static inline void isp4if_split_addr64(u64 addr, u32 *lo, u32 *hi) +{ + if (lo) + *lo = addr & 0xffffffff; + + if (hi) + *hi = addr >> 32; +} + +static inline u64 isp4if_join_addr64(u32 lo, u32 hi) +{ + return (((u64)hi) << 32) | (u64)lo; +} + +int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream, + struct isp4fw_resp *resp); + +int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size); + +int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id, + const void *package, u32 package_size); + +struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif, + u32 seq_num, u32 cmd_id); + +void isp4if_clear_cmdq(struct isp4_interface *ispif); + +void isp4if_clear_bufq(struct isp4_interface *ispif); + +void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node); + +struct isp4if_img_buf_node * +isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info); + +struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif); + +int isp4if_queue_buffer(struct isp4_interface *ispif, + struct isp4if_img_buf_node *buf_node); + +int isp4if_stop(struct isp4_interface *ispif); + +int isp4if_start(struct isp4_interface *ispif); + +int isp4if_deinit(struct isp4_interface *ispif); + +int isp4if_init(struct isp4_interface *ispif, struct device *dev, + void __iomem *isp_mmio); + +#endif /* _ISP4_INTERFACE_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c new file mode 100644 index 000000000000..48deea79ce6c --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.c @@ -0,0 +1,1047 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "isp4.h" +#include "isp4_debug.h" +#include "isp4_fw_cmd_resp.h" +#include "isp4_interface.h" + +#define ISP4SD_MIN_BUF_CNT_BEF_START_STREAM 4 + +#define ISP4SD_PERFORMANCE_STATE_LOW 0 +#define ISP4SD_PERFORMANCE_STATE_HIGH 1 + +/* align 32KB */ +#define ISP4SD_META_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000) + +#define to_isp4_subdev(sd) container_of(sd, struct isp4_subdev, sdev) + +static const char *isp4sd_entity_name = "amd isp4"; + +static const char *isp4sd_thread_name[ISP4SD_MAX_FW_RESP_STREAM_NUM] = { + "amd_isp4_thread_global", + "amd_isp4_thread_stream1", +}; + +static void isp4sd_module_enable(struct isp4_subdev *isp_subdev, bool enable) +{ + if (isp_subdev->enable_gpio) { + gpiod_set_value(isp_subdev->enable_gpio, enable ? 1 : 0); + dev_dbg(isp_subdev->dev, "%s isp_subdev module\n", + enable ? "enable" : "disable"); + } +} + +static int isp4sd_setup_fw_mem_pool(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + int ret; + + if (!ispif->fw_mem_pool) { + dev_err(dev, "fail to alloc mem pool\n"); + return -ENOMEM; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_MEM_POOL; + buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(ispif->fw_mem_pool->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = ispif->fw_mem_pool->mem_size; + + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send fw mem pool 0x%llx(%u) fail %d\n", + ispif->fw_mem_pool->gpu_mc_addr, + buf_type.buffer.buf_size_a, ret); + return ret; + } + + dev_dbg(dev, "send fw mem pool 0x%llx(%u) suc\n", + ispif->fw_mem_pool->gpu_mc_addr, buf_type.buffer.buf_size_a); + + return 0; +} + +static int isp4sd_set_stream_path(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_set_stream_cfg cmd; + struct device *dev = isp_subdev->dev; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id = + ISP4FW_SENSOR_ID_ON_MIPI0; + cmd.stream_cfg.mipi_pipe_path_cfg.b_enable = true; + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id = + ISP4FW_MIPI0_ISP_PIPELINE_ID; + + cmd.stream_cfg.b_enable_tnr = true; + dev_dbg(dev, "isp4fw_sensor_id %d, pipeId 0x%x EnableTnr %u\n", + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id, + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id, + cmd.stream_cfg.b_enable_tnr); + + return isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_STREAM_CONFIG, + &cmd, sizeof(cmd)); +} + +static int isp4sd_send_meta_buf(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf = + isp_subdev->ispif.meta_info_buf[i]; + int ret; + + if (!meta_info_buf) { + dev_err(dev, "fail for no meta info buf(%u)\n", i); + return -ENOMEM; + } + + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space = + ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(meta_info_buf->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = meta_info_buf->mem_size; + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send meta info(%u) fail\n", i); + return ret; + } + } + + dev_dbg(dev, "send meta info suc\n"); + return 0; +} + +static bool isp4sd_get_str_out_prop(struct isp4_subdev *isp_subdev, + struct isp4fw_image_prop *out_prop, + struct v4l2_subdev_state *state, u32 pad) +{ + struct device *dev = isp_subdev->dev; + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(dev, "fail get subdev state format\n"); + return false; + } + + switch (format->code) { + case MEDIA_BUS_FMT_YUYV8_1_5X8: + out_prop->image_format = ISP4FW_IMAGE_FORMAT_NV12; + out_prop->width = format->width; + out_prop->height = format->height; + out_prop->luma_pitch = format->width; + out_prop->chroma_pitch = out_prop->width; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + out_prop->image_format = ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED; + out_prop->width = format->width; + out_prop->height = format->height; + out_prop->luma_pitch = format->width * 2; + out_prop->chroma_pitch = 0; + break; + default: + dev_err(dev, "fail for bad image format:0x%x\n", + format->code); + return false; + } + + if (!out_prop->width || !out_prop->height) + return false; + + return true; +} + +static int isp4sd_kickoff_stream(struct isp4_subdev *isp_subdev, u32 w, u32 h) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + + if (sensor_info->status == ISP4SD_START_STATUS_STARTED) + return 0; + + if (sensor_info->status == ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + dev_dbg(dev, "w:%u,h:%u\n", w, h); + + if (isp4sd_send_meta_buf(isp_subdev)) { + dev_err(dev, "fail to send meta buf\n"); + sensor_info->status = ISP4SD_START_STATUS_START_FAIL; + return -EINVAL; + } + + sensor_info->status = ISP4SD_START_STATUS_OFF; + + if (!sensor_info->start_stream_cmd_sent && + sensor_info->buf_sent_cnt >= ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) { + int ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_START_STREAM, + NULL, 0); + if (ret) { + dev_err(dev, "fail to start stream\n"); + return ret; + } + + sensor_info->start_stream_cmd_sent = true; + } else { + dev_dbg(dev, + "no send START_STREAM, start_sent %u, buf_sent %u\n", + sensor_info->start_stream_cmd_sent, + sensor_info->buf_sent_cnt); + } + + return 0; +} + +static int isp4sd_setup_output(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info = + &isp_subdev->sensor_info.output_info; + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_set_out_ch_prop cmd_ch_prop; + struct isp4fw_cmd_enable_out_ch cmd_ch_en; + struct device *dev = isp_subdev->dev; + int ret; + + if (output_info->start_status == ISP4SD_START_STATUS_STARTED) + return 0; + + if (output_info->start_status == ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd_ch_prop, 0, sizeof(cmd_ch_prop)); + cmd_ch_prop.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + + if (!isp4sd_get_str_out_prop(isp_subdev, + &cmd_ch_prop.image_prop, state, pad)) { + dev_err(dev, "fail to get out prop\n"); + return -EINVAL; + } + + dev_dbg(dev, "channel:%s,fmt %s,w:h=%u:%u,lp:%u,cp%u\n", + isp4dbg_get_out_ch_str(cmd_ch_prop.ch), + isp4dbg_get_img_fmt_str(cmd_ch_prop.image_prop.image_format), + cmd_ch_prop.image_prop.width, cmd_ch_prop.image_prop.height, + cmd_ch_prop.image_prop.luma_pitch, + cmd_ch_prop.image_prop.chroma_pitch); + + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_OUT_CHAN_PROP, + &cmd_ch_prop, sizeof(cmd_ch_prop)); + if (ret) { + output_info->start_status = ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to set out prop\n"); + return ret; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&cmd_ch_en, 0, sizeof(cmd_ch_en)); + cmd_ch_en.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + cmd_ch_en.is_enable = true; + ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_en, sizeof(cmd_ch_en)); + if (ret) { + output_info->start_status = ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to enable channel\n"); + return ret; + } + + dev_dbg(dev, "enable channel %s\n", + isp4dbg_get_out_ch_str(cmd_ch_en.ch)); + + if (!sensor_info->start_stream_cmd_sent) { + ret = isp4sd_kickoff_stream(isp_subdev, + cmd_ch_prop.image_prop.width, + cmd_ch_prop.image_prop.height); + if (ret) { + dev_err(dev, "kickoff stream fail %d\n", ret); + return ret; + } + /* + * sensor_info->start_stream_cmd_sent will be set to true + * 1. in isp4sd_kickoff_stream, if app first send buffer then + * start stream + * 2. in isp_set_stream_buf, if app first start stream, then + * send buffer because ISP FW has the requirement, host needs + * to send buffer before send start stream cmd + */ + if (sensor_info->start_stream_cmd_sent) { + sensor_info->status = ISP4SD_START_STATUS_STARTED; + output_info->start_status = ISP4SD_START_STATUS_STARTED; + dev_dbg(dev, "kickoff stream suc,start cmd sent\n"); + } + } else { + dev_dbg(dev, "stream running, no need kickoff\n"); + output_info->start_status = ISP4SD_START_STATUS_STARTED; + } + + dev_dbg(dev, "setup output suc\n"); + return 0; +} + +static int isp4sd_init_stream(struct isp4_subdev *isp_subdev) +{ + struct device *dev = isp_subdev->dev; + int ret; + + ret = isp4sd_setup_fw_mem_pool(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup fw mem pool\n"); + return ret; + } + + ret = isp4sd_set_stream_path(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup stream path\n"); + return ret; + } + + return 0; +} + +static void isp4sd_uninit_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4sd_output_info *output_info = &sensor_info->output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(isp_subdev->dev, "fail to get v4l2 format\n"); + } else { + memset(format, 0, sizeof(*format)); + format->code = MEDIA_BUS_FMT_YUYV8_1_5X8; + } + + isp4if_clear_bufq(ispif); + isp4if_clear_cmdq(ispif); + + sensor_info->start_stream_cmd_sent = false; + sensor_info->buf_sent_cnt = 0; + + sensor_info->status = ISP4SD_START_STATUS_OFF; + output_info->start_status = ISP4SD_START_STATUS_OFF; +} + +static void isp4sd_fw_resp_cmd_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_cmd_done *para) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4if_cmd_element *ele = + isp4if_rm_cmd_from_cmdq(ispif, para->cmd_seq_num, para->cmd_id); + struct device *dev = isp_subdev->dev; + + dev_dbg(dev, "stream %d,cmd %s(0x%08x)(%d),seq %u, ele %p\n", + stream_id, + isp4dbg_get_cmd_str(para->cmd_id), + para->cmd_id, para->cmd_status, para->cmd_seq_num, + ele); + + if (ele) { + complete(&ele->cmd_done); + if (atomic_dec_and_test(&ele->refcnt)) + kfree(ele); + } +} + +static struct isp4fw_meta_info * +isp4sd_get_meta_by_mc(struct isp4_subdev *isp_subdev, u64 mc) +{ + for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf = + isp_subdev->ispif.meta_info_buf[i]; + + if (meta_info_buf->gpu_mc_addr == mc) + return meta_info_buf->sys_addr; + } + + return NULL; +} + +static void isp4sd_send_meta_info(struct isp4_subdev *isp_subdev, + u64 meta_info_mc) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev = isp_subdev->dev; + + if (isp_subdev->sensor_info.status != ISP4SD_START_STATUS_STARTED) { + dev_warn(dev, "not working status %i, meta_info 0x%llx\n", + isp_subdev->sensor_info.status, meta_info_mc); + return; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all + * compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(meta_info_mc, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a = ISP4SD_META_BUF_SIZE; + + if (isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type))) + dev_err(dev, "fail send meta_info 0x%llx\n", + meta_info_mc); + else + dev_dbg(dev, "resend meta_info 0x%llx\n", meta_info_mc); +} + +static void isp4sd_fw_resp_frame_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_param_package *para) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + struct isp4if_img_buf_node *prev; + struct isp4fw_meta_info *meta; + u64 mc; + + mc = isp4if_join_addr64(para->package_addr_lo, para->package_addr_hi); + meta = isp4sd_get_meta_by_mc(isp_subdev, mc); + if (!meta) { + dev_err(dev, "fail to get meta from mc %llx\n", mc); + return; + } + + dev_dbg(dev, "ts:%llu,streamId:%d,poc:%u,preview_en:%u,status:%s(%i)\n", + ktime_get_ns(), stream_id, meta->poc, meta->preview.enabled, + isp4dbg_get_buf_done_str(meta->preview.status), + meta->preview.status); + + if (meta->preview.enabled && + (meta->preview.status == ISP4FW_BUFFER_STATUS_SKIPPED || + meta->preview.status == ISP4FW_BUFFER_STATUS_DONE || + meta->preview.status == ISP4FW_BUFFER_STATUS_DIRTY)) { + prev = isp4if_dequeue_buffer(ispif); + if (prev) { + isp4dbg_show_bufmeta_info(dev, "prev", &meta->preview, + &prev->buf_info); + isp4vid_handle_frame_done(&isp_subdev->isp_vdev, + &prev->buf_info); + isp4if_dealloc_buffer_node(prev); + } else { + dev_err(dev, "fail null prev buf\n"); + } + } else if (meta->preview.enabled) { + dev_err(dev, "fail bad preview status %u(%s)\n", + meta->preview.status, + isp4dbg_get_buf_done_str(meta->preview.status)); + } + + if (isp_subdev->sensor_info.status == ISP4SD_START_STATUS_STARTED) + isp4sd_send_meta_info(isp_subdev, mc); + + dev_dbg(dev, "stream_id:%d, status:%d\n", stream_id, + isp_subdev->sensor_info.status); +} + +static void isp4sd_fw_resp_func(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + struct isp4fw_resp resp; + + if (stream_id == ISP4IF_STREAM_ID_1) + isp_fw_log_print(isp_subdev); + + while (true) { + if (isp4if_f2h_resp(ispif, stream_id, &resp)) { + /* Re-enable the interrupt */ + isp4_intr_enable(isp_subdev, stream_id, true); + /* + * Recheck to see if there is a new response. + * To ensure that an in-flight interrupt is not lost, + * enabling the interrupt must occur _before_ checking + * for a new response, hence a memory barrier is needed. + * Disable the interrupt again if there was a new + * response. + */ + mb(); + if (likely(isp4if_f2h_resp(ispif, stream_id, &resp))) + break; + + isp4_intr_enable(isp_subdev, stream_id, false); + } + + switch (resp.resp_id) { + case ISP4FW_RESP_ID_CMD_DONE: + isp4sd_fw_resp_cmd_done(isp_subdev, stream_id, + &resp.param.cmd_done); + break; + case ISP4FW_RESP_ID_NOTI_FRAME_DONE: + isp4sd_fw_resp_frame_done(isp_subdev, stream_id, + &resp.param.frame_done); + break; + default: + dev_err(dev, "-><- fail respid %s(0x%x)\n", + isp4dbg_get_resp_str(resp.resp_id), + resp.resp_id); + break; + } + } +} + +static s32 isp4sd_fw_resp_thread(void *context) +{ + struct isp4_subdev_thread_param *para = context; + struct isp4_subdev *isp_subdev = para->isp_subdev; + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[para->idx]; + struct device *dev = isp_subdev->dev; + + dev_dbg(dev, "[%u] fw resp thread started\n", para->idx); + while (true) { + wait_event_interruptible(thread_ctx->waitq, + thread_ctx->resp_ready); + thread_ctx->resp_ready = false; + + if (kthread_should_stop()) { + dev_dbg(dev, "[%u] fw resp thread quit\n", para->idx); + break; + } + + isp4sd_fw_resp_func(isp_subdev, para->idx); + } + + return 0; +} + +static int isp4sd_stop_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[i]; + + if (thread_ctx->thread) { + kthread_stop(thread_ctx->thread); + thread_ctx->thread = NULL; + } + } + + return 0; +} + +static int isp4sd_start_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + struct device *dev = isp_subdev->dev; + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx = + &isp_subdev->fw_resp_thread[i]; + + isp_subdev->isp_resp_para[i].idx = i; + isp_subdev->isp_resp_para[i].isp_subdev = isp_subdev; + init_waitqueue_head(&thread_ctx->waitq); + thread_ctx->resp_ready = false; + + thread_ctx->thread = kthread_run(isp4sd_fw_resp_thread, + &isp_subdev->isp_resp_para[i], + isp4sd_thread_name[i]); + if (IS_ERR(thread_ctx->thread)) { + dev_err(dev, "create thread [%d] fail\n", i); + thread_ctx->thread = NULL; + isp4sd_stop_resp_proc_threads(isp_subdev); + return -EINVAL; + } + } + + return 0; +} + +int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_LOW; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + if (sensor_info->status == ISP4SD_START_STATUS_STARTED) { + dev_err(dev, "fail for stream still running\n"); + return -EINVAL; + } + + sensor_info->status = ISP4SD_START_STATUS_OFF; + + if (isp_subdev->irq_enabled) { + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + disable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled = false; + } + + isp4sd_stop_resp_proc_threads(isp_subdev); + dev_dbg(dev, "isp_subdev stop resp proc threads suc\n"); + + isp4if_stop(ispif); + + ret = dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) + dev_err(dev, + "fail to set isp_subdev performance state %u,ret %d\n", + perf_state, ret); + + /* hold ccpu reset */ + isp4hw_wreg(isp_subdev->mmio, ISP_SOFT_RESET, 0); + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0); + ret = pm_runtime_put_sync(dev); + if (ret) + dev_err(dev, "power off isp_subdev fail %d\n", ret); + else + dev_dbg(dev, "power off isp_subdev suc\n"); + + ispif->status = ISP4IF_STATUS_PWR_OFF; + isp4if_clear_cmdq(ispif); + isp4sd_module_enable(isp_subdev, false); + + /* + * When opening the camera, isp4sd_module_enable(isp_subdev, true) is + * called. Hardware requires at least a 20ms delay between disabling + * and enabling the module, so a sleep is added to ensure ISP stability + * during quick reopen scenarios. + */ + msleep(20); + + return 0; +} + +int isp4sd_pwron_and_init(struct v4l2_subdev *sd) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + if (ispif->status == ISP4IF_STATUS_FW_RUNNING) { + dev_dbg(dev, "camera already opened, do nothing\n"); + return 0; + } + + isp4sd_module_enable(isp_subdev, true); + + if (ispif->status < ISP4IF_STATUS_PWR_ON) { + unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_HIGH; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "fail to power on isp_subdev ret %d\n", + ret); + goto err_deinit; + } + + /* ISPPG ISP Power Status */ + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0x7FF); + ret = dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) { + dev_err(dev, + "fail to set performance state %u, ret %d\n", + perf_state, ret); + goto err_deinit; + } + + ispif->status = ISP4IF_STATUS_PWR_ON; + } + + isp_subdev->sensor_info.start_stream_cmd_sent = false; + isp_subdev->sensor_info.buf_sent_cnt = 0; + + ret = isp4if_start(ispif); + if (ret) { + dev_err(dev, "fail to start isp_subdev interface\n"); + goto err_deinit; + } + + if (isp4sd_start_resp_proc_threads(isp_subdev)) { + dev_err(dev, "isp_start_resp_proc_threads fail\n"); + goto err_deinit; + } + + dev_dbg(dev, "create resp threads ok\n"); + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + enable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled = true; + + return 0; +err_deinit: + isp4sd_pwroff_and_deinit(sd); + return -EINVAL; +} + +static int isp4sd_stop_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4sd_output_info *output_info = &sensor_info->output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + + guard(mutex)(&isp_subdev->ops_mutex); + dev_dbg(dev, "status %i\n", output_info->start_status); + + if (output_info->start_status == ISP4SD_START_STATUS_STARTED) { + struct isp4fw_cmd_enable_out_ch cmd_ch_disable; + int ret; + + /* + * The struct will be shared with ISP FW, use memset() to + * guarantee padding bits are zeroed, since this is not + * guaranteed on all compilers. + */ + memset(&cmd_ch_disable, 0, sizeof(cmd_ch_disable)); + cmd_ch_disable.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW; + /* `cmd_ch_disable.is_enable` is already false */ + ret = isp4if_send_command_sync(ispif, + ISP4FW_CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_disable, + sizeof(cmd_ch_disable)); + if (ret) + dev_err(dev, "fail to disable stream\n"); + else + dev_dbg(dev, "wait disable stream suc\n"); + + ret = isp4if_send_command_sync(ispif, ISP4FW_CMD_ID_STOP_STREAM, + NULL, 0); + if (ret) + dev_err(dev, "fail to stop stream\n"); + else + dev_dbg(dev, "wait stop stream suc\n"); + } + + isp4sd_uninit_stream(isp_subdev, state, pad); + + /* + * Return success to ensure the stop process proceeds, + * and disregard any errors since they are not fatal. + */ + return 0; +} + +static int isp4sd_start_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info = + &isp_subdev->sensor_info.output_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + + if (ispif->status != ISP4IF_STATUS_FW_RUNNING) { + dev_err(dev, "fail, bad fsm %d\n", ispif->status); + return -EINVAL; + } + + switch (output_info->start_status) { + case ISP4SD_START_STATUS_OFF: + break; + case ISP4SD_START_STATUS_STARTED: + dev_dbg(dev, "stream already started, do nothing\n"); + return 0; + case ISP4SD_START_STATUS_START_FAIL: + dev_err(dev, "stream previously failed to start\n"); + return -EINVAL; + } + + ret = isp4sd_init_stream(isp_subdev); + if (ret) { + dev_err(dev, "fail to init isp_subdev stream\n"); + goto err_stop_stream; + } + + ret = isp4sd_setup_output(isp_subdev, state, pad); + if (ret) { + dev_err(dev, "fail to setup output\n"); + goto err_stop_stream; + } + + return 0; + +err_stop_stream: + isp4sd_stop_stream(isp_subdev, state, pad); + return ret; +} + +int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd, + struct isp4if_img_buf_info *buf_info) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + struct isp4_interface *ispif = &isp_subdev->ispif; + struct isp4if_img_buf_node *buf_node; + struct device *dev = isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + + if (ispif->status != ISP4IF_STATUS_FW_RUNNING) { + dev_err(dev, "fail send img buf for bad fsm %d\n", + ispif->status); + return -EINVAL; + } + + buf_node = isp4if_alloc_buffer_node(buf_info); + if (!buf_node) { + dev_err(dev, "fail alloc sys img buf info node\n"); + return -ENOMEM; + } + + ret = isp4if_queue_buffer(ispif, buf_node); + if (ret) { + dev_err(dev, "fail to queue image buf, %d\n", ret); + goto error_release_buf_node; + } + + if (!isp_subdev->sensor_info.start_stream_cmd_sent) { + isp_subdev->sensor_info.buf_sent_cnt++; + + if (isp_subdev->sensor_info.buf_sent_cnt >= + ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) { + ret = isp4if_send_command(ispif, + ISP4FW_CMD_ID_START_STREAM, + NULL, 0); + if (ret) { + dev_err(dev, "fail to START_STREAM"); + goto error_release_buf_node; + } + isp_subdev->sensor_info.start_stream_cmd_sent = true; + isp_subdev->sensor_info.output_info.start_status = + ISP4SD_START_STATUS_STARTED; + isp_subdev->sensor_info.status = + ISP4SD_START_STATUS_STARTED; + } else { + dev_dbg(dev, + "no send start, required %u, buf sent %u\n", + ISP4SD_MIN_BUF_CNT_BEF_START_STREAM, + isp_subdev->sensor_info.buf_sent_cnt); + } + } + + return 0; + +error_release_buf_node: + isp4if_dealloc_buffer_node(buf_node); + return ret; +} + +static const struct v4l2_subdev_video_ops isp4sd_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static int isp4sd_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct isp4sd_output_info *stream_info = + &(to_isp4_subdev(sd)->sensor_info.output_info); + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + + if (!fmt) { + dev_err(sd->dev, "fail to get state format\n"); + return -EINVAL; + } + + *fmt = format->format; + switch (fmt->code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + stream_info->image_size = fmt->width * fmt->height * 2; + break; + case MEDIA_BUS_FMT_YUYV8_1_5X8: + default: + stream_info->image_size = fmt->width * fmt->height * 3 / 2; + break; + } + + if (!stream_info->image_size) { + dev_err(sd->dev, + "fail set pad format,code 0x%x,width %u, height %u\n", + fmt->code, fmt->width, fmt->height); + return -EINVAL; + } + + dev_dbg(sd->dev, "set pad format suc, code:%x w:%u h:%u size:%u\n", + fmt->code, fmt->width, fmt->height, + stream_info->image_size); + + return 0; +} + +static int isp4sd_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + + return isp4sd_start_stream(isp_subdev, state, pad); +} + +static int isp4sd_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev = to_isp4_subdev(sd); + + return isp4sd_stop_stream(isp_subdev, state, pad); +} + +static const struct v4l2_subdev_pad_ops isp4sd_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = isp4sd_set_pad_format, + .enable_streams = isp4sd_enable_streams, + .disable_streams = isp4sd_disable_streams, +}; + +static const struct v4l2_subdev_ops isp4sd_subdev_ops = { + .video = &isp4sd_video_ops, + .pad = &isp4sd_pad_ops, +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]) +{ + struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info; + struct isp4_interface *ispif = &isp_subdev->ispif; + struct device *dev = v4l2_dev->dev; + int ret; + + isp_subdev->dev = dev; + v4l2_subdev_init(&isp_subdev->sdev, &isp4sd_subdev_ops); + isp_subdev->sdev.owner = THIS_MODULE; + isp_subdev->sdev.dev = dev; + snprintf(isp_subdev->sdev.name, sizeof(isp_subdev->sdev.name), "%s", + dev_name(dev)); + + isp_subdev->sdev.entity.name = isp4sd_entity_name; + isp_subdev->sdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP; + isp_subdev->sdev_pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&isp_subdev->sdev.entity, 1, + &isp_subdev->sdev_pad); + if (ret) { + dev_err(dev, "fail to init isp4 subdev entity pad %d\n", ret); + return ret; + } + + ret = v4l2_subdev_init_finalize(&isp_subdev->sdev); + if (ret < 0) { + dev_err(dev, "fail to init finalize isp4 subdev %d\n", + ret); + return ret; + } + + ret = v4l2_device_register_subdev(v4l2_dev, &isp_subdev->sdev); + if (ret) { + dev_err(dev, "fail to register isp4 subdev to V4L2 device %d\n", + ret); + goto err_media_clean_up; + } + + isp4if_init(ispif, dev, isp_subdev->mmio); + + mutex_init(&isp_subdev->ops_mutex); + sensor_info->status = ISP4SD_START_STATUS_OFF; + + /* create ISP enable gpio control */ + isp_subdev->enable_gpio = devm_gpiod_get(isp_subdev->dev, + "enable_isp", + GPIOD_OUT_LOW); + if (IS_ERR(isp_subdev->enable_gpio)) { + ret = PTR_ERR(isp_subdev->enable_gpio); + dev_err(dev, "fail to get gpiod %d\n", ret); + goto err_subdev_unreg; + } + + for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + isp_subdev->irq[i] = irq[i]; + + isp_subdev->host2fw_seq_num = 1; + ispif->status = ISP4IF_STATUS_PWR_OFF; + + ret = isp4vid_dev_init(&isp_subdev->isp_vdev, &isp_subdev->sdev); + if (ret) + goto err_subdev_unreg; + + return 0; + +err_subdev_unreg: + v4l2_device_unregister_subdev(&isp_subdev->sdev); +err_media_clean_up: + v4l2_subdev_cleanup(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + return ret; +} + +void isp4sd_deinit(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif = &isp_subdev->ispif; + + isp4vid_dev_deinit(&isp_subdev->isp_vdev); + v4l2_device_unregister_subdev(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + isp4if_deinit(ispif); + isp4sd_module_enable(isp_subdev, false); + + ispif->status = ISP4IF_STATUS_PWR_OFF; +} diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.h b/drivers/media/platform/amd/isp4/isp4_subdev.h new file mode 100644 index 000000000000..20ea08a830af --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_SUBDEV_H_ +#define _ISP4_SUBDEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "isp4_fw_cmd_resp.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" +#include "isp4_video.h" + +/* + * One is for none sensor specific response which is not used now. + * Another is for sensor specific response + */ +#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2 + +/* Indicates the ISP status */ +enum isp4sd_status { + ISP4SD_STATUS_PWR_OFF, + ISP4SD_STATUS_PWR_ON, + ISP4SD_STATUS_FW_RUNNING, + ISP4SD_STATUS_MAX +}; + +/* Indicates sensor and output stream status */ +enum isp4sd_start_status { + ISP4SD_START_STATUS_OFF, + ISP4SD_START_STATUS_STARTED, + ISP4SD_START_STATUS_START_FAIL, +}; + +struct isp4sd_img_buf_node { + struct list_head node; + struct isp4if_img_buf_info buf_info; +}; + +/* This is ISP output after processing Bayer raw sensor input */ +struct isp4sd_output_info { + enum isp4sd_start_status start_status; + u32 image_size; +}; + +/* + * Struct for sensor info used as ISP input or source. + * status: sensor status. + * output_info: ISP output after processing the sensor input. + * start_stream_cmd_sent: indicates if ISP4FW_CMD_ID_START_STREAM was sent + * to firmware. + * buf_sent_cnt: number of buffers sent to receive images. + */ +struct isp4sd_sensor_info { + struct isp4sd_output_info output_info; + enum isp4sd_start_status status; + bool start_stream_cmd_sent; + u32 buf_sent_cnt; +}; + +/* + * The thread is created by the driver to handle firmware responses which will + * be waken up when a firmware-to-driver response interrupt occurs. + */ +struct isp4sd_thread_handler { + struct task_struct *thread; + wait_queue_head_t waitq; + bool resp_ready; +}; + +struct isp4_subdev_thread_param { + u32 idx; + struct isp4_subdev *isp_subdev; +}; + +struct isp4_subdev { + struct v4l2_subdev sdev; + struct isp4_interface ispif; + struct isp4vid_dev isp_vdev; + + struct media_pad sdev_pad; + + enum isp4sd_status isp_status; + /* mutex used to synchronize the operation with firmware */ + struct mutex ops_mutex; + + struct isp4sd_thread_handler + fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + + u32 host2fw_seq_num; + + struct isp4sd_sensor_info sensor_info; + + /* gpio descriptor */ + struct gpio_desc *enable_gpio; + struct device *dev; + void __iomem *mmio; + struct isp4_subdev_thread_param + isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + bool irq_enabled; + /* spin lock to access ISP_SYS_INT0_EN exclusively */ + spinlock_t irq_lock; +#ifdef CONFIG_DEBUG_FS + bool enable_fw_log; + struct dentry *debugfs_dir; + char *fw_log_output; +#endif +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]); +void isp4sd_deinit(struct isp4_subdev *isp_subdev); +int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd, + struct isp4if_img_buf_info *buf_info); +int isp4sd_pwron_and_init(struct v4l2_subdev *sd); +int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd); + +#endif /* _ISP4_SUBDEV_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_video.c b/drivers/media/platform/amd/isp4/isp4_video.c new file mode 100644 index 000000000000..0cebb39f98e1 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_video.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "isp4_interface.h" +#include "isp4_subdev.h" +#include "isp4_video.h" + +#define ISP4VID_ISP_DRV_NAME "amd_isp_capture" +#define ISP4VID_MAX_PREVIEW_FPS 30 +#define ISP4VID_DEFAULT_FMT V4L2_PIX_FMT_NV12 + +#define ISP4VID_PAD_VIDEO_OUTPUT 0 + +/* time perframe default */ +#define ISP4VID_ISP_TPF_DEFAULT isp4vid_tpfs[0] + +static const char *const isp4vid_video_dev_name = "Preview"; + +/* Sizes must be in increasing order */ +static const struct v4l2_frmsize_discrete isp4vid_frmsize[] = { + {640, 360}, + {640, 480}, + {1280, 720}, + {1280, 960}, + {1920, 1080}, + {1920, 1440}, + {2560, 1440}, + {2880, 1620}, + {2880, 1624}, + {2888, 1808}, +}; + +static const u32 isp4vid_formats[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUYV +}; + +/* time perframe list */ +static const struct v4l2_fract isp4vid_tpfs[] = { + { 1, ISP4VID_MAX_PREVIEW_FPS } +}; + +void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev, + const struct isp4if_img_buf_info *img_buf) +{ + struct isp4vid_capture_buffer *isp4vid_buf; + void *vbuf; + + scoped_guard(mutex, &isp_vdev->buf_list_lock) { + isp4vid_buf = list_first_entry_or_null(&isp_vdev->buf_list, + typeof(*isp4vid_buf), + list); + if (!isp4vid_buf) + return; + + vbuf = vb2_plane_vaddr(&isp4vid_buf->vb2.vb2_buf, 0); + + if (vbuf != img_buf->planes[0].sys_addr) { + dev_err(isp_vdev->dev, "Invalid vbuf\n"); + return; + } + + list_del(&isp4vid_buf->list); + } + + /* Fill the buffer */ + isp4vid_buf->vb2.vb2_buf.timestamp = ktime_get_ns(); + isp4vid_buf->vb2.sequence = isp_vdev->sequence++; + isp4vid_buf->vb2.field = V4L2_FIELD_ANY; + + vb2_set_plane_payload(&isp4vid_buf->vb2.vb2_buf, + 0, isp_vdev->format.sizeimage); + + vb2_buffer_done(&isp4vid_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE); + + dev_dbg(isp_vdev->dev, "call vb2_buffer_done(size=%u)\n", + isp_vdev->format.sizeimage); +} + +static const struct v4l2_pix_format isp4vid_fmt_default = { + .width = 1920, + .height = 1080, + .pixelformat = ISP4VID_DEFAULT_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, +}; + +static void isp4vid_capture_return_all_buffers(struct isp4vid_dev *isp_vdev, + enum vb2_buffer_state state) +{ + struct isp4vid_capture_buffer *vbuf, *node; + + scoped_guard(mutex, &isp_vdev->buf_list_lock) { + list_for_each_entry_safe(vbuf, node, &isp_vdev->buf_list, list) + vb2_buffer_done(&vbuf->vb2.vb2_buf, state); + INIT_LIST_HEAD(&isp_vdev->buf_list); + } + + dev_dbg(isp_vdev->dev, "call vb2_buffer_done(%d)\n", state); +} + +static int isp4vid_vdev_link_validate(struct media_link *link) +{ + return 0; +} + +static const struct media_entity_operations isp4vid_vdev_ent_ops = { + .link_validate = isp4vid_vdev_link_validate, +}; + +static const struct v4l2_file_operations isp4vid_vdev_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int isp4vid_ioctl_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + strscpy(cap->driver, ISP4VID_ISP_DRV_NAME, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "%s", ISP4VID_ISP_DRV_NAME); + cap->capabilities |= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + + dev_dbg(isp_vdev->dev, "%s|capabilities=0x%X\n", isp_vdev->vdev.name, + cap->capabilities); + + return 0; +} + +static int isp4vid_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + f->fmt.pix = isp_vdev->format; + + return 0; +} + +static int isp4vid_fill_buffer_size(struct v4l2_pix_format *fmt) +{ + int ret = 0; + + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + fmt->bytesperline = fmt->width; + fmt->sizeimage = fmt->bytesperline * fmt->height * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + fmt->bytesperline = fmt->width * 2; + fmt->sizeimage = fmt->bytesperline * fmt->height; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int isp4vid_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + struct v4l2_pix_format *format = &f->fmt.pix; + const struct v4l2_frmsize_discrete *fsz; + size_t i; + + /* + * Check if the hardware supports the requested format, use the default + * format otherwise. + */ + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) + if (isp4vid_formats[i] == format->pixelformat) + break; + + if (i == ARRAY_SIZE(isp4vid_formats)) + format->pixelformat = ISP4VID_DEFAULT_FMT; + + switch (format->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUYV: + fsz = v4l2_find_nearest_size(isp4vid_frmsize, + ARRAY_SIZE(isp4vid_frmsize), + width, height, format->width, + format->height); + format->width = fsz->width; + format->height = fsz->height; + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, + format->pixelformat); + return -EINVAL; + } + + /* + * There is no need to check the return value, as failure will never + * happen here + */ + isp4vid_fill_buffer_size(format); + + if (format->field == V4L2_FIELD_ANY) + format->field = isp4vid_fmt_default.field; + + if (format->colorspace == V4L2_COLORSPACE_DEFAULT) + format->colorspace = isp4vid_fmt_default.colorspace; + + return 0; +} + +static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev, + struct v4l2_pix_format *pix_fmt) +{ + struct v4l2_subdev_format fmt = {}; + + switch (pix_fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + fmt.format.code = MEDIA_BUS_FMT_YUYV8_1_5X8; + break; + case V4L2_PIX_FMT_YUYV: + fmt.format.code = MEDIA_BUS_FMT_YUYV8_1X16; + break; + default: + return -EINVAL; + } + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT; + fmt.format.width = pix_fmt->width; + fmt.format.height = pix_fmt->height; + return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt); +} + +static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + int ret; + + /* Do not change the format while stream is on */ + if (vb2_is_busy(&isp_vdev->vbq)) + return -EBUSY; + + ret = isp4vid_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + dev_dbg(isp_vdev->dev, "%s|width height:%ux%u->%ux%u\n", + isp_vdev->vdev.name, + isp_vdev->format.width, isp_vdev->format.height, + f->fmt.pix.width, f->fmt.pix.height); + dev_dbg(isp_vdev->dev, "%s|pixelformat:0x%x-0x%x\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat, + f->fmt.pix.pixelformat); + dev_dbg(isp_vdev->dev, "%s|bytesperline:%u->%u\n", + isp_vdev->vdev.name, isp_vdev->format.bytesperline, + f->fmt.pix.bytesperline); + dev_dbg(isp_vdev->dev, "%s|sizeimage:%u->%u\n", + isp_vdev->vdev.name, isp_vdev->format.sizeimage, + f->fmt.pix.sizeimage); + + isp_vdev->format = f->fmt.pix; + ret = isp4vid_set_fmt_2_isp(isp_vdev->isp_sdev, &isp_vdev->format); + + return ret; +} + +static int isp4vid_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_NV12; + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_YUYV; + break; + default: + return -EINVAL; + } + + dev_dbg(isp_vdev->dev, "%s|index=%d, pixelformat=0x%X\n", + isp_vdev->vdev.name, f->index, f->pixelformat); + + return 0; +} + +static int isp4vid_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) { + if (isp4vid_formats[i] == fsize->pixel_format) + break; + } + + if (i == ARRAY_SIZE(isp4vid_formats)) + return -EINVAL; + + if (fsize->index < ARRAY_SIZE(isp4vid_frmsize)) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete = isp4vid_frmsize[fsize->index]; + dev_dbg(isp_vdev->dev, "%s|size[%d]=%dx%d\n", + isp_vdev->vdev.name, fsize->index, + fsize->discrete.width, fsize->discrete.height); + } else { + return -EINVAL; + } + + return 0; +} + +static int isp4vid_ioctl_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct isp4vid_dev *isp_vdev = video_drvdata(file); + size_t i; + + if (fival->index >= ARRAY_SIZE(isp4vid_tpfs)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) + if (isp4vid_formats[i] == fival->pixel_format) + break; + + if (i == ARRAY_SIZE(isp4vid_formats)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isp4vid_frmsize); i++) + if (isp4vid_frmsize[i].width == fival->width && + isp4vid_frmsize[i].height == fival->height) + break; + + if (i == ARRAY_SIZE(isp4vid_frmsize)) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = isp4vid_tpfs[fival->index]; + v4l2_simplify_fraction(&fival->discrete.numerator, + &fival->discrete.denominator, 8, 333); + + dev_dbg(isp_vdev->dev, "%s|interval[%d]=%d/%d\n", + isp_vdev->vdev.name, fival->index, + fival->discrete.numerator, + fival->discrete.denominator); + + return 0; +} + +static int isp4vid_ioctl_g_param(struct file *file, void *priv, + struct v4l2_streamparm *param) +{ + struct v4l2_captureparm *capture = ¶m->parm.capture; + struct isp4vid_dev *isp_vdev = video_drvdata(file); + + if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + capture->capability = V4L2_CAP_TIMEPERFRAME; + capture->timeperframe = isp_vdev->timeperframe; + capture->readbuffers = 0; + + dev_dbg(isp_vdev->dev, "%s|timeperframe=%d/%d\n", isp_vdev->vdev.name, + capture->timeperframe.numerator, + capture->timeperframe.denominator); + + return 0; +} + +static const struct v4l2_ioctl_ops isp4vid_vdev_ioctl_ops = { + .vidioc_querycap = isp4vid_ioctl_querycap, + .vidioc_enum_fmt_vid_cap = isp4vid_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = isp4vid_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = isp4vid_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = isp4vid_try_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_parm = isp4vid_ioctl_g_param, + .vidioc_s_parm = isp4vid_ioctl_g_param, + .vidioc_enum_framesizes = isp4vid_enum_framesizes, + .vidioc_enum_frameintervals = isp4vid_ioctl_enum_frameintervals, +}; + +static unsigned int isp4vid_get_image_size(struct v4l2_pix_format *fmt) +{ + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + return fmt->width * fmt->height * 3 / 2; + case V4L2_PIX_FMT_YUYV: + return fmt->width * fmt->height * 2; + default: + return 0; + } +} + +static int isp4vid_qops_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + unsigned int q_num_bufs = vb2_get_num_buffers(vq); + + if (*nplanes > 1) { + dev_err(isp_vdev->dev, + "fail to setup queue, no mplane supported %u\n", + *nplanes); + return -EINVAL; + } + + if (*nplanes == 1) { + unsigned int size; + + size = isp4vid_get_image_size(&isp_vdev->format); + if (sizes[0] < size) { + dev_err(isp_vdev->dev, + "fail for small plane size %u, %u expected\n", + sizes[0], size); + return -EINVAL; + } + } + + if (q_num_bufs + *nbuffers < ISP4IF_MAX_STREAM_BUF_COUNT) + *nbuffers = ISP4IF_MAX_STREAM_BUF_COUNT - q_num_bufs; + + switch (isp_vdev->format.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUYV: { + *nplanes = 1; + sizes[0] = max(sizes[0], isp_vdev->format.sizeimage); + isp_vdev->format.sizeimage = sizes[0]; + } + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat); + return -EINVAL; + } + + dev_dbg(isp_vdev->dev, "%s|*nbuffers=%u *nplanes=%u sizes[0]=%u\n", + isp_vdev->vdev.name, + *nbuffers, *nplanes, sizes[0]); + + return 0; +} + +static void isp4vid_qops_buffer_queue(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue); + struct isp4if_img_buf_info *img_buf = &buf->img_buf; + void *vaddr = vb2_plane_vaddr(vb, 0); + + dev_dbg(isp_vdev->dev, "queue buf, vaddr %p, gpuva 0x%llx, size %u\n", + vaddr, buf->gpu_addr, vb->planes[0].length); + + switch (isp_vdev->format.pixelformat) { + case V4L2_PIX_FMT_NV12: { + u32 y_size = isp_vdev->format.sizeimage / 3 * 2; + u32 uv_size = isp_vdev->format.sizeimage / 3; + + img_buf->planes[0].len = y_size; + img_buf->planes[0].sys_addr = vaddr; + img_buf->planes[0].mc_addr = buf->gpu_addr; + + dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n", + img_buf->planes[0].mc_addr, + img_buf->planes[0].len); + + img_buf->planes[1].len = uv_size; + img_buf->planes[1].sys_addr = vaddr + y_size; + img_buf->planes[1].mc_addr = buf->gpu_addr + y_size; + + dev_dbg(isp_vdev->dev, "img_buf[1]: mc=0x%llx size=%u\n", + img_buf->planes[1].mc_addr, + img_buf->planes[1].len); + + img_buf->planes[2].len = 0; + } + break; + case V4L2_PIX_FMT_YUYV: { + img_buf->planes[0].len = isp_vdev->format.sizeimage; + img_buf->planes[0].sys_addr = vaddr; + img_buf->planes[0].mc_addr = buf->gpu_addr; + + dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n", + img_buf->planes[0].mc_addr, + img_buf->planes[0].len); + + img_buf->planes[1].len = 0; + img_buf->planes[2].len = 0; + } + break; + default: + dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n", + isp_vdev->vdev.name, isp_vdev->format.pixelformat); + return; + } + + if (isp_vdev->stream_started) + isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, img_buf); + + scoped_guard(mutex, &isp_vdev->buf_list_lock) + list_add_tail(&buf->list, &isp_vdev->buf_list); +} + +static int isp4vid_qops_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + struct isp4vid_capture_buffer *isp4vid_buf; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret = 0; + + isp_vdev->sequence = 0; + + ret = isp4sd_pwron_and_init(isp_vdev->isp_sdev); + if (ret) { + dev_err(isp_vdev->dev, "power up isp fail %d\n", ret); + goto release_buffers; + } + + entity = &isp_vdev->vdev.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(isp_vdev->dev, "fail start streaming: %s %d\n", + subdev->name, ret); + goto release_buffers; + } + } + + list_for_each_entry(isp4vid_buf, &isp_vdev->buf_list, list) + isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, + &isp4vid_buf->img_buf); + + isp_vdev->stream_started = true; + + return 0; + +release_buffers: + isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void isp4vid_qops_stop_streaming(struct vb2_queue *vq) +{ + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq); + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret; + + entity = &isp_vdev->vdev.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 0); + + if (ret < 0 && ret != -ENOIOCTLCMD) + dev_dbg(isp_vdev->dev, "fail stop streaming: %s %d\n", + subdev->name, ret); + } + + isp_vdev->stream_started = false; + isp4sd_pwroff_and_deinit(isp_vdev->isp_sdev); + + /* Release all active buffers */ + isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_ERROR); +} + +static int isp4vid_qops_buf_init(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue); + void *mem_priv = vb->planes[0].mem_priv; + struct device *dev = isp_vdev->dev; + u64 gpu_addr; + void *bo; + int ret; + + if (vb->planes[0].dbuf) { + buf->dbuf = vb->planes[0].dbuf; + } else { + /* + * HAS_DMA is a Kconfig dependency so CONFIG_HAS_DMA is always + * defined when this driver is compiled. The #else branch is + * kept as a safeguard in case the dependency is ever removed. + */ +#ifdef CONFIG_HAS_DMA + buf->dbuf = vb2_vmalloc_memops.get_dmabuf(vb, mem_priv, 0); + if (IS_ERR_OR_NULL(buf->dbuf)) { + dev_err(dev, "fail to get dma buf\n"); + return -EINVAL; + } +#else + dev_err(dev, "get dmabuf fail -- CONFIG_HAS_DMA not defined\n"); + buf->dbuf = NULL; + return -EINVAL; +#endif + } + + /* create isp user BO and obtain gpu_addr */ + ret = isp_user_buffer_alloc(dev, buf->dbuf, &bo, &gpu_addr); + if (ret) { + dev_err(dev, "fail to create isp user BO\n"); + if (!vb->planes[0].dbuf) { + dma_buf_put(buf->dbuf); + buf->dbuf = NULL; + } + + return ret; + } + + buf->bo = bo; + buf->gpu_addr = gpu_addr; + return 0; +} + +static void isp4vid_qops_buf_cleanup(struct vb2_buffer *vb) +{ + struct isp4vid_capture_buffer *buf = + container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf); + + if (buf->bo) { + isp_user_buffer_free(buf->bo); + buf->bo = NULL; + } + + /* + * Only put dmabufs we obtained ourselves via get_dmabuf, not ones + * provided by the framework for DMABUF import + */ + if (buf->dbuf && buf->dbuf != vb->planes[0].dbuf) + dma_buf_put(buf->dbuf); + + buf->dbuf = NULL; +} + +static const struct vb2_ops isp4vid_qops = { + .queue_setup = isp4vid_qops_queue_setup, + .buf_init = isp4vid_qops_buf_init, + .buf_cleanup = isp4vid_qops_buf_cleanup, + .start_streaming = isp4vid_qops_start_streaming, + .stop_streaming = isp4vid_qops_stop_streaming, + .buf_queue = isp4vid_qops_buffer_queue, +}; + +int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd) +{ + const char *vdev_name = isp4vid_video_dev_name; + struct v4l2_device *v4l2_dev; + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + if (!isp_vdev || !isp_sd || !isp_sd->v4l2_dev) + return -EINVAL; + + v4l2_dev = isp_sd->v4l2_dev; + vdev = &isp_vdev->vdev; + + isp_vdev->isp_sdev = isp_sd; + isp_vdev->dev = v4l2_dev->dev; + + /* Initialize the vb2_queue struct */ + mutex_init(&isp_vdev->vbq_lock); + q = &isp_vdev->vbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->buf_struct_size = sizeof(struct isp4vid_capture_buffer); + q->min_queued_buffers = 2; + q->ops = &isp4vid_qops; + q->drv_priv = isp_vdev; + q->mem_ops = &vb2_vmalloc_memops; + q->lock = &isp_vdev->vbq_lock; + q->dev = v4l2_dev->dev; + ret = vb2_queue_init(q); + if (ret) { + dev_err(v4l2_dev->dev, "vb2_queue_init error:%d\n", ret); + return ret; + } + + /* Initialize buffer list and its lock */ + mutex_init(&isp_vdev->buf_list_lock); + INIT_LIST_HEAD(&isp_vdev->buf_list); + + /* Set default frame format */ + isp_vdev->format = isp4vid_fmt_default; + isp_vdev->timeperframe = ISP4VID_ISP_TPF_DEFAULT; + v4l2_simplify_fraction(&isp_vdev->timeperframe.numerator, + &isp_vdev->timeperframe.denominator, 8, 333); + + ret = isp4vid_fill_buffer_size(&isp_vdev->format); + if (ret) { + dev_err(v4l2_dev->dev, "fail to fill buffer size: %d\n", ret); + goto err_release_vb2_queue; + } + + ret = isp4vid_set_fmt_2_isp(isp_sd, &isp_vdev->format); + if (ret) { + dev_err(v4l2_dev->dev, "fail init format :%d\n", ret); + goto err_release_vb2_queue; + } + + /* Initialize the video_device struct */ + isp_vdev->vdev.entity.name = vdev_name; + isp_vdev->vdev.entity.function = MEDIA_ENT_F_IO_V4L; + isp_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&isp_vdev->vdev.entity, 1, + &isp_vdev->vdev_pad); + + if (ret) { + dev_err(v4l2_dev->dev, "init media entity pad fail:%d\n", ret); + goto err_release_vb2_queue; + } + + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + vdev->entity.ops = &isp4vid_vdev_ent_ops; + vdev->release = video_device_release_empty; + vdev->fops = &isp4vid_vdev_fops; + vdev->ioctl_ops = &isp4vid_vdev_ioctl_ops; + vdev->lock = NULL; + vdev->queue = q; + vdev->v4l2_dev = v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + strscpy(vdev->name, vdev_name, sizeof(vdev->name)); + video_set_drvdata(vdev, isp_vdev); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(v4l2_dev->dev, "register video device fail:%d\n", ret); + goto err_entity_cleanup; + } + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&isp_vdev->vdev.entity); +err_release_vb2_queue: + vb2_queue_release(q); + return ret; +} + +void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev) +{ + vb2_video_unregister_device(&isp_vdev->vdev); +} diff --git a/drivers/media/platform/amd/isp4/isp4_video.h b/drivers/media/platform/amd/isp4/isp4_video.h new file mode 100644 index 000000000000..c66451e26166 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_video.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_VIDEO_H_ +#define _ISP4_VIDEO_H_ + +#include +#include + +#include "isp4_interface.h" + +struct isp4vid_capture_buffer { + /* + * struct vb2_v4l2_buffer must be the first element + * the videobuf2 framework will allocate this struct based on + * buf_struct_size and use the first sizeof(struct vb2_buffer) bytes of + * memory as a vb2_buffer + */ + struct vb2_v4l2_buffer vb2; + struct isp4if_img_buf_info img_buf; + struct list_head list; + struct dma_buf *dbuf; + void *bo; + u64 gpu_addr; +}; + +struct isp4vid_dev { + struct video_device vdev; + struct media_pad vdev_pad; + struct v4l2_pix_format format; + + /* mutex that protects vbq */ + struct mutex vbq_lock; + struct vb2_queue vbq; + + /* mutex that protects buf_list */ + struct mutex buf_list_lock; + struct list_head buf_list; + + u32 sequence; + bool stream_started; + + struct device *dev; + struct v4l2_subdev *isp_sdev; + struct v4l2_fract timeperframe; +}; + +int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd); + +void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev); + +void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev, + const struct isp4if_img_buf_info *img_buf); + +#endif /* _ISP4_VIDEO_H_ */ diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c index 8bed59816e82..9e8e3dcf4bce 100644 --- a/drivers/mfd/bcm2835-pm.c +++ b/drivers/mfd/bcm2835-pm.c @@ -81,6 +81,7 @@ static int bcm2835_pm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pm); pm->dev = dev; + pm->soc = (uintptr_t)device_get_match_data(dev); ret = bcm2835_pm_get_pdata(pdev, pm); if (ret) @@ -97,7 +98,7 @@ static int bcm2835_pm_probe(struct platform_device *pdev) * bcm2835-pm binding as the key for whether we can reference * the full PM register range and support power domains. */ - if (pm->asb) + if (pm->asb || pm->soc == BCM2835_PM_SOC_BCM2712) return devm_mfd_add_devices(dev, -1, bcm2835_power_devs, ARRAY_SIZE(bcm2835_power_devs), NULL, 0, NULL); @@ -106,9 +107,9 @@ static int bcm2835_pm_probe(struct platform_device *pdev) static const struct of_device_id bcm2835_pm_of_match[] = { { .compatible = "brcm,bcm2835-pm-wdt", }, - { .compatible = "brcm,bcm2835-pm", }, - { .compatible = "brcm,bcm2711-pm", }, - { .compatible = "brcm,bcm2712-pm", }, + { .compatible = "brcm,bcm2835-pm", .data = (void *)BCM2835_PM_SOC_BCM2835 }, + { .compatible = "brcm,bcm2711-pm", .data = (void *)BCM2835_PM_SOC_BCM2711 }, + { .compatible = "brcm,bcm2712-pm", .data = (void *)BCM2835_PM_SOC_BCM2712 }, {}, }; MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 87414a2ddf6e..2de56017ee0d 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -170,6 +170,10 @@ #define GEM_PCSANNPTX 0x021c /* PCS AN Next Page TX */ #define GEM_PCSANNPLP 0x0220 /* PCS AN Next Page LP */ #define GEM_PCSANEXTSTS 0x023c /* PCS AN Extended Status */ +#define GEM_RXLPI 0x0270 /* RX LPI Transitions */ +#define GEM_RXLPITIME 0x0274 /* RX LPI Time */ +#define GEM_TXLPI 0x0278 /* TX LPI Transitions */ +#define GEM_TXLPITIME 0x027c /* TX LPI Time */ #define GEM_DCFG1 0x0280 /* Design Config 1 */ #define GEM_DCFG2 0x0284 /* Design Config 2 */ #define GEM_DCFG3 0x0288 /* Design Config 3 */ @@ -305,6 +309,8 @@ #define MACB_IRXFCS_SIZE 1 /* GEM specific NCR bitfields. */ +#define GEM_TXLPIEN_OFFSET 19 +#define GEM_TXLPIEN_SIZE 1 #define GEM_ENABLE_HS_MAC_OFFSET 31 #define GEM_ENABLE_HS_MAC_SIZE 1 @@ -518,6 +524,8 @@ #define GEM_IRQCOR_SIZE 1 #define GEM_DBWDEF_OFFSET 25 #define GEM_DBWDEF_SIZE 3 +#define GEM_USERIO_OFFSET 9 +#define GEM_USERIO_SIZE 1 #define GEM_NO_PCS_OFFSET 0 #define GEM_NO_PCS_SIZE 1 @@ -779,6 +787,10 @@ #define MACB_CAPS_DMA_PTP BIT(22) #define MACB_CAPS_RSC BIT(23) #define MACB_CAPS_NO_LSO BIT(24) +#define MACB_CAPS_EEE BIT(25) +#define MACB_CAPS_USRIO_HAS_MII BIT(26) +#define MACB_CAPS_USRIO_HAS_REFCLK_SOURCE BIT(27) +#define MACB_CAPS_USRIO_HAS_TSUCLK_SOURCE BIT(28) /* LSO settings */ #define MACB_LSO_UFO_ENABLE 0x01 @@ -1043,6 +1055,10 @@ struct gem_stats { u64 rx_ip_header_checksum_errors; u64 rx_tcp_checksum_errors; u64 rx_udp_checksum_errors; + u64 rx_lpi_transitions; + u64 rx_lpi_time; + u64 tx_lpi_transitions; + u64 tx_lpi_time; }; /* Describes the name and offset of an individual statistic register, as @@ -1142,6 +1158,10 @@ static const struct gem_statistic gem_statistics[] = { GEM_BIT(NDS_RXERR)), GEM_STAT_TITLE_BITS(RXUDPCCNT, "rx_udp_checksum_errors", GEM_BIT(NDS_RXERR)), + GEM_STAT_TITLE(RXLPI, "rx_lpi_transitions"), + GEM_STAT_TITLE(RXLPITIME, "rx_lpi_time"), + GEM_STAT_TITLE(TXLPI, "tx_lpi_transitions"), + GEM_STAT_TITLE(TXLPITIME, "tx_lpi_time"), }; #define GEM_STATS_LEN ARRAY_SIZE(gem_statistics) @@ -1210,7 +1230,10 @@ struct macb_usrio_config { u32 rmii; u32 rgmii; u32 refclk; + u32 clken; u32 hdfctlen; + u32 tsu_source; + bool refclk_default_external; }; struct macb_config { @@ -1357,6 +1380,11 @@ struct macb { struct work_struct hresp_err_bh_work; + /* EEE / LPI state */ + bool eee_active; + struct delayed_work tx_lpi_work; + u32 tx_lpi_timer; + int rx_bd_rd_prefetch; int tx_bd_rd_prefetch; @@ -1446,6 +1474,13 @@ static inline bool macb_dma_ptp(struct macb *bp) bp->caps & MACB_CAPS_DMA_PTP; } +static inline void macb_queue_isr_clear(struct macb *bp, + struct macb_queue *queue, u32 value) +{ + if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) + queue_writel(queue, ISR, value); +} + /** * struct macb_platform_data - platform data for MACB Ethernet used for PCI registration * @pclk: platform clock diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 99e7d5cf3786..a12aa21244e8 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ struct sifive_fu540_macb_mgmt { #define MACB_RX_BUFFER_SIZE 128 #define RX_BUFFER_MULTIPLE 64 /* bytes */ +#define RX_BUFFER_MAX (0xFF * RX_BUFFER_MULTIPLE) /* 16320 bytes */ #define DEFAULT_RX_RING_SIZE 512 /* must be power of 2 */ #define MIN_RX_RING_SIZE 64 @@ -68,6 +70,10 @@ struct sifive_fu540_macb_mgmt { #define MACB_TX_INT_FLAGS (MACB_TX_ERR_FLAGS | MACB_BIT(TCOMP) \ | MACB_BIT(TXUBR)) +#define MACB_INT_MISC_FLAGS (MACB_TX_ERR_FLAGS | MACB_BIT(RXUBR) | \ + MACB_BIT(ISR_ROVR) | MACB_BIT(HRESP) | \ + GEM_BIT(WOL) | MACB_BIT(WOL)) + /* Max length of transmit frame must be a multiple of 8 bytes */ #define MACB_TX_LEN_ALIGN 8 #define MACB_MAX_TX_LEN ((unsigned int)((1 << MACB_TX_FRMLEN_SIZE) - 1) & ~((unsigned int)(MACB_TX_LEN_ALIGN - 1))) @@ -558,10 +564,21 @@ static int macb_usx_pcs_config(struct phylink_pcs *pcs, return 0; } +static unsigned int macb_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; +} + static void macb_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode, struct phylink_link_state *state) { - state->link = 0; + struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs); + u16 bmsr, lpa; + + bmsr = gem_readl(bp, PCSSTS); + lpa = gem_readl(bp, PCSANLPBASE); + phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa); } static void macb_pcs_an_restart(struct phylink_pcs *pcs) @@ -575,6 +592,26 @@ static int macb_pcs_config(struct phylink_pcs *pcs, const unsigned long *advertising, bool permit_pause_to_mac) { + struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs); + u32 old, new; + + old = gem_readl(bp, PCSANADV); + new = phylink_mii_c22_pcs_encode_advertisement(interface, advertising); + if (new != -EINVAL && old != new) + gem_writel(bp, PCSANADV, new); + + /* Disable AN if it's not to be used, enable otherwise. + * Must be written after PCSSEL is set in NCFGR which is done in + * macb_mac_config(), otherwise writes will not take effect. + */ + old = gem_readl(bp, PCSCNTRL); + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + new = old | BMCR_ANENABLE; + else + new = old & ~BMCR_ANENABLE; + if (old != new) + gem_writel(bp, PCSCNTRL, new); + return 0; } @@ -585,11 +622,113 @@ static const struct phylink_pcs_ops macb_phylink_usx_pcs_ops = { }; static const struct phylink_pcs_ops macb_phylink_pcs_ops = { + .pcs_inband_caps = macb_pcs_inband_caps, .pcs_get_state = macb_pcs_get_state, .pcs_an_restart = macb_pcs_an_restart, .pcs_config = macb_pcs_config, }; +static bool macb_tx_lpi_set(struct macb *bp, bool enable) +{ + u32 old, ncr; + + lockdep_assert_held(&bp->lock); + + ncr = macb_readl(bp, NCR); + old = ncr; + if (enable) + ncr |= GEM_BIT(TXLPIEN); + else + ncr &= ~GEM_BIT(TXLPIEN); + if (old != ncr) + macb_writel(bp, NCR, ncr); + + return old != ncr; +} + +static bool macb_tx_all_queues_idle(struct macb *bp) +{ + struct macb_queue *queue; + unsigned int q; + + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { + if (READ_ONCE(queue->tx_head) != READ_ONCE(queue->tx_tail)) + return false; + } + return true; +} + +static void macb_tx_lpi_work_fn(struct work_struct *work) +{ + struct macb *bp = container_of(work, struct macb, tx_lpi_work.work); + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + if (bp->eee_active && macb_tx_all_queues_idle(bp)) + macb_tx_lpi_set(bp, true); + spin_unlock_irqrestore(&bp->lock, flags); +} + +static void macb_tx_lpi_schedule(struct macb *bp) +{ + if (bp->eee_active) + mod_delayed_work(system_wq, &bp->tx_lpi_work, + usecs_to_jiffies(bp->tx_lpi_timer)); +} + +/* Wake from LPI before transmitting. The MAC must deassert TXLPIEN + * and wait for the PHY to exit LPI before any frame can be sent. + * IEEE 802.3az Tw_sys is ~17us for 1000BASE-T, ~30us for 100BASE-TX; + * we use a conservative 50us. + */ +static void macb_tx_lpi_wake(struct macb *bp) +{ + lockdep_assert_held(&bp->lock); + + if (!bp->eee_active) + return; + + if (!macb_tx_lpi_set(bp, false)) + return; + + cancel_delayed_work(&bp->tx_lpi_work); + udelay(50); +} + +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); + unsigned long flags; + + cancel_delayed_work_sync(&bp->tx_lpi_work); + + spin_lock_irqsave(&bp->lock, flags); + bp->eee_active = false; + macb_tx_lpi_set(bp, false); + spin_unlock_irqrestore(&bp->lock, flags); +} + +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); + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + bp->tx_lpi_timer = timer; + bp->eee_active = true; + spin_unlock_irqrestore(&bp->lock, flags); + + /* Defer initial LPI entry by 1 second after link-up per + * IEEE 802.3az section 22.7a. + */ + mod_delayed_work(system_wq, &bp->tx_lpi_work, msecs_to_jiffies(1000)); + + return 0; +} + static void macb_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { @@ -629,22 +768,6 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode, if (old_ncr ^ ncr) macb_or_gem_writel(bp, NCR, ncr); - /* Disable AN for SGMII fixed link configuration, enable otherwise. - * Must be written after PCSSEL is set in NCFGR, - * otherwise writes will not take effect. - */ - if (macb_is_gem(bp) && state->interface == PHY_INTERFACE_MODE_SGMII) { - u32 pcsctrl, old_pcsctrl; - - old_pcsctrl = gem_readl(bp, PCSCNTRL); - if (mode == MLO_AN_FIXED) - pcsctrl = old_pcsctrl & ~GEM_BIT(PCSAUTONEG); - else - pcsctrl = old_pcsctrl | GEM_BIT(PCSAUTONEG); - if (old_pcsctrl != pcsctrl) - gem_writel(bp, PCSCNTRL, pcsctrl); - } - spin_unlock_irqrestore(&bp->lock, flags); } @@ -845,6 +968,8 @@ static const struct phylink_mac_ops macb_phylink_ops = { .mac_config = macb_mac_config, .mac_link_down = macb_mac_link_down, .mac_link_up = macb_mac_link_up, + .mac_disable_tx_lpi = macb_mac_disable_tx_lpi, + .mac_enable_tx_lpi = macb_mac_enable_tx_lpi, }; static bool macb_phy_handle_exists(struct device_node *dn) @@ -909,6 +1034,12 @@ static int macb_mii_probe(struct net_device *dev) if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { bp->phylink_config.poll_fixed_state = true; bp->phylink_config.get_fixed_state = macb_get_pcs_fixed_state; + /* The PCSAUTONEG bit in PCSCNTRL is on out of reset. Setting + * default_an_inband to true tells phylink to turn it off only + * if necessary (e.g. a fixed link or a PHY that doesn't support + * inband). + */ + bp->phylink_config.default_an_inband = true; } bp->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | @@ -940,6 +1071,18 @@ static int macb_mii_probe(struct net_device *dev) } } + /* Configure EEE LPI if supported */ + if (bp->caps & MACB_CAPS_EEE) { + __set_bit(PHY_INTERFACE_MODE_MII, + bp->phylink_config.lpi_interfaces); + __set_bit(PHY_INTERFACE_MODE_GMII, + bp->phylink_config.lpi_interfaces); + phy_interface_set_rgmii(bp->phylink_config.lpi_interfaces); + bp->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD; + bp->phylink_config.lpi_timer_default = 250000; + bp->phylink_config.eee_enabled_default = true; + } + bp->phylink = phylink_create(&bp->phylink_config, bp->pdev->dev.fwnode, bp->phy_interface, &macb_phylink_ops); if (IS_ERR(bp->phylink)) { @@ -1336,6 +1479,9 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) netif_wake_subqueue(bp->dev, queue_index); spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); + if (packets) + macb_tx_lpi_schedule(bp); + return packets; } @@ -1745,8 +1891,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); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(RCOMP)); + macb_queue_isr_clear(bp, queue, MACB_BIT(RCOMP)); netdev_vdbg(bp->dev, "poll: packets pending, reschedule\n"); napi_schedule(napi); } @@ -1833,8 +1978,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)); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(TCOMP)); + macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP)); netdev_vdbg(bp->dev, "TX poll: packets pending, reschedule\n"); napi_schedule(napi); } @@ -1882,62 +2026,92 @@ static void macb_hresp_error_task(struct work_struct *work) netif_tx_start_all_queues(dev); } -static irqreturn_t macb_wol_interrupt(int irq, void *dev_id) +static void macb_wol_interrupt(struct macb_queue *queue, u32 status) { - struct macb_queue *queue = dev_id; struct macb *bp = queue->bp; - u32 status; - - status = queue_readl(queue, ISR); - - if (unlikely(!status)) - return IRQ_NONE; - spin_lock(&bp->lock); - - if (status & MACB_BIT(WOL)) { - queue_writel(queue, IDR, MACB_BIT(WOL)); - macb_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "MACB WoL: queue = %u, isr = 0x%08lx\n", - (unsigned int)(queue - bp->queues), - (unsigned long)status); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(WOL)); - pm_wakeup_event(&bp->pdev->dev, 0); - } + queue_writel(queue, IDR, MACB_BIT(WOL)); + macb_writel(bp, WOL, 0); + netdev_vdbg(bp->dev, "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)); + pm_wakeup_event(&bp->pdev->dev, 0); +} - spin_unlock(&bp->lock); +static void gem_wol_interrupt(struct macb_queue *queue, u32 status) +{ + struct macb *bp = queue->bp; - return IRQ_HANDLED; + queue_writel(queue, IDR, GEM_BIT(WOL)); + gem_writel(bp, WOL, 0); + netdev_vdbg(bp->dev, "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)); + pm_wakeup_event(&bp->pdev->dev, 0); } -static irqreturn_t gem_wol_interrupt(int irq, void *dev_id) +static int macb_interrupt_misc(struct macb_queue *queue, u32 status) { - struct macb_queue *queue = dev_id; struct macb *bp = queue->bp; - u32 status; + struct net_device *dev; + u32 ctrl; - status = queue_readl(queue, ISR); + dev = bp->dev; - if (unlikely(!status)) - return IRQ_NONE; + if (unlikely(status & (MACB_TX_ERR_FLAGS))) { + queue_writel(queue, IDR, MACB_TX_INT_FLAGS); + schedule_work(&queue->tx_error_task); + macb_queue_isr_clear(bp, queue, MACB_TX_ERR_FLAGS); + return -1; + } - spin_lock(&bp->lock); + /* Link change detection isn't possible with RMII, so we'll + * add that if/when we get our hands on a full-blown MII PHY. + */ - if (status & GEM_BIT(WOL)) { - queue_writel(queue, IDR, GEM_BIT(WOL)); - gem_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "GEM WoL: queue = %u, isr = 0x%08lx\n", - (unsigned int)(queue - bp->queues), - (unsigned long)status); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, GEM_BIT(WOL)); - pm_wakeup_event(&bp->pdev->dev, 0); + /* There is a hardware issue under heavy load where DMA can + * stop, this causes endless "used buffer descriptor read" + * interrupts but it can be cleared by re-enabling RX. See + * the at91rm9200 manual, section 41.3.1 or the Zynq manual + * section 16.7.4 for details. RXUBR is only enabled for + * these two versions. + */ + if (status & MACB_BIT(RXUBR)) { + ctrl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctrl & ~MACB_BIT(RE)); + wmb(); + macb_writel(bp, NCR, ctrl | MACB_BIT(RE)); + macb_queue_isr_clear(bp, queue, MACB_BIT(RXUBR)); } - spin_unlock(&bp->lock); + if (status & MACB_BIT(ISR_ROVR)) { + /* We missed at least one packet */ + spin_lock(&bp->stats_lock); + if (macb_is_gem(bp)) + bp->hw_stats.gem.rx_overruns++; + else + bp->hw_stats.macb.rx_overruns++; + spin_unlock(&bp->stats_lock); + macb_queue_isr_clear(bp, queue, MACB_BIT(ISR_ROVR)); + } - return IRQ_HANDLED; + 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"); + macb_queue_isr_clear(bp, queue, MACB_BIT(HRESP)); + } + + if (macb_is_gem(bp)) { + if (status & GEM_BIT(WOL)) + gem_wol_interrupt(queue, status); + } else { + if (status & MACB_BIT(WOL)) + macb_wol_interrupt(queue, status); + } + + return 0; } static irqreturn_t macb_interrupt(int irq, void *dev_id) @@ -1945,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; - u32 status, ctrl; + u32 status; status = queue_readl(queue, ISR); @@ -1958,8 +2132,7 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) /* close possible race with dev_close */ if (unlikely(!netif_running(dev))) { queue_writel(queue, IDR, -1); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, -1); + macb_queue_isr_clear(bp, queue, -1); break; } @@ -1975,84 +2148,27 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) * now. */ queue_writel(queue, IDR, bp->rx_intr_mask); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(RCOMP)); - - if (napi_schedule_prep(&queue->napi_rx)) { - netdev_vdbg(bp->dev, "scheduling RX softirq\n"); - __napi_schedule(&queue->napi_rx); - } + macb_queue_isr_clear(bp, queue, MACB_BIT(RCOMP)); + napi_schedule_irqoff(&queue->napi_rx); } if (status & (MACB_BIT(TCOMP) | MACB_BIT(TXUBR))) { queue_writel(queue, IDR, MACB_BIT(TCOMP)); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(TCOMP) | - MACB_BIT(TXUBR)); - + macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP) | + MACB_BIT(TXUBR)); if (status & MACB_BIT(TXUBR)) { queue->txubr_pending = true; wmb(); // ensure softirq can see update } - if (napi_schedule_prep(&queue->napi_tx)) { - netdev_vdbg(bp->dev, "scheduling TX softirq\n"); - __napi_schedule(&queue->napi_tx); - } - } - - if (unlikely(status & (MACB_TX_ERR_FLAGS))) { - queue_writel(queue, IDR, MACB_TX_INT_FLAGS); - schedule_work(&queue->tx_error_task); - - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_TX_ERR_FLAGS); - - break; - } - - /* Link change detection isn't possible with RMII, so we'll - * add that if/when we get our hands on a full-blown MII PHY. - */ - - /* There is a hardware issue under heavy load where DMA can - * stop, this causes endless "used buffer descriptor read" - * interrupts but it can be cleared by re-enabling RX. See - * the at91rm9200 manual, section 41.3.1 or the Zynq manual - * section 16.7.4 for details. RXUBR is only enabled for - * these two versions. - */ - if (status & MACB_BIT(RXUBR)) { - ctrl = macb_readl(bp, NCR); - macb_writel(bp, NCR, ctrl & ~MACB_BIT(RE)); - wmb(); - macb_writel(bp, NCR, ctrl | MACB_BIT(RE)); - - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(RXUBR)); - } - - if (status & MACB_BIT(ISR_ROVR)) { - /* We missed at least one packet */ - spin_lock(&bp->stats_lock); - if (macb_is_gem(bp)) - bp->hw_stats.gem.rx_overruns++; - else - bp->hw_stats.macb.rx_overruns++; - spin_unlock(&bp->stats_lock); - - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(ISR_ROVR)); + napi_schedule_irqoff(&queue->napi_tx); } - 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"); + if (unlikely(status & MACB_INT_MISC_FLAGS)) + if (macb_interrupt_misc(queue, status)) + break; - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, MACB_BIT(HRESP)); - } status = queue_readl(queue, ISR); } @@ -2442,6 +2558,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) skb->len); spin_lock(&bp->lock); + macb_tx_lpi_wake(bp); macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART)); spin_unlock(&bp->lock); @@ -2459,7 +2576,7 @@ static void macb_init_rx_buffer_size(struct macb *bp, size_t size) if (!macb_is_gem(bp)) { bp->rx_buffer_size = MACB_RX_BUFFER_SIZE; } else { - bp->rx_buffer_size = size; + bp->rx_buffer_size = MIN(size, RX_BUFFER_MAX); if (bp->rx_buffer_size % RX_BUFFER_MULTIPLE) { netdev_dbg(bp->dev, @@ -2746,8 +2863,7 @@ static void macb_reset_hw(struct macb *bp) for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { queue_writel(queue, IDR, -1); queue_readl(queue, ISR); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, -1); + macb_queue_isr_clear(bp, queue, -1); } } @@ -3107,6 +3223,8 @@ static int macb_close(struct net_device *dev) netdev_tx_reset_queue(netdev_get_tx_queue(dev, q)); } + cancel_delayed_work_sync(&bp->tx_lpi_work); + phylink_stop(bp->phylink); phylink_disconnect_phy(bp->phylink); @@ -3242,7 +3360,6 @@ 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) { - char stat_string[ETH_GSTRING_LEN]; struct macb *bp = netdev_priv(dev); struct macb_queue *queue; unsigned int i; @@ -3255,11 +3372,8 @@ static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) ETH_GSTRING_LEN); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { - for (i = 0; i < QUEUE_STATS_LEN; i++, p += ETH_GSTRING_LEN) { - snprintf(stat_string, ETH_GSTRING_LEN, "q%d_%s", - q, queue_statistics[i].stat_string); - memcpy(p, stat_string, ETH_GSTRING_LEN); - } + for (i = 0; i < QUEUE_STATS_LEN; i++) + ethtool_sprintf(&p, "q%u_%s", q, queue_statistics[i].stat_string); } break; } @@ -3624,15 +3738,14 @@ static unsigned int gem_get_tsu_rate(struct macb *bp) struct clk *tsu_clk; unsigned int tsu_rate; - tsu_clk = devm_clk_get(&bp->pdev->dev, "tsu_clk"); - if (!IS_ERR(tsu_clk)) - tsu_rate = clk_get_rate(tsu_clk); - /* try pclk instead */ - else if (!IS_ERR(bp->pclk)) { + if (!IS_ERR_OR_NULL(bp->tsu_clk)) { + tsu_rate = clk_get_rate(bp->tsu_clk); + } else { tsu_clk = bp->pclk; tsu_rate = clk_get_rate(tsu_clk); - } else - return -ENOTSUPP; + dev_warn(&bp->pdev->dev, "devicetree missing tsu_clk, using pclk as fallback\n"); + } + return tsu_rate; } @@ -4024,6 +4137,20 @@ 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) +{ + struct macb *bp = netdev_priv(dev); + + return phylink_ethtool_get_eee(bp->phylink, eee); +} + +static int macb_set_eee(struct net_device *dev, struct ethtool_keee *eee) +{ + struct macb *bp = netdev_priv(dev); + + return phylink_ethtool_set_eee(bp->phylink, eee); +} + static const struct ethtool_ops gem_ethtool_ops = { .get_regs_len = macb_get_regs_len, .get_regs = macb_get_regs, @@ -4045,6 +4172,9 @@ static const struct ethtool_ops gem_ethtool_ops = { .get_rxnfc = gem_get_rxnfc, .set_rxnfc = gem_set_rxnfc, .get_rx_ring_count = gem_get_rx_ring_count, + .nway_reset = phy_ethtool_nway_reset, + .get_eee = macb_get_eee, + .set_eee = macb_set_eee, }; static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) @@ -4418,14 +4548,12 @@ static const struct net_device_ops macb_netdev_ops = { static void macb_configure_caps(struct macb *bp, const struct macb_config *dt_conf) { - struct device_node *np = bp->pdev->dev.of_node; - bool refclk_ext; u32 dcfg; - refclk_ext = of_property_read_bool(np, "cdns,refclk-ext"); + bp->caps = dt_conf->caps; - if (dt_conf) - bp->caps = dt_conf->caps; + if (!dt_conf->usrio) + bp->caps |= MACB_CAPS_USRIO_DISABLED; if (hw_is_gem(bp->regs, bp->native_io)) { bp->caps |= MACB_CAPS_MACB_IS_GEM; @@ -4435,6 +4563,8 @@ static void macb_configure_caps(struct macb *bp, bp->caps |= MACB_CAPS_ISR_CLEAR_ON_WRITE; if (GEM_BFEXT(NO_PCS, dcfg) == 0) bp->caps |= MACB_CAPS_PCS; + if (!(dcfg & GEM_BIT(USERIO))) + bp->caps |= MACB_CAPS_USRIO_DISABLED; dcfg = gem_readl(bp, DCFG12); if (GEM_BFEXT(HIGH_SPEED, dcfg) == 1) bp->caps |= MACB_CAPS_HIGH_SPEED; @@ -4456,9 +4586,6 @@ static void macb_configure_caps(struct macb *bp, } } - if (refclk_ext) - bp->caps |= MACB_CAPS_USRIO_HAS_CLKEN; - dev_dbg(&bp->pdev->dev, "Cadence caps 0x%08x\n", bp->caps); } @@ -4497,9 +4624,9 @@ static void macb_clks_disable(struct clk *pclk, struct clk *hclk, struct clk *tx clk_bulk_disable_unprepare(ARRAY_SIZE(clks), clks); } -static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, - struct clk **hclk, struct clk **tx_clk, - struct clk **rx_clk, struct clk **tsu_clk) +static int macb_clk_init_dflt(struct platform_device *pdev, struct clk **pclk, + struct clk **hclk, struct clk **tx_clk, + struct clk **rx_clk, struct clk **tsu_clk) { struct macb_platform_data *pdata; int err; @@ -4582,7 +4709,20 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, return err; } -static int macb_init(struct platform_device *pdev) +static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, + struct clk **hclk, struct clk **tx_clk, + struct clk **rx_clk, struct clk **tsu_clk, + const struct macb_config *config) +{ + if (config->clk_init) + return config->clk_init(pdev, pclk, hclk, tx_clk, rx_clk, + tsu_clk); + else + return macb_clk_init_dflt(pdev, pclk, hclk, tx_clk, rx_clk, + tsu_clk); +} + +static int macb_init_dflt(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); unsigned int hw_q, q; @@ -4603,7 +4743,7 @@ static int macb_init(struct platform_device *pdev) queue->bp = bp; spin_lock_init(&queue->tx_ptr_lock); netif_napi_add(dev, &queue->napi_rx, macb_rx_poll); - netif_napi_add(dev, &queue->napi_tx, macb_tx_poll); + netif_napi_add_tx(dev, &queue->napi_tx, macb_tx_poll); if (hw_q) { queue->ISR = GEM_ISR(hw_q - 1); queue->IER = GEM_IER(hw_q - 1); @@ -4713,16 +4853,52 @@ static int macb_init(struct platform_device *pdev) if (!(bp->caps & MACB_CAPS_USRIO_DISABLED)) { val = 0; - if (phy_interface_mode_is_rgmii(bp->phy_interface)) - val = bp->usrio->rgmii; - else if (bp->phy_interface == PHY_INTERFACE_MODE_RMII && - (bp->caps & MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII)) - val = bp->usrio->rmii; - else if (!(bp->caps & MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII)) - val = bp->usrio->mii; + if (bp->caps & MACB_CAPS_USRIO_HAS_MII) { + if (phy_interface_mode_is_rgmii(bp->phy_interface)) + val = bp->usrio->rgmii; + else if (bp->phy_interface == PHY_INTERFACE_MODE_RMII && + (bp->caps & MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII)) + val = bp->usrio->rmii; + else if (!(bp->caps & MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII)) + val = bp->usrio->mii; + } if (bp->caps & MACB_CAPS_USRIO_HAS_CLKEN) - val |= bp->usrio->refclk; + val |= bp->usrio->clken; + + if (bp->caps & MACB_CAPS_USRIO_HAS_REFCLK_SOURCE) { + const char *prop; + bool refclk_ext; + int ret; + + /* Default to whatever was set in the match data for + * this device. There's two properties for refclk + * control, but the boolean one is deprecated so is + * a lower priority to check, no device should have + * both. + */ + refclk_ext = bp->usrio->refclk_default_external; + + ret = of_property_read_string(pdev->dev.of_node, + "cdns,refclk-source", &prop); + if (!ret) { + if (!strcmp(prop, "external")) + refclk_ext = true; + else + refclk_ext = false; + } else { + ret = of_property_read_bool(pdev->dev.of_node, + "cdns,refclk-ext"); + if (ret) + refclk_ext = true; + } + + if (refclk_ext) + val |= bp->usrio->refclk; + } + + if (bp->caps & MACB_CAPS_USRIO_HAS_TSUCLK_SOURCE) + val |= bp->usrio->tsu_source; macb_or_gem_writel(bp, USRIO, val); } @@ -4737,11 +4913,20 @@ static int macb_init(struct platform_device *pdev) return 0; } -static const struct macb_usrio_config macb_default_usrio = { +static int macb_init(struct platform_device *pdev, + const struct macb_config *config) +{ + if (config->init) + return config->init(pdev); + else + return macb_init_dflt(pdev); +} + +static const struct macb_usrio_config at91_default_usrio = { .mii = MACB_BIT(MII), .rmii = MACB_BIT(RMII), .rgmii = GEM_BIT(RGMII), - .refclk = MACB_BIT(CLKEN), + .clken = MACB_BIT(CLKEN), }; #if defined(CONFIG_OF) @@ -5190,7 +5375,7 @@ static int fu540_c000_clk_init(struct platform_device *pdev, struct clk **pclk, struct clk_init_data init; int err = 0; - err = macb_clk_init(pdev, pclk, hclk, tx_clk, rx_clk, tsu_clk); + err = macb_clk_init_dflt(pdev, pclk, hclk, tx_clk, rx_clk, tsu_clk); if (err) return err; @@ -5237,7 +5422,7 @@ static int fu540_c000_init(struct platform_device *pdev) if (IS_ERR(mgmt->reg)) return PTR_ERR(mgmt->reg); - return macb_init(pdev); + return macb_init_dflt(pdev); } static int init_reset_optional(struct platform_device *pdev) @@ -5287,7 +5472,7 @@ static int init_reset_optional(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "failed to reset controller"); } - ret = macb_init(pdev); + ret = macb_init_dflt(pdev); err_out_phy_exit: if (ret) @@ -5312,130 +5497,134 @@ static int eyeq5_init(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "failed to init PHY\n"); - ret = macb_init(pdev); + ret = macb_init_dflt(pdev); if (ret) phy_exit(bp->phy); return ret; } -static const struct macb_usrio_config sama7g5_usrio = { +static const struct macb_usrio_config mpfs_usrio = { + .tsu_source = 0, +}; + +static const struct macb_usrio_config sama7g5_gem_usrio = { + .mii = 0, + .rmii = 1, + .rgmii = 2, + .refclk = BIT(2), + .refclk_default_external = false, + .hdfctlen = BIT(6), +}; + +static const struct macb_usrio_config sama7g5_emac_usrio = { .mii = 0, .rmii = 1, .rgmii = 2, .refclk = BIT(2), + .refclk_default_external = true, .hdfctlen = BIT(6), }; static const struct macb_config fu540_c000_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | - MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, .clk_init = fu540_c000_clk_init, .init = fu540_c000_init, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config at91sam9260_config = { - .caps = MACB_CAPS_USRIO_HAS_CLKEN | MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .caps = MACB_CAPS_USRIO_HAS_CLKEN | MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | + MACB_CAPS_USRIO_HAS_MII, + .usrio = &at91_default_usrio, }; static const struct macb_config sama5d3macb_config = { .caps = MACB_CAPS_SG_DISABLED | - MACB_CAPS_USRIO_HAS_CLKEN | MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + MACB_CAPS_USRIO_HAS_CLKEN | MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | + MACB_CAPS_USRIO_HAS_MII, + .usrio = &at91_default_usrio, }; static const struct macb_config pc302gem_config = { - .caps = MACB_CAPS_SG_DISABLED | MACB_CAPS_GIGABIT_MODE_AVAILABLE, + .caps = MACB_CAPS_SG_DISABLED | MACB_CAPS_GIGABIT_MODE_AVAILABLE | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config sama5d2_config = { - .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_JUMBO, + .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_JUMBO | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config sama5d29_config = { - .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_GEM_HAS_PTP, + .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config sama5d3_config = { .caps = MACB_CAPS_SG_DISABLED | MACB_CAPS_GIGABIT_MODE_AVAILABLE | - MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_JUMBO, + MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | MACB_CAPS_JUMBO | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config sama5d4_config = { - .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII, + .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 4, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config emac_config = { - .caps = MACB_CAPS_NEEDS_RSTONUBR | MACB_CAPS_MACB_IS_EMAC, + .caps = MACB_CAPS_NEEDS_RSTONUBR | MACB_CAPS_MACB_IS_EMAC | + MACB_CAPS_USRIO_HAS_MII, .clk_init = at91ether_clk_init, .init = at91ether_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config np4_config = { .caps = MACB_CAPS_USRIO_DISABLED, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, }; static const struct macb_config zynqmp_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | - MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH, + MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, .init = init_reset_optional, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config zynq_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_NO_GIGABIT_HALF | - MACB_CAPS_NEEDS_RSTONUBR, + MACB_CAPS_NEEDS_RSTONUBR | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config mpfs_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | - MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_USRIO_HAS_TSUCLK_SOURCE, .dma_burst_length = 16, - .clk_init = macb_clk_init, .init = init_reset_optional, - .usrio = &macb_default_usrio, + .usrio = &mpfs_usrio, .max_tx_length = 4040, /* Cadence Erratum 1686 */ .jumbo_max_len = 4040, }; @@ -5443,57 +5632,63 @@ static const struct macb_config mpfs_config = { static const struct macb_config sama7g5_gem_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG | MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | - MACB_CAPS_MIIONRGMII | MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_USRIO_HAS_REFCLK_SOURCE | + MACB_CAPS_MIIONRGMII | MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &sama7g5_usrio, + .usrio = &sama7g5_gem_usrio, }; static const struct macb_config sama7g5_emac_config = { .caps = MACB_CAPS_USRIO_DEFAULT_IS_MII_GMII | - MACB_CAPS_USRIO_HAS_CLKEN | MACB_CAPS_MIIONRGMII | - MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_MIIONRGMII | + MACB_CAPS_USRIO_HAS_REFCLK_SOURCE | + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &sama7g5_usrio, + .usrio = &sama7g5_emac_usrio, }; static const struct macb_config versal_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH | MACB_CAPS_NEED_TSUCLK | MACB_CAPS_QUEUE_DISABLE | - MACB_CAPS_QBV, + MACB_CAPS_QBV | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, .init = init_reset_optional, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, }; static const struct macb_config eyeq5_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_QUEUE_DISABLE | - MACB_CAPS_NO_LSO, + MACB_CAPS_NO_LSO | MACB_CAPS_EEE, .dma_burst_length = 16, - .clk_init = macb_clk_init, .init = eyeq5_init, .jumbo_max_len = 10240, - .usrio = &macb_default_usrio, }; static const struct macb_config raspberrypi_rp1_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG | MACB_CAPS_JUMBO | - MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_EEE | + MACB_CAPS_USRIO_HAS_MII, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, .jumbo_max_len = 10240, }; +static const struct macb_config pic64hpsc_config = { + .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | + MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_USRIO_DISABLED, + .dma_burst_length = 16, + .init = init_reset_optional, + .jumbo_max_len = 16383, +}; + static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config }, { .compatible = "cdns,macb" }, @@ -5512,6 +5707,7 @@ static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,zynq-gem", .data = &zynq_config }, /* deprecated */ { .compatible = "sifive,fu540-c000-gem", .data = &fu540_c000_config }, { .compatible = "microchip,mpfs-macb", .data = &mpfs_config }, + { .compatible = "microchip,pic64hpsc-gem", .data = &pic64hpsc_config}, { .compatible = "microchip,sama7g5-gem", .data = &sama7g5_gem_config }, { .compatible = "microchip,sama7g5-emac", .data = &sama7g5_emac_config }, { .compatible = "mobileye,eyeq5-gem", .data = &eyeq5_config }, @@ -5529,9 +5725,7 @@ static const struct macb_config default_gem_config = { MACB_CAPS_JUMBO | MACB_CAPS_GEM_HAS_PTP, .dma_burst_length = 16, - .clk_init = macb_clk_init, - .init = macb_init, - .usrio = &macb_default_usrio, + .usrio = &at91_default_usrio, .jumbo_max_len = 10240, }; @@ -5559,7 +5753,8 @@ static int macb_probe(struct platform_device *pdev) if (!macb_config) macb_config = &default_gem_config; - err = macb_config->clk_init(pdev, &pclk, &hclk, &tx_clk, &rx_clk, &tsu_clk); + err = macb_clk_init(pdev, &pclk, &hclk, &tx_clk, &rx_clk, &tsu_clk, + macb_config); if (err) return err; @@ -5619,6 +5814,13 @@ static int macb_probe(struct platform_device *pdev) bp->usrio = macb_config->usrio; + if (of_property_read_bool(bp->pdev->dev.of_node, "cdns,timer-adjust") && + IS_ENABLED(CONFIG_MACB_USE_HWSTAMP)) { + dev_err(&pdev->dev, "Timer adjust mode is not supported\n"); + err = -EINVAL; + goto err_out_free_netdev; + } + /* By default we set to partial store and forward mode for zynqmp. * Disable if not set in devicetree. */ @@ -5665,7 +5867,8 @@ static int macb_probe(struct platform_device *pdev) /* MTU range: 68 - 1518 or 10240 */ dev->min_mtu = GEM_MTU_MIN_SIZE; if ((bp->caps & MACB_CAPS_JUMBO) && bp->jumbo_max_len) - dev->max_mtu = bp->jumbo_max_len - ETH_HLEN - ETH_FCS_LEN; + dev->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; @@ -5699,7 +5902,7 @@ static int macb_probe(struct platform_device *pdev) bp->phy_interface = interface; /* IP specific init */ - err = macb_config->init(pdev); + err = macb_init(pdev, macb_config); if (err) goto err_out_free_netdev; @@ -5716,6 +5919,7 @@ static int macb_probe(struct platform_device *pdev) } 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", macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID), @@ -5759,6 +5963,7 @@ static void macb_remove(struct platform_device *pdev) mdiobus_free(bp->mii_bus); device_set_wakeup_enable(&bp->pdev->dev, 0); + cancel_delayed_work_sync(&bp->tx_lpi_work); cancel_work_sync(&bp->hresp_err_bh_work); pm_runtime_disable(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); @@ -5778,7 +5983,6 @@ static int __maybe_unused macb_suspend(struct device *dev) unsigned long flags; u32 tmp, ifa_local; unsigned int q; - int err; if (!device_may_wakeup(&bp->dev->dev)) phy_exit(bp->phy); @@ -5827,8 +6031,7 @@ static int __maybe_unused macb_suspend(struct device *dev) /* Disable all interrupts */ queue_writel(queue, IDR, -1); queue_readl(queue, ISR); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(queue, ISR, -1); + macb_queue_isr_clear(bp, queue, -1); } /* Enable Receive engine */ macb_writel(bp, NCR, tmp | MACB_BIT(RE)); @@ -5842,39 +6045,15 @@ static int __maybe_unused macb_suspend(struct device *dev) /* write IP address into register */ tmp |= MACB_BFEXT(IP, ifa_local); } - spin_unlock_irqrestore(&bp->lock, flags); - /* Change interrupt handler and - * Enable WoL IRQ on queue 0 - */ - devm_free_irq(dev, bp->queues[0].irq, bp->queues); if (macb_is_gem(bp)) { - err = devm_request_irq(dev, bp->queues[0].irq, gem_wol_interrupt, - IRQF_SHARED, netdev->name, bp->queues); - if (err) { - dev_err(dev, - "Unable to request IRQ %d (error %d)\n", - bp->queues[0].irq, err); - return err; - } - spin_lock_irqsave(&bp->lock, flags); queue_writel(bp->queues, IER, GEM_BIT(WOL)); gem_writel(bp, WOL, tmp); - spin_unlock_irqrestore(&bp->lock, flags); } else { - err = devm_request_irq(dev, bp->queues[0].irq, macb_wol_interrupt, - IRQF_SHARED, netdev->name, bp->queues); - if (err) { - dev_err(dev, - "Unable to request IRQ %d (error %d)\n", - bp->queues[0].irq, err); - return err; - } - spin_lock_irqsave(&bp->lock, flags); queue_writel(bp->queues, IER, MACB_BIT(WOL)); macb_writel(bp, WOL, tmp); - spin_unlock_irqrestore(&bp->lock, flags); } + spin_unlock_irqrestore(&bp->lock, flags); enable_irq_wake(bp->queues[0].irq); } @@ -5916,7 +6095,6 @@ static int __maybe_unused macb_resume(struct device *dev) struct macb_queue *queue; unsigned long flags; unsigned int q; - int err; if (!device_may_wakeup(&bp->dev->dev)) phy_init(bp->phy); @@ -5939,21 +6117,9 @@ static int __maybe_unused macb_resume(struct device *dev) } /* Clear ISR on queue 0 */ queue_readl(bp->queues, ISR); - if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) - queue_writel(bp->queues, ISR, -1); + macb_queue_isr_clear(bp, bp->queues, -1); spin_unlock_irqrestore(&bp->lock, flags); - /* Replace interrupt handler on queue 0 */ - devm_free_irq(dev, bp->queues[0].irq, bp->queues); - err = devm_request_irq(dev, bp->queues[0].irq, macb_interrupt, - IRQF_SHARED, netdev->name, bp->queues); - if (err) { - dev_err(dev, - "Unable to request IRQ %d (error %d)\n", - bp->queues[0].irq, err); - return err; - } - disable_irq_wake(bp->queues[0].irq); /* Now make sure we disable phy before moving diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c index b3d34433bd14..a6c08175d9dd 100644 --- a/drivers/nfc/nxp-nci/i2c.c +++ b/drivers/nfc/nxp-nci/i2c.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -267,6 +268,7 @@ static int nxp_nci_i2c_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct nxp_nci_i2c_phy *phy; + unsigned long irqflags; int r; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { @@ -303,9 +305,26 @@ static int nxp_nci_i2c_probe(struct i2c_client *client) if (r < 0) return r; + /* + * ACPI platforms may report incorrect IRQ trigger types + * (e.g. level-high), which can lead to interrupt storms. + * + * Use the historically stable rising-edge trigger for ACPI devices. + * + * On non-ACPI systems (e.g. Device Tree), prefer the firmware- + * provided trigger type, falling back to rising-edge if not set. + */ + if (ACPI_COMPANION(dev)) { + irqflags = IRQF_TRIGGER_RISING; + } else { + irqflags = irq_get_trigger_type(client->irq); + if (!irqflags) + irqflags = IRQF_TRIGGER_RISING; + } + r = request_threaded_irq(client->irq, NULL, nxp_nci_i2c_irq_thread_fn, - IRQF_ONESHOT, + irqflags | IRQF_ONESHOT, NXP_NCI_I2C_DRIVER_NAME, phy); if (r < 0) nfc_err(&client->dev, "Unable to register IRQ handler\n"); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 48946cca4be7..a1f5fe8e583f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4508,6 +4508,30 @@ static void quirk_aspeed_pci_bridge_no_alias(struct pci_dev *pdev) } DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASPEED, 0x1150, quirk_aspeed_pci_bridge_no_alias); +/* + * PCI BAR 5 is not setup correctly for the on-board AHCI controller + * on Broadcom's Vulcan processor. Added a quirk to fix BAR 5 by + * using BAR 4's resources which are populated correctly and NOT + * actually used by the AHCI controller. + */ +static void quirk_fix_vulcan_ahci_bars(struct pci_dev *dev) +{ + struct resource *r = &dev->resource[4]; + + if (!(r->flags & IORESOURCE_MEM) || (r->start == 0)) + return; + + /* Set BAR5 resource to BAR4 */ + dev->resource[5] = *r; + + /* Update BAR5 in pci config space */ + pci_write_config_dword(dev, PCI_BASE_ADDRESS_5, r->start); + + /* Clear BAR4's resource */ + memset(r, 0, sizeof(*r)); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9027, quirk_fix_vulcan_ahci_bars); + /* * Intersil/Techwell TW686[4589]-based video capture cards have an empty (zero) * class code. Fix it. diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 24960ba9caa9..32597cdb72ef 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5894,6 +5894,13 @@ static void hub_event(struct work_struct *work) (u16) hub->change_bits[0], (u16) hub->event_bits[0]); + /* Don't disconnect USB-SATA on TrimSlice */ + if (strcmp(dev_name(hdev->bus->controller), "tegra-ehci.0") == 0) { + if ((hdev->state == 7) && (hub->change_bits[0] == 0) && + (hub->event_bits[0] == 0x2)) + hub->event_bits[0] = 0; + } + /* Lock the device, then check to see if we were * disconnected while waiting for the lock to succeed. */ usb_lock_device(hdev); diff --git a/include/crypto/krb5.h b/include/crypto/krb5.h index 71dd38f59be1..aac3ecf88467 100644 --- a/include/crypto/krb5.h +++ b/include/crypto/krb5.h @@ -121,9 +121,12 @@ size_t crypto_krb5_how_much_buffer(const struct krb5_enctype *krb5, size_t crypto_krb5_how_much_data(const struct krb5_enctype *krb5, enum krb5_crypto_mode mode, size_t *_buffer_size, size_t *_offset); -void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, - enum krb5_crypto_mode mode, - size_t *_offset, size_t *_len); +int crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t *_offset, size_t *_len); +int crypto_krb5_check_data_len(const struct krb5_enctype *krb5, + enum krb5_crypto_mode mode, + size_t len, size_t min_content); struct crypto_aead *crypto_krb5_prepare_encryption(const struct krb5_enctype *krb5, const struct krb5_buffer *TK, u32 usage, gfp_t gfp); diff --git a/include/linux/efi.h b/include/linux/efi.h index 664898d09ff5..34f476d02181 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -45,6 +45,8 @@ struct screen_info; #define EFI_ABORTED (21 | (1UL << (BITS_PER_LONG-1))) #define EFI_SECURITY_VIOLATION (26 | (1UL << (BITS_PER_LONG-1))) +#define EFI_IS_ERROR(x) ((x) & (1UL << (BITS_PER_LONG-1))) + typedef unsigned long efi_status_t; typedef u8 efi_bool_t; typedef u16 efi_char16_t; /* UNICODE character */ @@ -868,6 +870,14 @@ static inline int efi_range_is_wc(unsigned long start, unsigned long len) #define EFI_MEM_ATTR 9 /* Did firmware publish an EFI_MEMORY_ATTRIBUTES table? */ #define EFI_MEM_NO_SOFT_RESERVE 10 /* Is the kernel configured to ignore soft reservations? */ #define EFI_PRESERVE_BS_REGIONS 11 /* Are EFI boot-services memory segments available? */ +#define EFI_SECURE_BOOT 12 /* Are we in Secure Boot mode? */ + +enum efi_secureboot_mode { + efi_secureboot_mode_unset, + efi_secureboot_mode_unknown, + efi_secureboot_mode_disabled, + efi_secureboot_mode_enabled, +}; #ifdef CONFIG_EFI /* @@ -879,6 +889,8 @@ static inline bool efi_enabled(int feature) } extern void efi_reboot(enum reboot_mode reboot_mode, const char *__unused); +extern void __init efi_set_secure_boot(enum efi_secureboot_mode mode); + bool __pure __efi_soft_reserve_enabled(void); static inline bool __pure efi_soft_reserve_enabled(void) @@ -900,6 +912,8 @@ static inline bool efi_enabled(int feature) static inline void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) {} +static inline void efi_set_secure_boot(enum efi_secureboot_mode mode) {} + static inline bool efi_soft_reserve_enabled(void) { return false; @@ -914,6 +928,7 @@ static inline void efi_find_mirror(void) {} #endif extern int efi_status_to_err(efi_status_t status); +extern const char *efi_status_to_str(efi_status_t status); /* * Variable Attributes @@ -1131,13 +1146,6 @@ extern unsigned long efi_call_virt_save_flags(void); void efi_runtime_assert_lock_held(void); -enum efi_secureboot_mode { - efi_secureboot_mode_unset, - efi_secureboot_mode_unknown, - efi_secureboot_mode_disabled, - efi_secureboot_mode_enabled, -}; - static inline enum efi_secureboot_mode efi_get_secureboot_mode(efi_get_variable_t *get_var) { diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index b4958167e381..64ffe8c2dacd 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -451,6 +451,7 @@ LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap) LSM_HOOK(int, 0, locked_down, enum lockdown_reason what) + #ifdef CONFIG_PERF_EVENTS LSM_HOOK(int, 0, perf_event_open, int type) LSM_HOOK(int, 0, perf_event_alloc, struct perf_event *event) diff --git a/include/linux/mfd/bcm2835-pm.h b/include/linux/mfd/bcm2835-pm.h index f70a810c55f7..d2e17ab1dbfc 100644 --- a/include/linux/mfd/bcm2835-pm.h +++ b/include/linux/mfd/bcm2835-pm.h @@ -5,11 +5,18 @@ #include +enum bcm2835_soc { + BCM2835_PM_SOC_BCM2835, + BCM2835_PM_SOC_BCM2711, + BCM2835_PM_SOC_BCM2712, +}; + struct bcm2835_pm { struct device *dev; void __iomem *base; void __iomem *asb; void __iomem *rpivid_asb; + enum bcm2835_soc soc; }; #endif /* BCM2835_MFD_PM_H */ diff --git a/include/linux/rmi.h b/include/linux/rmi.h index ab7eea01ab42..fff7c5f737fc 100644 --- a/include/linux/rmi.h +++ b/include/linux/rmi.h @@ -364,6 +364,7 @@ struct rmi_driver_data { struct rmi4_attn_data attn_data; DECLARE_KFIFO(attn_fifo, struct rmi4_attn_data, 16); + struct work_struct attn_work; }; int rmi_register_transport_device(struct rmi_transport_dev *xport); diff --git a/include/linux/security.h b/include/linux/security.h index 8d2d4856934e..d6b1aec4340f 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2427,4 +2427,13 @@ static inline void security_initramfs_populated(void) } #endif /* CONFIG_SECURITY */ +#ifdef CONFIG_SECURITY_LOCKDOWN_LSM +extern int security_lock_kernel_down(const char *where, enum lockdown_reason level); +#else +static inline int security_lock_kernel_down(const char *where, enum lockdown_reason level) +{ + return 0; +} +#endif /* CONFIG_SECURITY_LOCKDOWN_LSM */ + #endif /* ! __LINUX_SECURITY_H */ diff --git a/include/trace/events/rxrpc.h b/include/trace/events/rxrpc.h index 573f2df3a2c9..704a10de6670 100644 --- a/include/trace/events/rxrpc.h +++ b/include/trace/events/rxrpc.h @@ -71,6 +71,7 @@ EM(rxkad_abort_resp_unknown_tkt, "rxkad-resp-unknown-tkt") \ EM(rxkad_abort_resp_version, "rxkad-resp-version") \ /* RxGK security errors */ \ + EM(rxgk_abort_1_short_header, "rxgk1-short-hdr") \ EM(rxgk_abort_1_verify_mic_eproto, "rxgk1-vfy-mic-eproto") \ EM(rxgk_abort_2_decrypt_eproto, "rxgk2-dec-eproto") \ EM(rxgk_abort_2_short_data, "rxgk2-short-data") \ diff --git a/kernel/module/signing.c b/kernel/module/signing.c index a2ff4242e623..f0d2be1ee4f1 100644 --- a/kernel/module/signing.c +++ b/kernel/module/signing.c @@ -61,10 +61,17 @@ int mod_verify_sig(const void *mod, struct load_info *info) modlen -= sig_len + sizeof(ms); info->len = modlen; - return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len, + ret = verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len, VERIFY_USE_SECONDARY_KEYRING, VERIFYING_MODULE_SIGNATURE, NULL, NULL); + if (ret == -ENOKEY && IS_ENABLED(CONFIG_INTEGRITY_PLATFORM_KEYRING)) { + ret = verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len, + VERIFY_USE_PLATFORM_KEYRING, + VERIFYING_MODULE_SIGNATURE, + NULL, NULL); + } + return ret; } int module_sig_check(struct load_info *info, int flags) diff --git a/net/core/gro.c b/net/core/gro.c index 9f8960789b2c..c7b8eab61e02 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -123,6 +123,9 @@ int skb_gro_receive(struct sk_buff *p, struct sk_buff *skb) lp = NAPI_GRO_CB(p)->last; pinfo = skb_shinfo(lp); + if (skb_zcopy(skb) || skb_zcopy(lp)) + return -ETOOMANYREFS; + if (headlen <= offset) { skb_frag_t *frag; skb_frag_t *frag2; diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index 6a5febbdbee4..2d7daca8516c 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -410,6 +410,7 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info * struct page *page; struct sk_buff *trailer; int tailen = esp->tailen; + unsigned int allocsize; /* this is non-NULL only with TCP/UDP Encapsulation */ if (x->encap) { @@ -419,8 +420,8 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info * return err; } - if (ALIGN(tailen, L1_CACHE_BYTES) > PAGE_SIZE || - ALIGN(skb->data_len, L1_CACHE_BYTES) > PAGE_SIZE) + allocsize = ALIGN(skb->data_len + tailen, L1_CACHE_BYTES); + if (allocsize > PAGE_SIZE) goto cow; if (!skb_cloned(skb)) { diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 9c06c5a1419d..0fad1dc558b8 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -440,6 +440,7 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info struct page *page; struct sk_buff *trailer; int tailen = esp->tailen; + unsigned int allocsize; if (x->encap) { int err = esp6_output_encap(x, skb, esp); @@ -448,8 +449,8 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info return err; } - if (ALIGN(tailen, L1_CACHE_BYTES) > PAGE_SIZE || - ALIGN(skb->data_len, L1_CACHE_BYTES) > PAGE_SIZE) + allocsize = ALIGN(skb->data_len + tailen, L1_CACHE_BYTES); + if (allocsize > PAGE_SIZE) goto cow; if (!skb_cloned(skb)) { diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h index 27c2aa2dd023..98f2165159d7 100644 --- a/net/rxrpc/ar-internal.h +++ b/net/rxrpc/ar-internal.h @@ -213,8 +213,6 @@ struct rxrpc_skb_priv { struct { u16 offset; /* Offset of data */ u16 len; /* Length of data */ - u8 flags; -#define RXRPC_RX_VERIFIED 0x01 }; struct { rxrpc_seq_t first_ack; /* First packet in acks table */ @@ -309,15 +307,16 @@ struct rxrpc_security { struct sk_buff *challenge); /* verify a response */ - int (*verify_response)(struct rxrpc_connection *, - struct sk_buff *); + int (*verify_response)(struct rxrpc_connection *conn, + struct sk_buff *response_skb, + void *response, unsigned int len); /* clear connection security */ void (*clear)(struct rxrpc_connection *); /* Default ticket -> key decoder */ int (*default_decode_ticket)(struct rxrpc_connection *conn, struct sk_buff *skb, - unsigned int ticket_offset, unsigned int ticket_len, + void *ticket, unsigned int ticket_len, struct key **_key); }; @@ -774,6 +773,11 @@ struct rxrpc_call { struct sk_buff_head recvmsg_queue; /* Queue of packets ready for recvmsg() */ struct sk_buff_head rx_queue; /* Queue of packets for this call to receive */ struct sk_buff_head rx_oos_queue; /* Queue of out of sequence packets */ + void *rx_dec_buffer; /* Decryption buffer */ + unsigned short rx_dec_bsize; /* rx_dec_buffer size */ + unsigned short rx_dec_offset; /* Decrypted packet data offset */ + unsigned short rx_dec_len; /* Decrypted packet data len */ + rxrpc_seq_t rx_dec_seq; /* Packet in decryption buffer */ rxrpc_seq_t rx_highest_seq; /* Higest sequence number received */ rxrpc_seq_t rx_consumed; /* Highest packet consumed */ diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c index 2b19b252225e..fec59d9338b9 100644 --- a/net/rxrpc/call_event.c +++ b/net/rxrpc/call_event.c @@ -332,27 +332,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call) saw_ack |= sp->hdr.type == RXRPC_PACKET_TYPE_ACK; - if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA && - sp->hdr.securityIndex != 0 && - (skb_cloned(skb) || - skb_has_frag_list(skb) || - skb_has_shared_frag(skb))) { - /* Unshare the packet so that it can be - * modified by in-place decryption. - */ - struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC); - - if (nskb) { - rxrpc_new_skb(nskb, rxrpc_skb_new_unshared); - rxrpc_input_call_packet(call, nskb); - rxrpc_free_skb(nskb, rxrpc_skb_put_call_rx); - } else { - /* OOM - Drop the packet. */ - rxrpc_see_skb(skb, rxrpc_skb_see_unshare_nomem); - } - } else { - rxrpc_input_call_packet(call, skb); - } + rxrpc_input_call_packet(call, skb); rxrpc_free_skb(skb, rxrpc_skb_put_call_rx); did_receive = true; } diff --git a/net/rxrpc/call_object.c b/net/rxrpc/call_object.c index f035f486c139..fcb9d38bb521 100644 --- a/net/rxrpc/call_object.c +++ b/net/rxrpc/call_object.c @@ -152,6 +152,7 @@ struct rxrpc_call *rxrpc_alloc_call(struct rxrpc_sock *rx, gfp_t gfp, spin_lock_init(&call->notify_lock); refcount_set(&call->ref, 1); call->debug_id = debug_id; + call->rx_pkt_offset = USHRT_MAX; call->tx_total_len = -1; call->tx_jumbo_max = 1; call->next_rx_timo = 20 * HZ; @@ -553,6 +554,7 @@ static void rxrpc_cleanup_rx_buffers(struct rxrpc_call *call) rxrpc_purge_queue(&call->recvmsg_queue); rxrpc_purge_queue(&call->rx_queue); rxrpc_purge_queue(&call->rx_oos_queue); + kfree(call->rx_dec_buffer); } /* diff --git a/net/rxrpc/conn_event.c b/net/rxrpc/conn_event.c index 442414d90ba1..c96ca615b787 100644 --- a/net/rxrpc/conn_event.c +++ b/net/rxrpc/conn_event.c @@ -243,28 +243,22 @@ static void rxrpc_call_is_secure(struct rxrpc_call *call) static int rxrpc_verify_response(struct rxrpc_connection *conn, struct sk_buff *skb) { + unsigned int len = skb->len - sizeof(struct rxrpc_wire_header); + void *buffer; int ret; - if (skb_cloned(skb) || skb_has_frag_list(skb) || - skb_has_shared_frag(skb)) { - /* Copy the packet if shared so that we can do in-place - * decryption. - */ - struct sk_buff *nskb = skb_copy(skb, GFP_NOFS); - - if (nskb) { - rxrpc_new_skb(nskb, rxrpc_skb_new_unshared); - ret = conn->security->verify_response(conn, nskb); - rxrpc_free_skb(nskb, rxrpc_skb_put_response_copy); - } else { - /* OOM - Drop the packet. */ - rxrpc_see_skb(skb, rxrpc_skb_see_unshare_nomem); - ret = -ENOMEM; - } - } else { - ret = conn->security->verify_response(conn, skb); - } + buffer = kmalloc(len, GFP_NOFS); + if (!buffer) + return -ENOMEM; + + ret = skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), buffer, len); + if (ret < 0) + goto out; + + ret = conn->security->verify_response(conn, skb, buffer, len); +out: + kfree(buffer); return ret; } diff --git a/net/rxrpc/insecure.c b/net/rxrpc/insecure.c index 0a260df45d25..0b39046bdc61 100644 --- a/net/rxrpc/insecure.c +++ b/net/rxrpc/insecure.c @@ -32,9 +32,6 @@ static int none_secure_packet(struct rxrpc_call *call, struct rxrpc_txbuf *txb) static int none_verify_packet(struct rxrpc_call *call, struct sk_buff *skb) { - struct rxrpc_skb_priv *sp = rxrpc_skb(skb); - - sp->flags |= RXRPC_RX_VERIFIED; return 0; } @@ -57,9 +54,10 @@ static int none_sendmsg_respond_to_challenge(struct sk_buff *challenge, } static int none_verify_response(struct rxrpc_connection *conn, - struct sk_buff *skb) + struct sk_buff *response_skb, + void *response, unsigned int len) { - return rxrpc_abort_conn(conn, skb, RX_PROTOCOL_ERROR, -EPROTO, + return rxrpc_abort_conn(conn, response_skb, RX_PROTOCOL_ERROR, -EPROTO, rxrpc_eproto_rxnull_response); } diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c index e1f7513a46db..c940600117a4 100644 --- a/net/rxrpc/recvmsg.c +++ b/net/rxrpc/recvmsg.c @@ -147,15 +147,52 @@ static void rxrpc_rotate_rx_window(struct rxrpc_call *call) } /* - * Decrypt and verify a DATA packet. + * Decrypt and verify a DATA packet. The content of the packet is pulled out + * into a flat buffer rather than decrypting in place in the skbuff. This also + * has the advantage of aligning the buffer correctly for the crypto routines. + * + * We keep track of the sequence number of the packet currently decrypted into + * the buffer in ->rx_dec_seq. If MSG_PEEK is used and steps onto a new + * packet, subsequent recvmsg() calls will have to go back and re-decrypt the + * current packet. */ static int rxrpc_verify_data(struct rxrpc_call *call, struct sk_buff *skb) { struct rxrpc_skb_priv *sp = rxrpc_skb(skb); + int ret; - if (sp->flags & RXRPC_RX_VERIFIED) - return 0; - return call->security->verify_packet(call, skb); + if (sp->len > call->rx_dec_bsize) { + /* Make sure we can hold a 1412-byte jumbo subpacket and make + * sure that the buffer size is aligned to a crypto blocksize. + */ + size_t size = clamp(round_up(sp->len, 32), 2048, 65535); + void *buffer = krealloc(call->rx_dec_buffer, size, GFP_NOFS); + + if (!buffer) + return -ENOMEM; + call->rx_dec_buffer = buffer; + call->rx_dec_bsize = size; + } + + ret = -EFAULT; + if (skb_copy_bits(skb, sp->offset, call->rx_dec_buffer, sp->len) < 0) + goto err; + + call->rx_dec_offset = 0; + call->rx_dec_len = sp->len; + call->rx_dec_seq = sp->hdr.seq; + ret = call->security->verify_packet(call, skb); + if (ret < 0) + goto err; + return 0; + +err: + kfree(call->rx_dec_buffer); + call->rx_dec_buffer = NULL; + call->rx_dec_bsize = 0; + call->rx_dec_offset = 0; + call->rx_dec_len = 0; + return ret; } /* @@ -283,16 +320,21 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call, if (msg) sock_recv_timestamp(msg, sock->sk, skb); - if (rx_pkt_offset == 0) { + if (call->rx_dec_seq != sp->hdr.seq || + !call->rx_dec_buffer) { ret2 = rxrpc_verify_data(call, skb); trace_rxrpc_recvdata(call, rxrpc_recvmsg_next, seq, - sp->offset, sp->len, ret2); + call->rx_dec_offset, + call->rx_dec_len, ret2); if (ret2 < 0) { ret = ret2; goto out; } - rx_pkt_offset = sp->offset; - rx_pkt_len = sp->len; + } + + if (rx_pkt_offset == USHRT_MAX) { + rx_pkt_offset = call->rx_dec_offset; + rx_pkt_len = call->rx_dec_len; } else { trace_rxrpc_recvdata(call, rxrpc_recvmsg_cont, seq, rx_pkt_offset, rx_pkt_len, 0); @@ -304,10 +346,10 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call, if (copy > remain) copy = remain; if (copy > 0) { - ret2 = skb_copy_datagram_iter(skb, rx_pkt_offset, iter, - copy); - if (ret2 < 0) { - ret = ret2; + ret2 = copy_to_iter(call->rx_dec_buffer + rx_pkt_offset, + copy, iter); + if (ret2 != copy) { + ret = -EFAULT; goto out; } @@ -328,7 +370,7 @@ static int rxrpc_recvmsg_data(struct socket *sock, struct rxrpc_call *call, /* The whole packet has been transferred. */ if (sp->hdr.flags & RXRPC_LAST_PACKET) ret = 1; - rx_pkt_offset = 0; + rx_pkt_offset = USHRT_MAX; rx_pkt_len = 0; skb = skb_peek_next(skb, &call->recvmsg_queue); diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c index 0d5e654da918..a1ee102abae1 100644 --- a/net/rxrpc/rxgk.c +++ b/net/rxrpc/rxgk.c @@ -473,15 +473,20 @@ static int rxgk_verify_packet_integrity(struct rxrpc_call *call, struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxgk_header *hdr; struct krb5_buffer metadata; - unsigned int offset = sp->offset, len = sp->len; + unsigned int len = call->rx_dec_len; size_t data_offset = 0, data_len = len; + void *data = call->rx_dec_buffer, *p = data; u32 ac = 0; int ret = -ENOMEM; _enter(""); - crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE, - &data_offset, &data_len); + if (crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE, + &data_offset, &data_len) < 0) { + ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, + rxgk_abort_1_short_header); + goto put_gk; + } hdr = kzalloc_obj(*hdr, GFP_NOFS); if (!hdr) @@ -496,16 +501,15 @@ static int rxgk_verify_packet_integrity(struct rxrpc_call *call, metadata.len = sizeof(*hdr); metadata.data = hdr; - ret = rxgk_verify_mic_skb(gk->krb5, gk->rx_Kc, &metadata, - skb, &offset, &len, &ac); + ret = rxgk_verify_mic(gk->krb5, gk->rx_Kc, &metadata, &p, &len, &ac); kfree(hdr); if (ret < 0) { if (ret != -ENOMEM) rxrpc_abort_eproto(call, skb, ac, rxgk_abort_1_verify_mic_eproto); } else { - sp->offset = offset; - sp->len = len; + call->rx_dec_offset = p - data; + call->rx_dec_len = len; } put_gk: @@ -522,49 +526,53 @@ static int rxgk_verify_packet_encrypted(struct rxrpc_call *call, struct sk_buff *skb) { struct rxrpc_skb_priv *sp = rxrpc_skb(skb); - struct rxgk_header hdr; - unsigned int offset = sp->offset, len = sp->len; + struct rxgk_header *hdr; + unsigned int offset = 0, len = call->rx_dec_len; + void *data = call->rx_dec_buffer, *p = data; int ret; u32 ac = 0; _enter(""); - ret = rxgk_decrypt_skb(gk->krb5, gk->rx_enc, skb, &offset, &len, &ac); + if (crypto_krb5_check_data_len(gk->krb5, KRB5_ENCRYPT_MODE, + len, sizeof(*hdr)) < 0) { + ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, + rxgk_abort_2_short_header); + goto error; + } + + ret = rxgk_decrypt(gk->krb5, gk->rx_enc, &p, &len, &ac); if (ret < 0) { if (ret != -ENOMEM) rxrpc_abort_eproto(call, skb, ac, rxgk_abort_2_decrypt_eproto); goto error; } + offset = p - data; - if (len < sizeof(hdr)) { + if (len < sizeof(*hdr)) { ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, rxgk_abort_2_short_header); goto error; } /* Extract the header from the skb */ - ret = skb_copy_bits(skb, offset, &hdr, sizeof(hdr)); - if (ret < 0) { - ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT, - rxgk_abort_2_short_encdata); - goto error; - } - offset += sizeof(hdr); - len -= sizeof(hdr); - - if (ntohl(hdr.epoch) != call->conn->proto.epoch || - ntohl(hdr.cid) != call->cid || - ntohl(hdr.call_number) != call->call_id || - ntohl(hdr.seq) != sp->hdr.seq || - ntohl(hdr.sec_index) != call->security_ix || - ntohl(hdr.data_len) > len) { + hdr = data + offset; + offset += sizeof(*hdr); + len -= sizeof(*hdr); + + if (ntohl(hdr->epoch) != call->conn->proto.epoch || + ntohl(hdr->cid) != call->cid || + ntohl(hdr->call_number) != call->call_id || + ntohl(hdr->seq) != sp->hdr.seq || + ntohl(hdr->sec_index) != call->security_ix || + ntohl(hdr->data_len) > len) { ret = rxrpc_abort_eproto(call, skb, RXGK_SEALEDINCON, rxgk_abort_2_short_data); goto error; } - sp->offset = offset; - sp->len = ntohl(hdr.data_len); + call->rx_dec_offset = offset; + call->rx_dec_len = ntohl(hdr->data_len); ret = 0; error: rxgk_put(gk); @@ -1076,11 +1084,12 @@ static int rxgk_sendmsg_respond_to_challenge(struct sk_buff *challenge, * unsigned int call_numbers<>; * }; */ -static int rxgk_do_verify_authenticator(struct rxrpc_connection *conn, - const struct krb5_enctype *krb5, - struct sk_buff *skb, - __be32 *p, __be32 *end) +static int rxgk_verify_authenticator(struct rxrpc_connection *conn, + const struct krb5_enctype *krb5, + struct sk_buff *skb, + void *auth, unsigned int auth_len) { + __be32 *p = auth, *end = auth + auth_len; u32 app_len, call_count, level, epoch, cid, i; _enter(""); @@ -1143,37 +1152,6 @@ static int rxgk_do_verify_authenticator(struct rxrpc_connection *conn, return 0; } -/* - * Extract the authenticator and verify it. - */ -static int rxgk_verify_authenticator(struct rxrpc_connection *conn, - const struct krb5_enctype *krb5, - struct sk_buff *skb, - unsigned int auth_offset, unsigned int auth_len) -{ - void *auth; - __be32 *p; - int ret; - - auth = kmalloc(auth_len, GFP_NOFS); - if (!auth) - return -ENOMEM; - - ret = skb_copy_bits(skb, auth_offset, auth, auth_len); - if (ret < 0) { - ret = rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO, - rxgk_abort_resp_short_auth); - goto error; - } - - p = auth; - ret = rxgk_do_verify_authenticator(conn, krb5, skb, p, - p + auth_len / sizeof(*p)); -error: - kfree(auth); - return ret; -} - /* * Verify a response. * @@ -1184,49 +1162,45 @@ static int rxgk_verify_authenticator(struct rxrpc_connection *conn, * }; */ static int rxgk_verify_response(struct rxrpc_connection *conn, - struct sk_buff *skb) + struct sk_buff *skb, + void *buffer, unsigned int len) { const struct krb5_enctype *krb5; struct rxrpc_key_token *token; struct rxrpc_skb_priv *sp = rxrpc_skb(skb); - struct rxgk_response rhdr; + struct rxgk_response *rhdr; struct rxgk_context *gk; struct key *key = NULL; - unsigned int offset = sizeof(struct rxrpc_wire_header); - unsigned int len = skb->len - sizeof(struct rxrpc_wire_header); - unsigned int token_offset, token_len; - unsigned int auth_offset, auth_len; + unsigned int resp_token_len, auth_len; + void *resp_token, *auth; __be32 xauth_len; int ret, ec; _enter("{%d}", conn->debug_id); /* Parse the RXGK_Response object */ - if (sizeof(rhdr) + sizeof(__be32) > len) + if (len < sizeof(*rhdr) + sizeof(__be32)) goto short_packet; - - if (skb_copy_bits(skb, offset, &rhdr, sizeof(rhdr)) < 0) - goto short_packet; - offset += sizeof(rhdr); - len -= sizeof(rhdr); - - token_offset = offset; - token_len = ntohl(rhdr.token_len); - if (token_len > len || - xdr_round_up(token_len) + sizeof(__be32) > len) + rhdr = buffer; + buffer += sizeof(*rhdr); + len -= sizeof(*rhdr); + + resp_token = buffer; + resp_token_len = ntohl(rhdr->token_len); + if (resp_token_len > len || + xdr_round_up(resp_token_len) + sizeof(__be32) > len) goto short_packet; - trace_rxrpc_rx_response(conn, sp->hdr.serial, 0, sp->hdr.cksum, token_len); + trace_rxrpc_rx_response(conn, sp->hdr.serial, 0, sp->hdr.cksum, resp_token_len); - offset += xdr_round_up(token_len); - len -= xdr_round_up(token_len); + buffer += xdr_round_up(resp_token_len); + len -= xdr_round_up(resp_token_len); - if (skb_copy_bits(skb, offset, &xauth_len, sizeof(xauth_len)) < 0) - goto short_packet; - offset += sizeof(xauth_len); + xauth_len = *(__be32 *)buffer; + buffer += sizeof(xauth_len); len -= sizeof(xauth_len); - auth_offset = offset; + auth = buffer; auth_len = ntohl(xauth_len); if (auth_len > len) goto short_packet; @@ -1241,7 +1215,7 @@ static int rxgk_verify_response(struct rxrpc_connection *conn, * to the app to deal with - which might mean a round trip to * userspace. */ - ret = rxgk_extract_token(conn, skb, token_offset, token_len, &key); + ret = rxgk_extract_token(conn, skb, resp_token, resp_token_len, &key); if (ret < 0) goto out; @@ -1255,7 +1229,7 @@ static int rxgk_verify_response(struct rxrpc_connection *conn, */ token = key->payload.data[0]; conn->security_level = token->rxgk->level; - conn->rxgk.start_time = __be64_to_cpu(rhdr.start_time); + conn->rxgk.start_time = __be64_to_cpu(rhdr->start_time); gk = rxgk_generate_transport_key(conn, token->rxgk, sp->hdr.cksum, GFP_NOFS); if (IS_ERR(gk)) { @@ -1265,18 +1239,18 @@ static int rxgk_verify_response(struct rxrpc_connection *conn, krb5 = gk->krb5; - trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum, token_len); + trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum, + resp_token_len); /* Decrypt, parse and verify the authenticator. */ - ret = rxgk_decrypt_skb(krb5, gk->resp_enc, skb, - &auth_offset, &auth_len, &ec); + ret = rxgk_decrypt(krb5, gk->resp_enc, &auth, &auth_len, &ec); if (ret < 0) { rxrpc_abort_conn(conn, skb, RXGK_SEALEDINCON, ret, rxgk_abort_resp_auth_dec); goto out_gk; } - ret = rxgk_verify_authenticator(conn, krb5, skb, auth_offset, auth_len); + ret = rxgk_verify_authenticator(conn, krb5, skb, auth, auth_len); if (ret < 0) goto out_gk; diff --git a/net/rxrpc/rxgk_app.c b/net/rxrpc/rxgk_app.c index 0ef2a29eb695..200a30064fae 100644 --- a/net/rxrpc/rxgk_app.c +++ b/net/rxrpc/rxgk_app.c @@ -40,7 +40,7 @@ * }; */ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, - unsigned int ticket_offset, unsigned int ticket_len, + void *buffer, unsigned int ticket_len, struct key **_key) { struct rxrpc_key_token *token; @@ -49,7 +49,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, size_t pre_ticket_len, payload_len; unsigned int klen, enctype; void *payload, *ticket; - __be32 *t, *p, *q, tmp[2]; + __be32 *t, *p, *q, *tmp; int ret; _enter(""); @@ -59,10 +59,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, rxgk_abort_resp_short_yfs_tkt); /* Get the session key length */ - ret = skb_copy_bits(skb, ticket_offset, tmp, sizeof(tmp)); - if (ret < 0) - return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO, - rxgk_abort_resp_short_yfs_klen); + tmp = buffer; enctype = ntohl(tmp[0]); klen = ntohl(tmp[1]); @@ -84,12 +81,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, * it. */ ticket = payload + pre_ticket_len; - ret = skb_copy_bits(skb, ticket_offset, ticket, ticket_len); - if (ret < 0) { - ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO, - rxgk_abort_resp_short_yfs_tkt); - goto error; - } + memcpy(ticket, buffer, ticket_len); /* Fill out the form header. */ p = payload; @@ -131,7 +123,7 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, goto error; } - /* Ticket read in with skb_copy_bits above */ + /* Ticket appended above. */ q += xdr_round_up(ticket_len) / 4; if (WARN_ON((unsigned long)q - (unsigned long)payload != payload_len)) { ret = -EIO; @@ -182,14 +174,15 @@ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, * [tools.ietf.org/html/draft-wilkinson-afs3-rxgk-afs-08 sec 6.1] */ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb, - unsigned int token_offset, unsigned int token_len, + void *token, unsigned int token_len, struct key **_key) { const struct krb5_enctype *krb5; const struct krb5_buffer *server_secret; struct crypto_aead *token_enc = NULL; struct key *server_key; - unsigned int ticket_offset, ticket_len; + unsigned int ticket_len; + void *ticket; u32 kvno, enctype; int ret, ec = 0; @@ -197,24 +190,23 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb, __be32 kvno; __be32 enctype; __be32 token_len; - } container; + } *container; - if (token_len < sizeof(container)) + if (token_len < sizeof(*container)) goto short_packet; /* Decode the RXGK_TokenContainer object. This tells us which server * key we should be using. We can then fetch the key, get the secret * and set up the crypto to extract the token. */ - if (skb_copy_bits(skb, token_offset, &container, sizeof(container)) < 0) - goto short_packet; + container = token; + token += sizeof(*container); - kvno = ntohl(container.kvno); - enctype = ntohl(container.enctype); - ticket_len = ntohl(container.token_len); - ticket_offset = token_offset + sizeof(container); + kvno = ntohl(container->kvno); + enctype = ntohl(container->enctype); + ticket_len = ntohl(container->token_len); - if (ticket_len > xdr_round_down(token_len - sizeof(container))) + if (ticket_len > xdr_round_down(token_len - sizeof(*container))) goto short_packet; _debug("KVNO %u", kvno); @@ -237,8 +229,8 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb, * gain access to K0, from which we can derive the transport key and * thence decode the authenticator. */ - ret = rxgk_decrypt_skb(krb5, token_enc, skb, - &ticket_offset, &ticket_len, &ec); + ticket = token; + ret = rxgk_decrypt(krb5, token_enc, &ticket, &ticket_len, &ec); crypto_free_aead(token_enc); token_enc = NULL; if (ret < 0) { @@ -248,7 +240,7 @@ int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb, return ret; } - ret = conn->security->default_decode_ticket(conn, skb, ticket_offset, + ret = conn->security->default_decode_ticket(conn, skb, ticket, ticket_len, _key); if (ret < 0) goto cant_get_token; diff --git a/net/rxrpc/rxgk_common.h b/net/rxrpc/rxgk_common.h index 1e257d7ab8ec..3deed5863f5a 100644 --- a/net/rxrpc/rxgk_common.h +++ b/net/rxrpc/rxgk_common.h @@ -41,10 +41,10 @@ struct rxgk_context { * rxgk_app.c */ int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb, - unsigned int ticket_offset, unsigned int ticket_len, + void *ticket, unsigned int ticket_len, struct key **_key); int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb, - unsigned int token_offset, unsigned int token_len, + void *token, unsigned int token_len, struct key **_key); /* @@ -62,31 +62,30 @@ int rxgk_set_up_token_cipher(const struct krb5_buffer *server_key, gfp_t gfp); /* - * Apply decryption and checksumming functions to part of an skbuff. The - * offset and length are updated to reflect the actual content of the encrypted + * Apply decryption and checksumming functions a flat data buffer. The data + * point and length are updated to reflect the actual content of the encrypted * region. */ -static inline -int rxgk_decrypt_skb(const struct krb5_enctype *krb5, - struct crypto_aead *aead, - struct sk_buff *skb, - unsigned int *_offset, unsigned int *_len, - int *_error_code) +static inline int rxgk_decrypt(const struct krb5_enctype *krb5, + struct crypto_aead *aead, + void **_data, unsigned int *_len, + int *_error_code) { - struct scatterlist sg[16]; + struct scatterlist sg[1]; size_t offset = 0, len = *_len; - int nr_sg, ret; + int ret; - sg_init_table(sg, ARRAY_SIZE(sg)); - nr_sg = skb_to_sgvec(skb, sg, *_offset, len); - if (unlikely(nr_sg < 0)) - return nr_sg; + sg_init_one(sg, *_data, len); - ret = crypto_krb5_decrypt(krb5, aead, sg, nr_sg, - &offset, &len); + ret = crypto_krb5_decrypt(krb5, aead, sg, 1, &offset, &len); switch (ret) { case 0: - *_offset += offset; + if (offset & 3) { + *_error_code = RXGK_INCONSISTENCY; + ret = -EPROTO; + break; + } + *_data += offset; *_len = len; break; case -EBADMSG: /* Checksum mismatch. */ @@ -106,31 +105,26 @@ int rxgk_decrypt_skb(const struct krb5_enctype *krb5, } /* - * Check the MIC on a region of an skbuff. The offset and length are updated - * to reflect the actual content of the secure region. + * Check the MIC on a flat buffer. The data pointer and length are updated to + * reflect the actual content of the secure region. */ static inline -int rxgk_verify_mic_skb(const struct krb5_enctype *krb5, - struct crypto_shash *shash, - const struct krb5_buffer *metadata, - struct sk_buff *skb, - unsigned int *_offset, unsigned int *_len, - u32 *_error_code) +int rxgk_verify_mic(const struct krb5_enctype *krb5, + struct crypto_shash *shash, + const struct krb5_buffer *metadata, + void **_data, unsigned int *_len, + u32 *_error_code) { - struct scatterlist sg[16]; + struct scatterlist sg[1]; size_t offset = 0, len = *_len; - int nr_sg, ret; + int ret; - sg_init_table(sg, ARRAY_SIZE(sg)); - nr_sg = skb_to_sgvec(skb, sg, *_offset, len); - if (unlikely(nr_sg < 0)) - return nr_sg; + sg_init_one(sg, *_data, len); - ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, nr_sg, - &offset, &len); + ret = crypto_krb5_verify_mic(krb5, shash, metadata, sg, 1, &offset, &len); switch (ret) { case 0: - *_offset += offset; + *_data += offset; *_len = len; break; case -EBADMSG: /* Checksum mismatch */ diff --git a/net/rxrpc/rxkad.c b/net/rxrpc/rxkad.c index cba7935977f0..6fbd883401ac 100644 --- a/net/rxrpc/rxkad.c +++ b/net/rxrpc/rxkad.c @@ -430,27 +430,25 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb, rxrpc_seq_t seq, struct skcipher_request *req) { - struct rxkad_level1_hdr sechdr; + struct rxkad_level1_hdr *sechdr; struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxrpc_crypt iv; - struct scatterlist sg[16]; - u32 data_size, buf; + struct scatterlist sg[1]; + void *data = call->rx_dec_buffer; + u32 len = sp->len, data_size, buf; u16 check; int ret; _enter(""); - if (sp->len < 8) + if (len < 8) return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON, rxkad_abort_1_short_header); /* Decrypt the skbuff in-place. TODO: We really want to decrypt * directly into the target buffer. */ - sg_init_table(sg, ARRAY_SIZE(sg)); - ret = skb_to_sgvec(skb, sg, sp->offset, 8); - if (unlikely(ret < 0)) - return ret; + sg_init_one(sg, data, len); /* start the decryption afresh */ memset(&iv, 0, sizeof(iv)); @@ -464,13 +462,11 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb, return ret; /* Extract the decrypted packet length */ - if (skb_copy_bits(skb, sp->offset, &sechdr, sizeof(sechdr)) < 0) - return rxrpc_abort_eproto(call, skb, RXKADDATALEN, - rxkad_abort_1_short_encdata); - sp->offset += sizeof(sechdr); - sp->len -= sizeof(sechdr); + sechdr = data; + call->rx_dec_offset = sizeof(*sechdr); + len -= sizeof(*sechdr); - buf = ntohl(sechdr.data_size); + buf = ntohl(sechdr->data_size); data_size = buf & 0xffff; check = buf >> 16; @@ -479,10 +475,10 @@ static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb, if (check != 0) return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON, rxkad_abort_1_short_check); - if (data_size > sp->len) + if (data_size > len) return rxrpc_abort_eproto(call, skb, RXKADDATALEN, rxkad_abort_1_short_data); - sp->len = data_size; + call->rx_dec_len = data_size; _leave(" = 0 [dlen=%x]", data_size); return 0; @@ -496,43 +492,28 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb, struct skcipher_request *req) { const struct rxrpc_key_token *token; - struct rxkad_level2_hdr sechdr; + struct rxkad_level2_hdr *sechdr; struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxrpc_crypt iv; - struct scatterlist _sg[4], *sg; - u32 data_size, buf; + struct scatterlist sg[1]; + void *data = call->rx_dec_buffer; + u32 len = sp->len, data_size, buf; u16 check; - int nsg, ret; + int ret; - _enter(",{%d}", sp->len); + _enter(",{%d}", len); - if (sp->len < 8) + if (len < 8) return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON, rxkad_abort_2_short_header); /* Don't let the crypto algo see a misaligned length. */ - sp->len = round_down(sp->len, 8); + len = round_down(len, 8); - /* Decrypt the skbuff in-place. TODO: We really want to decrypt - * directly into the target buffer. + /* Decrypt in place in the call's decryption buffer. TODO: We really + * want to decrypt directly into the target buffer. */ - sg = _sg; - nsg = skb_shinfo(skb)->nr_frags + 1; - if (nsg <= 4) { - nsg = 4; - } else { - sg = kmalloc_objs(*sg, nsg, GFP_NOIO); - if (!sg) - return -ENOMEM; - } - - sg_init_table(sg, nsg); - ret = skb_to_sgvec(skb, sg, sp->offset, sp->len); - if (unlikely(ret < 0)) { - if (sg != _sg) - kfree(sg); - return ret; - } + sg_init_one(sg, data, len); /* decrypt from the session key */ token = call->conn->key->payload.data[0]; @@ -540,11 +521,9 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb, skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); skcipher_request_set_callback(req, 0, NULL, NULL); - skcipher_request_set_crypt(req, sg, sg, sp->len, iv.x); + skcipher_request_set_crypt(req, sg, sg, len, iv.x); ret = crypto_skcipher_decrypt(req); skcipher_request_zero(req); - if (sg != _sg) - kfree(sg); if (ret < 0) { if (ret == -ENOMEM) return ret; @@ -553,13 +532,11 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb, } /* Extract the decrypted packet length */ - if (skb_copy_bits(skb, sp->offset, &sechdr, sizeof(sechdr)) < 0) - return rxrpc_abort_eproto(call, skb, RXKADDATALEN, - rxkad_abort_2_short_len); - sp->offset += sizeof(sechdr); - sp->len -= sizeof(sechdr); + sechdr = data; + call->rx_dec_offset = sizeof(*sechdr); + len -= sizeof(*sechdr); - buf = ntohl(sechdr.data_size); + buf = ntohl(sechdr->data_size); data_size = buf & 0xffff; check = buf >> 16; @@ -569,17 +546,18 @@ static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb, return rxrpc_abort_eproto(call, skb, RXKADSEALEDINCON, rxkad_abort_2_short_check); - if (data_size > sp->len) + if (data_size > len) return rxrpc_abort_eproto(call, skb, RXKADDATALEN, rxkad_abort_2_short_data); - sp->len = data_size; + call->rx_dec_len = data_size; _leave(" = 0 [dlen=%x]", data_size); return 0; } /* - * Verify the security on a received packet and the subpackets therein. + * Verify the security on a received (sub)packet. If the packet needs + * modifying (e.g. decrypting), it must be copied. */ static int rxkad_verify_packet(struct rxrpc_call *call, struct sk_buff *skb) { @@ -985,7 +963,6 @@ static int rxkad_decrypt_ticket(struct rxrpc_connection *conn, *_expiry = 0; ASSERT(server_key->payload.data[0] != NULL); - ASSERTCMP((unsigned long) ticket & 7UL, ==, 0); memcpy(&iv, &server_key->payload.data[2], sizeof(iv)); @@ -1134,14 +1111,15 @@ static int rxkad_decrypt_response(struct rxrpc_connection *conn, * verify a response */ static int rxkad_verify_response(struct rxrpc_connection *conn, - struct sk_buff *skb) + struct sk_buff *skb, + void *buffer, unsigned int len) { struct rxkad_response *response; struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxrpc_crypt session_key; struct key *server_key; time64_t expiry; - void *ticket = NULL; + void *ticket; u32 version, kvno, ticket_len, level; __be32 csum; int ret, i; @@ -1164,13 +1142,8 @@ static int rxkad_verify_response(struct rxrpc_connection *conn, } } - ret = -ENOMEM; - response = kzalloc_obj(struct rxkad_response, GFP_NOFS); - if (!response) - goto error; - - if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), - response, sizeof(*response)) < 0) { + response = buffer; + if (len < sizeof(*response)) { ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO, rxkad_abort_resp_short); goto error; @@ -1182,6 +1155,9 @@ static int rxkad_verify_response(struct rxrpc_connection *conn, trace_rxrpc_rx_response(conn, sp->hdr.serial, version, kvno, ticket_len); + buffer += sizeof(*response); + len -= sizeof(*response); + if (version != RXKAD_VERSION) { ret = rxrpc_abort_conn(conn, skb, RXKADINCONSISTENCY, -EPROTO, rxkad_abort_resp_version); @@ -1201,13 +1177,8 @@ static int rxkad_verify_response(struct rxrpc_connection *conn, } /* extract the kerberos ticket and decrypt and decode it */ - ret = -ENOMEM; - ticket = kmalloc(ticket_len, GFP_NOFS); - if (!ticket) - goto error; - - if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header) + sizeof(*response), - ticket, ticket_len) < 0) { + ticket = buffer; + if (ticket_len > len) { ret = rxrpc_abort_conn(conn, skb, RXKADPACKETSHORT, -EPROTO, rxkad_abort_resp_short_tkt); goto error; @@ -1287,8 +1258,6 @@ static int rxkad_verify_response(struct rxrpc_connection *conn, ret = rxrpc_get_server_data_key(conn, &session_key, expiry, kvno); error: - kfree(ticket); - kfree(response); key_put(server_key); _leave(" = %d", ret); return ret; diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index bc20f08a2789..1aa95c34ad87 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -323,8 +324,10 @@ static bool offset_valid(struct sk_buff *skb, int offset) if (offset > 0 && offset > skb->len) return false; - if (offset < 0 && -offset > skb_headroom(skb)) - return false; + if (offset < 0) { + if (offset == INT_MIN || -offset > skb_headroom(skb)) + return false; + } return true; } @@ -398,12 +401,21 @@ TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, parms = rcu_dereference_bh(p->parms); - max_offset = (skb_transport_header_was_set(skb) ? - skb_transport_offset(skb) : - skb_network_offset(skb)) + - parms->tcfp_off_max_hint; - if (skb_ensure_writable(skb, min(skb->len, max_offset))) - goto done; + max_offset = min_t(u32, skb->len, + (skb_transport_header_was_set(skb) ? + skb_transport_offset(skb) : + skb_network_offset(skb)) + + parms->tcfp_off_max_hint); + + /* If the skb has shared frags the user is likely using zero-copy + * (e.g. sendfile). Those page frags may point to page-cache pages; + * writing into them would silently corrupt the page cache. + * Linearize so pedit operates on a private copy. + * TL;DR if you want to use ZC, don't use pedit */ + if (skb_has_shared_frag(skb)) { + if (__skb_linearize(skb)) + goto bad; + } tcf_lastuse_update(&p->tcf_tm); tcf_action_update_bstats(&p->common, skb); @@ -414,8 +426,9 @@ TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, for (i = parms->tcfp_nkeys; i > 0; i--, tkey++) { int offset = tkey->off; int hoffset = 0; + int write_offset, write_len; u32 *ptr, hdata; - u32 val; + u32 val, write_end; int rc; if (tkey_ex) { @@ -451,12 +464,45 @@ TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, } } - if (!offset_valid(skb, hoffset + offset)) { - pr_info_ratelimited("tc action pedit offset %d out of bounds\n", hoffset + offset); + write_offset = hoffset + offset; + if (unlikely(check_add_overflow(hoffset, offset, + &write_offset))) { + pr_info_ratelimited("tc action pedit offset overflow\n"); goto bad; } - ptr = skb_header_pointer(skb, hoffset + offset, + if (!offset_valid(skb, write_offset)) { + pr_info_ratelimited("tc action pedit offset %d out of bounds\n", + write_offset); + goto bad; + } + + /* Earlier edits can change later header-relative offsets, so + * grow the writable window from the final per-key store. + */ + if (write_offset >= 0) { + write_end = (u32)write_offset + sizeof(hdata); + if (write_end > max_offset) { + max_offset = min_t(u32, skb->len, write_end); + if (skb_ensure_writable(skb, max_offset)) + goto bad; + } + } + + if (write_offset < 0) { + if (skb_cow(skb, -write_offset)) + goto bad; + } else { + if (unlikely(check_add_overflow(write_offset, + (int)sizeof(hdata), + &write_len))) + goto bad; + if (skb_ensure_writable(skb, min_t(int, skb->len, + write_len))) + goto bad; + } + + ptr = skb_header_pointer(skb, write_offset, sizeof(hdata), &hdata); if (!ptr) goto bad; @@ -475,7 +521,7 @@ TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, *ptr = ((*ptr & tkey->mask) ^ val); if (ptr == &hdata) - skb_store_bits(skb, hoffset + offset, ptr, 4); + skb_store_bits(skb, write_offset, ptr, sizeof(hdata)); } goto done; diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0718e39cedda..10e94a1b8b6e 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -201,7 +201,10 @@ objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) +# RHEL-only: don't enforce OBJTOOL_WERROR for out of tree modules +ifeq ($(KBUILD_EXTMOD),) objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror +endif objtool-args = $(objtool-args-y) \ $(if $(delay-objtool), --link) \ diff --git a/scripts/tags.sh b/scripts/tags.sh index 243373683f98..2affd5e58ee8 100755 --- a/scripts/tags.sh +++ b/scripts/tags.sh @@ -16,6 +16,8 @@ fi ignore="$(echo "$RCS_FIND_IGNORE" | sed 's|\\||g' )" # tags and cscope files should also ignore MODVERSION *.mod.c files ignore="$ignore ( -name *.mod.c ) -prune -o" +# RHEL tags and cscope should also ignore redhat/rpm +ignore="$ignore ( -path redhat/rpm ) -prune -o" # ignore arbitrary directories if [ -n "${IGNORE_DIRS}" ]; then diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c index d1fdd113450a..182e8090cfe8 100644 --- a/security/integrity/platform_certs/load_uefi.c +++ b/security/integrity/platform_certs/load_uefi.c @@ -74,7 +74,8 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid, return NULL; if (*status != EFI_BUFFER_TOO_SMALL) { - pr_err("Couldn't get size: 0x%lx\n", *status); + pr_err("Couldn't get size: %s (0x%lx)\n", + efi_status_to_str(*status), *status); return NULL; } @@ -85,7 +86,8 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid, *status = efi.get_variable(name, guid, NULL, &lsize, db); if (*status != EFI_SUCCESS) { kfree(db); - pr_err("Error reading db var: 0x%lx\n", *status); + pr_err("Error reading db var: %s (0x%lx)\n", + efi_status_to_str(*status), *status); return NULL; } diff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig index e84ddf484010..d0501353a4b9 100644 --- a/security/lockdown/Kconfig +++ b/security/lockdown/Kconfig @@ -16,6 +16,19 @@ config SECURITY_LOCKDOWN_LSM_EARLY subsystem is fully initialised. If enabled, lockdown will unconditionally be called before any other LSMs. +config LOCK_DOWN_IN_EFI_SECURE_BOOT + bool "Lock down the kernel in EFI Secure Boot mode" + default n + depends on EFI && SECURITY_LOCKDOWN_LSM_EARLY + help + UEFI Secure Boot provides a mechanism for ensuring that the firmware + will only load signed bootloaders and kernels. Secure boot mode may + be determined from EFI variables provided by the system firmware if + not indicated by the boot parameters. + + Enabling this option results in kernel lockdown being triggered if + EFI Secure Boot is set. + choice prompt "Kernel default lockdown mode" default LOCK_DOWN_KERNEL_FORCE_NONE diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c index 8d46886d2cca..14a9cdff942b 100644 --- a/security/lockdown/lockdown.c +++ b/security/lockdown/lockdown.c @@ -72,6 +72,17 @@ static int lockdown_is_locked_down(enum lockdown_reason what) return 0; } +/** + * security_lock_kernel_down() - Put the kernel into lock-down mode. + * + * @where: Where the lock-down is originating from (e.g. command line option) + * @level: The lock-down level (can only increase) + */ +int security_lock_kernel_down(const char *where, enum lockdown_reason level) +{ + return lock_kernel_down(where, level); +} + static struct security_hook_list lockdown_hooks[] __ro_after_init = { LSM_HOOK_INIT(locked_down, lockdown_is_locked_down), }; diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index a9c8d9545281..097e191cedde 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -95,6 +95,22 @@ static const struct dmi_system_id soc_sdw_quirk_table[] = { }, .driver_data = (void *)(ASOC_SDW_CODEC_SPKR), }, + { + .callback = soc_sdw_quirk_cb, + .matches = { /* Lenovo P16s G5 AMD */ + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_SKU, "21XG"), + }, + .driver_data = (void *)(ASOC_SDW_ACP_DMIC), + }, + { + .callback = soc_sdw_quirk_cb, + .matches = { /* Lenovo P16s G5 AMD */ + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_SKU, "21XH"), + }, + .driver_data = (void *)(ASOC_SDW_ACP_DMIC), + }, { .callback = soc_sdw_quirk_cb, .matches = { @@ -553,7 +569,7 @@ static int mc_probe(struct platform_device *pdev) return -ENOMEM; if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s mic:dmic cfg-mics:%d", + "%s mic:acp-dmic cfg-mics:%d", card->components, 1); if (!card->components) diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c index 79b8b7e70a33..23d2f63d68ef 100644 --- a/sound/soc/codecs/rt722-sdca.c +++ b/sound/soc/codecs/rt722-sdca.c @@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol, return changed; } +static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722) +{ + int err; + unsigned int ch_l, ch_r; + + ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00; + ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00; + + err = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l); + if (err < 0) + return err; + + err = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r); + if (err < 0) + return err; + + return 0; +} + +static int rt722_sdca_fu06_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = !rt722->fu06_mixer_l_mute; + ucontrol->value.integer.value[1] = !rt722->fu06_mixer_r_mute; + return 0; +} + +static int rt722_sdca_fu06_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + int err, changed = 0; + + if (rt722->fu06_mixer_l_mute != !ucontrol->value.integer.value[0] || + rt722->fu06_mixer_r_mute != !ucontrol->value.integer.value[1]) + changed = 1; + + rt722->fu06_mixer_l_mute = !ucontrol->value.integer.value[0]; + rt722->fu06_mixer_r_mute = !ucontrol->value.integer.value[1]; + + err = rt722_sdca_set_fu06_playback_ctl(rt722); + if (err < 0) + return err; + + return changed; +} + static int rt722_sdca_set_fu0f_capture_ctl(struct rt722_sdca_priv *rt722) { int err; @@ -718,6 +773,8 @@ static const struct snd_kcontrol_new rt722_sdca_controls[] = { RT722_SDCA_CTL_FU_CH_GAIN, CH_R), 8, 3, 0, rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, boost_vol_tlv), /* AMP playback settings */ + SOC_DOUBLE_EXT("FU06 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, + rt722_sdca_fu06_playback_get, rt722_sdca_fu06_playback_put), SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_VOLUME, CH_L), @@ -807,27 +864,17 @@ static int rt722_sdca_fu21_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); - unsigned char unmute = 0x0, mute = 0x1; switch (event) { case SND_SOC_DAPM_POST_PMU: - regmap_write(rt722->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, - RT722_SDCA_CTL_FU_MUTE, CH_L), unmute); - regmap_write(rt722->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, - RT722_SDCA_CTL_FU_MUTE, CH_R), unmute); + rt722->fu06_dapm_mute = false; break; case SND_SOC_DAPM_PRE_PMD: - regmap_write(rt722->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, - RT722_SDCA_CTL_FU_MUTE, CH_L), mute); - regmap_write(rt722->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, - RT722_SDCA_CTL_FU_MUTE, CH_R), mute); + rt722->fu06_dapm_mute = true; break; } - return 0; + + return rt722_sdca_set_fu06_playback_ctl(rt722); } static int rt722_sdca_fu113_event(struct snd_soc_dapm_widget *w, @@ -1324,6 +1371,8 @@ int rt722_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave rt722->first_hw_init = false; rt722->fu1e_dapm_mute = true; rt722->fu0f_dapm_mute = true; + rt722->fu06_dapm_mute = true; + rt722->fu06_mixer_l_mute = rt722->fu06_mixer_r_mute = false; rt722->fu0f_mixer_l_mute = rt722->fu0f_mixer_r_mute = true; rt722->fu1e_mixer_mute[0] = rt722->fu1e_mixer_mute[1] = rt722->fu1e_mixer_mute[2] = rt722->fu1e_mixer_mute[3] = true; diff --git a/sound/soc/codecs/rt722-sdca.h b/sound/soc/codecs/rt722-sdca.h index 823abee9ab76..fc50beff9424 100644 --- a/sound/soc/codecs/rt722-sdca.h +++ b/sound/soc/codecs/rt722-sdca.h @@ -36,6 +36,10 @@ struct rt722_sdca_priv { bool fu0f_dapm_mute; bool fu0f_mixer_l_mute; bool fu0f_mixer_r_mute; + /* For AMP */ + bool fu06_dapm_mute; + bool fu06_mixer_l_mute; + bool fu06_mixer_r_mute; /* For DMIC */ bool fu1e_dapm_mute; bool fu1e_mixer_mute[4]; diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 65485967c968..e44f47e01027 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -497,7 +497,7 @@ LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c \ LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c # Generate both light skeleton and libbpf skeleton for these -LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \ +LSKELS_EXTRA := test_ksyms_module.c kfunc_call_test.c \ kfunc_call_test_subprog.c SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED) diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c index 1d7a2f1e0731..b22f3a9cb8b8 100644 --- a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c +++ b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c @@ -7,7 +7,6 @@ #include "test_ksyms_btf.skel.h" #include "test_ksyms_btf_null_check.skel.h" #include "test_ksyms_weak.skel.h" -#include "test_ksyms_weak.lskel.h" #include "test_ksyms_btf_write_check.skel.h" static int duration; @@ -111,33 +110,6 @@ static void test_weak_syms(void) test_ksyms_weak__destroy(skel); } -static void test_weak_syms_lskel(void) -{ - struct test_ksyms_weak_lskel *skel; - struct test_ksyms_weak_lskel__data *data; - int err; - - skel = test_ksyms_weak_lskel__open_and_load(); - if (!ASSERT_OK_PTR(skel, "test_ksyms_weak_lskel__open_and_load")) - return; - - err = test_ksyms_weak_lskel__attach(skel); - if (!ASSERT_OK(err, "test_ksyms_weak_lskel__attach")) - goto cleanup; - - /* trigger tracepoint */ - usleep(1); - - data = skel->data; - ASSERT_EQ(data->out__existing_typed, 0, "existing typed ksym"); - ASSERT_NEQ(data->out__existing_typeless, -1, "existing typeless ksym"); - ASSERT_EQ(data->out__non_existent_typeless, 0, "nonexistent typeless ksym"); - ASSERT_EQ(data->out__non_existent_typed, 0, "nonexistent typed ksym"); - -cleanup: - test_ksyms_weak_lskel__destroy(skel); -} - static void test_write_check(bool test_handler1) { struct test_ksyms_btf_write_check *skel; @@ -180,9 +152,6 @@ void test_ksyms_btf(void) if (test__start_subtest("weak_ksyms")) test_weak_syms(); - if (test__start_subtest("weak_ksyms_lskel")) - test_weak_syms_lskel(); - if (test__start_subtest("write_check1")) test_write_check(true);