* Sat May 23 2026 Justin M. Forbes <jforbes@fedoraproject.org> [7.0.10-0] - Linux v7.0.10 Resolves: Signed-off-by: Justin M. Forbes <jforbes@fedoraproject.org>
9258 lines
277 KiB
Diff
9258 lines
277 KiB
Diff
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:: <isonum.txt>
|
|
+
|
|
+====================================
|
|
+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 | {<port0> 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 <bin.du@amd.com>
|
|
+M: Nirujogi Pratap <pratap.nirujogi@amd.com>
|
|
+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 <Felix.Kuehling@amd.com>
|
|
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 <dt-bindings/power/raspberrypi-power.h>
|
|
|
|
/ {
|
|
- 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 <dt-bindings/interrupt-controller/arm-gic.h>
|
|
+#include <dt-bindings/soc/bcm2835-pm.h>
|
|
|
|
/ {
|
|
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 = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ 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 = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH>,
|
|
+ <GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ 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 = <GIC_SPI 178 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ 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 <linux/sched/task.h>
|
|
#include <linux/scs.h>
|
|
#include <linux/mm.h>
|
|
+#include <linux/security.h>
|
|
+#include <linux/libfdt.h>
|
|
|
|
#include <asm/acpi.h>
|
|
#include <asm/fixmap.h>
|
|
@@ -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 <linux/crash_dump.h>
|
|
#include <linux/memory.h>
|
|
#include <linux/start_kernel.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/kmemleak.h>
|
|
|
|
@@ -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 <linux/pci.h>
|
|
#include <linux/random.h>
|
|
#include <linux/root_dev.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/static_call.h>
|
|
#include <linux/sysfb.h>
|
|
#include <linux/swiotlb.h>
|
|
@@ -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 <linux/uuid.h>
|
|
#include <linux/nospec.h>
|
|
#include <linux/vmalloc.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/delay.h>
|
|
|
|
#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 <linux/memblock.h>
|
|
#include <linux/security.h>
|
|
#include <linux/notifier.h>
|
|
+#include <linux/bsearch.h>
|
|
|
|
#include <asm/early_ioremap.h>
|
|
|
|
@@ -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 <linux/efi.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/printk.h>
|
|
+
|
|
+/*
|
|
+ * 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 <linux/init.h>
|
|
#include <linux/types.h>
|
|
#include <linux/device.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fs.h>
|
|
@@ -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 <linux/amba/bus.h>
|
|
#include <linux/device.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/bug.h>
|
|
@@ -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 <linux/irq.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <media/v4l2-ioctl.h>
|
|
+
|
|
+#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 <bin.du@amd.com>");
|
|
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
|
|
+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 <drm/amd/isp.h>
|
|
+#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 <linux/dev_printk.h>
|
|
+#include <linux/printk.h>
|
|
+
|
|
+#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 <linux/io.h>
|
|
+
|
|
+#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 <linux/iopoll.h>
|
|
+
|
|
+#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 <drm/amd/isp.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+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 <linux/pm_domain.h>
|
|
+#include <linux/units.h>
|
|
+
|
|
+#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 <linux/debugfs.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <media/v4l2-device.h>
|
|
+
|
|
+#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 <media/v4l2-ioctl.h>
|
|
+#include <media/v4l2-mc.h>
|
|
+#include <media/videobuf2-vmalloc.h>
|
|
+
|
|
+#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 <media/v4l2-dev.h>
|
|
+#include <media/videobuf2-memops.h>
|
|
+
|
|
+#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 <linux/clk-provider.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/crc32.h>
|
|
+#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/firmware/xlnx-zynqmp.h>
|
|
@@ -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 <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
+#include <linux/irq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nfc.h>
|
|
#include <linux/gpio/consumer.h>
|
|
@@ -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 <linux/regmap.h>
|
|
|
|
+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 <linux/ip.h>
|
|
#include <linux/ipv6.h>
|
|
#include <linux/slab.h>
|
|
+#include <linux/overflow.h>
|
|
#include <net/ipv6.h>
|
|
#include <net/netlink.h>
|
|
#include <net/pkt_sched.h>
|
|
@@ -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);
|
|
|